You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2017/09/06 17:45:21 UTC

[1/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Repository: nifi-registry
Updated Branches:
  refs/heads/master a1629c86d -> 7fa56bea9


http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 47cf327..91dbfaa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,34 +14,37 @@
   limitations under the License.
 -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache</groupId>
         <artifactId>apache</artifactId>
         <version>17</version>
-        <relativePath />
+        <relativePath/>
     </parent>
     <groupId>org.apache.nifi.registry</groupId>
     <artifactId>nifi-registry</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <packaging>pom</packaging>
-    <description>Provides a central location for storage and management of shared resources across one or more instances of NiFi and/or MiNiFi.</description>
+    <description>Provides a central location for storage and management of shared resources across one or more instances
+        of NiFi and/or MiNiFi.
+    </description>
     <modules>
         <module>nifi-registry-properties</module>
         <module>nifi-registry-utils</module>
-	    <module>nifi-registry-data-model</module>
+        <module>nifi-registry-data-model</module>
         <module>nifi-registry-jetty</module>
         <module>nifi-registry-resources</module>
         <module>nifi-registry-runtime</module>
         <module>nifi-registry-security</module>
         <module>nifi-registry-framework</module>
-	    <module>nifi-registry-provider-api</module>
-	    <module>nifi-registry-provider-impl</module>
+        <module>nifi-registry-provider-api</module>
+        <module>nifi-registry-provider-impl</module>
         <module>nifi-registry-web-api</module>
         <module>nifi-registry-web-ui</module>
         <module>nifi-registry-bootstrap</module>
-	    <module>nifi-registry-assembly</module>
+        <module>nifi-registry-assembly</module>
     </modules>
     <url>https://nifi.apache.org/registry.html</url>
     <organization>
@@ -98,8 +101,8 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <inceptionYear>2017</inceptionYear>
-    	<org.slf4j.version>1.7.12</org.slf4j.version>
-        <jetty.version>9.2.11.v20150529</jetty.version>
+        <org.slf4j.version>1.7.12</org.slf4j.version>
+        <jetty.version>9.4.3.v20170317</jetty.version>
         <jersey.version>2.25.1</jersey.version>
     </properties>
 
@@ -140,7 +143,7 @@
         </repository>
     </repositories>
 
-  <dependencyManagement>
+    <dependencyManagement>
         <dependencies>
             <dependency>
                 <groupId>ch.qos.logback</groupId>
@@ -200,28 +203,28 @@
             </dependency>
             <dependency>
                 <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-jsp</artifactId>
+                <artifactId>jetty-annotations</artifactId>
                 <version>${jetty.version}</version>
             </dependency>
             <dependency>
-                <groupId>javax.servlet</groupId>
-                <artifactId>javax.servlet-api</artifactId>
-                <version>3.1.0</version>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>apache-jsp</artifactId>
+                <version>${jetty.version}</version>
             </dependency>
             <dependency>
-                <groupId>javax.servlet.jsp</groupId>
-                <artifactId>javax.servlet.jsp-api</artifactId>
-                <version>2.3.1</version>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>apache-jstl</artifactId>
+                <version>${jetty.version}</version>
             </dependency>
             <dependency>
-                <groupId>javax.el</groupId>
-                <artifactId>javax.el-api</artifactId>
-                <version>3.0.0</version>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>3.4</version>
             </dependency>
             <dependency>
-                <groupId>javax.servlet.jsp.jstl</groupId>
-                <artifactId>javax.servlet.jsp.jstl-api</artifactId>
-                <version>1.2.1</version>
+                <groupId>javax.servlet</groupId>
+                <artifactId>javax.servlet-api</artifactId>
+                <version>3.1.0</version>
             </dependency>
             <dependency>
                 <groupId>org.glassfish.jersey.core</groupId>
@@ -362,7 +365,9 @@
                             <include>**/*Spec.class</include>
                         </includes>
                         <redirectTestOutputToFile>true</redirectTestOutputToFile>
-                        <argLine combine.children="append">-Xmx1G -Djava.net.preferIPv4Stack=true ${maven.surefire.arguments}</argLine>
+                        <argLine combine.children="append">-Xmx1G -Djava.net.preferIPv4Stack=true
+                            ${maven.surefire.arguments}
+                        </argLine>
                     </configuration>
                     <dependencies>
                         <dependency>
@@ -551,78 +556,84 @@
                 <configuration>
                     <checkstyleRules>
                         <module name="Checker">
-                            <property name="charset" value="UTF-8" />
-                            <property name="severity" value="warning" />
+                            <property name="charset" value="UTF-8"/>
+                            <property name="severity" value="warning"/>
                             <!-- Checks for whitespace -->
                             <!-- See http://checkstyle.sf.net/config_whitespace.html -->
                             <module name="FileTabCharacter">
-                                <property name="eachLine" value="true" />
+                                <property name="eachLine" value="true"/>
                             </module>
                             <module name="TreeWalker">
                                 <module name="RegexpSinglelineJava">
-                                    <property name="format" value="\s+$" />
-                                    <property name="message" value="Line has trailing whitespace." />
+                                    <property name="format" value="\s+$"/>
+                                    <property name="message" value="Line has trailing whitespace."/>
                                 </module>
                                 <module name="RegexpSinglelineJava">
-                                    <property name="format" value="[@]see\s+[{][@]link" />
-                                    <property name="message" value="Javadoc @see does not need @link: pick one or the other." />
+                                    <property name="format" value="[@]see\s+[{][@]link"/>
+                                    <property name="message"
+                                              value="Javadoc @see does not need @link: pick one or the other."/>
                                 </module>
-                                <module name="OuterTypeFilename" />
+                                <module name="OuterTypeFilename"/>
                                 <module name="LineLength">
                                     <!-- needs extra, because Eclipse formatter ignores the ending left 
                                     brace -->
-                                    <property name="max" value="200" />
-                                    <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://" />
+                                    <property name="max" value="200"/>
+                                    <property name="ignorePattern"
+                                              value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
                                 </module>
-                                <module name="AvoidStarImport" />
+                                <module name="AvoidStarImport"/>
                                 <module name="UnusedImports">
-                                    <property name="processJavadoc" value="true" />
+                                    <property name="processJavadoc" value="true"/>
                                 </module>
-                                <module name="NoLineWrap" />
+                                <module name="NoLineWrap"/>
                                 <module name="LeftCurly">
-                                    <property name="maxLineLength" value="160" />
+                                    <property name="maxLineLength" value="160"/>
                                 </module>
-                                <module name="RightCurly" />
+                                <module name="RightCurly"/>
                                 <module name="RightCurly">
-                                    <property name="option" value="alone" />
-                                    <property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT" />
+                                    <property name="option" value="alone"/>
+                                    <property name="tokens"
+                                              value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
                                 </module>
                                 <module name="SeparatorWrap">
-                                    <property name="tokens" value="DOT" />
-                                    <property name="option" value="nl" />
+                                    <property name="tokens" value="DOT"/>
+                                    <property name="option" value="nl"/>
                                 </module>
                                 <module name="SeparatorWrap">
-                                    <property name="tokens" value="COMMA" />
-                                    <property name="option" value="EOL" />
+                                    <property name="tokens" value="COMMA"/>
+                                    <property name="option" value="EOL"/>
                                 </module>
                                 <module name="PackageName">
-                                    <property name="format" value="^[a-z]+(\.[a-z][a-zA-Z0-9]*)*$" />
+                                    <property name="format" value="^[a-z]+(\.[a-z][a-zA-Z0-9]*)*$"/>
                                 </module>
                                 <module name="MethodTypeParameterName">
-                                    <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)" />
+                                    <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
                                 </module>
-                                <module name="MethodParamPad" />
+                                <module name="MethodParamPad"/>
                                 <module name="OperatorWrap">
-                                    <property name="option" value="NL" />
-                                    <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, QUESTION, SL, SR, STAR " />
+                                    <property name="option" value="NL"/>
+                                    <property name="tokens"
+                                              value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, QUESTION, SL, SR, STAR "/>
                                 </module>
                                 <module name="AnnotationLocation">
-                                    <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF" />
+                                    <property name="tokens"
+                                              value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
                                 </module>
                                 <module name="AnnotationLocation">
-                                    <property name="tokens" value="VARIABLE_DEF" />
-                                    <property name="allowSamelineMultipleAnnotations" value="true" />
+                                    <property name="tokens" value="VARIABLE_DEF"/>
+                                    <property name="allowSamelineMultipleAnnotations" value="true"/>
                                 </module>
-                                <module name="NonEmptyAtclauseDescription" />
+                                <module name="NonEmptyAtclauseDescription"/>
                                 <module name="JavadocMethod">
-                                    <property name="allowMissingJavadoc" value="true" />
-                                    <property name="allowMissingParamTags" value="true" />
-                                    <property name="allowMissingThrowsTags" value="true" />
-                                    <property name="allowMissingReturnTag" value="true" />
-                                    <property name="allowedAnnotations" value="Override,Test,BeforeClass,AfterClass,Before,After" />
-                                    <property name="allowThrowsTagsForSubclasses" value="true" />
+                                    <property name="allowMissingJavadoc" value="true"/>
+                                    <property name="allowMissingParamTags" value="true"/>
+                                    <property name="allowMissingThrowsTags" value="true"/>
+                                    <property name="allowMissingReturnTag" value="true"/>
+                                    <property name="allowedAnnotations"
+                                              value="Override,Test,BeforeClass,AfterClass,Before,After"/>
+                                    <property name="allowThrowsTagsForSubclasses" value="true"/>
                                 </module>
-                                <module name="SingleLineJavadoc" />
+                                <module name="SingleLineJavadoc"/>
                             </module>
                         </module>
                     </checkstyleRules>
@@ -637,8 +648,10 @@
                     <excludes>
                         <exclude>nb-configuration.xml</exclude> <!-- courtesy excludes for netbeans users -->
                         <exclude>nbactions.xml</exclude> <!-- courtesy excludes for netbeans users -->
-                        <exclude>DEPENDENCIES</exclude> <!-- auto generated file by apache's maven config while building sources.zip -->
-                        <exclude>.github/PULL_REQUEST_TEMPLATE.md</exclude> <!-- PR Template for GitHub that does not have a mechanism of including comments -->
+                        <exclude>DEPENDENCIES
+                        </exclude> <!-- auto generated file by apache's maven config while building sources.zip -->
+                        <exclude>.github/PULL_REQUEST_TEMPLATE.md
+                        </exclude> <!-- PR Template for GitHub that does not have a mechanism of including comments -->
                     </excludes>
                 </configuration>
                 <dependencies>
@@ -658,7 +671,7 @@
             </plugin>
         </plugins>
     </build>
-    
+
     <profiles>
         <profile>
             <!-- Checks style and licensing requirements. This is a good idea to run 


[4/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.html b/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.html
new file mode 100644
index 0000000..7e9baf7
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.html
@@ -0,0 +1,3060 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<md-sidenav-container>
+    <md-sidenav #sidenav mode="over" align="end" opened="false">
+        <div fxLayout="column" fxLayoutAlign="space-between center">
+            <p>You can also open a dialog from a side nav.</p>
+            <button md-raised-button color="fds-primary" (click)="openDialog()">Show simple dialog</button>
+        </div>
+    </md-sidenav>
+    <div id="fds-demo">
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Fluid Design System</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content class="pad-top-sm">
+                <p>With the Fluid Design System module, we get an atomic, reusable component platform for the Apache Nifi Registry to consume, while collaborating in an open source model. This module packages the <a class="link" href="https://material.angular.io/components" target="_blank">Angular Material</a> module as well as the <a class="link" href="https://teradata.github.io/covalent/#/components" target="_blank">Teradata Covalent</a> module. These modules have been themed to match the FDS color palette.</p>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Setup</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <p>Import the FDS Core NgModule into your AppModule</p>
+                <p>HTML:</p>
+                <pre lang="javascript">
+        <![CDATA[
+        var ngCore = require('@angular/core');
+        var fdsCore = require('@fluid-design-system/core');
+        // other imports
+          ...
+        new ngCore.NgModule({
+        imports: [
+            fdsCore,
+            // (optional) Additional imports
+          ],
+          ...
+        })
+        ]]>
+    </pre>
+                <p>The core FDS styles also need to be included in your `index.html` like:</p>
+                <p>HTML:</p>
+                <pre lang="html">
+        <![CDATA[
+        <link href="../node_modules/@fluid-design-system/core/common/styles/css/fluid-design-system.css" rel="stylesheet">
+        ]]>
+    </pre>
+                <p>Or, if you are using the Angular CLI you will need to add a new entry to the "styles" list in .angular-cli.json.</p>
+                <p>JSON:</p>
+                <pre lang="json">
+        <![CDATA[
+        "styles": [
+            "../node_modules/@fluid-design-system/core/common/styles/fluid-design-system.scss"
+        ]
+        ]]>
+    </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Typography</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <p class="md-body-1">Angular Material provides <a class="link" href="https://material.io/guidelines/style/typography.html" target="_blank">typography</a> CSS classes you can use to create visual consistency across your application.</p>
+                <p class="md-body-1">
+                    <strong>Note:</strong>
+                    <span>Base font size is 10px for easy rem units (1.2rem = 12px). Body font size is 14px. sp = scalable pixels.</span>
+                </p>
+                <h3 class="md-title">Header Styles</h3>
+                <md-divider class="push-top push-bottom"></md-divider>
+                <p class="md-body-1">To preserve semantic structures, you can optionally style the <code>&lt;h1&gt;</code> - <code>&lt;h6&gt;</code> heading tags with the styling classes shown below:</p>
+                <p>CSS:</p>
+                <div layout-align="center end">
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-display-4</code>
+                        <span class="md-display-4">Light 112px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-display-3</code>
+                        <span class="md-display-3">Regular 56px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-display-2</code>
+                        <span class="md-display-2">Regular 45px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-display-1</code>
+                        <span class="md-display-1">Regular 34px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-headline</code>
+                        <span class="md-headline">Regular 24px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-title</code>
+                        <span class="md-title">Medium 20px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-subhead</code>
+                        <span class="md-subhead">Regular 16px</span>
+                    </div>
+                </div>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+    <![CDATA[
+        <!-- Large Heading -->
+        <h1 class="md-display-4">Roboto is the standard typeface on Android.</h1>
+        <h2 class="md-display-3">Roboto and Noto are the standard typefaces on Android and Chrome.</h2>
+        <h3 class="md-display-2">Noto is the standard typeface for all languages on Chrome and Android for all languages not covered by Roboto.</h3>
+        <!-- Medium Heading -->
+        <h1 class="md-display-1">Roboto is the standard typeface on Android.</h1>
+        <h2 class="md-headline">Roboto and Noto are the standard typefaces on Android and Chrome.</h2>
+        <h3 class="md-subhead">Noto is the standard typeface for all languages on Chrome and Android for all languages not covered by Roboto.</h3>
+    ]]>
+    </pre>
+                <h3 class="md-title">Body Copy</h3>
+                <md-divider class="push-top push-bottom"></md-divider>
+                <p>CSS:</p>
+                <div class="md-body-1" layout-align="center end">
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-body-1</code>
+                        <span class="md-body-1">Regular 14px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-body-2</code>
+                        <span class="md-body-2">Medium 14px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-button</code>
+                        <span class="md-button md-raised">Medium 14px</span>
+                    </div>
+                    <div layout="row" layout-align="start center">
+                        <code flex="15">.md-caption</code>
+                        <span class="md-caption">Regular 12px</span>
+                    </div>
+                </div>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+    <![CDATA[
+        <!-- body copy -->
+        <p class="md-body-1">Roboto is the standard typeface on Android.</p>
+        <p class="md-body-2">Roboto and Noto are the standard typefaces on Android and Chrome.</p>
+        <p class="md-button">Roboto and Noto are the standard typefaces on Android and Chrome.</p>
+        <p class="md-caption">Noto is the standard typeface for all languages on Chrome and Android for all languages not covered by Roboto.</p>
+    ]]>
+    </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Raised Buttons</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <p>Tip: Use UPPERCASE text for 1-2 words, and Titlecase text for 3+ words.</p>
+                <button md-raised-button color="primary">Primary</button>
+                <button md-raised-button color="accent">Accent</button>
+                <button md-raised-button color="warn">Warn</button>
+                <button md-raised-button color="fds-primary">FDS Primary</button>
+                <button md-raised-button color="fds-secondary">FDS Secondary</button>
+                <button md-raised-button color="fds-regular">FDS regular</button>
+                <button md-raised-button color="fds-warn">FDS warn</button>
+                <button md-raised-button color="fds-critical">FDS critical</button>
+                <button md-raised-button disabled color="primary">Primary</button>
+                <button md-raised-button disabled color="accent">Accent</button>
+                <button md-raised-button disabled color="warn">Warn</button>
+                <button md-raised-button disabled color="fds-primary">FDS primary</button>
+                <button md-raised-button disabled color="fds-secondary">FDS Secondary</button>
+                <button md-raised-button disabled color="fds-regular">FDS regular</button>
+                <button md-raised-button disabled color="fds-warn">FDS warn</button>
+                <button md-raised-button disabled color="fds-critical">FDS critical</button>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+    <![CDATA[
+        <!-- Themed Raised Buttons -->
+        <button md-raised-button color="primary">Primary</button>
+        <button md-raised-button color="accent">Secondary</button>
+        <button md-raised-button color="warn">warn</button>
+        <button md-raised-button color="fds-primary">FDS Primary</button>
+        <button md-raised-button color="fds-secondary">FDS Secondary</button>
+        <button md-raised-button color="fds-regular">FDS regular</button>
+        <button md-raised-button color="fds-warn">FDS warn</button>
+        <button md-raised-button color="fds-critical">FDS critical</button>
+        <!-- Disabled Raised Buttons -->
+        <button md-raised-button disabled color="primary">Primary</button>
+        <button md-raised-button disabled color="accent">Secondary</button>
+        <button md-raised-button disabled color="warn">warn</button>
+        <button md-raised-button disabled color="fds-primary">FDS primary</button>
+        <button md-raised-button disabled color="fds-secondary">FDS Secondary</button>
+        <button md-raised-button disabled color="fds-regular">FDS regular</button>
+        <button md-raised-button disabled color="fds-warn">FDS warn</button>
+        <button md-raised-button disabled color="fds-critical">FDS critical</button>
+    ]]>
+    </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Links</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <a class="link" href="https://material.angular.io" target="_blank">Angular Material</a>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+                <![CDATA[
+        <a class="link" href="https://material.angular.io" target="_blank">Angular Material</a>
+                ]]>
+            </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Flat Buttons</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <md-card-actions>
+                    <button md-button>Defualt</button>
+                    <button md-button color="primary">Primary</button>
+                    <button md-button color="accent">Secondary</button>
+                    <button md-button color="warn">Warn</button>
+                    <button md-button disabled>Disabled Defualt</button>
+                    <button md-button disabled color="primary">Disabled Primary</button>
+                    <button md-button disabled color="accent">Disabled Secondary</button>
+                    <button md-button disabled color="warn">Disabled Warn</button>
+                </md-card-actions>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+    <![CDATA[
+        <!-- Themed Flat Buttons -->
+        <button md-button>Defualt</button>
+        <button md-button color="primary">Primary</button>
+        <button md-button color="accent">Secondary</button>
+        <button md-button color="warn">warn</button>
+        <!-- Disabled Flat Buttons -->
+        <button md-button disabled>disabled Defualt</button>
+        <button md-button disabled color="primary">disabled primary</button>
+        <button md-button disabled color="accent">disabled Secondary</button>
+        <button md-button disabled color="warn">disabled warn</button>
+    ]]>
+    </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Fab Buttons</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <md-card-actions class="pad-left-sm">
+                    <button md-mini-fab color="primary">P</button>
+                    <button md-mini-fab color="accent">A</button>
+                    <button md-mini-fab color="warn">W</button>
+                    <button md-mini-fab disabled color="primary">P</button>
+                    <button md-mini-fab disabled color="accent">A</button>
+                    <button md-mini-fab disabled color="warn">W</button>
+                </md-card-actions>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+    <![CDATA[
+        <!-- Themed Fab Buttons -->
+        <button md-mini-fab color="primary">P</button>
+        <button md-mini-fab color="accent">A</button>
+        <button md-mini-fab color="warn">W</button>
+        <!-- Disabled Fab Buttons -->
+        <button md-mini-fab disabled color="primary">P</button>
+        <button md-mini-fab disabled color="accent">A</button>
+        <button md-mini-fab disabled color="warn">W</button>
+    ]]>
+    </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Button Toggles</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <md-button-toggle-group name="alignment">
+                    <md-button-toggle value="left">
+                        <md-icon>format_align_left</md-icon>
+                    </md-button-toggle>
+                    <md-button-toggle value="center">
+                        <md-icon>format_align_center</md-icon>
+                    </md-button-toggle>
+                    <md-button-toggle value="right">
+                        <md-icon>format_align_right</md-icon>
+                    </md-button-toggle>
+                    <md-button-toggle value="justify">
+                        <md-icon>format_align_justify</md-icon>
+                    </md-button-toggle>
+                </md-button-toggle-group>
+                <div fxLayout="row" class="pad-top-md pad-bot-md"></div>
+                <md-button-toggle-group name="onOffToggle" class="on-off-toggle-group">
+                    <md-button-toggle value="on" [checked]="true">
+                        ON
+                    </md-button-toggle>
+                    <md-button-toggle value="off" class="off-toggle">
+                        OFF
+                    </md-button-toggle>
+                </md-button-toggle-group>
+                <div fxLayout="row" class="pad-top-md pad-bot-md"></div>
+                <md-button-toggle-group fxLayout="row" fxLayoutAlign="space-between center" class="expansion-panel-filter-toggle-group" multiple>
+                    <md-button-toggle>
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">34</div>
+                            <div class="pad-top-sm" fxFlex="45">Assets</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle>
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">300</div>
+                            <div class="pad-top-sm" fxFlex="45">Extensions</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle>
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">5000</div>
+                            <div class="pad-top-sm" fxFlex="45">Flows</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle>
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">0</div>
+                            <div class="pad-top-sm" fxFlex="45">Certifications</div>
+                        </div>
+                    </md-button-toggle>
+                </md-button-toggle-group>
+                <div fxLayout="row" class="pad-top-md pad-bot-md"></div>
+                <md-button-toggle-group name="nifi-registry-administration-perspective" fxLayout="row" class="tab-toggle-group">
+                    <md-button-toggle value="general" class="uppercase">
+                        general
+                    </md-button-toggle>
+                    <div fxLayout="row" class="pad-left-md"></div>
+                    <md-button-toggle value="users" class="uppercase">
+                        Users
+                    </md-button-toggle>
+                    <div fxLayout="row" class="pad-left-md"></div>
+                    <md-button-toggle value="workflow" class="uppercase">
+                        Workflow
+                    </md-button-toggle>
+                </md-button-toggle-group>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+      <![CDATA[
+         <md-button-toggle-group name="alignment">
+            <md-button-toggle value="left">
+                <md-icon>format_align_left</md-icon>
+            </md-button-toggle>
+            <md-button-toggle value="center">
+                <md-icon>format_align_center</md-icon>
+            </md-button-toggle>
+            <md-button-toggle value="right">
+                <md-icon>format_align_right</md-icon>
+            </md-button-toggle>
+            <md-button-toggle value="justify">
+                <md-icon>format_align_justify</md-icon>
+            </md-button-toggle>
+        </md-button-toggle-group>
+        <div fxLayout="row" class="pad-top-md pad-bot-md"></div>
+        <md-button-toggle-group name="onOffToggle" class="on-off-toggle-group">
+            <md-button-toggle value="on" [checked]="true">
+                ON
+            </md-button-toggle>
+            <md-button-toggle value="off" class="off-toggle">
+                OFF
+            </md-button-toggle>
+        </md-button-toggle-group>
+        <div fxLayout="row" class="pad-top-md pad-bot-md"></div>
+        <md-button-toggle-group fxLayout="row" fxLayoutAlign="space-between center" class="expansion-panel-filter-toggle-group" multiple>
+            <md-button-toggle>
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">34</div>
+                    <div class="pad-top-sm" fxFlex="45">Assets</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle>
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">300</div>
+                    <div class="pad-top-sm" fxFlex="45">Extensions</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle>
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">5000</div>
+                    <div class="pad-top-sm" fxFlex="45">Flows</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle>
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">0</div>
+                    <div class="pad-top-sm" fxFlex="45">Certifications</div>
+                </div>
+            </md-button-toggle>
+        </md-button-toggle-group>
+        <div fxLayout="row" class="pad-top-md pad-bot-md"></div>
+        <md-button-toggle-group name="nifi-registry-administration-perspective" fxLayout="row" class="tab-toggle-group">
+            <md-button-toggle value="general" class="uppercase">
+                general
+            </md-button-toggle>
+            <div fxLayout="row" class="pad-left-md"></div>
+            <md-button-toggle value="users" class="uppercase">
+                Users
+            </md-button-toggle>
+            <div fxLayout="row" class="pad-left-md"></div>
+            <md-button-toggle value="workflow" class="uppercase">
+                Workflow
+            </md-button-toggle>
+        </md-button-toggle-group>
+        ]]>
+      </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Input</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <form>
+                    <div layout="row" layout-margin>
+                        <md-input-container floatPlaceholder="always" flex>
+                            <input mdInput placeholder="Company (disabled)" disabled value="Google">
+                        </md-input-container>
+                    </div>
+                    <div layout="row" layout-margin>
+                        <md-input-container floatPlaceholder="always" flex>
+                            <input mdInput disabled placeholder="First name">
+                        </md-input-container>
+                        <md-input-container flex>
+                            <input mdInput placeholder="Long Last Name That Will Be Truncated">
+                        </md-input-container>
+                    </div>
+                    <div layout="row" layout-margin>
+                        <div flex fxLayoutAlign="start center">
+                            <md-input-container flex>
+                                <input mdInput placeholder="Button Addon with dropdown">
+                            </md-input-container>
+                            <button class="input-button" color="fds-regular" md-raised-button [mdMenuTriggerFor]="inputButtonDropdownAddonMenu">
+                                Select<i class="fa fa-caret-down" aria-hidden="true"></i>
+                            </button>
+                            <md-menu x-position="before" #inputButtonDropdownAddonMenu="mdMenu" [overlapTrigger]="false">
+                                <button md-menu-item> Refresh </button>
+                                <button md-menu-item> Settings </button>
+                                <button md-menu-item> Help </button>
+                                <button md-menu-item disabled> Sign Out </button>
+                            </md-menu>
+                        </div>
+                        <div flex fxLayoutAlign="start center">
+                            <md-input-container floatPlaceholder="always" flex>
+                                <input mdInput placeholder="Button Addon">
+                            </md-input-container>
+                            <button class="input-button" color="fds-regular" md-raised-button>
+                                Search
+                            </button>
+                        </div>
+                    </div>
+                    <div layout="row" layout-margin>
+                        <div flex fxLayoutAlign="start center">
+                            <md-input-container floatPlaceholder="always" flex>
+                                <input disabled mdInput placeholder="Button Addon with dropdown">
+                            </md-input-container>
+                            <button disabled class="input-button" color="fds-regular" md-raised-button>
+                                Select<i class="fa fa-caret-down" aria-hidden="true"></i>
+                            </button>
+                        </div>
+                        <div flex fxLayoutAlign="start center">
+                            <md-input-container floatPlaceholder="always" flex>
+                                <input disabled mdInput placeholder="Button Addon">
+                            </md-input-container>
+                            <button disabled class="input-button" color="fds-regular" md-raised-button>
+                                Search
+                            </button>
+                        </div>
+                    </div>
+                    <div layout="row" layout-margin>
+                        <md-input-container flex>
+                            <textarea mdInput placeholder="Address" value="1600 Amphitheatre Pkway"></textarea>
+                        </md-input-container>
+                    </div>
+                    <div layout="row" layout-margin>
+                        <md-input-container floatPlaceholder="always" flex>
+                            <textarea disabled value="Address 2 Value" mdInput placeholder="Address 2"></textarea>
+                        </md-input-container>
+                    </div>
+                    <div layout="row" layout-margin>
+                        <md-input-container floatPlaceholder="always" flex>
+                            <input mdInput placeholder="City">
+                        </md-input-container>
+                        <md-input-container floatPlaceholder="always" flex>
+                            <input mdInput placeholder="State">
+                        </md-input-container>
+                        <md-input-container floatPlaceholder="always" flex>
+                            <input mdInput #postalCode maxlength="5" placeholder="Postal Code" value="94043">
+                            <md-hint align="end">{{postalCode.value.length}} / 5</md-hint>
+                        </md-input-container>
+                    </div>
+                </form>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+    <![CDATA[
+        <!-- Inputs -->
+        <form>
+            <div layout="row" layout-margin>
+                <md-input-container floatPlaceholder="always" flex>
+                    <input mdInput placeholder="Company (disabled)" disabled value="Google">
+                </md-input-container>
+            </div>
+            <div layout="row" layout-margin>
+                <md-input-container floatPlaceholder="always" flex>
+                    <input mdInput disabled placeholder="First name">
+                </md-input-container>
+                <md-input-container flex>
+                    <input mdInput placeholder="Long Last Name That Will Be Truncated">
+                </md-input-container>
+            </div>
+            <div layout="row" layout-margin>
+                <div flex fxLayoutAlign="start center">
+                    <md-input-container flex>
+                        <input mdInput placeholder="Button Addon with dropdown">
+                    </md-input-container>
+                    <button class="input-button" color="fds-regular" md-raised-button [mdMenuTriggerFor]="inputButtonDropdownAddonMenu">
+                        Select<i class="fa fa-caret-down" aria-hidden="true"></i>
+                    </button>
+                    <md-menu x-position="before" #inputButtonDropdownAddonMenu="mdMenu" [overlapTrigger]="false">
+                        <button md-menu-item> Refresh </button>
+                        <button md-menu-item> Settings </button>
+                        <button md-menu-item> Help </button>
+                        <button md-menu-item disabled> Sign Out </button>
+                    </md-menu>
+                </div>
+                <div flex fxLayoutAlign="start center">
+                    <md-input-container floatPlaceholder="always" flex>
+                        <input mdInput placeholder="Button Addon">
+                    </md-input-container>
+                    <button class="input-button" color="fds-regular" md-raised-button>
+                        Search
+                    </button>
+                </div>
+            </div>
+            <div layout="row" layout-margin>
+                <div flex fxLayoutAlign="start center">
+                    <md-input-container floatPlaceholder="always" flex>
+                        <input disabled mdInput placeholder="Button Addon with dropdown">
+                    </md-input-container>
+                    <button disabled class="input-button" color="fds-regular" md-raised-button>
+                        Select<i class="fa fa-caret-down" aria-hidden="true"></i>
+                    </button>
+                </div>
+                <div flex fxLayoutAlign="start center">
+                    <md-input-container floatPlaceholder="always" flex>
+                        <input disabled mdInput placeholder="Button Addon">
+                    </md-input-container>
+                    <button disabled class="input-button" color="fds-regular" md-raised-button>
+                        Search
+                    </button>
+                </div>
+            </div>
+            <div layout="row" layout-margin>
+                <md-input-container flex>
+                    <textarea mdInput placeholder="Address" value="1600 Amphitheatre Pkway"></textarea>
+                </md-input-container>
+            </div>
+            <div layout="row" layout-margin>
+                <md-input-container floatPlaceholder="always" flex>
+                    <textarea disabled value="Address 2 Value" mdInput placeholder="Address 2"></textarea>
+                </md-input-container>
+            </div>
+            <div layout="row" layout-margin>
+                <md-input-container floatPlaceholder="always" flex>
+                    <input mdInput placeholder="City">
+                </md-input-container>
+                <md-input-container floatPlaceholder="always" flex>
+                    <input mdInput placeholder="State">
+                </md-input-container>
+                <md-input-container floatPlaceholder="always" flex>
+                    <input mdInput #postalCode maxlength="5" placeholder="Postal Code" value="94043">
+                    <md-hint align="end">{{postalCode.value.length}} / 5</md-hint>
+                </md-input-container>
+            </div>
+        </form>
+    ]]>
+    </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Tabs</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <md-tab-group dynamicHeight>
+                    <md-tab>
+                        <ng-template md-tab-label>One</ng-template>
+                        <h3 class="md-title">First tab content</h3>
+                        <p>Plaid echo park knausgaard normcore franzen cronut. Pickled humblebrag tofu hoodie, umami salvia farm-to-table schlitz try-hard food truck knausgaard pabst. Yuccie portland jean shorts, authentic mixtape waistcoat gentrify blue bottle. Fixie kickstarter church-key small batch seitan, shabby chic vegan listicle before they sold out. Hammock raw denim flannel tousled seitan you probably haven't heard of them. Trust fund man bun pug, kickstarter artisan selvage letterpress cornhole tote bag butcher locavore. Affogato try-hard kickstarter seitan, DIY pickled hella godard pork belly four loko ugh.</p>
+                    </md-tab>
+                    <md-tab>
+                        <ng-template md-tab-label>Two</ng-template>
+                        <h3 class="md-title">Second tab content</h3>
+                        <p>Hashtag distillery skateboard man bun gochujang, salvia man braid art party meggings heirloom kitsch farm-to-table. Franzen beard fingerstache gentrify, heirloom portland ennui XOXO microdosing kitsch plaid. Chicharrones bushwick chia, banh mi irony tattooed hammock butcher shabby chic taxidermy semiotics marfa post-ironic. Blue bottle keffiyeh farm-to-table ennui, chambray pitchfork art party pinterest artisan pop-up. Etsy banjo marfa, blue bottle kombucha crucifix XOXO tousled beard. Tilde disrupt kale chips bicycle rights skateboard master cleanse hella shoreditch, meditation retro shabby chic vice heirloom. Etsy listicle vice actually, iPhone chia hoodie four loko.</p>
+                    </md-tab>
+                </md-tab-group>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+      <![CDATA[
+        <md-tab-group dynamicHeight>
+          <md-tab>
+            <ng-template md-tab-label>First tab content</ng-template>
+            <h1>First content</h1>
+            <p>...</p>
+          </md-tab>
+          <md-tab>
+            <ng-template md-tab-label>Second tab content</ng-template>
+            <h1>Second tab content</h1>
+            <p>...</p>
+          </md-tab>
+        </md-tab-group>
+        ]]>
+      </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Autocomplete</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <div class="pad-top-sm" layout="row">
+                    <md-input-container flex="50">
+                        <input mdInput placeholder="State" [mdAutocomplete]="tdAuto" [(ngModel)]="currentState" #modelDir="ngModel" (ngModelChange)="this.tdStates = filterStates(currentState)" [disabled]="tdDisabled">
+                    </md-input-container>
+                </div>
+                <div class="push-top">
+                    <button md-button (click)="modelDir.reset()" class="text-upper">Reset</button>
+                    <button md-button (click)="currentState='California'" class="text-upper">Set value</button>
+                    <button md-button (click)="tdDisabled=!tdDisabled" class="text-upper">Toggle disabled</button>
+                </div>
+                <md-autocomplete #tdAuto="mdAutocomplete">
+                    <md-option *ngFor="let state of tdStates" [value]="state.name">
+                        <span>{{ state.name }}</span>
+                        <span class="demo-secondary-text"> ({{state.code}}) </span>
+                    </md-option>
+                </md-autocomplete>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+      <![CDATA[
+        <div class="pad-top-sm" layout="row">
+            <md-input-container flex="50">
+                <input mdInput placeholder="State" [mdAutocomplete]="tdAuto" [(ngModel)]="currentState" #modelDir="ngModel" (ngModelChange)="this.tdStates = filterStates(currentState)" [disabled]="tdDisabled">
+            </md-input-container>
+        </div>
+        <div class="push-top">
+            <button md-button (click)="modelDir.reset()" class="text-upper">Reset</button>
+            <button md-button (click)="currentState='California'" class="text-upper">Set value</button>
+            <button md-button (click)="tdDisabled=!tdDisabled" class="text-upper">Toggle disabled</button>
+        </div>
+        <md-autocomplete #tdAuto="mdAutocomplete">
+            <md-option *ngFor="let state of tdStates" [value]="state.name">
+                <span>{ { state.name } }</span>
+                <span class="demo-secondary-text"> ({ {state.code} }) </span>
+            </md-option>
+        </md-autocomplete>
+        ]]>
+      </pre>
+                <p>Javascript:</p>
+                <pre lang="javascript">
+      <![CDATA[
+        this.currentState = '';
+        this.reactiveStates = '';
+        this.tdStates = [];
+        this.tdDisabled = false;
+        this.states = [
+            { code: 'AL', name: 'Alabama' },
+            { code: 'AK', name: 'Alaska' },
+            { code: 'AZ', name: 'Arizona' },
+            { code: 'AR', name: 'Arkansas' },
+            { code: 'CA', name: 'California' },
+            { code: 'CO', name: 'Colorado' },
+            { code: 'CT', name: 'Connecticut' },
+            { code: 'DE', name: 'Delaware' },
+            { code: 'FL', name: 'Florida' },
+            { code: 'GA', name: 'Georgia' },
+            { code: 'HI', name: 'Hawaii' },
+            { code: 'ID', name: 'Idaho' },
+            { code: 'IL', name: 'Illinois' },
+            { code: 'IN', name: 'Indiana' },
+            { code: 'IA', name: 'Iowa' },
+            { code: 'KS', name: 'Kansas' },
+            { code: 'KY', name: 'Kentucky' },
+            { code: 'LA', name: 'Louisiana' },
+            { code: 'ME', name: 'Maine' },
+            { code: 'MD', name: 'Maryland' },
+            { code: 'MA', name: 'Massachusetts' },
+            { code: 'MI', name: 'Michigan' },
+            { code: 'MN', name: 'Minnesota' },
+            { code: 'MS', name: 'Mississippi' },
+            { code: 'MO', name: 'Missouri' },
+            { code: 'MT', name: 'Montana' },
+            { code: 'NE', name: 'Nebraska' },
+            { code: 'NV', name: 'Nevada' },
+            { code: 'NH', name: 'New Hampshire' },
+            { code: 'NJ', name: 'New Jersey' },
+            { code: 'NM', name: 'New Mexico' },
+            { code: 'NY', name: 'New York' },
+            { code: 'NC', name: 'North Carolina' },
+            { code: 'ND', name: 'North Dakota' },
+            { code: 'OH', name: 'Ohio' },
+            { code: 'OK', name: 'Oklahoma' },
+            { code: 'OR', name: 'Oregon' },
+            { code: 'PA', name: 'Pennsylvania' },
+            { code: 'RI', name: 'Rhode Island' },
+            { code: 'SC', name: 'South Carolina' },
+            { code: 'SD', name: 'South Dakota' },
+            { code: 'TN', name: 'Tennessee' },
+            { code: 'TX', name: 'Texas' },
+            { code: 'UT', name: 'Utah' },
+            { code: 'VT', name: 'Vermont' },
+            { code: 'VA', name: 'Virginia' },
+            { code: 'WA', name: 'Washington' },
+            { code: 'WV', name: 'West Virginia' },
+            { code: 'WI', name: 'Wisconsin' },
+            { code: 'WY', name: 'Wyoming' },
+        ];
+
+        ...
+
+        displayFn: function(value) {
+            return value && typeof value === 'object' ? value.name : value;
+        },
+
+        filterStates: function(val) {
+            return val ? this.states.filter((s) => s.name.match(new RegExp(val, 'gi'))) : this.states;
+        },
+
+        ...
+        ]]>
+      </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Chips</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <h3 class="md-title">Chips (with colors)</h3>
+                <md-chip-list>
+                    <md-chip *ngFor="let chip of chips" [selected]="chip.selected" color="{{chip.color}}">
+                        {{chip.name}}<i class="fa fa-times" aria-hidden="true"></i>
+                    </md-chip>
+                </md-chip-list>
+                <md-divider class="push-top push-bottom"></md-divider>
+                <h3 class="md-title">Chip Stacked (with colors)</h3>
+                <div layout-gt-xs="row">
+                    <md-chip-list flex-gt-xs="50" class="mat-chip-list-stacked">
+                        <md-chip *ngFor="let chip of chips" [selected]="chip.selected" color="{{chip.color}}">
+                            {{chip.name}}<i class="fa fa-times" aria-hidden="true"></i>
+                        </md-chip>
+                    </md-chip-list>
+                </div>
+                <h3 class="md-title">Usage</h3>
+                <p>HTML:</p>
+                <pre lang="html">
+      <![CDATA[
+        <md-chip-list>
+            <md-chip *ngFor="let chip of chips" [selected]="chip.selected" color="{ {chip.color} }">
+                { {chip.name} }<i class="fa fa-times" aria-hidden="true"></i>
+            </md-chip>
+        </md-chip-list>
+        ]]>
+      </pre>
+                <p>Javascript:</p>
+                <pre lang="javascript">
+      <![CDATA[
+        chips: [
+            { name: 'Default', color: '', selected: false },
+            { name: 'Default (selected)', color: '', selected: true },
+            { name: 'Primary', color: 'primary', selected: false },
+            { name: 'Primary (selected)', color: 'primary', selected: true },
+            { name: 'Accent', color: 'accent', selected: false },
+            { name: 'Accent (selected)', color: 'accent', selected: true },
+            { name: 'Warn', color: 'warn', selected: false },
+            { name: 'Warn (selected)', color: 'warn', selected: true },
+        ];
+        ]]>
+      </pre>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Filter</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <h3 class="md-title">Autocomplete with chips and no custom inputs</h3>
+                <md-divider class="push-top push-bottom"></md-divider>
+                <md-tab-group md-stretch-tabs dynamicHeight>
+                    <md-tab>
+                        <ng-template md-tab-label>Demo</ng-template>
+                        <div class="push">
+                            <div class="md-body-1">Type and select a preset option:</div>
+                            <td-chips [items]="items" [(ngModel)]="itemsRequireMatch" placeholder="Enter autocomplete strings" [readOnly]="readOnly" requireMatch></td-chips>
+                        </div>
+                    </md-tab>
+                    <md-tab>
+                        <ng-template md-tab-label>Code</ng-template>
+                        <md-card-content>
+                            <p>HTML:</p>
+                            <pre lang="html">
+                            <![CDATA[
+        <td-chips [items]="items" [(ngModel)]="itemsRequireMatch" placeholder="Enter autocomplete strings" [readOnly]="readOnly" requireMatch></td-chips>
+          ]]>
+                        </pre>
+                            <p>Javascript:</p>
+                            <pre lang="javascript">
+                            <![CDATA[
+        this.readOnly = false;
+
+        this.items = [
+            'stepper',
+            'expansion-panel',
+            'markdown',
+            'highlight',
+            'loading',
+            'media',
+            'chips',
+            'http',
+            'json-formatter',
+            'pipes',
+            'need more?',
+        ];
+
+        this.itemsRequireMatch = this.items.slice(0, 6);
+
+        ...
+
+        toggleReadOnly: function() {
+            this.readOnly = !this.readOnly;
+        },
+
+        ...
+          ]]>
+                        </pre>
+                        </md-card-content>
+                    </md-tab>
+                </md-tab-group>
+                <md-divider></md-divider>
+                <md-card-actions>
+                    <button md-button color="primary" (click)="toggleReadOnly()" class="text-upper">Toggle ReadOnly</button>
+                </md-card-actions>
+            </md-card-content>
+            <md-card-content>
+                <h3 class="md-title">Autocomplete with custom inputs</h3>
+                <md-divider class="push-top push-bottom"></md-divider>
+                <md-tab-group md-stretch-tabs dynamicHeight>
+                    <md-tab>
+                        <ng-template md-tab-label>Demo</ng-template>
+                        <div class="push">
+                            <div class="md-body-1">Type and select option or enter custom text and press enter:</div>
+                            <td-chips [items]="items" placeholder="Enter any string"></td-chips>
+                        </div>
+                    </md-tab>
+                    <md-tab>
+                        <ng-template md-tab-label>Code</ng-template>
+                        <md-card-content>
+                            <p>HTML:</p>
+                            <pre lang="html">
+                            <![CDATA[
+        <td-chips [items]="items" placeholder="Enter any string"></td-chips>
+          ]]>
+                        </pre>
+                            <p>Javascript:</p>
+                            <pre lang="javascript">
+                            <![CDATA[
+        this.items = [
+            'stepper',
+            'expansion-panel',
+            'markdown',
+            'highlight',
+            'loading',
+            'media',
+            'chips',
+            'http',
+            'json-formatter',
+            'pipes',
+            'need more?',
+        ];
+          ]]>
+                        </pre>
+                        </md-card-content>
+                    </md-tab>
+                </md-tab-group>
+            </md-card-content>
+            <md-card-content>
+                <h3 class="md-title">Demo allowing custom inputs for tags</h3>
+                <md-divider class="push-top push-bottom"></md-divider>
+                <md-tab-group md-stretch-tabs dynamicHeight>
+                    <md-tab>
+                        <ng-template md-tab-label>Demo</ng-template>
+                        <div class="push">
+                            <div class="md-body-1">Type any test and press enter:</div>
+                            <td-chips placeholder="Enter any string"></td-chips>
+                        </div>
+                    </md-tab>
+                    <md-tab>
+                        <ng-template md-tab-label>Code</ng-template>
+                        <md-card-content>
+                            <p>HTML:</p>
+                            <pre lang="html">
+                            <![CDATA[
+        <td-chips placeholder="Enter any string"></td-chips>
+          ]]>
+                        </pre>
+                        </md-card-content>
+                    </md-tab>
+                </md-tab-group>
+            </md-card-content>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Searchable/Filterable Expansion Panels</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <md-tab-group md-stretch-tabs>
+                    <md-tab>
+                        <ng-template md-tab-label>Demo</ng-template>
+                        <div class="pad-top-md pad-bottom-md pad-right-xxl pad-left-xxl">
+                            <div class="pad-top-md pad-bottom-sm">
+                                <md-button-toggle-group fxLayout="row" fxLayoutAlign="space-between center" class="expansion-panel-filter-toggle-group" multiple>
+                                    <md-button-toggle (change)="toggleDropletsFilter('type:asset')" [checked]="isDropletFilterChecked('type:asset')">
+                                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                                            <div class="md-display-1 pad-top-sm" fxFlex="55">{{getDropletTypeCount('asset')}}</div>
+                                            <div class="pad-top-sm" fxFlex="45">Assets</div>
+                                        </div>
+                                    </md-button-toggle>
+                                    <md-button-toggle (change)="toggleDropletsFilter('type:extension')" [checked]="isDropletFilterChecked('type:extension')">
+                                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                                            <div class="md-display-1 pad-top-sm" fxFlex="55">{{getDropletTypeCount('extension')}}</div>
+                                            <div class="pad-top-sm" fxFlex="45">Extensions</div>
+                                        </div>
+                                    </md-button-toggle>
+                                    <md-button-toggle (change)="toggleDropletsFilter('type:flow')" [checked]="isDropletFilterChecked('type:flow')">
+                                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                                            <div class="md-display-1 pad-top-sm" fxFlex="55">{{getDropletTypeCount('flow')}}</div>
+                                            <div class="pad-top-sm" fxFlex="45">Flows</div>
+                                        </div>
+                                    </md-button-toggle>
+                                    <md-button-toggle (change)="toggleDropletsFilter('compliant.label:Compliant')" [checked]="isDropletFilterChecked('compliant.label:Compliant')">
+                                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                                            <div class="md-display-1 pad-top-sm" fxFlex="55">{{getDropletCertificationCount('compliant')}}</div>
+                                            <div class="pad-top-sm" fxFlex="45">Compliant</div>
+                                        </div>
+                                    </md-button-toggle>
+                                    <md-button-toggle (change)="toggleDropletsFilter('fleet.label:Fleet')" [checked]="isDropletFilterChecked('fleet.label:Fleet')">
+                                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                                            <div class="md-display-1 pad-top-sm" fxFlex="55">{{getDropletCertificationCount('fleet')}}</div>
+                                            <div class="pad-top-sm" fxFlex="45">Fleet</div>
+                                        </div>
+                                    </md-button-toggle>
+                                    <md-button-toggle (change)="toggleDropletsFilter('prod.label:Production Ready')" [checked]="isDropletFilterChecked('prod.label:Production Ready')">
+                                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                                            <div class="md-display-1 pad-top-sm" fxFlex="55">{{getDropletCertificationCount('prod')}}</div>
+                                            <div class="pad-top-sm" fxFlex="45">Production Ready</div>
+                                        </div>
+                                    </md-button-toggle>
+                                    <md-button-toggle (change)="toggleDropletsFilter('secure.label:Secure')" [checked]="isDropletFilterChecked('secure.label:Secure')">
+                                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                                            <div class="md-display-1 pad-top-sm" fxFlex="55">{{getDropletCertificationCount('secure')}}</div>
+                                            <div class="pad-top-sm" fxFlex="45">Secure</div>
+                                        </div>
+                                    </md-button-toggle>
+                                </md-button-toggle-group>
+                                <div id="nf-registry-droplet-filter-clear-grouping-button-container">
+                                    <span *ngIf="dropletsSearchTerms.length > 0" (click)="dropletsSearchTerms = [];filterDroplets(activeColumn.name, activeColumn.sortOrder);"><i class="fa fa-plus-circle fa-rotate-45" aria-hidden="true"></i><span class="pad-left-sm link">Clear Grouping</span></span>
+                                </div>
+                            </div>
+                            <div layout="row" layout-align="space-between center">
+                                <div flex fxLayout="row" fxLayoutAlign="end center">
+                                    <td-chips [(ngModel)]="dropletsSearchTerms" [items]="autoCompleteDroplets" (add)="dropletsSearchAdd($event)" (remove)="dropletsSearchRemove($event)" class="push-right-sm"></td-chips>
+                                    <span class="pad-right-sm">Sort by:</span>
+                                    <button color="fds-primary" md-raised-button [mdMenuTriggerFor]="dropletGridSortMenu">
+                                        {{getSortBy()}}<i class="fa fa-caret-down" aria-hidden="true"></i>
+                                    </button>
+                                </div>
+                                <md-menu class="fds-primary-dropdown-button-menu" #dropletGridSortMenu="mdMenu" [overlapTrigger]="false">
+                                    <div *ngFor="let column of dropletColumns">
+                                        <button md-menu-item *ngIf="column.sortable" (click)="sortDroplets($event, column);">{{column.label}} {{(column.sortOrder === 'ASC') ? 'DESC' : 'ASC'}}</button>
+                                    </div>
+                                </md-menu>
+                            </div>
+                        </div>
+                        <div class="pad-right-xxl pad-left-xxl">
+                            <div *ngFor="let droplet of filteredDroplets">
+                                <td-expansion-panel class="mat-elevation-z5" label={{droplet.label}} sublabel={{droplet.sublabel}} [disabled]="disabled">
+                                    <ng-template td-expansion-panel-label>
+                                        <div fxLayout="column" fxLayoutAlign="space-between start">
+                                            <span class="md-title capitalize">{{droplet.displayName}}</span>
+                                            <span class="md-subhead">{{droplet.type}}</span>
+                                        </div>
+                                    </ng-template>
+                                    <ng-template td-expansion-panel-sublabel>
+                                        <div fxLayout="row" fxLayoutAlign="space-between center">
+                                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                                <span class="uppercase">Versions</span> {{droplet.versions.length}}
+                                            </div>
+                                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                                <span class="uppercase">Flows</span> {{droplet.flows.length}}
+                                            </div>
+                                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                                <span class="uppercase">Extensions</span> {{droplet.extensions.length}}
+                                            </div>
+                                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                                <span class="uppercase">Assests</span> {{droplet.assets.length}}
+                                            </div>
+                                        </div>
+                                    </ng-template>
+                                    <div class="md-padding">
+                                        <div fxLayout="column" fxLayoutAlign="space-between stretch">
+                                            <div class="pad-bottom-sm" fxLayout="row" fxLayoutAlign="end center">
+                                                <button color="fds-primary" [mdMenuTriggerFor]="primaryButtonDropdownMenu" md-raised-button>
+                                                    Actions<i class="fa fa-caret-down" aria-hidden="true"></i>
+                                                </button>
+                                                <md-menu class="fds-primary-dropdown-button-menu" #primaryButtonDropdownMenu="mdMenu" [overlapTrigger]="false">
+                                                    <button md-menu-item *ngFor="let action of droplet.actions">
+                                                        <span>{{action.name}}</span>
+                                                    </button>
+                                                </md-menu>
+                                            </div>
+                                            <div fxLayout="row">
+                                                <div fxFlex="25">
+                                                    <span class="uppercase">Description</span>
+                                                    <p>Blah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah bla</p>
+                                                </div>
+                                                <div fxFlex="50">
+                                                    <md-card fxFlex>
+                                                        <md-card-content class="pad-top-sm">
+                                                            <img src="{{droplet.img}}">
+                                                        </md-card-content>
+                                                    </md-card>
+                                                </div>
+                                                <div fxFlex="25">
+                                                    <span class="uppercase">Change Log</span>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </td-expansion-panel>
+                                <div class="pad-bottom-sm"></div>
+                            </div>
+                        </div>
+                    </md-tab>
+                    <md-tab>
+                        <ng-template md-tab-label>Code</ng-template>
+                        <p>HTML:</p>
+                        <pre lang="html">
+                        <![CDATA[
+        <div class="pad-top-md pad-bottom-md pad-right-xxl pad-left-xxl">
+            <div class="pad-top-md pad-bottom-sm">
+                <md-button-toggle-group fxLayout="row" fxLayoutAlign="space-between center" class="expansion-panel-filter-toggle-group" multiple>
+                    <md-button-toggle (change)="toggleDropletsFilter('type:asset')" [checked]="isDropletFilterChecked('type:asset')">
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">{ {getDropletTypeCount('asset')} }</div>
+                            <div class="pad-top-sm" fxFlex="45">Assets</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle (change)="toggleDropletsFilter('type:extension')" [checked]="isDropletFilterChecked('type:extension')">
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">{ {getDropletTypeCount('extension')} }</div>
+                            <div class="pad-top-sm" fxFlex="45">Extensions</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle (change)="toggleDropletsFilter('type:flow')" [checked]="isDropletFilterChecked('type:flow')">
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">{ {getDropletTypeCount('flow')} }</div>
+                            <div class="pad-top-sm" fxFlex="45">Flows</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle (change)="toggleDropletsFilter('compliant.label:Compliant')" [checked]="isDropletFilterChecked('compliant.label:Compliant')">
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">{ {getDropletCertificationCount('compliant')} }</div>
+                            <div class="pad-top-sm" fxFlex="45">Compliant</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle (change)="toggleDropletsFilter('fleet.label:Fleet')" [checked]="isDropletFilterChecked('fleet.label:Fleet')">
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">{ {getDropletCertificationCount('fleet')} }</div>
+                            <div class="pad-top-sm" fxFlex="45">Fleet</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle (change)="toggleDropletsFilter('prod.label:Production Ready')" [checked]="isDropletFilterChecked('prod.label:Production Ready')">
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">{ {getDropletCertificationCount('prod')} }</div>
+                            <div class="pad-top-sm" fxFlex="45">Production Ready</div>
+                        </div>
+                    </md-button-toggle>
+                    <md-button-toggle (change)="toggleDropletsFilter('secure.label:Secure')" [checked]="isDropletFilterChecked('secure.label:Secure')">
+                        <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                            <div class="md-display-1 pad-top-sm" fxFlex="55">{ {getDropletCertificationCount('secure')} }</div>
+                            <div class="pad-top-sm" fxFlex="45">Secure</div>
+                        </div>
+                    </md-button-toggle>
+                </md-button-toggle-group>
+                <button *ngIf="activeColumn" md-button color="primary" (click)="dropletsSearchTerms = [];filterDroplets(activeColumn.name, activeColumn.sortOrder);">Clear Grouping</button>
+            </div>
+            <div layout="row" layout-align="space-between center">
+                <div flex fxLayout="row" fxLayoutAlign="end center">
+                    <td-chips [(ngModel)]="dropletsSearchTerms" [items]="autoCompleteDroplets" (add)="dropletsSearchAdd($event)" (remove)="dropletsSearchRemove($event)" class="push-right-sm"></td-chips>
+                    <span class="pad-right-sm">Sort by:</span>
+                    <button color="fds-primary" md-raised-button [mdMenuTriggerFor]="dropletGridSortMenu">
+                        { {getSortBy()} }<i class="fa fa-caret-down" aria-hidden="true"></i>
+                    </button>
+                </div>
+                <md-menu class="fds-primary-dropdown-button-menu" #dropletGridSortMenu="mdMenu" [overlapTrigger]="false">
+                    <div *ngFor="let column of dropletColumns">
+                        <button md-menu-item *ngIf="column.sortable" (click)="sortDroplets($event, column);">{ {column.label} } { {(column.sortOrder === 'ASC') ? 'DESC' : 'ASC'} }</button>
+                    </div>
+                </md-menu>
+            </div>
+        </div>
+        <div class="pad-right-xxl pad-left-xxl">
+            <div *ngFor="let droplet of filteredDroplets">
+                <td-expansion-panel class="mat-elevation-z5" label={ {droplet.label} } sublabel={ {droplet.sublabel} } [disabled]="disabled">
+                    <ng-template td-expansion-panel-label>
+                        <div fxLayout="column" fxLayoutAlign="space-between start">
+                            <span class="md-title capitalize">{ {droplet.displayName} }</span>
+                            <span class="md-subhead">{ {droplet.type} }</span>
+                        </div>
+                    </ng-template>
+                    <ng-template td-expansion-panel-sublabel>
+                        <div fxLayout="row" fxLayoutAlign="space-between center">
+                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                <span class="uppercase">Versions</span> { {droplet.versions.length} }
+                            </div>
+                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                <span class="uppercase">Flows</span> { {droplet.flows.length} }
+                            </div>
+                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                <span class="uppercase">Extensions</span> { {droplet.extensions.length} }
+                            </div>
+                            <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                                <span class="uppercase">Assests</span> { {droplet.assets.length} }
+                            </div>
+                        </div>
+                    </ng-template>
+                    <div class="md-padding">
+                        <div fxLayout="column" fxLayoutAlign="space-between stretch">
+                            <div class="pad-bottom-sm" fxLayout="row" fxLayoutAlign="end center">
+                                <button color="fds-primary" [mdMenuTriggerFor]="primaryButtonDropdownMenu" md-raised-button>
+                                    Actions<i class="fa fa-caret-down" aria-hidden="true"></i>
+                                </button>
+                                <md-menu class="fds-primary-dropdown-button-menu" #primaryButtonDropdownMenu="mdMenu" [overlapTrigger]="false">
+                                    <button md-menu-item *ngFor="let action of droplet.actions">
+                                        <span>{ {action.name} }</span>
+                                    </button>
+                                </md-menu>
+                            </div>
+                            <div fxLayout="row">
+                                <div fxFlex="25">
+                                    <span class="uppercase">Description</span>
+                                    <p>Blah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah blaBlah blah bla, bla bla, blah blah bla</p>
+                                </div>
+                                <div fxFlex="50">
+                                    <md-card fxFlex>
+                                        <md-card-content class="pad-top-sm">
+                                            <img src="{ {droplet.img} }">
+                                        </md-card-content>
+                                    </md-card>
+                                </div>
+                                <div fxFlex="25">
+                                    <span class="uppercase">Change Log</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </td-expansion-panel>
+                <div class="pad-bottom-sm"></div>
+            </div>
+        </div>
+          ]]>
+                    </pre>
+                        <p>Javascript:</p>
+                        <pre lang="javascript">
+                        <![CDATA[
+        this.dataTableService = TdDataTableService;
+
+        this.droplets = [{
+            id: '23f6cc59-0156-1000-09b4-2b0610089090',
+            name: "Decompression_Circular_Flow",
+            displayName: 'Decompressed Circular flow',
+            type: 'flow',
+            sublabel: 'A sublabel',
+            compliant: {
+                id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                label: 'Compliant',
+                type: 'certification'
+            },
+            fleet: {
+                id: '23f6cc59-3549-0001-05g6-4d4567890765',
+                label: 'Fleet',
+                type: 'certification'
+            },
+            prod: {
+                id: '52fd6vv87-3549-0001-05g6-4d4567890765',
+                label: 'Production Ready',
+                type: 'certification'
+            },
+            secure: {
+                id: '32f6cc59-3549-0001-05g6-4d4567890765',
+                label: 'Secure',
+                type: 'certification'
+            },
+            versions: [{
+                id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                revision: '1',
+                dependentFlows: [{
+                    id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+                }],
+                created: date.setDate(date.getDate() - 1),
+                updated: new Date()
+            }, {
+                id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                revision: '2',
+                dependentFlows: [{
+                    id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                }],
+                created: new Date(),
+                updated: new Date()
+            }],
+            flows: [],
+            extensions: [],
+            assets: [],
+            actions: [{
+                'name': 'Delete',
+                'icon': 'fa fa-close',
+                'tooltip': 'Delete User'
+            }, {
+                'name': 'Manage',
+                'icon': 'fa fa-user',
+                'tooltip': 'Manage User'
+            }, {
+                'name': 'Action 3',
+                'icon': 'fa fa-question',
+                'tooltip': 'Whatever else we want to do...'
+            }]
+        }, {
+            id: '25fd6vv87-3249-0001-05g6-4d4767890765',
+            name: "DateConversion",
+            displayName: 'Date conversion',
+            type: 'asset',
+            sublabel: 'A sublabel',
+            compliant: {
+                id: '25fd6vv34-3549-0001-05g6-4d4567890765',
+                label: 'Compliant',
+                type: 'certification'
+            },
+            prod: {
+                id: '52vn6vv87-3549-0001-05g6-4d4567890765',
+                label: 'Production Ready',
+                type: 'certification'
+            },
+            versions: [{
+                id: '23f6ic59-0156-1000-06b4-2b0810089090',
+                revision: '1',
+                dependentFlows: [{
+                    id: '23f6cc19-0156-1000-06b4-2b0810089090'
+                }],
+                created: new Date(),
+                updated: new Date()
+            }],
+            flows: [],
+            extensions: [],
+            assets: [],
+            actions: [{
+                'name': 'Delete',
+                'icon': 'fa fa-close',
+                'tooltip': 'Delete User'
+            }]
+        }, {
+            id: '52fd6vv87-3294-0001-05g6-4d4767890765',
+            name: "nifi-email-bundle",
+            displayName: 'nifi-email-bundle',
+            type: 'extension',
+            sublabel: 'A sublabel',
+            compliant: {
+                id: '33fd6vv87-3549-0001-05g6-4d4567890765',
+                label: 'Compliant',
+                test: {
+                    label: 'test'
+                },
+                type: 'certification'
+            },
+            versions: [{
+                id: '23d3cc59-0156-1000-06b4-2b0810089090',
+                revision: '1',
+                dependentFlows: [{
+                    id: '23f6cc89-0156-1000-06b4-2b0810089090'
+                }],
+                created: new Date(),
+                updated: new Date()
+            }],
+            flows: [],
+            extensions: [],
+            assets: [],
+            actions: [{
+                'name': 'Delete',
+                'icon': 'fa fa-close',
+                'tooltip': 'Delete User'
+            }, {
+                'name': 'Manage',
+                'icon': 'fa fa-user',
+                'tooltip': 'Manage User'
+            }, ]
+        }];
+
+        this.filteredDroplets = [];
+
+        this.dropletColumns = [
+            { name: 'id', label: 'ID', sortable: true },
+            { name: 'name', label: 'Name', sortable: true },
+            { name: 'displayName', label: 'Display Name', sortable: true },
+            { name: 'sublabel', label: 'Label', sortable: true },
+            { name: 'type', label: 'Type', sortable: true }
+        ];
+
+        this.autoCompleteDroplets = [];
+        this.dropletsSearchTerms = [];
+
+        ...
+
+        isDropletFilterChecked: function(term) {
+            return (this.dropletsSearchTerms.indexOf(term) > -1);
+        },
+
+        getDropletTypeCount: function(type) {
+            return this.filteredDroplets.filter(function(droplet) {
+                return droplet.type === type;
+            }).length;
+        },
+
+        getDropletCertificationCount: function(certification) {
+            return this.filteredDroplets.filter(droplet => {
+                return Object.keys(droplet).find((key) => {
+                    if (key === certification && droplet[certification].type === 'certification') {
+                        return droplet;
+                    }
+                });
+            }).length;
+        },
+
+        getSortBy: function() {
+            var sortByColumnLabel;
+            var arrayLength = this.dropletColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.dropletColumns[i].active === true) {
+                    sortByColumnLabel = this.dropletColumns[i].label;
+                    break;
+                }
+            }
+            return sortByColumnLabel;
+        },
+
+        sortDroplets: function(sortEvent, column) {
+            if (column.sortable === true) {
+                // toggle column sort order
+                var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+                this.filterDroplets(column.name, sortOrder);
+                this.activeColumn = column;
+                //only one column can be actively sorted so we reset all to inactive
+                this.dropletColumns.forEach(c => c.active = false);
+                //and set this column as the actively sorted column
+                column.active = true;
+            }
+        },
+
+        dropletsSearchRemove: function(searchTerm) {
+            this.filterDroplets(this.activeColumn.name, this.activeColumn.sortOrder);
+        },
+
+        dropletsSearchAdd: function(searchTerm) {
+            this.filterDroplets(this.activeColumn.name, this.activeColumn.sortOrder);
+        },
+
+        toggleDropletsFilter: function(searchTerm) {
+            var applySearchTerm = true;
+            // check if the search term is already applied and remove it if true
+            if (this.dropletsSearchTerms.length > 0) {
+                var arrayLength = this.dropletsSearchTerms.length;
+                for (var i = 0; i < arrayLength; i++) {
+                    var index = this.dropletsSearchTerms.indexOf(searchTerm);
+                    if (index > -1) {
+                        this.dropletsSearchTerms.splice(index, 1);
+                        applySearchTerm = false;
+                    }
+                }
+            }
+
+            // if we just removed the search term do NOT apply it again
+            if (applySearchTerm) {
+                this.dropletsSearchTerms.push(searchTerm);
+            }
+
+            this.filterDroplets(this.activeColumn.name, this.activeColumn.sortOrder);
+        },
+
+        filterDroplets: function(sortBy, sortOrder) {
+            // if `sortBy` is `undefined` then find the first sortable column in this.dropletColumns
+            if (sortBy === undefined) {
+                var arrayLength = this.dropletColumns.length;
+                for (var i = 0; i < arrayLength; i++) {
+                    if (this.dropletColumns[i].sortable === true) {
+                        sortBy = this.dropletColumns[i].name;
+                        this.activeColumn = this.dropletColumns[i];
+                        //only one column can be actively sorted so we reset all to inactive
+                        this.dropletColumns.forEach(c => c.active = false);
+                        //and set this column as the actively sorted column
+                        this.dropletColumns[i].active = true;
+                        break;
+                    }
+                }
+            }
+
+            // if `sortOrder` is `undefined` then use 'ASC'
+            if (sortOrder === undefined) {
+                sortOrder = 'ASC'
+            }
+
+            var newData = this.droplets;
+
+            for (var i = 0; i < this.dropletsSearchTerms.length; i++) {
+                newData = this.filterData(newData, this.dropletsSearchTerms[i], true, this.activeColumn.name);
+            }
+
+            newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+            this.filteredDroplets = newData;
+            this.getAutoCompleteDroplets();
+        },
+
+        getAutoCompleteDroplets: function() {
+            this.autoCompleteDroplets = [];
+            this.dropletColumns.forEach(c => this.filteredDroplets.forEach(r => (r[c.name.toLowerCase()]) ? this.autoCompleteDroplets.push(r[c.name.toLowerCase()].toString()) : ''));
+        },
+
+        filterData: function(data, searchTerm, ignoreCase) {
+            var field = '';
+            if (searchTerm.indexOf(":") > -1) {
+                field = searchTerm.split(':')[0].trim();
+                searchTerm = searchTerm.split(':')[1].trim();
+            }
+            var filter = searchTerm ? (ignoreCase ? searchTerm.toLowerCase() : searchTerm) : '';
+
+            if (filter) {
+                data = data.filter(item => {
+                    var res = Object.keys(item).find((key) => {
+                        if (field.indexOf(".") > -1) {
+                            var objArray = field.split(".");
+                            var obj = item;
+                            var arrayLength = objArray.length;
+                            for (var i = 0; i < arrayLength; i++) {
+                                try {
+                                    obj = obj[objArray[i]];
+                                } catch (e) {
+                                    return false;
+                                }
+                            }
+                            var preItemValue = ('' + obj);
+                            var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                            return itemValue.indexOf(filter) > -1;
+                        } else {
+                            if (key !== field && field !== '') {
+                                return false;
+                            }
+                            var preItemValue = ('' + item[key]);
+                            var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                            return itemValue.indexOf(filter) > -1;
+                        }
+                    });
+                    return !(typeof res === 'undefined');
+                });
+            }
+            return data;
+        },
+
+        ...
+          ]]>
+                    </pre>
+                    </md-tab>
+                </md-tab-group>
+            </md-card-content>
+            <md-divider class="pad-bottom-sm"></md-divider>
+        </md-card>
+        <md-card>
+            <md-card-title class="pad-bottom-sm">Table</md-card-title>
+            <md-divider></md-divider>
+            <md-card-content>
+                <p>Example table with: Paging Bar / Filter / Sortable Columns / Multi-select with available Actions</p>
+                <md-tab-group md-stretch-tabs>
+                    <md-tab>
+                        <ng-template md-tab-label>Demo</ng-template>
+                        <div layout="row" layout-align="space-between center" class="pad-top-md pad-bottom-sm pad-left-md pad-right-md">
+                            <span class="table-title">
+                            <span>Table title</span>
+                            </span>
+                            <div flex class="push-right-sm" fxLayout="row" fxLayoutAlign="end center">
+                                <td-chips [items]="autoCompleteData" (add)="searchAdd($event)" (remove)="searchRemove($event)"></td-chips>
+                                <button color="fds-primary" md-raised-button [mdMenuTriggerFor]="dataTableActionMenu">
+                                    Actions<i class="fa fa-caret-down" aria-hidden="true"></i>
+                                </button>
+                            </div>
+                            <md-menu class="fds-primary-dropdown-button-menu" #dataTableActionMenu="mdMenu" [overlapTrigger]="false">
+                                <button md-menu-item> Option 1 </button>
+                                <button md-menu-item> Option 2 </button>
+                            </md-menu>
+                        </div>
+                        <div class="pad-left-md pad-right-md">
+                            <div fxLayout="row" fxLayoutAlign="space-between center" class="td-data-table">
+                                <div class="td-data-table-column" (click)="sort($event, column)" [mdTooltip]="column.tooltip" *ngFor="let column of columns" fxFlex="{{column.width}}">
+                                    {{column.label}}
+                                    <i *ngIf="column.active && column.sortable && column.sortOrder === 'ASC'" class="fa fa-caret-up" aria-hidden="true"></i>
+                                    <i *ngIf="column.active && column.sortable && column.sortOrder === 'DESC'" class="fa fa-caret-down" aria-hidden="true"></i>
+                                </div>
+                                <div class="td-data-table-column" fxFlex=10>
+                                    <div fxLayout="row" fxLayoutAlign="end center">
+                                        <md-checkbox class="pad-left-sm" [(ngModel)]="allRowsSelected" (checked)="allRowsSelected" (change)="toggleSelectAll()"></md-checkbox>
+                                    </div>
+                                </div>
+                            </div>
+                            <div>
+                                <div fxLayout="row" fxLayoutAlign="space-between center" class="td-data-table-row" [ngClass]="{'selected' : row.checked}" *ngFor="let row of filteredData" (click)="row.checked = !row.checked;toggleSelect(row)">
+                                    <div class="td-data-table-cell" *ngFor="let column of columns" fxFlex="{{column.width}}">
+                                        <div *ngIf="column.name !== 'comments' || row['comments']">
+                                            {{column.format ? column.format(row[column.name]) : row[column.name]}}
+                                        </div>
+                                    </div>
+                                    <div class="td-data-table-cell" fxFlex=10>
+                                        <div *ngIf="row.actions">
+                                            <div *ngIf="row.actions.length <= 4" fxLayout="row" fxLayoutAlign="end center">
+                                                <button (click)="row.checked = !row.checked" *ngFor="let action of row.actions" mdTooltip="{{action.tooltip}}" md-icon-button color="accent" [disabled]="action.disabled ? '' : null">
+                                                    <i class="{{action.icon}}" aria-hidden="true"></i>
+                                                </button>
+                                                <md-checkbox class="pad-left-sm" [(ngModel)]="row.checked" [checked]="row.checked" (change)="toggleSelect(row)" (click)="row.checked = !row.checked;toggleSelect(row)"></md-checkbox>
+                                            </div>
+                                            <div *ngIf="row.actions.length > 4" fxLayout="row" fxLayoutAlign="end center">
+                                                <button (click)="row.checked = !row.checked" mdTooltip="Actions" md-icon-button color="accent" [mdMenuTriggerFor]="tableActionMenu">
+                                                    <i class="fa fa-ellipsis-h" aria-hidden="true"></i>
+                                                </button>
+                                                <md-menu #tableActionMenu="mdMenu" [overlapTrigger]="false">
+                                                    <button *ngFor="let action of row.actions" mdTooltip="{{action.tooltip}}" md-menu-item [disabled]="action.disabled ? '' : null">
+                                                        <i class="{{action.icon}}" aria-hidden="true"></i>
+                                                        <span>{{action.name}}</span>
+                                                    </button>
+                                                </md-menu>
+                                                <md-checkbox class="pad-left-sm" [(ngModel)]="row.checked" [checked]="row.checked" (change)="toggleSelect(row)" (click)="row.checked = !row.checked;toggleSelect(row)"></md-checkbox>
+                                            </div>
+                                        </div>
+                                        <div *ngIf="!row.actions" fxLayout="row" fxLayoutAlign="end center">
+                                            <md-checkbox class="pad

<TRUNCATED>

[5/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js
new file mode 100644
index 0000000..9a681d2
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryDropletGridListViewer constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param ActivatedRoute        The angular activated route module.
+ * @constructor
+ */
+function NfRegistryDropletGridListViewer(nfRegistryService, ActivatedRoute) {
+    this.route = ActivatedRoute;
+    this.nfRegistryService = nfRegistryService;
+};
+
+NfRegistryDropletGridListViewer.prototype = {
+    constructor: NfRegistryDropletGridListViewer,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.route.params
+            .switchMap(function (params) {
+                return self.nfRegistryService.getDroplets(self.nfRegistryService.registry.id, self.nfRegistryService.bucket.id, params['dropletId']);
+            })
+            .subscribe(function (droplets) {
+                self.nfRegistryService.droplet = droplets[0];
+                self.nfRegistryService.droplets = self.nfRegistryService.filteredDroplets = droplets;
+                self.nfRegistryService.filterDroplets();
+            });
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        var self = this;
+        this.nfRegistryService.droplet = {};
+        this.nfRegistryService.getDroplets(this.nfRegistryService.registry.id,
+            this.nfRegistryService.bucket.id).then(
+            function (droplets) {
+                self.nfRegistryService.droplets = self.nfRegistryService.filteredDroplets = droplets;
+                self.nfRegistryService.filterDroplets();
+            });
+    }
+};
+
+NfRegistryDropletGridListViewer.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-droplet-grid-list-viewer.html!text')
+    })
+];
+
+NfRegistryDropletGridListViewer.parameters = [NfRegistryService, ngRouter.ActivatedRoute];
+
+module.exports = NfRegistryDropletGridListViewer;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.html b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.html
new file mode 100644
index 0000000..694065b
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.html
@@ -0,0 +1,18 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js
new file mode 100644
index 0000000..a992768
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryBucketGridListViewer constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param ActivatedRoute        The angular activated route module.
+ * @constructor
+ */
+function NfRegistryBucketGridListViewer(nfRegistryService, ActivatedRoute) {
+    this.route = ActivatedRoute;
+    this.nfRegistryService = nfRegistryService;
+};
+
+NfRegistryBucketGridListViewer.prototype = {
+    constructor: NfRegistryBucketGridListViewer,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.route.params
+            .switchMap(function (params) {
+                return self.nfRegistryService.getBuckets(self.nfRegistryService.registry.id, params['bucketId']);
+            })
+            .subscribe(function (buckets) {
+                self.nfRegistryService.bucket = buckets[0];
+                self.nfRegistryService.getDroplets(self.nfRegistryService.registry.id, self.nfRegistryService.bucket.id).then(function (droplets) {
+                    self.nfRegistryService.droplets = self.nfRegistryService.filteredDroplets = droplets;
+                    self.nfRegistryService.filterDroplets();
+                });
+            });
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        var self = this;
+        this.nfRegistryService.bucket = {};
+        this.nfRegistryService.getDroplets(this.nfRegistryService.registry.id).then(
+            function (droplets) {
+                self.nfRegistryService.droplets = self.nfRegistryService.filteredDroplets = droplets;
+                self.nfRegistryService.filterDroplets();
+            });
+    }
+};
+
+NfRegistryBucketGridListViewer.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-bucket-grid-list-viewer.html!text')
+    })
+];
+
+NfRegistryBucketGridListViewer.parameters = [NfRegistryService, ngRouter.ActivatedRoute];
+
+module.exports = NfRegistryBucketGridListViewer;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.html b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.html
new file mode 100644
index 0000000..d6113c0
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.html
@@ -0,0 +1,186 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div class="pad-top-sm pad-bottom-sm pad-right-xxl pad-left-xxl">
+    <div *ngIf="false" class="pad-top-md pad-bottom-sm">
+        <md-button-toggle-group fxLayout="row" fxLayoutAlign="space-between center"
+                                class="expansion-panel-filter-toggle-group" multiple>
+            <md-button-toggle (change)="nfRegistryService.toggleDropletsFilter('type:asset')"
+                              [checked]="nfRegistryService.isDropletFilterChecked('type:asset')">
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">
+                        {{nfRegistryService.getDropletTypeCount('asset')}}
+                    </div>
+                    <div class="pad-top-sm" fxFlex="45">Assets</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle (change)="nfRegistryService.toggleDropletsFilter('type:extension')"
+                              [checked]="nfRegistryService.isDropletFilterChecked('type:extension')">
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">
+                        {{nfRegistryService.getDropletTypeCount('extension')}}
+                    </div>
+                    <div class="pad-top-sm" fxFlex="45">Extensions</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle (change)="nfRegistryService.toggleDropletsFilter('type:flow')"
+                              [checked]="nfRegistryService.isDropletFilterChecked('type:flow')">
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">{{nfRegistryService.getDropletTypeCount('flow')}}
+                    </div>
+                    <div class="pad-top-sm" fxFlex="45">Flows</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle (change)="nfRegistryService.toggleDropletsFilter('compliant.label:Compliant')"
+                              [checked]="nfRegistryService.isDropletFilterChecked('compliant.label:Compliant')">
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">
+                        {{nfRegistryService.getDropletCertificationCount('compliant')}}
+                    </div>
+                    <div class="pad-top-sm" fxFlex="45">Compliant</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle (change)="nfRegistryService.toggleDropletsFilter('fleet.label:Fleet')"
+                              [checked]="nfRegistryService.isDropletFilterChecked('fleet.label:Fleet')">
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">
+                        {{nfRegistryService.getDropletCertificationCount('fleet')}}
+                    </div>
+                    <div class="pad-top-sm" fxFlex="45">Fleet</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle (change)="nfRegistryService.toggleDropletsFilter('prod.label:Production Ready')"
+                              [checked]="nfRegistryService.isDropletFilterChecked('prod.label:Production Ready')">
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">
+                        {{nfRegistryService.getDropletCertificationCount('prod')}}
+                    </div>
+                    <div class="pad-top-sm" fxFlex="45">Production Ready</div>
+                </div>
+            </md-button-toggle>
+            <md-button-toggle (change)="nfRegistryService.toggleDropletsFilter('secure.label:Secure')"
+                              [checked]="nfRegistryService.isDropletFilterChecked('secure.label:Secure')">
+                <div fxFlex fxLayout="column" fxLayoutAlign="space-around stretch">
+                    <div class="md-display-1 pad-top-sm" fxFlex="55">
+                        {{nfRegistryService.getDropletCertificationCount('secure')}}
+                    </div>
+                    <div class="pad-top-sm" fxFlex="45">Secure</div>
+                </div>
+            </md-button-toggle>
+        </md-button-toggle-group>
+        <div id="nf-registry-droplet-filter-clear-grouping-button-container">
+            <span *ngIf="nfRegistryService.dropletsSearchTerms.length > 0"
+                  (click)="nfRegistryService.dropletsSearchTerms = [];nfRegistryService.filterDroplets(nfRegistryService.activeDropletColumn.name, nfRegistryService.activeDropletColumn.sortOrder);"><i
+                    class="fa fa-plus-circle fa-rotate-45" aria-hidden="true"></i><span class="pad-left-sm link">Clear Grouping</span></span>
+        </div>
+    </div>
+    <div layout="row" layout-align="space-between center">
+        <div flex fxLayout="row" fxLayoutAlign="end center">
+            <td-chips [(ngModel)]="nfRegistryService.dropletsSearchTerms"
+                      [items]="nfRegistryService.autoCompleteDroplets"
+                      (add)="nfRegistryService.dropletsSearchAdd($event)"
+                      (remove)="nfRegistryService.dropletsSearchRemove($event)" class="push-right-sm"></td-chips>
+            <span class="pad-right-sm">Sort by:</span>
+            <div fxLayout="row" fxLayoutAlign="end center" [mdMenuTriggerFor]="dropletGridSortMenu">
+                <div id="droplet-sort-by-field">{{nfRegistryService.getSortByLabel()}}</div>
+                <i class="fa fa-caret-down pad-left-sm" aria-hidden="true"></i>
+            </div>
+        </div>
+        <md-menu #dropletGridSortMenu="mdMenu" [overlapTrigger]="false">
+            <div *ngFor="let column of nfRegistryService.dropletColumns">
+                <button md-menu-item *ngIf="column.sortable" (click)="nfRegistryService.sortDroplets($event, column);">
+                    {{nfRegistryService.generateSortMenuLabels(column)}}
+                </button>
+            </div>
+        </md-menu>
+    </div>
+</div>
+<div id="nifi-registry-explorer-grid-list-viewer-droplet-container" class="pad-right-xxl pad-left-xxl"
+     *ngIf="nfRegistryService.filteredDroplets.length > 0">
+    <div *ngFor="let droplet of nfRegistryService.filteredDroplets" [@flyInOut]>
+        <td-expansion-panel class="mat-elevation-z5" label={{droplet.label}} sublabel={{droplet.sublabel}}
+                            [disabled]="disabled">
+            <ng-template td-expansion-panel-label>
+                <div fxLayout="column" fxLayoutAlign="space-between start">
+                    <span class="md-title capitalize">{{droplet.displayName}}</span>
+                    <span class="md-subhead">{{droplet.type}}</span>
+                </div>
+            </ng-template>
+            <ng-template td-expansion-panel-sublabel>
+                <div fxLayout="row" fxLayoutAlign="space-between center">
+                    <div class="pad-right-xxl pad-left-xxl" fxLayout="column" fxLayoutAlign="space-between start">
+                        <span class="uppercase">Versions</span> {{droplet.versions.length}}
+                    </div>
+                </div>
+            </ng-template>
+            <div id="nifi-registry-explorer-grid-list-viewer-droplet-container-details" class="md-padding">
+                <div fxLayout="column" fxLayoutAlign="space-between stretch">
+                    <div class="pad-bottom-sm" fxLayout="row" fxLayoutAlign="end center">
+                        <button color="fds-primary" [mdMenuTriggerFor]="primaryButtonDropdownMenu" md-raised-button>
+                            Actions<i class="fa fa-caret-down" aria-hidden="true"></i>
+                        </button>
+                        <md-menu class="fds-primary-dropdown-button-menu" #primaryButtonDropdownMenu="mdMenu"
+                                 [overlapTrigger]="false">
+                            <button md-menu-item *ngFor="let action of droplet.actions"
+                                    (click)="execute(action, droplet)">
+                                <span>{{action.name}}</span>
+                            </button>
+                        </md-menu>
+                    </div>
+                    <div fxLayout="row">
+                        <div fxFlex="25">
+                            <span class="uppercase">Description</span>
+                        </div>
+                        <div fxFlex="75">
+                            <span class="uppercase">Change Log</span>
+                        </div>
+                    </div>
+                    <div fxLayout="row">
+                        <div fxFlex="25">
+                            <p>{{droplet.description}}</p>
+                        </div>
+                        <div id="nifi-registry-explorer-grid-list-viewer-droplet-container-details-change-log"
+                             fxFlex="75">
+                            <td-steps mode="vertical">
+                                <td-step label="{{version.revision}}" sublabel="by {{version.author}}"
+                                         *ngFor="let version of droplet.versions; let i = index"
+                                         [active]="i === 0 ? true : false">
+                                    <ng-template td-step-label>
+                                        <span>{{version.created | amTimeAgo}}</span>
+                                    </ng-template>
+                                    <div fxLayout="column" fxLayoutAlign="space-between stretch">
+                                        <div fxLayout="row" class="md-body-2">
+                                            {{version.comment}}
+                                        </div>
+                                        <div fxLayout="row" class="md-caption">
+                                            {{version.created}}
+                                        </div>
+                                    </div>
+                                </td-step>
+                            </td-steps>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </td-expansion-panel>
+        <div class="pad-bottom-sm"></div>
+    </div>
+</div>
+<div class="pad-right-xxl pad-left-xxl" *ngIf="nfRegistryService.filteredDroplets.length <= 0">
+    <p class="text-center">No results match this query.</p>
+</div>
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js
new file mode 100644
index 0000000..a7aa2fd
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+var fdsDialogsModule = require('@fluid-design-system/dialogs');
+
+/**
+ * NfRegistryGridListViewer constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param ActivatedRoute        The angular activated route module.
+ * @param TdDialogService       The covalent dialog service module.
+ * @constructor
+ */
+function NfRegistryGridListViewer(nfRegistryService, ActivatedRoute, FdsDialogService) {
+    this.route = ActivatedRoute;
+    this.nfRegistryService = nfRegistryService;
+    this.dialogService = FdsDialogService;
+};
+
+NfRegistryGridListViewer.prototype = {
+    constructor: NfRegistryGridListViewer,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.nfRegistryService.explorerViewType = 'grid-list';
+        this.route.params
+            .switchMap(function (params) {
+                return self.nfRegistryService.getRegistry(params['registryId']);
+            })
+            .subscribe(function (registry) {
+                self.nfRegistryService.registry = registry;
+                self.nfRegistryService.getBuckets(self.nfRegistryService.registry.id).then(function (buckets) {
+                    self.nfRegistryService.buckets = buckets;
+                    self.nfRegistryService.getDroplets(self.nfRegistryService.registry.id, self.nfRegistryService.bucket.id, self.nfRegistryService.droplet.id).then(function (droplets) {
+                        self.nfRegistryService.droplets = self.nfRegistryService.filteredDroplets = droplets;
+                        self.nfRegistryService.filterDroplets();
+                    });
+                })
+            });
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.registry = {};
+        this.nfRegistryService.buckets = [];
+        this.nfRegistryService.droplets = [];
+        this.nfRegistryService.filteredDroplets = [];
+    },
+
+    /**
+     * Execute the given droplet action.
+     *
+     * @param action        The action object.
+     * @param droplet       The droplet object the `action` will act upon.
+     */
+    execute: function (action, droplet) {
+        var self = this;
+        if (action.name.toLowerCase() === 'delete') {
+            this.dialogService.openConfirm({
+                title: 'Delete Data Flow',
+                message: 'All versions of this data flow will be deleted.',
+                cancelButton: 'Cancel',
+                acceptButton: 'Delete',
+                acceptButtonColor: 'fds-warn'
+            }).afterClosed().subscribe(
+                function (accept) {
+                    if (accept) {
+                        self.nfRegistryService.deleteDroplet(droplet.id);
+                    }
+                });
+        }
+    }
+};
+
+NfRegistryGridListViewer.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-grid-list-viewer.html!text'),
+        animations: [nfRegistryAnimations.flyInOutAnimation]
+    })
+];
+
+NfRegistryGridListViewer.parameters = [NfRegistryService, ngRouter.ActivatedRoute, fdsDialogsModule.FdsDialogService];
+
+module.exports = NfRegistryGridListViewer;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.html b/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.html
new file mode 100644
index 0000000..694065b
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.html
@@ -0,0 +1,18 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.js b/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.js
new file mode 100644
index 0000000..3c70077
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/nf-registry-explorer.js
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+
+/**
+ * NfRegistryExplorer constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @constructor
+ */
+function NfRegistryExplorer(nfRegistryService) {
+    this.nfRegistryService = nfRegistryService;
+};
+
+NfRegistryExplorer.prototype = {
+    constructor: NfRegistryExplorer,
+
+    /**
+     * Initialize the component
+     */
+    ngOnInit: function () {
+        this.nfRegistryService.perspective = 'explorer';
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.perspective = '';
+    }
+};
+
+NfRegistryExplorer.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-explorer.html!text'),
+        animations: [nfRegistryAnimations.slideInLeftAnimation],
+        host: {
+            '[@routeAnimation]': 'routeAnimation'
+        }
+    })
+];
+
+NfRegistryExplorer.parameters = [NfRegistryService];
+
+module.exports = NfRegistryExplorer;


[3/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.js b/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.js
new file mode 100644
index 0000000..84d6212
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/fluid-design-system/fds-demo.js
@@ -0,0 +1,1029 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngCore = require('@angular/core');
+var NfRegistryExplorer = require('nifi-registry/components/explorer/nf-registry-explorer.js');
+var covalentCore = require('@covalent/core');
+var ngRouter = require('@angular/router');
+var ngMaterial = require('@angular/material');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+var fdsDialogsModule = require('@fluid-design-system/dialogs');
+
+var NUMBER_FORMAT = function (v) {
+    return v;
+};
+var DECIMAL_FORMAT = function (v) {
+    return v.toFixed(2);
+};
+var date = new Date();
+
+/**
+ * FdsDemo constructor.
+ *
+ * @param snackBarService       The angular material snack bar service module.
+ * @param dialog                The angular material dialog module.
+ * @param TdDialogService       The covalent dialog service module.
+ * @param TdDataTableService    The covalent data table service module.
+ * @constructor
+ */
+function FdsDemo(snackBarService, dialog, TdDataTableService, FdsDialogService) {
+
+    //<editor-fold desc="Snack Bars">
+
+    this.snackBarService = snackBarService;
+
+    //</editor-fold>
+
+    //<editor-fold desc="Dialog">
+
+    this.dialog = dialog;
+
+    //</editor-fold>
+
+    //<editor-fold desc="Simple Dialogs">
+
+    this.dialogService = FdsDialogService;
+
+    //</editor-fold>
+
+    //<editor-fold desc="Expansion Panel">
+
+    this.expandCollapseExpansion1Msg = 'No expanded/collapsed detected yet';
+    this.expansion1 = false;
+    this.disabled = false;
+
+    //</editor-fold>
+
+    //<editor-fold desc="Autocomplete">
+
+    this.currentState = '';
+    this.reactiveStates = '';
+    this.tdStates = [];
+    this.tdDisabled = false;
+    this.states = [
+        {code: 'AL', name: 'Alabama'},
+        {code: 'AK', name: 'Alaska'},
+        {code: 'AZ', name: 'Arizona'},
+        {code: 'AR', name: 'Arkansas'},
+        {code: 'CA', name: 'California'},
+        {code: 'CO', name: 'Colorado'},
+        {code: 'CT', name: 'Connecticut'},
+        {code: 'DE', name: 'Delaware'},
+        {code: 'FL', name: 'Florida'},
+        {code: 'GA', name: 'Georgia'},
+        {code: 'HI', name: 'Hawaii'},
+        {code: 'ID', name: 'Idaho'},
+        {code: 'IL', name: 'Illinois'},
+        {code: 'IN', name: 'Indiana'},
+        {code: 'IA', name: 'Iowa'},
+        {code: 'KS', name: 'Kansas'},
+        {code: 'KY', name: 'Kentucky'},
+        {code: 'LA', name: 'Louisiana'},
+        {code: 'ME', name: 'Maine'},
+        {code: 'MD', name: 'Maryland'},
+        {code: 'MA', name: 'Massachusetts'},
+        {code: 'MI', name: 'Michigan'},
+        {code: 'MN', name: 'Minnesota'},
+        {code: 'MS', name: 'Mississippi'},
+        {code: 'MO', name: 'Missouri'},
+        {code: 'MT', name: 'Montana'},
+        {code: 'NE', name: 'Nebraska'},
+        {code: 'NV', name: 'Nevada'},
+        {code: 'NH', name: 'New Hampshire'},
+        {code: 'NJ', name: 'New Jersey'},
+        {code: 'NM', name: 'New Mexico'},
+        {code: 'NY', name: 'New York'},
+        {code: 'NC', name: 'North Carolina'},
+        {code: 'ND', name: 'North Dakota'},
+        {code: 'OH', name: 'Ohio'},
+        {code: 'OK', name: 'Oklahoma'},
+        {code: 'OR', name: 'Oregon'},
+        {code: 'PA', name: 'Pennsylvania'},
+        {code: 'RI', name: 'Rhode Island'},
+        {code: 'SC', name: 'South Carolina'},
+        {code: 'SD', name: 'South Dakota'},
+        {code: 'TN', name: 'Tennessee'},
+        {code: 'TX', name: 'Texas'},
+        {code: 'UT', name: 'Utah'},
+        {code: 'VT', name: 'Vermont'},
+        {code: 'VA', name: 'Virginia'},
+        {code: 'WA', name: 'Washington'},
+        {code: 'WV', name: 'West Virginia'},
+        {code: 'WI', name: 'Wisconsin'},
+        {code: 'WY', name: 'Wyoming'},
+    ];
+
+    //</editor-fold>
+
+    //<editor-fold desc="Searchable Expansion Panels">
+
+    this.dataTableService = TdDataTableService;
+
+    this.droplets = [{
+        id: '23f6cc59-0156-1000-09b4-2b0610089090',
+        name: "Decompression_Circular_Flow",
+        displayName: 'Decompressed Circular flow',
+        type: 'flow',
+        sublabel: 'A sublabel',
+        compliant: {
+            id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+            label: 'Compliant',
+            type: 'certification'
+        },
+        fleet: {
+            id: '23f6cc59-3549-0001-05g6-4d4567890765',
+            label: 'Fleet',
+            type: 'certification'
+        },
+        prod: {
+            id: '52fd6vv87-3549-0001-05g6-4d4567890765',
+            label: 'Production Ready',
+            type: 'certification'
+        },
+        secure: {
+            id: '32f6cc59-3549-0001-05g6-4d4567890765',
+            label: 'Secure',
+            type: 'certification'
+        },
+        versions: [{
+            id: '23f6cc59-0156-1000-06b4-2b0810089090',
+            revision: '1',
+            dependentFlows: [{
+                id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+            }],
+            created: date.setDate(date.getDate() - 1),
+            updated: new Date()
+        }, {
+            id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+            revision: '2',
+            dependentFlows: [{
+                id: '23f6cc59-0156-1000-06b4-2b0810089090'
+            }],
+            created: new Date(),
+            updated: new Date()
+        }],
+        flows: [],
+        extensions: [],
+        assets: [],
+        actions: [{
+            'name': 'Delete',
+            'icon': 'fa fa-close',
+            'tooltip': 'Delete User'
+        }, {
+            'name': 'Manage',
+            'icon': 'fa fa-user',
+            'tooltip': 'Manage User'
+        }, {
+            'name': 'Action 3',
+            'icon': 'fa fa-question',
+            'tooltip': 'Whatever else we want to do...'
+        }]
+    }, {
+        id: '25fd6vv87-3249-0001-05g6-4d4767890765',
+        name: "DateConversion",
+        displayName: 'Date conversion',
+        type: 'asset',
+        sublabel: 'A sublabel',
+        compliant: {
+            id: '25fd6vv34-3549-0001-05g6-4d4567890765',
+            label: 'Compliant',
+            type: 'certification'
+        },
+        prod: {
+            id: '52vn6vv87-3549-0001-05g6-4d4567890765',
+            label: 'Production Ready',
+            type: 'certification'
+        },
+        versions: [{
+            id: '23f6ic59-0156-1000-06b4-2b0810089090',
+            revision: '1',
+            dependentFlows: [{
+                id: '23f6cc19-0156-1000-06b4-2b0810089090'
+            }],
+            created: new Date(),
+            updated: new Date()
+        }],
+        flows: [],
+        extensions: [],
+        assets: [],
+        actions: [{
+            'name': 'Delete',
+            'icon': 'fa fa-close',
+            'tooltip': 'Delete User'
+        }]
+    }, {
+        id: '52fd6vv87-3294-0001-05g6-4d4767890765',
+        name: "nifi-email-bundle",
+        displayName: 'nifi-email-bundle',
+        type: 'extension',
+        sublabel: 'A sublabel',
+        compliant: {
+            id: '33fd6vv87-3549-0001-05g6-4d4567890765',
+            label: 'Compliant',
+            test: {
+                label: 'test'
+            },
+            type: 'certification'
+        },
+        versions: [{
+            id: '23d3cc59-0156-1000-06b4-2b0810089090',
+            revision: '1',
+            dependentFlows: [{
+                id: '23f6cc89-0156-1000-06b4-2b0810089090'
+            }],
+            created: new Date(),
+            updated: new Date()
+        }],
+        flows: [],
+        extensions: [],
+        assets: [],
+        actions: [{
+            'name': 'Delete',
+            'icon': 'fa fa-close',
+            'tooltip': 'Delete User'
+        }, {
+            'name': 'Manage',
+            'icon': 'fa fa-user',
+            'tooltip': 'Manage User'
+        },]
+    }];
+
+    this.filteredDroplets = [];
+
+    this.dropletColumns = [
+        {name: 'id', label: 'ID', sortable: true},
+        {name: 'name', label: 'Name', sortable: true,},
+        {name: 'displayName', label: 'Display Name', sortable: true},
+        {name: 'sublabel', label: 'Label', sortable: true},
+        {name: 'type', label: 'Type', sortable: true}
+    ];
+
+    this.autoCompleteDroplets = [];
+    this.dropletsSearchTerms = [];
+
+    //</editor-fold>
+
+    //<editor-fold desc="Data Tables">
+
+    this.data = [{
+        'id': 1,
+        'name': 'Frozen yogurt',
+        'type': 'Ice cream',
+        'calories': 159.0,
+        'fat': 6.0,
+        'carbs': 24.0,
+        'protein': 4.0,
+        'sodium': 87.0,
+        'calcium': 14.0,
+        'iron': 1.0,
+        'comments': 'I love froyo!',
+        'actions': [{
+            'name': 'Action 1',
+            'icon': 'fa fa-user',
+            'tooltip': 'Manage Users'
+        }, {
+            'name': 'Action 2',
+            'icon': 'fa fa-key',
+            'tooltip': 'Manage Permissions'
+        }]
+    }, {
+        'id': 2,
+        'name': 'Ice cream sandwich',
+        'type': 'Ice cream',
+        'calories': 237.0,
+        'fat': 9.0,
+        'carbs': 37.0,
+        'protein': 4.3,
+        'sodium': 129.0,
+        'calcium': 8.0,
+        'iron': 1.0,
+        'actions': [{
+            'name': 'Action 1',
+            'icon': 'fa fa-user',
+            'tooltip': 'Manage Users'
+        }, {
+            'name': 'Action 2',
+            'icon': 'fa fa-key',
+            'tooltip': 'Manage Permissions'
+        }, {
+            'name': 'Action 3',
+            'tooltip': 'Action 3'
+        }, {
+            'name': 'Action 4',
+            'disabled': true,
+            'tooltip': 'Action 4'
+        }, {
+            'name': 'Action 5',
+            'tooltip': 'Action 5'
+        }]
+    }, {
+        'id': 3,
+        'name': 'Eclair',
+        'type': 'Pastry',
+        'calories': 262.0,
+        'fat': 16.0,
+        'carbs': 24.0,
+        'protein': 6.0,
+        'sodium': 337.0,
+        'calcium': 6.0,
+        'iron': 7.0,
+        'actions': [{
+            'name': 'Action 1',
+            'icon': 'fa fa-user',
+            'tooltip': 'Manage Users'
+        }, {
+            'name': 'Action 2',
+            'icon': 'fa fa-key',
+            'tooltip': 'Manage Permissions'
+        }, {
+            'name': 'Action 3',
+            'tooltip': 'Action 3'
+        }, {
+            'name': 'Action 4',
+            'disabled': true,
+            'tooltip': 'Action 4'
+        }, {
+            'name': 'Action 5',
+            'tooltip': 'Action 5'
+        }],
+    }, {
+        'id': 4,
+        'name': 'Cupcake',
+        'type': 'Pastry',
+        'calories': 305.0,
+        'fat': 3.7,
+        'carbs': 67.0,
+        'protein': 4.3,
+        'sodium': 413.0,
+        'calcium': 3.0,
+        'iron': 8.0,
+        'actions': [{
+            'name': 'Action 1',
+            'icon': 'fa fa-user',
+            'tooltip': 'Manage Users'
+        }, {
+            'name': 'Action 2',
+            'icon': 'fa fa-key',
+            'tooltip': 'Manage Permissions'
+        }, {
+            'name': 'Action 3',
+            'tooltip': 'Action 3'
+        }, {
+            'name': 'Action 4',
+            'disabled': true,
+            'tooltip': 'Action 4'
+        }, {
+            'name': 'Action 5',
+            'tooltip': 'Action 5'
+        }],
+    }, {
+        'id': 5,
+        'name': 'Jelly bean',
+        'type': 'Candy',
+        'calories': 375.0,
+        'fat': 0.0,
+        'carbs': 94.0,
+        'protein': 0.0,
+        'sodium': 50.0,
+        'calcium': 0.0,
+        'iron': 0.0,
+    }, {
+        'id': 6,
+        'name': 'Lollipop',
+        'type': 'Candy',
+        'calories': 392.0,
+        'fat': 0.2,
+        'carbs': 98.0,
+        'protein': 0.0,
+        'sodium': 38.0,
+        'calcium': 0.0,
+        'iron': 2.0,
+    }, {
+        'id': 7,
+        'name': 'Honeycomb',
+        'type': 'Other',
+        'calories': 408.0,
+        'fat': 3.2,
+        'carbs': 87.0,
+        'protein': 6.5,
+        'sodium': 562.0,
+        'calcium': 0.0,
+        'iron': 45.0,
+    }, {
+        'id': 8,
+        'name': 'Donut',
+        'type': 'Pastry',
+        'calories': 452.0,
+        'fat': 25.0,
+        'carbs': 51.0,
+        'protein': 4.9,
+        'sodium': 326.0,
+        'calcium': 2.0,
+        'iron': 22.0,
+    }, {
+        'id': 9,
+        'name': 'KitKat',
+        'type': 'Candy',
+        'calories': 518.0,
+        'fat': 26.0,
+        'carbs': 65.0,
+        'protein': 7.0,
+        'sodium': 54.0,
+        'calcium': 12.0,
+        'iron': 6.0,
+    }, {
+        'id': 10,
+        'name': 'Chocolate',
+        'type': 'Candy',
+        'calories': 518.0,
+        'fat': 26.0,
+        'carbs': 65.0,
+        'protein': 7.0,
+        'sodium': 54.0,
+        'calcium': 12.0,
+        'iron': 6.0,
+    }, {
+        'id': 11,
+        'name': 'Chamoy',
+        'type': 'Candy',
+        'calories': 518.0,
+        'fat': 26.0,
+        'carbs': 65.0,
+        'protein': 7.0,
+        'sodium': 54.0,
+        'calcium': 12.0,
+        'iron': 6.0,
+    },];
+
+    this.filteredData = this.data;
+    this.filteredTotal = this.data.length;
+
+    this.columns = [
+        {name: 'comments', label: 'Comments', width: 10},
+        {name: 'name', label: 'Dessert (100g serving)', sortable: true, width: 10},
+        {name: 'type', label: 'Type', sortable: true, width: 10},
+        {name: 'calories', label: 'Calories', numeric: true, format: NUMBER_FORMAT, sortable: true, width: 10},
+        {name: 'fat', label: 'Fat (g)', numeric: true, format: DECIMAL_FORMAT, sortable: true, width: 10},
+        {name: 'carbs', label: 'Carbs (g)', numeric: true, format: NUMBER_FORMAT, sortable: true, width: 10},
+        {name: 'protein', label: 'Protein (g)', numeric: true, format: DECIMAL_FORMAT, sortable: true, width: 10},
+        {name: 'sodium', label: 'Sodium (mg)', numeric: true, format: NUMBER_FORMAT, sortable: true, width: 10},
+        {name: 'calcium', label: 'Calcium (%)', numeric: true, format: NUMBER_FORMAT, sortable: true, width: 10},
+        {name: 'iron', label: 'Iron (%)', numeric: true, format: NUMBER_FORMAT, width: 10},
+    ];
+
+    this.allRowsSelected = false;
+    this.autoCompleteData = [];
+    this.selectedRows = [];
+
+    this.searchTerm = [];
+    this.fromRow = 1;
+    this.currentPage = 1;
+    this.pageSize = 5;
+    this.pageCount = 0;
+
+    //</editor-fold>
+
+    //<editor-fold desc="Chips $ Autocomplete">
+
+    this.readOnly = false;
+
+    this.items = [
+        'stepper',
+        'expansion-panel',
+        'markdown',
+        'highlight',
+        'loading',
+        'media',
+        'chips',
+        'http',
+        'json-formatter',
+        'pipes',
+        'need more?',
+    ];
+
+    this.itemsRequireMatch = this.items.slice(0, 6);
+
+    //</editor-fold>
+
+    //<editor-fold desc="Radios">
+
+    this.favoriteSeason = 'Autumn';
+
+    this.seasonOptions = [
+        'Winter',
+        'Spring',
+        'Summer',
+        'Autumn',
+    ];
+
+    //</editor-fold>
+
+    //<editor-fold desc="Select">
+
+    this.selectedValue = '';
+
+    this.foods = [
+        {value: 'steak-0', viewValue: 'Steak'},
+        {value: 'pizza-1', viewValue: 'Pizza'},
+        {value: 'tacos-2', viewValue: 'Tacos'},
+    ];
+
+    //</editor-fold>
+
+    //<editor-fold desc="Chips">
+
+    this.chips = [
+        {name: 'Default', color: '', selected: false},
+        {name: 'Default (selected)', color: '', selected: true},
+        {name: 'Primary (selected)', color: 'primary', selected: true},
+        {name: 'Accent (selected)', color: 'accent', selected: true},
+        {name: 'Warn (selected)', color: 'warn', selected: true},
+    ];
+
+    //</editor-fold>
+
+    //<editor-fold desc="Checkbox">
+
+    this.user = {
+        agreesToTOS: false
+    };
+
+    this.groceries = [{
+        bought: true,
+        name: 'Seitan',
+    }, {
+        bought: false,
+        name: 'Almond Meal Flour',
+    }, {
+        bought: false,
+        name: 'Organic Eggs',
+    },];
+
+    //</editor-fold>
+
+    //<editor-fold desc="Slide Toggle">
+
+    this.systems = [{
+        name: 'Lights',
+        on: false,
+        color: 'primary',
+    }, {
+        name: 'Surround Sound',
+        on: true,
+        color: 'accent',
+    }, {
+        name: 'T.V.',
+        on: true,
+        color: 'warn',
+    },];
+
+    this.house = {
+        lockHouse: false,
+    };
+
+    //</editor-fold>
+};
+
+FdsDemo.prototype = {
+    constructor: FdsDemo,
+
+    //<editor-fold desc="Autocomplete">
+
+    displayFn: function (value) {
+        return value && typeof value === 'object' ? value.name : value;
+    },
+
+    filterStates: function (val) {
+        return val ? this.states.filter(function (s) {
+            return s.name.match(new RegExp(val, 'gi'));
+        }) : this.states;
+    },
+
+    //</editor-fold>
+
+    //<editor-fold desc="Snack Bars">
+
+    showSnackBar: function () {
+        var snackBarRef = this.snackBarService.open('Message', 'Action', {duration: 3000});
+    },
+
+    //</editor-fold>
+
+    //<editor-fold desc="Dialog">
+
+    openDialog: function () {
+        this.dialog.open(NfRegistryExplorer, {
+            height: '50%', // can be px or %
+            width: '60%', // can be px or %
+        });
+    },
+
+    //</editor-fold>
+
+    //<editor-fold desc="Expansion Panel">
+
+    toggleExpansion1: function () {
+        if (!this.disabled) {
+            this.expansion1 = !this.expansion1;
+        }
+    },
+
+    toggleDisabled: function () {
+        this.disabled = !this.disabled;
+    },
+
+    expandExpansion1Event: function () {
+        this.expandCollapseExpansion1Msg = 'Expand event emitted.';
+    },
+
+    collapseExpansion1Event: function () {
+        this.expandCollapseExpansion1Msg = 'Collapse event emitted.';
+    },
+
+    //</editor-fold>
+
+    //<editor-fold desc="Simple Dialogs">
+
+    openAlert: function () {
+        this.dialogService.openAlert({
+            title: 'Alert',
+            disableClose: true,
+            message: 'This is how simple it is to create an alert with this wrapper service.',
+        });
+    },
+
+    openConfirm: function () {
+        this.dialogService.openConfirm({
+            title: 'Confirm',
+            message: 'This is how simple it is to create a confirm with this wrapper service. Do you agree?',
+            cancelButton: 'Disagree',
+            acceptButton: 'Agree',
+        });
+    },
+
+    openPrompt: function () {
+        this.dialogService.openPrompt({
+            title: 'Prompt',
+            message: 'This is how simple it is to create a prompt with this wrapper service. Prompt something.',
+            value: 'Populated value',
+            cancelButton: 'Cancel',
+            acceptButton: 'Ok',
+        });
+    },
+
+    //</editor-fold>
+
+    //<editor-fold desc="Searchable Expansion Panels">
+
+    isDropletFilterChecked: function (term) {
+        return (this.dropletsSearchTerms.indexOf(term) > -1);
+    },
+
+    getDropletTypeCount: function (type) {
+        return this.filteredDroplets.filter(function (droplet) {
+            return droplet.type === type;
+        }).length;
+    },
+
+    getDropletCertificationCount: function (certification) {
+        return this.filteredDroplets.filter(function (droplet) {
+            return Object.keys(droplet).find(function (key) {
+                if (key === certification && droplet[certification].type === 'certification') {
+                    return droplet;
+                }
+            });
+        }).length;
+    },
+
+    getSortBy: function () {
+        var sortByColumnLabel;
+        var arrayLength = this.dropletColumns.length;
+        for (var i = 0; i < arrayLength; i++) {
+            if (this.dropletColumns[i].active === true) {
+                sortByColumnLabel = this.dropletColumns[i].label;
+                break;
+            }
+        }
+        return sortByColumnLabel;
+    },
+
+    sortDroplets: function (sortEvent, column) {
+        if (column.sortable === true) {
+            // toggle column sort order
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterDroplets(column.name, sortOrder);
+            this.activeColumn = column;
+            //only one column can be actively sorted so we reset all to inactive
+            this.dropletColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    dropletsSearchRemove: function (searchTerm) {
+        this.filterDroplets(this.activeColumn.name, this.activeColumn.sortOrder);
+    },
+
+    dropletsSearchAdd: function (searchTerm) {
+        this.filterDroplets(this.activeColumn.name, this.activeColumn.sortOrder);
+    },
+
+    toggleDropletsFilter: function (searchTerm) {
+        var applySearchTerm = true;
+        // check if the search term is already applied and remove it if true
+        if (this.dropletsSearchTerms.length > 0) {
+            var arrayLength = this.dropletsSearchTerms.length;
+            for (var i = 0; i < arrayLength; i++) {
+                var index = this.dropletsSearchTerms.indexOf(searchTerm);
+                if (index > -1) {
+                    this.dropletsSearchTerms.splice(index, 1);
+                    applySearchTerm = false;
+                }
+            }
+        }
+
+        // if we just removed the search term do NOT apply it again
+        if (applySearchTerm) {
+            this.dropletsSearchTerms.push(searchTerm);
+        }
+
+        this.filterDroplets(this.activeColumn.name, this.activeColumn.sortOrder);
+    },
+
+    filterDroplets: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+        // if `sortBy` is `undefined` then find the first sortable column in this.dropletColumns
+        if (sortBy === undefined) {
+            var arrayLength = this.dropletColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.dropletColumns[i].sortable === true) {
+                    sortBy = this.dropletColumns[i].name;
+                    this.activeColumn = this.dropletColumns[i];
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.dropletColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.dropletColumns[i].active = true;
+                    this.dropletColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newData = this.droplets;
+
+        for (var i = 0; i < this.dropletsSearchTerms.length; i++) {
+            newData = this.filterData(newData, this.dropletsSearchTerms[i], true, this.activeColumn.name);
+        }
+
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.filteredDroplets = newData;
+        this.getAutoCompleteDroplets();
+    },
+
+    getAutoCompleteDroplets: function () {
+        var self = this;
+        this.autoCompleteDroplets = [];
+        this.dropletColumns.forEach(function (c) {
+            self.filteredDroplets.forEach(function (r) {
+                (r[c.name.toLowerCase()]) ? self.autoCompleteDroplets.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    //</editor-fold>
+
+    filterData: function (data, searchTerm, ignoreCase) {
+        var field = '';
+        if (searchTerm.indexOf(":") > -1) {
+            field = searchTerm.split(':')[0].trim();
+            searchTerm = searchTerm.split(':')[1].trim();
+        }
+        var filter = searchTerm ? (ignoreCase ? searchTerm.toLowerCase() : searchTerm) : '';
+
+        if (filter) {
+            data = data.filter(function (item) {
+                var res = Object.keys(item).find(function (key) {
+                    if (field.indexOf(".") > -1) {
+                        var objArray = field.split(".");
+                        var obj = item;
+                        var arrayLength = objArray.length;
+                        for (var i = 0; i < arrayLength; i++) {
+                            try {
+                                obj = obj[objArray[i]];
+                            } catch (e) {
+                                return false;
+                            }
+                        }
+                        var preItemValue = ('' + obj);
+                        var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                        return itemValue.indexOf(filter) > -1;
+                    } else {
+                        if (key !== field && field !== '') {
+                            return false;
+                        }
+                        var preItemValue = ('' + item[key]);
+                        var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                        return itemValue.indexOf(filter) > -1;
+                    }
+                });
+                return !(typeof res === 'undefined');
+            });
+        }
+        return data;
+    },
+
+    //<editor-fold desc="Data Tables">
+
+    sort: function (sortEvent, column) {
+        if (column.sortable) {
+            var sortBy = column.name;
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filter(sortBy, sortOrder);
+
+            //only one column can be actively sorted so we reset all to inactive
+            this.columns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    searchRemove: function (searchTerm) {
+        //only remove the first occurrence of the search term
+        var index = this.searchTerm.indexOf(searchTerm);
+        if (index !== -1) {
+            this.searchTerm.splice(index, 1);
+        }
+        this.fromRow = 1;
+        this.currentPage = 1;
+        this.pageSize = 1;
+        this.filter();
+    },
+
+    searchAdd: function (searchTerm) {
+        this.searchTerm.push(searchTerm);
+        this.fromRow = 1;
+        this.currentPage = 1;
+        this.pageSize = 1;
+        this.filter();
+    },
+
+    page: function (pagingEvent) {
+        this.fromRow = pagingEvent.fromRow;
+        this.currentPage = pagingEvent.page;
+        this.pageSize = pagingEvent.pageSize;
+        this.filter();
+    },
+
+    filter: function (sortBy, sortOrder) {
+        if (this.allRowsSelected) {
+            this.toggleSelectAll();
+        }
+        this.deselectAll();
+        var newData = this.data;
+
+        for (var i = 0; i < this.searchTerm.length; i++) {
+            newData = this.filterData(newData, this.searchTerm[i], true);
+        }
+        this.filteredTotal = newData.length;
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.pageCount = newData.length;
+        newData = this.dataTableService.pageData(newData, this.fromRow, this.currentPage * this.pageSize);
+        this.filteredData = newData;
+        this.getAutoCompleteData();
+    },
+
+    toggleSelect: function (row) {
+        if (this.allFilteredRowsSelected()) {
+            this.allRowsSelected = true;
+        } else {
+            this.allRowsSelected = false;
+        }
+    },
+
+    toggleSelectAll: function () {
+        if (this.allRowsSelected) {
+            this.selectAll();
+        } else {
+            this.deselectAll();
+        }
+    },
+
+    selectAll: function () {
+        this.filteredData.forEach(function (c) {
+            c.checked = true;
+        });
+    },
+
+    deselectAll: function () {
+        this.filteredData.forEach(function (c) {
+            c.checked = false;
+        });
+    },
+
+    allFilteredRowsSelected: function () {
+        var allFilteredRowsSelected = true;
+        this.filteredData.forEach(function (c) {
+            if (c.checked === undefined || c.checked === false) {
+                allFilteredRowsSelected = false;
+            }
+        });
+
+        return allFilteredRowsSelected;
+    },
+
+    areTooltipsOn: function () {
+        return this.columns[0].hasOwnProperty('tooltip');
+    },
+
+    toggleTooltips: function () {
+        if (this.columns[0].tooltip) {
+            this.columns.forEach(function (c) {
+                delete c.tooltip;
+            });
+        } else {
+            this.columns.forEach(function (c) {
+                c.tooltip = 'This is ' + c.label + '!';
+            });
+        }
+    },
+
+    openDataTablePrompt: function (row, name) {
+        this.dialogService.openPrompt({
+            message: 'Enter comment?',
+            value: row[name],
+        }).afterClosed().subscribe(function (value) {
+            if (value !== undefined) {
+                row[name] = value;
+            }
+        })
+    },
+
+    getAutoCompleteData: function () {
+        var self = this;
+        this.autoCompleteData = [];
+        this.columns.forEach(function (c) {
+            self.filteredData.forEach(function (r) {
+                (r[c.name.toLowerCase()]) ? self.autoCompleteData.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    //</editor-fold>
+
+    //<editor-fold desc="Chips $ Autocomplete">
+
+    toggleReadOnly: function () {
+        this.readOnly = !this.readOnly;
+    },
+
+    //</editor-fold>
+
+    //<editor-fold desc="Life Cycle Listeners">
+
+    /**
+     * Initialize the component
+     */
+    ngOnInit: function () {
+        this.filter();
+        this.filterDroplets();
+    }
+
+    //</editor-fold>
+};
+
+FdsDemo.annotations = [
+    new ngCore.Component({
+        template: require('./fds-demo.html!text'),
+        animations: [nfRegistryAnimations.slideInLeftAnimation],
+        host: {
+            '[@routeAnimation]': 'routeAnimation'
+        }
+    })
+];
+
+FdsDemo.parameters = [ngMaterial.MdSnackBar, ngMaterial.MdDialog, covalentCore.TdDataTableService, fdsDialogsModule.FdsDialogService];
+
+module.exports = FdsDemo;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/page-not-found/nf-registry-page-not-found.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/page-not-found/nf-registry-page-not-found.js b/nifi-registry-web-ui/src/main/webapp/components/page-not-found/nf-registry-page-not-found.js
new file mode 100644
index 0000000..26c676b
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/page-not-found/nf-registry-page-not-found.js
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+
+/**
+ * NfPageNotFoundComponent constructor.
+ */
+function NfPageNotFoundComponent() {
+    this.title = "Page Not Found!!!!";
+};
+
+NfPageNotFoundComponent.prototype = {
+    constructor: NfPageNotFoundComponent
+};
+
+NfPageNotFoundComponent.annotations = [
+    new ngCore.Component({
+        template: '<h1>Hello {{title}}!</h1>'
+    })
+];
+
+module.exports = NfPageNotFoundComponent;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/css/main.css
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/css/main.css b/nifi-registry-web-ui/src/main/webapp/css/main.css
deleted file mode 100644
index 6e9d02d..0000000
--- a/nifi-registry-web-ui/src/main/webapp/css/main.css
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* general */
-
-body {
-    font-family: Open Sans;
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/images/registry-favicon.png
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/images/registry-favicon.png b/nifi-registry-web-ui/src/main/webapp/images/registry-favicon.png
new file mode 100644
index 0000000..87dc8c9
Binary files /dev/null and b/nifi-registry-web-ui/src/main/webapp/images/registry-favicon.png differ

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/images/registry-logo-web-app.svg
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/images/registry-logo-web-app.svg b/nifi-registry-web-ui/src/main/webapp/images/registry-logo-web-app.svg
new file mode 100644
index 0000000..25cf2ae
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/images/registry-logo-web-app.svg
@@ -0,0 +1,17 @@
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<svg id="registryl-logo-mini" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 37.49 48"><defs><style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:#acbbbf;}.cls-3{fill:#899ea3;}.cls-4{fill:#678289;}</style><linearGradient id="linear-gradient" x1="20.82" y1="33.66" x2="20.82" y2="20.7" gradientUnits="userSpaceOnUse"><stop offset="0.3" stop-color="#678289" stop-opacity="0.9"/><stop offset="1" stop-color="#678289" stop-opacity="0"/></linearGradient></defs><title>registry-logo-web-app</title><polygon class="cls-1" points="14.34 33.66 14.34 20.7 27.3 20.7 14.34 33.66"/><polygon class="cls-2" points="0 48 0 35.05 12.95 35.05 0 48"/><rect class="cls-2" y="20.7" width="12.95" height="12.95" transform="translate(33.66 20.7) rotate(90)"/><path class="cls-2" d="M9.79,16.32a9.7,9.7,0,0,1-2.13.24,11.47,11.47,0,0,1-5.33-1.41c-.22-.11-.55-.29-.46-.63l0-.2.23-.1h.13a2.16,2.16,0,0,1,.62.14,15.94,15.94,0,0,0,2,.66,8.59,8.59,0,0,0,2,.23A8.34,8.34,0,0,0,9,15a
 19.33,19.33,0,0,0,3.46-1.41l.47-.22v-7H0v13H13V15.23A19.51,19.51,0,0,1,9.79,16.32Z"/><path class="cls-3" d="M12.48,13.54a16.71,16.71,0,0,1,4.38-1.64,10.29,10.29,0,0,1,1.77-.15,11.14,11.14,0,0,1,4.42.94l1.82-.77L19.81,0,7.89,5.06l3.77,8.88Z"/><path class="cls-3" d="M13,16.95c.21-1.13,1.9-1.8,2.82-2.16a9.72,9.72,0,0,1,2.15-.62c.45-.06,1.06,0,1.13-.18s-1.55-.25-3.12.14a28.83,28.83,0,0,0-3.61,1.35A9.11,9.11,0,0,0,13,16.95Z"/><path class="cls-4" d="M27.24,16a9.89,9.89,0,0,1,1.42,3.3l8.82-8.82L28.33,1.35l-9.16,9.16L20.62,12A10.26,10.26,0,0,1,27.24,16Z"/></svg>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry-bootstrap.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry-bootstrap.js b/nifi-registry-web-ui/src/main/webapp/nf-registry-bootstrap.js
new file mode 100644
index 0000000..14bdc3c
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry-bootstrap.js
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require('core-js');
+require('zone.js');
+require('hammerjs');
+require('switchMap');
+var $ = require('jquery');
+var NfRegistryModule = require('nifi-registry/nf-registry.module.js');
+var ngPlatformBrowserDynamic = require('@angular/platform-browser-dynamic');
+var ngCore = require('@angular/core');
+
+// Comment out this line when developing to assert for unidirectional data flow
+ngCore.enableProdMode();
+
+// Get the locale id from the global
+var locale = navigator.language;
+
+var providers = [];
+
+// No locale or U.S. English: no translation providers so go ahead and bootstrap the app
+if (!locale || locale === 'en-US') {
+    ngPlatformBrowserDynamic.platformBrowserDynamic().bootstrapModule(NfRegistryModule, {providers: providers});
+} else { //load the translation providers and bootstrap the module
+    var translationFile = './nifi-registry/messages.' + locale + '.xlf';
+
+    $.ajax({
+        url: translationFile
+    }).done(function (translations) {
+        // add providers if translation file for locale is loaded
+        if (translations) {
+            providers.push({provide: ngCore.TRANSLATIONS, useValue: translations});
+            providers.push({provide: ngCore.TRANSLATIONS_FORMAT, useValue: 'xlf'});
+            providers.push({provide: ngCore.LOCALE_ID, useValue: locale});
+        }
+        ngPlatformBrowserDynamic.platformBrowserDynamic().bootstrapModule(NfRegistryModule, {providers: providers});
+    }).fail(function () {
+        ngPlatformBrowserDynamic.platformBrowserDynamic().bootstrapModule(NfRegistryModule, {providers: providers});
+    });
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry.animations.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.animations.js b/nifi-registry-web-ui/src/main/webapp/nf-registry.animations.js
new file mode 100644
index 0000000..c446012
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.animations.js
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngAnimate = require('@angular/animations');
+
+/**
+ * NfAnimations constructor.
+ *
+ * @constructor
+ */
+function NfAnimations() {
+};
+
+NfAnimations.prototype = {
+    constructor: NfAnimations,
+
+    /**
+     * Fade animation
+     */
+    fadeAnimation: ngAnimate.trigger('routeAnimation', [
+        ngAnimate.state('*',
+            ngAnimate.style({
+                opacity: 1
+            })
+        ),
+        ngAnimate.transition(':enter', [
+            ngAnimate.style({
+                opacity: 0
+            }),
+            ngAnimate.animate('0.3s ease-in')
+        ]),
+        ngAnimate.transition(':leave', [
+            ngAnimate.animate('0.5s ease-out', ngAnimate.style({
+                opacity: 0
+            }))
+        ])
+    ]),
+
+    /**
+     * Slide in from the left animation
+     */
+    slideInLeftAnimation: ngAnimate.trigger('routeAnimation', [
+        ngAnimate.state('*',
+            ngAnimate.style({
+                opacity: 1,
+                transform: 'translateX(0)'
+            })
+        ),
+        ngAnimate.transition(':enter', [
+            ngAnimate.style({
+                opacity: 0,
+                transform: 'translateX(-100%)'
+            }),
+            ngAnimate.animate('0.3s ease-in')
+        ]),
+        ngAnimate.transition(':leave', [
+            ngAnimate.animate('0.5s ease-out', ngAnimate.style({
+                opacity: 0,
+                transform: 'translateX(100%)'
+            }))
+        ])
+    ]),
+
+    /**
+     * Slide in from the top animation
+     */
+    slideInDownAnimation: ngAnimate.trigger('routeAnimation', [
+        ngAnimate.state('*',
+            ngAnimate.style({
+                opacity: 1,
+                transform: 'translateY(0)'
+            })
+        ),
+        ngAnimate.transition(':enter', [
+            ngAnimate.style({
+                opacity: 0,
+                transform: 'translateY(-100%)'
+            }),
+            ngAnimate.animate('0.3s ease-in')
+        ]),
+        ngAnimate.transition(':leave', [
+            ngAnimate.animate('0.5s ease-out', ngAnimate.style({
+                opacity: 0,
+                transform: 'translateY(100%)'
+            }))
+        ])
+    ]),
+
+    /**
+     * Fly in/out animation
+     */
+    flyInOutAnimation: ngAnimate.trigger('flyInOut', [
+        ngAnimate.state('in',
+            ngAnimate.style({transform: 'translateX(0)'})
+        ),
+        ngAnimate.transition('void => *', [
+            ngAnimate.style({transform: 'translateX(100%)'}),
+            ngAnimate.animate('0.5s 0.1s ease-in')
+        ]),
+        ngAnimate.transition('* => void', ngAnimate.animate('0.3s ease-out', ngAnimate.style({transform: 'translateX(-100%)'})))
+    ])
+
+};
+
+module.exports = new NfAnimations();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry.e2e-spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.e2e-spec.js b/nifi-registry-web-ui/src/main/webapp/nf-registry.e2e-spec.js
new file mode 100644
index 0000000..fdf10bd
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.e2e-spec.js
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var protractor = require('protractor');
+
+describe('NifiRegistry E2E Tests', function () {
+
+    beforeEach(function () {
+        protractor.browser.get('');
+    });
+
+    it('true is true', function () {
+        expect(true).toEqual(true);
+    });
+
+});

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.html b/nifi-registry-web-ui/src/main/webapp/nf-registry.html
new file mode 100644
index 0000000..f8798dd
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.html
@@ -0,0 +1,88 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<md-sidenav-container>
+    <md-sidenav #sidenav mode="over" align="end" opened="false" disableClose="true">
+        <router-outlet name="sidenav"></router-outlet>
+    </md-sidenav>
+    <div id="nf-registry-app-container">
+        <md-toolbar id="nifi-registry-toolbar">
+            <img id="nifi-registry-logo" src="nifi-registry/images/registry-logo-web-app.svg">
+            <div fxFlex="1 1 auto" class="pad-left-xl" [@flyInOut]="nfRegistryService.breadCrumbState">
+                <span routerLink="/nifi-registry/explorer/{{(nfRegistryService.explorerViewType) ? nfRegistryService.explorerViewType : 'grid-list'}}/{{nfRegistryService.registry.id}}">{{nfRegistryService.registry.name}}</span>
+                <md-menu #availableRegistriesMenu="mdMenu" [overlapTrigger]="false">
+                    <button md-menu-item *ngFor="let registry of nfRegistryService.registries"
+                            routerLink="/nifi-registry/explorer/{{(nfRegistryService.explorerViewType) ? nfRegistryService.explorerViewType : 'grid-list'}}/{{registry.id}}">
+                        <span>{{registry.name}}</span>
+                    </button>
+                </md-menu>
+                <span *ngIf="nfRegistryService.perspective === 'administration'"> / Administration</span>
+                <span *ngIf="(nfRegistryService.perspective === 'explorer') && nfRegistryService.bucket.id"
+                      [mdMenuTriggerFor]="availableBucketsMenu"> / {{nfRegistryService.bucket.name}}<i
+                        class="fa fa-caret-down pad-left-sm" aria-hidden="true"></i></span>
+                <span *ngIf="nfRegistryService.perspective === 'explorer' && nfRegistryService.registry.id && !nfRegistryService.bucket.id"
+                      [mdMenuTriggerFor]="availableBucketsMenu"> / All<i class="fa fa-caret-down pad-left-sm"
+                                                                         aria-hidden="true"></i></span>
+                <md-menu #availableBucketsMenu="mdMenu" [overlapTrigger]="false">
+                    <button md-menu-item
+                            routerLink="/nifi-registry/explorer/{{(nfRegistryService.explorerViewType) ? nfRegistryService.explorerViewType : 'grid-list'}}/{{nfRegistryService.registry.id}}">
+                        <span>All buckets...</span>
+                    </button>
+                    <button md-menu-item *ngFor="let bucket of nfRegistryService.buckets"
+                            routerLink="/nifi-registry/explorer/{{(nfRegistryService.explorerViewType) ? nfRegistryService.explorerViewType : 'grid-list'}}/{{nfRegistryService.registry.id}}/{{bucket.id}}">
+                        <span>{{bucket.name}}</span>
+                    </button>
+                </md-menu>
+                <span *ngIf="nfRegistryService.perspective === 'explorer' && nfRegistryService.droplet.id"
+                      [mdMenuTriggerFor]="availableDropletsMenu"> / {{nfRegistryService.droplet.name}}<i
+                        class="fa fa-caret-down pad-left-sm" aria-hidden="true"></i></span>
+                <span [mdMenuTriggerFor]="availableDropletsMenu"
+                      *ngIf="nfRegistryService.perspective === 'explorer' && nfRegistryService.bucket.id && !nfRegistryService.droplet.id"> / All<i
+                        class="fa fa-caret-down pad-left-sm" aria-hidden="true"></i></span>
+                <md-menu #availableDropletsMenu="mdMenu" [overlapTrigger]="false">
+                    <button md-menu-item
+                            routerLink="/nifi-registry/explorer/{{(nfRegistryService.explorerViewType) ? nfRegistryService.explorerViewType : 'grid-list'}}/{{nfRegistryService.registry.id}}/{{nfRegistryService.bucket.id}}">
+                        <span>All droplets...</span>
+                    </button>
+                    <button md-menu-item *ngFor="let droplet of nfRegistryService.droplets"
+                            routerLink="/nifi-registry/explorer/{{(nfRegistryService.explorerViewType) ? nfRegistryService.explorerViewType : 'grid-list'}}/{{nfRegistryService.registry.id}}/{{nfRegistryService.bucket.id}}/{{droplet.id}}">
+                        <span>{{droplet.name}}</span>
+                    </button>
+                </md-menu>
+            </div>
+            <div id="nifi-registry-alerts-count" *ngIf="nfRegistryService.alerts.length > 0">
+                {{nfRegistryService.alerts.length}}
+            </div>
+            <button *ngIf="false" mdTooltip="Alerts" md-icon-button>
+                <i class="fa fa-bell" aria-hidden="true"></i>
+            </button>
+            <button md-ripple [@flyInOut] *ngIf="nfRegistryService.perspective === 'explorer'" md-icon-button
+                    mdTooltip="Registry Administration"
+                    routerLink="/nifi-registry/administration/{{nfRegistryService.registry.id}}/general">
+                <i class="fa fa-wrench" aria-hidden="true"></i>
+            </button>
+            <button md-ripple [@flyInOut] *ngIf="nfRegistryService.perspective === 'administration'" md-mini-fab
+                    mdTooltip="Close settings and return to Explorer perspective."
+                    routerLink="/nifi-registry/explorer/{{(nfRegistryService.explorerViewType) ? nfRegistryService.explorerViewType : 'grid-list'}}/{{nfRegistryService.registry.id}}">
+                <md-icon color="primary">close</md-icon>
+            </button>
+        </md-toolbar>
+        <div id="nf-registry-perspectives-container">
+            <router-outlet></router-outlet>
+        </div>
+    </div>
+</md-sidenav-container>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.js b/nifi-registry-web-ui/src/main/webapp/nf-registry.js
new file mode 100644
index 0000000..4c9263b
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+
+function NfRegistry(nfRegistryService, changeDetectorRef) {
+    this.nfRegistryService = nfRegistryService;
+    this.cd = changeDetectorRef;
+};
+
+NfRegistry.prototype = {
+    constructor: NfRegistry,
+
+    ngOnInit: function () {
+        var self = this;
+        this.nfRegistryService.sidenav = this.sidenav;
+        this.nfRegistryService.getRegistries().then(function (registries) {
+            self.nfRegistryService.registries = registries;
+        });
+    },
+
+    ngAfterViewChecked: function () {
+        // since the child views are updating the nfRegistryService values that are used to display
+        // the breadcrumbs in this component's view we need to manually detect changes at the correct
+        // point in the lifecycle.
+        this.cd.detectChanges();
+    }
+};
+
+NfRegistry.annotations = [
+    new ngCore.Component({
+        selector: 'nf-registry-app',
+        template: require('./nf-registry.html!text'),
+        queries: {
+            sidenav: new ngCore.ViewChild('sidenav')
+        },
+        animations: [nfRegistryAnimations.flyInOutAnimation]
+    })
+];
+
+NfRegistry.parameters = [NfRegistryService, ngCore.ChangeDetectorRef];
+
+module.exports = NfRegistry;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js b/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js
new file mode 100644
index 0000000..9e83795
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.module.js
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngCore = require('@angular/core');
+var ngMoment = require('angular2-moment');
+var NfRegistryRoutes = require('nifi-registry/nf-registry.routes.js');
+var FdsDemo = require('nifi-registry/components/fluid-design-system/fds-demo.js');
+var NfRegistry = require('nifi-registry/nf-registry.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfPageNotFoundComponent = require('nifi-registry/components/page-not-found/nf-registry-page-not-found.js');
+var NfRegistryExplorer = require('nifi-registry/components/explorer/nf-registry-explorer.js');
+var NfRegistryExplorerGridListViewer = require('nifi-registry/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js');
+var NfRegistryAdministration = require('nifi-registry/components/administration/nf-registry-administration.js');
+var NfRegistryGeneralAdministration = require('nifi-registry/components/administration/general/nf-registry-general-administration.js');
+var NfRegistryUsersAdministration = require('nifi-registry/components/administration/users/nf-registry-users-administration.js');
+var NfRegistryAddUser = require('nifi-registry/components/administration/users/add/nf-registry-add-user.js');
+var NfRegistryUserDetails = require('nifi-registry/components/administration/users/details/nf-registry-user-details.js');
+var NfRegistryUserPermissions = require('nifi-registry/components/administration/users/permissions/nf-registry-user-permissions.js');
+var NfRegistryBucketPermissions = require('nifi-registry/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js');
+var NfRegistryWorkflowAdministration = require('nifi-registry/components/administration/workflow/nf-registry-workflow-administration.js');
+var NfRegistryGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js');
+var NfRegistryBucketGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js');
+var NfRegistryDropletGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js');
+var fdsCore = require('@fluid-design-system/core');
+
+function NfRegistryModule() {
+};
+
+NfRegistryModule.prototype = {
+    constructor: NfRegistryModule
+};
+
+NfRegistryModule.annotations = [
+    new ngCore.NgModule({
+        imports: [
+            ngMoment.MomentModule,
+            fdsCore,
+            NfRegistryRoutes
+        ],
+        declarations: [FdsDemo, NfRegistry, NfRegistryExplorer, NfRegistryExplorerGridListViewer, NfRegistryAdministration, NfRegistryGeneralAdministration, NfRegistryUsersAdministration, NfRegistryUserDetails, NfRegistryUserPermissions, NfRegistryBucketPermissions, NfRegistryAddUser, NfRegistryWorkflowAdministration, NfRegistryGridListViewer, NfRegistryBucketGridListViewer, NfRegistryDropletGridListViewer, NfPageNotFoundComponent],
+        providers: [NfRegistryService],
+        bootstrap: [NfRegistry]
+    })
+];
+
+module.exports = NfRegistryModule;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry.routes.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.routes.js b/nifi-registry-web-ui/src/main/webapp/nf-registry.routes.js
new file mode 100644
index 0000000..46bcbec
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.routes.js
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngRouter = require('@angular/router');
+var FdsDemo = require('nifi-registry/components/fluid-design-system/fds-demo.js');
+var NfPageNotFoundComponent = require('nifi-registry/components/page-not-found/nf-registry-page-not-found.js');
+var NfRegistryExplorer = require('nifi-registry/components/explorer/nf-registry-explorer.js');
+var NfRegistryExplorerGridListViewer = require('nifi-registry/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js');
+var NfRegistryAdministration = require('nifi-registry/components/administration/nf-registry-administration.js');
+var NfRegistryGeneralAdministration = require('nifi-registry/components/administration/general/nf-registry-general-administration.js');
+var NfRegistryUsersAdministration = require('nifi-registry/components/administration/users/nf-registry-users-administration.js');
+var NfRegistryAddUser = require('nifi-registry/components/administration/users/add/nf-registry-add-user.js');
+var NfRegistryUserDetails = require('nifi-registry/components/administration/users/details/nf-registry-user-details.js');
+var NfRegistryUserPermissions = require('nifi-registry/components/administration/users/permissions/nf-registry-user-permissions.js');
+var NfRegistryBucketPermissions = require('nifi-registry/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js');
+var NfRegistryWorkflowAdministration = require('nifi-registry/components/administration/workflow/nf-registry-workflow-administration.js');
+var NfRegistryGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js');
+var NfRegistryBucketGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js');
+var NfRegistryDropletGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js');
+
+var NfRegistryRoutes = new ngRouter.RouterModule.forRoot([{
+    path: 'nifi-registry/explorer',
+    component: NfRegistryExplorer,
+    children: [{
+        path: 'grid-list',
+        component: NfRegistryExplorerGridListViewer,
+        children: [{
+            path: ':registryId',
+            component: NfRegistryGridListViewer,
+            children: [{
+                path: ':bucketId',
+                component: NfRegistryBucketGridListViewer,
+                children: [{
+                    path: ':dropletId',
+                    component: NfRegistryDropletGridListViewer
+                }]
+            }]
+        }]
+    }]
+    // canActivate: [AuthGuard] //TODO: https://angular.io/api/router/CanActivate https://scotch.io/tutorials/routing-angular-2-single-page-apps-with-the-component-router
+}, {
+    path: 'nifi-registry/fluid-design-system',
+    component: FdsDemo
+}, {
+    path: 'nifi-registry/administration/:registryId',
+    component: NfRegistryAdministration,
+    children: [{
+        path: '',
+        redirectTo: 'general',
+        pathMatch: 'full'
+    }, {
+        path: 'general',
+        component: NfRegistryGeneralAdministration
+    }, {
+        path: 'users',
+        component: NfRegistryUsersAdministration
+    }, {
+        path: 'workflow',
+        component: NfRegistryWorkflowAdministration
+    }]
+}, {
+    path: 'nifi-registry',
+    redirectTo: '/nifi-registry/explorer/grid-list/23f6cc59-0156-1000-06b4-2b0810089090',
+    pathMatch: 'full'
+}, {
+    path: '',
+    redirectTo: '/nifi-registry/explorer/grid-list/23f6cc59-0156-1000-06b4-2b0810089090',
+    pathMatch: 'full'
+}, {
+    path: '**',
+    component: NfPageNotFoundComponent
+}, {
+    path: 'user/details/:userId',
+    component: NfRegistryUserDetails,
+    outlet: 'sidenav'
+}, {
+    path: 'user/permissions/:userId',
+    component: NfRegistryUserPermissions,
+    outlet: 'sidenav'
+}, {
+    path: 'user/add',
+    component: NfRegistryAddUser,
+    outlet: 'sidenav'
+}, {
+    path: 'bucket/permissions/:bucketId',
+    component: NfRegistryBucketPermissions,
+    outlet: 'sidenav'
+}]);
+
+module.exports = NfRegistryRoutes;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/nf-registry.spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.spec.js b/nifi-registry-web-ui/src/main/webapp/nf-registry.spec.js
new file mode 100644
index 0000000..7ed1206
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.spec.js
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var NfRegistryRoutes = require('nifi-registry/nf-registry.routes.js');
+var ngCoreTesting = require('@angular/core/testing');
+var ngCommon = require('@angular/common');
+var FdsDemo = require('nifi-registry/components/fluid-design-system/fds-demo.js');
+var NfRegistry = require('nifi-registry/nf-registry.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfPageNotFoundComponent = require('nifi-registry/components/page-not-found/nf-registry-page-not-found.js');
+var NfRegistryExplorer = require('nifi-registry/components/explorer/nf-registry-explorer.js');
+var NfRegistryExplorerGridListViewer = require('nifi-registry/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js');
+var NfRegistryAdministration = require('nifi-registry/components/administration/nf-registry-administration.js');
+var NfRegistryGeneralAdministration = require('nifi-registry/components/administration/general/nf-registry-general-administration.js');
+var NfRegistryUsersAdministration = require('nifi-registry/components/administration/users/nf-registry-users-administration.js');
+var NfRegistryAddUser = require('nifi-registry/components/administration/users/add/nf-registry-add-user.js');
+var NfRegistryUserDetails = require('nifi-registry/components/administration/users/details/nf-registry-user-details.js');
+var NfRegistryUserPermissions = require('nifi-registry/components/administration/users/permissions/nf-registry-user-permissions.js');
+var NfRegistryBucketDetails = require('nifi-registry/components/administration/workflow/buckets/details/nf-registry-bucket-details.js');
+var NfRegistryBucketPermissions = require('nifi-registry/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js');
+var NfRegistryWorkflowAdministration = require('nifi-registry/components/administration/workflow/nf-registry-workflow-administration.js');
+var NfRegistryGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js');
+var NfRegistryBucketGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js');
+var NfRegistryDropletGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js');
+var fdsCore = require('@fluid-design-system/core');
+
+describe('NfRegistry Component', function () {
+    var comp;
+    var fixture;
+
+    beforeEach(ngCoreTesting.async(function () {
+        ngCoreTesting.TestBed.configureTestingModule({
+            imports: [
+                fdsCore,
+                NfRegistryRoutes
+            ],
+            declarations: [FdsDemo, NfRegistry, NfRegistryExplorer, NfRegistryExplorerGridListViewer, NfRegistryAdministration, NfRegistryGeneralAdministration, NfRegistryUsersAdministration, NfRegistryUserDetails, NfRegistryUserPermissions, NfRegistryBucketDetails, NfRegistryBucketPermissions, NfRegistryAddUser, NfRegistryWorkflowAdministration, NfRegistryGridListViewer, NfRegistryBucketGridListViewer, NfRegistryDropletGridListViewer, NfPageNotFoundComponent],
+            providers: [NfRegistryService, {provide: ngCommon.APP_BASE_HREF, useValue: '/'}],
+            bootstrap: [NfRegistry]
+        });
+    }));
+
+    it('should create component', function () {
+        fixture = ngCoreTesting.TestBed.createComponent(NfRegistry);
+        fixture.detectChanges();
+        comp = fixture.componentInstance;
+        expect(comp).toBeDefined()
+    });
+});
\ No newline at end of file


[2/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
new file mode 100644
index 0000000..55f58d1
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
@@ -0,0 +1,981 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var covalentCore = require('@covalent/core');
+
+function filterData(data, searchTerm, ignoreCase) {
+    var field = '';
+    if (searchTerm.indexOf(":") > -1) {
+        field = searchTerm.split(':')[0].trim();
+        searchTerm = searchTerm.split(':')[1].trim();
+    }
+    var filter = searchTerm ? (ignoreCase ? searchTerm.toLowerCase() : searchTerm) : '';
+
+    if (filter) {
+        data = data.filter(function (item) {
+            var res = Object.keys(item).find(function (key) {
+                if (field.indexOf(".") > -1) {
+                    var objArray = field.split(".");
+                    var obj = item;
+                    var arrayLength = objArray.length;
+                    for (var i = 0; i < arrayLength; i++) {
+                        try {
+                            obj = obj[objArray[i]];
+                        } catch (e) {
+                            return false;
+                        }
+                    }
+                    var preItemValue = ('' + obj);
+                    var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                    return itemValue.indexOf(filter) > -1;
+                } else {
+                    if (key !== field && field !== '') {
+                        return false;
+                    }
+                    var preItemValue = ('' + item[key]);
+                    var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                    return itemValue.indexOf(filter) > -1;
+                }
+            });
+            return !(typeof res === 'undefined');
+        });
+    }
+    return data;
+};
+
+function NfRegistryService(TdDataTableService) {
+    this.registries = [];
+    this.registry = {};
+    this.bucket = {};
+    this.buckets = [];
+    this.droplet = {};
+    this.droplets = [];
+    this.certifications = [];
+    this.user = {};
+    this.users = [];
+    this.alerts = [];
+    this.explorerViewType = '';
+    this.perspective = '';
+    this.breadCrumbState = 'out';
+    this.dataTableService = TdDataTableService;
+
+    this.filteredDroplets = [];
+
+    this.dropletColumns = [
+        {name: 'name', label: 'Name', sortable: true},
+        {name: 'updated', label: 'Updated', sortable: true}
+    ];
+
+    this.autoCompleteDroplets = [];
+    this.dropletsSearchTerms = [];
+
+    this.filteredBuckets = [];
+
+    this.bucketColumns = [
+        {name: 'name', label: 'Bucket Name', sortable: true, tooltip: 'Sort Buckets by name.'}
+    ];
+
+    this.allBucketsSelected = false;
+    this.autoCompleteBuckets = [];
+    this.bucketsSearchTerms = [];
+
+    this.filteredUsers = [];
+
+    this.userColumns = [
+        {name: 'status', label: 'Status', sortable: true, tooltip: 'User Status.', width: 18},
+        {name: 'name', label: 'Name', sortable: true, tooltip: 'User name.', width: 30},
+        {name: 'provider', label: 'Provider', sortable: true, tooltip: 'Authentication provider.', width: 30}
+    ];
+
+    this.allUsersSelected = false;
+    this.autoCompleteUsers = [];
+    this.selectedUsers = [];
+
+    this.usersSearchTerms = [];
+    this.usersFromRow = 1;
+    this.usersCurrentPage = 1;
+    this.usersPageSize = 5;
+    this.usersPageCount = 0;
+
+    this.filteredCertifications = [];
+
+    this.certificationColumns = [
+        {name: 'name', label: 'Label Name', sortable: true, tooltip: 'Sort Certifications by name.', width: 40},
+        {name: 'usage', label: 'Usage', sortable: true, tooltip: 'Sort Certifications by usage.', width: 30},
+        {name: 'badge', label: 'Badge Design', sortable: false, tooltip: 'Certification badge.', width: 30}
+    ];
+
+    this.autoCompleteCertifications = [];
+    this.certificationsSearchTerms = [];
+};
+
+NfRegistryService.prototype = {
+    constructor: NfRegistryService,
+
+    deleteDroplet: function (id) {
+        //TODO: REST call to API to delete droplet by id.
+    },
+
+    deleteBucket: function (id) {
+        //TODO: REST call to API to delete bucket by id.
+    },
+
+    deleteUser: function (id) {
+        //TODO: REST call to API to delete user by id.
+    },
+
+    suspendUser: function (id) {
+        //TODO: REST call to API to suspend user by id.
+    },
+
+    getRegistries: function () {
+        //TODO: leverage $http service to make call to nifi registry api. For now just return mock data...
+        var self = this;
+        var date = new Date();
+        return new Promise(
+            function (resolve) {
+                setTimeout(
+                    function () {
+                        resolve(self.registries = [{
+                            id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                            name: "Nifi Registry",
+                            users: [{
+                                id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                name: 'Scotty 2 Hotty',
+                                status: 'authorized',
+                                provider: 'Friendly LDAP Provider',
+                                type: 'user',
+                                activities: [{
+                                    id: '25fd6vv87-3249-0001-05g6-4d4767890765',
+                                    description: 'Saved something...',
+                                    created: date.setDate(date.getDate() - 1),
+                                    updated: new Date()
+                                }],
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav',
+
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Suspend',
+                                    'icon': 'fa fa-ban',
+                                    'tooltip': 'Suspend User'
+                                }]
+                            }, {
+                                id: '25fd6vv87-3249-0001-05g6-4d4767890765',
+                                name: 'Group 1',
+                                status: 'suspended',
+                                provider: 'IOAT',
+                                type: 'group',
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Reauthorize',
+                                    'icon': 'fa fa-check-circle',
+                                    'tooltip': 'Reauthorize User'
+                                }]
+                            }, {
+                                id: '98f6cc59-0156-1000-06b4-2b0810089090',
+                                name: 'G$',
+                                status: 'authorized',
+                                provider: 'Friendly LDAP Provider',
+                                type: 'user',
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Suspend',
+                                    'icon': 'fa fa-ban',
+                                    'tooltip': 'Suspend User'
+                                }]
+                            }, {
+                                id: '65fd6vv87-3249-0001-05g6-4d4767890765',
+                                name: 'Group 2',
+                                status: 'suspended',
+                                provider: 'IOAT',
+                                type: 'group',
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Reauthorize',
+                                    'icon': 'fa fa-check-circle',
+                                    'tooltip': 'Reauthorize User'
+                                }]
+                            }],
+                            buckets: [{
+                                id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                                name: "My Flows",
+                                actions: [{
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage Bucket Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete Bucket'
+                                }],
+                                droplets: [{
+                                    id: '23f6cc59-0156-1000-09b4-2b0610089090',
+                                    name: "Security_Dev_Ops",
+                                    displayName: 'Security Dev Ops',
+                                    type: 'Data Flow',
+                                    sublabel: 'Some info',
+                                    updated: new Date(),
+                                    description: 'This is the most secure flow ever!',
+                                    versions: [{
+                                        id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                        revision: '1',
+                                        dependentFlows: [{
+                                            id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+                                        }],
+                                        author: '2Hot',
+                                        comment: 'delete ListenHttp',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '2',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: '2Hot',
+                                        comment: 'added Labels for better description of groups of processors',
+                                        created: new Date(),
+                                        updated: new Date()
+                                    }],
+                                    flows: [],
+                                    extensions: [],
+                                    assets: [],
+                                    actions: [{
+                                        'name': 'Delete',
+                                        'icon': 'fa fa-close',
+                                        'tooltip': 'Delete User'
+                                    }]
+                                }]
+                            }, {
+                                id: '23f6cc59-0156-1000-09b4-2b0810089080',
+                                name: "Development Flows",
+                                droplets: [{
+                                    id: '23f6cc59-0156-1000-09b4-2b0610089090',
+                                    name: "Fraud Detection Flow",
+                                    displayName: 'Fraud Detection Flow',
+                                    type: 'Data Flow',
+                                    sublabel: 'A sublabel',
+                                    updated: new Date(date.setDate(date.getDate() - 2)),
+                                    description: 'This flow detects fraud!',
+                                    versions: [{
+                                        id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                        revision: '1',
+                                        dependentFlows: [{
+                                            id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+                                        }],
+                                        author: 'G$',
+                                        comment: 'added funnel',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '2',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: '2Hot',
+                                        comment: 'added Execute script',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '77fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '3',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: 'Payne',
+                                        comment: 'removed Execute script',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '96fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '4',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: 'G$',
+                                        comment: 'add Execute script',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }],
+                                    flows: [],
+                                    extensions: [],
+                                    assets: [],
+                                    actions: [{
+                                        'name': 'Delete',
+                                        'icon': 'fa fa-close'
+                                    }]
+                                }, {
+                                    id: '59f6cc23-0156-1000-09b4-2b0610089090',
+                                    name: "Cyber Security",
+                                    displayName: 'Cyber Security',
+                                    type: 'Data Flow',
+                                    sublabel: 'A sublabel',
+                                    updated: new Date(date.setDate(date.getDate() - 1)),
+                                    description: 'This is the most cyber secure flow ever!',
+                                    versions: [{
+                                        id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                        revision: '1',
+                                        dependentFlows: [{
+                                            id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+                                        }],
+                                        author: 'G$',
+                                        comment: 'added funnel',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }],
+                                    flows: [],
+                                    extensions: [],
+                                    assets: [],
+                                    actions: [{
+                                        'name': 'Delete',
+                                        'icon': 'fa fa-close',
+                                        'tooltip': 'Delete User'
+                                    }]
+                                }],
+                                actions: [{
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage Bucket Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete Bucket'
+                                }]
+                            }] // some data model for the contents of a registry
+                        }])
+                    }, 0);
+            }
+        );
+    },
+
+    getRegistry: function (registryId) {
+        return this.getRegistries().then(
+            function (registries) {
+                return registries.find(
+                    function (registry) {
+                        if (registryId === registry.id) {
+                            return registry;
+                        }
+                    });
+            });
+    },
+
+    getDroplet: function (registryId, bucketId, dropletId) {
+        return this.getDroplets(registryId, bucketId, dropletId).then(
+            function (droplets) {
+                return droplets[0];
+            });
+    },
+
+    getDroplets: function (registryIds, bucketIds, dropletIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var buckets = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.buckets.find(
+                                function (bucket) {
+                                    if (bucketIds === undefined || bucketIds.indexOf(bucket.id) >= 0) {
+                                        buckets.push(bucket);
+                                    }
+                                });
+                        }
+                    });
+
+                var droplets = [];
+
+                buckets.find(
+                    function (bucket) {
+                        bucket.droplets.find(
+                            function (droplet) {
+                                if (dropletIds === undefined || dropletIds.indexOf(droplet.id) >= 0) {
+                                    droplets.push(droplet);
+                                }
+                            });
+                    });
+
+                return droplets;
+            });
+
+    },
+
+    isDropletFilterChecked: function (term) {
+        return (this.dropletsSearchTerms.indexOf(term) > -1);
+    },
+
+    getDropletTypeCount: function (type) {
+        return this.filteredDroplets.filter(function (droplet) {
+            return droplet.type === type;
+        }).length;
+    },
+
+    getDropletCertificationCount: function (certification) {
+        return this.filteredDroplets.filter(function (droplet) {
+            return Object.keys(droplet).find(function (key) {
+                if (key === certification && droplet[certification].type === 'certification') {
+                    return droplet;
+                }
+            });
+        }).length;
+    },
+
+    getSortByLabel: function () {
+        var sortByColumn;
+        var arrayLength = this.dropletColumns.length;
+        for (var i = 0; i < arrayLength; i++) {
+            if (this.dropletColumns[i].active === true) {
+                sortByColumn = this.dropletColumns[i];
+                break;
+            }
+        }
+
+        if (sortByColumn) {
+            var label = '';
+            switch (sortByColumn.label) {
+                case 'Updated':
+                    label = (sortByColumn.sortOrder === 'ASC') ? 'Newest (update)' : 'Oldest (update)';
+                    break;
+                case 'Name':
+                    label = (sortByColumn.sortOrder === 'ASC') ? 'Name (a - z)' : 'Name (z - a)';
+                    break;
+            }
+            return label;
+        }
+    },
+
+    generateSortMenuLabels: function (col) {
+        var label = '';
+        switch (col.label) {
+            case 'Updated':
+                label = (col.sortOrder !== 'ASC') ? 'Newest (update)' : 'Oldest (update)';
+                break;
+            case 'Name':
+                label = (col.sortOrder !== 'ASC') ? 'Name (a - z)' : 'Name (z - a)';
+                break;
+        }
+        return label;
+    },
+
+    sortDroplets: function (sortEvent, column) {
+        if (column.sortable === true) {
+            // toggle column sort order
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterDroplets(column.name, sortOrder);
+            this.activeDropletColumn = column;
+            //only one column can be actively sorted so we reset all to inactive
+            this.dropletColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    dropletsSearchRemove: function (searchTerm) {
+        this.filterDroplets(this.activeDropletColumn.name, this.activeDropletColumn.sortOrder);
+    },
+
+    dropletsSearchAdd: function (searchTerm) {
+        this.filterDroplets(this.activeDropletColumn.name, this.activeDropletColumn.sortOrder);
+    },
+
+    toggleDropletsFilter: function (searchTerm) {
+        var applySearchTerm = true;
+        // check if the search term is already applied and remove it if true
+        if (this.dropletsSearchTerms.length > 0) {
+            var arrayLength = this.dropletsSearchTerms.length;
+            for (var i = 0; i < arrayLength; i++) {
+                var index = this.dropletsSearchTerms.indexOf(searchTerm);
+                if (index > -1) {
+                    this.dropletsSearchTerms.splice(index, 1);
+                    applySearchTerm = false;
+                }
+            }
+        }
+
+        // if we just removed the search term do NOT apply it again
+        if (applySearchTerm) {
+            this.dropletsSearchTerms.push(searchTerm);
+        }
+
+        this.filterDroplets(this.activeDropletColumn.name, this.activeDropletColumn.sortOrder);
+    },
+
+    filterDroplets: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+        // if `sortBy` is `undefined` then find the first sortable column in this.dropletColumns
+        if (sortBy === undefined) {
+            var arrayLength = this.dropletColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.dropletColumns[i].sortable === true) {
+                    sortBy = this.dropletColumns[i].name;
+                    this.activeDropletColumn = this.dropletColumns[i];
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.dropletColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.dropletColumns[i].active = true;
+                    this.dropletColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newData = this.droplets;
+
+        for (var i = 0; i < this.dropletsSearchTerms.length; i++) {
+            newData = filterData(newData, this.dropletsSearchTerms[i], true, this.activeDropletColumn.name);
+        }
+
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.filteredDroplets = newData;
+        this.getAutoCompleteDroplets();
+    },
+
+    getAutoCompleteDroplets: function () {
+        var self = this;
+        this.autoCompleteDroplets = [];
+        this.dropletColumns.forEach(function (c) {
+            return self.filteredDroplets.forEach(function (r) {
+                return (r[c.name.toLowerCase()]) ? self.autoCompleteDroplets.push(r[c.name.toLowerCase()].toString()) : '';
+            })
+        });
+    },
+
+    getBucket: function (registryId, bucketId) {
+        return this.getBuckets(registryId, bucketId).then(
+            function (buckets) {
+                return buckets[0];
+            });
+    },
+
+    getBuckets: function (registryIds, bucketIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var buckets = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.buckets.find(
+                                function (bucket) {
+                                    if (bucketIds === undefined || bucketIds.indexOf(bucket.id) >= 0) {
+                                        buckets.push(bucket);
+                                    }
+                                });
+                        }
+                    });
+
+                return buckets;
+            });
+
+    },
+
+    filterBuckets: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+
+        // if `sortBy` is `undefined` then find the first sortable column in this.bucketColumns
+        if (sortBy === undefined) {
+            var arrayLength = this.bucketColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.bucketColumns[i].sortable === true) {
+                    sortBy = this.bucketColumns[i].name;
+                    this.activeBucketColumn = this.bucketColumns[i];
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.bucketColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.bucketColumns[i].active = true;
+                    this.bucketColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newData = this.buckets;
+
+        for (var i = 0; i < this.bucketsSearchTerms.length; i++) {
+            newData = filterData(newData, this.bucketsSearchTerms[i], true, this.activeBucketColumn.name);
+        }
+
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.filteredBuckets = newData;
+        this.getAutoCompleteBuckets();
+    },
+
+    getAutoCompleteBuckets: function () {
+        var self = this;
+        this.autoCompleteBuckets = [];
+        this.bucketColumns.forEach(function (c) {
+            return self.filteredBuckets.forEach(function (r) {
+                return (r[c.name.toLowerCase()]) ? self.autoCompleteBuckets.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    sortBuckets: function (sortEvent, column) {
+        if (column.sortable === true) {
+            // toggle column sort order
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterBuckets(column.name, sortOrder);
+            this.activeBucketsColumn = column;
+            //only one column can be actively sorted so we reset all to inactive
+            this.bucketColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    bucketsSearchRemove: function (searchTerm) {
+        this.filterDroplets(this.activeBucketsColumn.name, this.activeBucketsColumn.sortOrder);
+    },
+
+    bucketsSearchAdd: function (searchTerm) {
+        this.filterDroplets(this.activeBucketsColumn.name, this.activeBucketsColumn.sortOrder);
+    },
+
+    allFilteredBucketsSelected: function () {
+        this.filteredBuckets.forEach(function (c) {
+            if (c.checked === undefined || c.checked === false) {
+                return false;
+            }
+        });
+
+        return true;
+    },
+
+    toggleBucketSelect: function (row) {
+        if (this.allFilteredBucketsSelected()) {
+            this.allBucketsSelected = true;
+        } else {
+            this.allBucketsSelected = false;
+        }
+    },
+
+    getCertification: function (registryId, certificatonId) {
+        return this.getCertifications(registryId, certificatonId).then(
+            function (certificatons) {
+                return certificatons[0];
+            });
+    },
+    getCertifications: function (registryIds, certificatonIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var certificatons = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.certifications.find(
+                                function (certificaton) {
+                                    if (certificatonIds === undefined || certificatonIds.indexOf(certificaton.id) >= 0) {
+                                        certificatons.push(certificaton);
+                                    }
+                                });
+                        }
+                    });
+
+                return certificatons;
+            });
+
+    },
+
+    filterCertifications: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+
+        // if `sortBy` is `undefined` then find the first sortable column in this.bucketColumns
+        if (sortBy === undefined) {
+            var arrayLength = this.bucketColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.bucketColumns[i].sortable === true) {
+                    sortBy = this.bucketColumns[i].name;
+                    this.activeBucketColumn = this.bucketColumns[i];
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.bucketColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.bucketColumns[i].active = true;
+                    this.bucketColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newData = this.certifications;
+
+        for (var i = 0; i < this.certificationsSearchTerms.length; i++) {
+            newData = filterData(newData, this.certificationsSearchTerms[i], true, this.activeBucketColumn.name);
+        }
+
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.filteredCertifications = newData;
+        this.getAutoCompleteCertifications();
+    },
+
+    getAutoCompleteCertifications: function () {
+        var self = this;
+        this.autoCompleteCertifications = [];
+        this.bucketColumns.forEach(function (c) {
+            return self.filteredCertifications.forEach(function (r) {
+                return (r[c.name.toLowerCase()]) ? self.autoCompleteCertifications.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    sortCertifications: function (sortEvent, column) {
+        if (column.sortable === true) {
+            // toggle column sort order
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterCertifications(column.name, sortOrder);
+            this.activeCertificationsColumn = column;
+            //only one column can be actively sorted so we reset all to inactive
+            this.bucketColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    certificationsSearchRemove: function (searchTerm) {
+        this.filterDroplets(this.activeCertificationsColumn.name, this.activeCertificationsColumn.sortOrder);
+    },
+
+    certificationsSearchAdd: function (searchTerm) {
+        this.filterDroplets(this.activeCertificationsColumn.name, this.activeCertificationsColumn.sortOrder);
+    },
+
+    getUser: function (registryId, userId) {
+        return this.getUsers(registryId, userId).then(
+            function (users) {
+                return users[0];
+            });
+    },
+
+    getUsers: function (registryIds, userIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var users = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.users.find(
+                                function (user) {
+                                    if (userIds === undefined || userIds.indexOf(user.id) >= 0) {
+                                        users.push(user);
+                                    }
+                                });
+                        }
+                    });
+
+                return users;
+            });
+
+    },
+
+    sortUsers: function (sortEvent, column) {
+        if (column.sortable) {
+            var sortBy = column.name;
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterUsers(sortBy, sortOrder);
+
+            //only one column can be actively sorted so we reset all to inactive
+            this.userColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    usersSearchRemove: function (searchTerm) {
+        //only remove the first occurrence of the search term
+        var index = this.usersSearchTerms.indexOf(searchTerm);
+        if (index !== -1) {
+            this.usersSearchTerms.splice(index, 1);
+        }
+        this.usersCurrentPage = 1;
+        this.usersFromRow = 1;
+        this.usersPageSize = 1;
+        this.filterUsers();
+    },
+
+    usersSearchAdd: function (searchTerm) {
+        this.usersSearchTerms.push(searchTerm);
+        this.usersCurrentPage = 1;
+        this.usersFromRow = 1;
+        this.usersPageSize = 1;
+        this.filterUsers();
+    },
+
+    pageUsers: function (pagingEvent) {
+        this.usersFromRow = pagingEvent.fromRow;
+        this.usersCurrentPage = pagingEvent.page;
+        this.usersPageSize = pagingEvent.pageSize;
+        this.filterUsers();
+    },
+
+    filterUsers: function (sortBy, sortOrder) {
+        if (this.allUsersSelected) {
+            this.toggleUsersSelectAll();
+        }
+        this.deselectAllUsers();
+        var newData = this.users;
+
+        for (var i = 0; i < this.usersSearchTerms.length; i++) {
+            newData = filterData(newData, this.usersSearchTerms[i], true);
+        }
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.usersPageCount = newData.length;
+        newData = this.dataTableService.pageData(newData, this.usersFromRow, this.usersCurrentPage * this.usersPageSize);
+        this.filteredUsers = newData;
+        this.getAutoCompleteUsers();
+    },
+
+    toggleUserSelect: function (row) {
+        if (this.allFilteredUsersSelected()) {
+            this.allUsersSelected = true;
+        } else {
+            this.allUsersSelected = false;
+        }
+    },
+
+    toggleUsersSelectAll: function () {
+        if (this.allUsersSelected) {
+            this.selectAllUsers();
+        } else {
+            this.deselectAllUsers();
+        }
+    },
+
+    selectAllUsers: function () {
+        this.filteredUsers.forEach(function (c) {
+            c.checked = true;
+        });
+    },
+
+    deselectAllUsers: function () {
+        this.filteredUsers.forEach(function (c) {
+            c.checked = false;
+        });
+    },
+    allFilteredUsersSelected: function () {
+        var allFilteredUsersSelected = true;
+        this.filteredUsers.forEach(function (c) {
+            if (c.checked === undefined || c.checked === false) {
+                allFilteredUsersSelected = false;
+            }
+        });
+
+        return allFilteredUsersSelected;
+    },
+
+    getAutoCompleteUsers: function () {
+        var self = this;
+        this.autoCompleteUsers = [];
+        this.userColumns.forEach(function (c) {
+            self.filteredUsers.forEach(function (r) {
+                (r[c.name.toLowerCase()]) ? self.autoCompleteUsers.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    //</editor-fold>
+
+    setBreadcrumbState: function (state) {
+        this.breadCrumbState = state;
+    }
+};
+
+NfRegistryService.parameters = [covalentCore.TdDataTableService];
+
+module.exports = NfRegistryService;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js b/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js
new file mode 100644
index 0000000..8e33bb5
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
+var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
+var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
+
+module.exports.translate = function (load) {
+    if (load.source.indexOf('moduleId') != -1) return load;
+
+    var url = document.createElement('a');
+    url.href = load.address;
+
+    var basePathParts = url.pathname.split('/');
+
+    basePathParts.pop();
+    var basePath = basePathParts.join('/');
+
+    var baseHref = document.createElement('a');
+    baseHref.href = this.baseURL;
+    baseHref = baseHref.pathname;
+
+    if (!baseHref.startsWith('/base/')) { // it is not karma
+        basePath = basePath.replace(baseHref, '');
+    }
+
+    load.source = load.source
+        .replace(templateUrlRegex, function (match, quote, url) {
+            var resolvedUrl = url;
+
+            if (url.startsWith('.')) {
+                resolvedUrl = basePath + url.substr(1);
+            }
+
+            return 'templateUrl: "' + resolvedUrl + '"';
+        })
+        .replace(stylesRegex, function (match, relativeUrls) {
+            var urls = [];
+
+            while ((match = stringRegex.exec(relativeUrls)) !== null) {
+                if (match[2].startsWith('.')) {
+                    urls.push('"' + basePath + match[2].substr(1) + '"');
+                } else {
+                    urls.push('"' + match[2] + '"');
+                }
+            }
+
+            return "styleUrls: [" + urls.join(', ') + "]";
+        });
+
+    return load;
+};

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js b/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js
new file mode 100644
index 0000000..1d96854
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function (global) {
+    System.config({
+        baseURL: "./webapp/",
+        paths: {
+            // paths serve as alias
+            'npm:': './node_modules/'
+        },
+        // map tells the System loader where to look for things
+        map: {
+            'text': 'npm:systemjs-plugin-text/text.js',
+            'app': 'webapp/',
+
+            // jquery
+            'jquery': 'npm:jquery/dist/jquery.min.js',
+
+            // Angular
+            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
+            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
+            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
+            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
+            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
+            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
+            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
+            '@angular/flex-layout': 'npm:@angular/flex-layout/bundles/flex-layout.umd.js',
+            '@angular/material': 'npm:@angular/material/bundles/material.umd.js',
+            '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
+            '@angular/cdk': 'npm:@angular/cdk/bundles/cdk.umd.js',
+            '@angular/cdk/a11y': 'npm:@angular/cdk/bundles/cdk-a11y.umd.js',
+            '@angular/cdk/collections': 'npm:@angular/cdk/bundles/cdk-collections.umd.js',
+            '@angular/cdk/observers': 'npm:@angular/cdk/bundles/cdk-observers.umd.js',
+            '@angular/cdk/overlay': 'npm:@angular/cdk/bundles/cdk-overlay.umd.js',
+            '@angular/cdk/platform': 'npm:@angular/cdk/bundles/cdk-platform.umd.js',
+            '@angular/cdk/portal': 'npm:@angular/cdk/bundles/cdk-portal.umd.js',
+            '@angular/cdk/keycodes': 'npm:@angular/cdk/bundles/cdk-keycodes.umd.js',
+            '@angular/cdk/bidi': 'npm:@angular/cdk/bundles/cdk-bidi.umd.js',
+            '@angular/cdk/coercion': 'npm:@angular/cdk/bundles/cdk-coercion.umd.js',
+            '@angular/cdk/table': 'npm:@angular/cdk/bundles/cdk-table.umd.js',
+            '@angular/cdk/rxjs': 'npm:@angular/cdk/bundles/cdk-rxjs.umd.js',
+            '@angular/cdk/scrolling': 'npm:@angular/cdk/bundles/cdk-scrolling.umd.js',
+            '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
+            '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
+            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
+
+            // needed to support gestures for angular material
+            'hammerjs': 'npm:hammerjs/hammer.min.js',
+
+            // Covalent
+            '@covalent/core': 'npm:@covalent/core/core.umd.js',
+
+            // other libraries
+            'rxjs': 'npm:rxjs',
+            'moment': 'npm:moment',
+            'angular2-moment': 'npm:angular2-moment',
+            'switchMap': 'npm:rxjs/add/operator/switchMap',
+            'zone.js': 'npm:zone.js/dist/zone.js',
+            'core-js': 'npm:core-js/client/shim.min.js',
+
+            // Fluid Design System
+            '@fluid-design-system/core': 'npm:@fluid-design-system/dist/platform/core/fluid-design-system.module.js',
+            '@fluid-design-system/dialogs': 'npm:@fluid-design-system/dist/platform/core/dialogs/fds-dialogs.module.js',
+            '@fluid-design-system/dialog-component': 'npm:@fluid-design-system/dist/platform/core/dialogs/fds-dialog.component.js',
+            '@fluid-design-system/dialog-service': 'npm:@fluid-design-system/dist/platform/core/dialogs/services/dialog.service.js',
+            '@fluid-design-system/confirm-dialog-component': 'npm:@fluid-design-system/dist/platform/core/dialogs/confirm-dialog/confirm-dialog.component.js',
+
+            // Nifi Registry
+            'nifi-registry/nf-registry.module.js': 'nf-registry.module.js',
+            'nifi-registry/nf-registry.animations.js': 'nf-registry.animations.js',
+            'nifi-registry/nf-registry.routes.js': 'nf-registry.routes.js',
+            'nifi-registry/components/fluid-design-system/fds-demo.js': 'components/fluid-design-system/fds-demo.js',
+            'nifi-registry/nf-registry.js': 'nf-registry.js',
+            'nifi-registry/services/nf-registry.service.js': 'services/nf-registry.service.js',
+            'nifi-registry/components/page-not-found/nf-registry-page-not-found.js': 'components/page-not-found/nf-registry-page-not-found.js',
+            'nifi-registry/components/explorer/nf-registry-explorer.js': 'components/explorer/nf-registry-explorer.js',
+            'nifi-registry/components/explorer/list/nf-registry-explorer-list-viewer.js': 'components/explorer/list/nf-registry-explorer-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js': 'components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js',
+            'nifi-registry/components/administration/nf-registry-administration.js': 'components/administration/nf-registry-administration.js',
+            'nifi-registry/components/administration/general/nf-registry-general-administration.js': 'components/administration/general/nf-registry-general-administration.js',
+            'nifi-registry/components/administration/users/nf-registry-users-administration.js': 'components/administration/users/nf-registry-users-administration.js',
+            'nifi-registry/components/administration/users/add/nf-registry-add-user.js': 'components/administration/users/add/nf-registry-add-user.js',
+            'nifi-registry/components/administration/users/details/nf-registry-user-details.js': 'components/administration/users/details/nf-registry-user-details.js',
+            'nifi-registry/components/administration/users/permissions/nf-registry-user-permissions.js': 'components/administration/users/permissions/nf-registry-user-permissions.js',
+            'nifi-registry/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js': 'components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js',
+            'nifi-registry/components/administration/workflow/nf-registry-workflow-administration.js': 'components/administration/workflow/nf-registry-workflow-administration.js',
+            'nifi-registry/components/explorer/list/registry/nf-registry-list-viewer.js': 'components/explorer/list/registry/nf-registry-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js': 'components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/nf-registry-bucket-list-viewer.js': 'components/explorer/list/registry/bucket/nf-registry-bucket-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js': 'components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/droplet/nf-registry-droplet-list-viewer.js': 'components/explorer/list/registry/bucket/droplet/nf-registry-droplet-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js': 'components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js',
+            'nifi-registry/components/explorer/list/registry/nf-registry-details-viewer.js': 'components/explorer/list/registry/nf-registry-details-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/nf-registry-bucket-details-viewer.js': 'components/explorer/list/registry/bucket/nf-registry-bucket-details-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/droplet/nf-registry-droplet-details-viewer.js': 'components/explorer/list/registry/bucket/droplet/nf-registry-droplet-details-viewer.js'
+        },
+        // packages tells the System loader how to load when no filename and/or no extension
+        packages: {
+            'app': {
+                defaultExtension: 'js',
+                meta: {
+                    './*.js': {
+                        loader: 'systemjs-angular-loader.js'
+                    }
+                }
+            },
+            'systemjs-angular-loader.js': {
+                loader: false
+            },
+            'rxjs': {
+                defaultExtension: 'js'
+            },
+            'moment': {
+                main: './moment.js',
+                defaultExtension: 'js'
+            },
+            'angular2-moment': {
+                main: './index.js',
+                defaultExtension: 'js'
+            }
+        }
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js b/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js
new file mode 100644
index 0000000..e2a256a
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Add barrels and stuff
+ */
+(function (global) {
+    System.config({
+        packages: {
+            // add packages here
+        }
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js b/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js
new file mode 100644
index 0000000..a9d72c6
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function (global) {
+    System.config({
+        paths: {
+            // paths serve as alias
+            'npm:': 'nifi-registry/node_modules/'
+        },
+        // map tells the System loader where to look for things
+        map: {
+            'text': 'npm:systemjs-plugin-text/text.js',
+            'app': './webapp',
+
+            // jquery
+            'jquery': 'npm:jquery/dist/jquery.min.js',
+
+            // Angular
+            '@angular/core': 'npm:@angular/core/bundles/core.umd.min.js',
+            '@angular/common': 'npm:@angular/common/bundles/common.umd.min.js',
+            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.min.js',
+            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.min.js',
+            '@angular/http': 'npm:@angular/http/bundles/http.umd.min.js',
+            '@angular/router': 'npm:@angular/router/bundles/router.umd.min.js',
+            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.min.js',
+            '@angular/flex-layout': 'npm:@angular/flex-layout/bundles/flex-layout.umd.js',
+            '@angular/material': 'npm:@angular/material/bundles/material.umd.min.js',
+            '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.min.js',
+            '@angular/cdk': 'npm:@angular/cdk/bundles/cdk.umd.min.js',
+            '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.min.js',
+            '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.min.js',
+            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.min.js',
+
+            // Covalent
+            '@covalent/core': 'npm:@covalent/core/core.umd.js',
+
+            // other libraries
+            'rxjs': 'npm:rxjs',
+            'switchMap': 'npm:rxjs/add/operator/switchMap',
+
+            // Fluid Design System
+            '@fluid-design-system/core': 'npm:@fluid-design-system/dist/platform/core/fluid-design-system.module.js',
+        },
+        // packages tells the System loader how to load when no filename and/or no extension
+        packages: {
+            app: {
+                defaultExtension: 'js',
+                meta: {
+                    './*.js': {
+                        loader: 'nifi-registry/systemjs-angular-loader.js'
+                    }
+                }
+            },
+            'nifi-registry/systemjs-angular-loader.js': {
+                loader: false
+            },
+            rxjs: {
+                defaultExtension: 'js'
+            }
+        }
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss b/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss
new file mode 100644
index 0000000..a106f77
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the 'License'); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.fa-rotate-45 {
+    -webkit-transform: rotate(45deg);
+    -moz-transform: rotate(45deg);
+    -ms-transform: rotate(45deg);
+    -o-transform: rotate(45deg);
+    transform: rotate(45deg);
+}
+
+.capitalize {
+    text-transform: capitalize;
+}
+
+.uppercase {
+    text-transform: uppercase;
+}
+
+.info {
+    color: $blue8;
+}
+
+.authorized {
+    color: $red2;
+}
+
+.suspended {
+    color: $green3;
+}
+
+.nf-registry-button-container {
+    position: absolute;
+    bottom: 0px;
+    height: 64px;
+    left: 0px;
+    right: 0px;
+    border-top: 1px solid $grey4;
+}
+
+.nf-registry-button-container .done-button {
+    float: right;
+    margin-right: 15px;
+    margin-top: 15px;
+}
+
+.td-expansion-content {
+    background: $grey6;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
new file mode 100644
index 0000000..78c19d1
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+body {
+    background: $grey5;
+}
+
+#nf-registry-app-container {
+    margin: 0;
+    width: 100%;
+    height: 100%;
+}
+
+#nifi-registry-logo {
+    height: 44px;
+}
+
+#nifi-registry-alerts-count {
+    border-radius: 50%;
+    background: $warnColor;
+    height: 14px;
+    width: 14px;
+    position: relative;
+    left: 9px;
+    color: #FFFFFF;
+    font-size: 9px;
+    text-align: center;
+    line-height: 14px;
+}
+
+#nifi-registry-toolbar {
+    min-width: 1045px;
+    background-color: #FFFFFF;
+    position: absolute;
+    z-index: 1000;
+    background: $grey1;
+}
+
+#nifi-registry-toolbar .mat-icon-button {
+    color: $grey5;
+    font-size: 20px;
+    margin: 0;
+}
+
+#nifi-registry-toolbar .mat-select-value-text, #nifi-registry-toolbar .mat-select-arrow{
+    color: white;
+}
+
+#nifi-registry-toolbar span, #nifi-registry-toolbar .link{
+    color: $grey5;
+}
+
+#nf-registry-perspectives-container {
+    position: absolute;
+    top: 64px;
+    left: 0px;
+    right: 0px;
+    bottom: 0px;
+    background: $grey5;
+    min-height: 370px;
+    min-width: 1045px;
+    overflow: auto;
+}
+
+md-sidenav {
+    width: 40%;
+    min-width: 418px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss
new file mode 100644
index 0000000..aa59100
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#nifi-registry-administration-perspective {
+    position: absolute;
+    top: 20px;
+    left: 50px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss
new file mode 100644
index 0000000..ab3548d
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#nifi-registry-general-administration-perspective {
+    position: absolute;
+    top: 79px;
+    left: 50px;
+    right: calc(50% - 5px);
+    bottom: 20px;
+    background: white;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss
new file mode 100644
index 0000000..650593f
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#nifi-registry-users-administration-perspective {
+    position: absolute;
+    top: 79px;
+    left: 50px;
+    right: 20px;
+    bottom: 20px;
+    background: white;
+}
+
+#nifi-registry-users-administration-list-container {
+    overflow-y: auto;
+    position: absolute;
+    bottom: 49px;
+    top: 124px;
+    left: 30px;
+    right: 30px;
+    overflow-x: hidden;
+}
+
+#nifi-registry-users-administration-list-paging-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+}
+
+#nf-registry-user-permissions-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+
+#nf-registry-add-user-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+
+#nf-registry-user-details-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss
new file mode 100644
index 0000000..0384079
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#nifi-registry-workflow-administration-perspective-buckets-container {
+    position: absolute;
+    top: 79px;
+    left: 50px;
+    right: calc(50% - 5px);
+    bottom: 20px;
+    background: white;
+}
+
+//#nifi-registry-workflow-administration-perspective-certifications-container {
+//    position: absolute;
+//    top: 79px;
+//    left: calc(50% + 25px);
+//    right: 20px;
+//    bottom: 20px;
+//    background: white;
+//}
+
+#nifi-registry-workflow-administration-buckets-list-container {
+    overflow-y: auto;
+    position: absolute;
+    bottom: 30px;
+    top: 171px;
+    left: 30px;
+    right: 30px;
+    overflow-x: hidden;
+}
+
+//#nifi-registry-workflow-administration-certifications-list-container {
+//    overflow-y: auto;
+//    position: absolute;
+//    bottom: 30px;
+//    top: 171px;
+//    left: 30px;
+//    right: 30px;
+//    overflow-x: hidden;
+//}
+
+#nf-registry-workflow-bucket-permissions-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
new file mode 100644
index 0000000..0038782
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#nf-registry-droplet-filter-clear-grouping-button-container {
+    height: 36px;
+    line-height: 36px;
+}
+
+#nf-registry-droplet-filter-clear-grouping-button-container i{
+    color: $red2;
+}
+
+#droplet-sort-by-field {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    //width: 85px;
+    text-align: end;
+}
+
+#nifi-registry-explorer-grid-list-viewer-droplet-container-details {
+    position: relative;
+}
+
+#nifi-registry-explorer-grid-list-viewer-droplet-container-details-change-log {
+    position: relative;
+    left: 0px;
+    max-height: 230px;
+    overflow: auto;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss
new file mode 100644
index 0000000..75c5ebb
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#fds-demo {
+    position: absolute;
+    top: 0px;
+    bottom: 0px;
+    left: 0px;
+    right: 0px;
+    overflow: auto;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss b/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
new file mode 100644
index 0000000..8d9e878
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the 'License'); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Welcome to Compass.
+ * In this file you should centralize your imports. After compilation simply import this file using the following HTML or equivalent:
+ * <link href='/stylesheets/nifi-flow-registry.css' media='screen, projection' rel='stylesheet' type='text/css' /> */
+
+@import '../../platform/core/common/styles/globalVars';
+@import 'structureElements';
+@import 'helperClasses';
+@import 'components/administration/structureElements';
+@import 'components/administration/general/structureElements';
+@import 'components/administration/users/structureElements';
+@import 'components/administration/workflow/structureElements';
+@import 'components/explorer/grid-list/structureElements';
+@import 'components/fluid-design-system/structureElements';


[7/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/frontend/protractor.config.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/frontend/protractor.config.js b/nifi-registry-web-ui/src/main/frontend/protractor.config.js
new file mode 100644
index 0000000..e8ef721
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/frontend/protractor.config.js
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var fs = require('fs');
+var path = require('canonical-path');
+var _ = require('lodash');
+
+exports.config = {
+    directConnect: true,
+
+    // Capabilities to be passed to the webdriver instance.
+    capabilities: {
+        'browserName': 'chrome'
+    },
+
+    // Framework to use. Jasmine is recommended.
+    framework: 'jasmine',
+
+    // Spec patterns are relative to this config file
+    specs: ['**/*e2e-spec.js'],
+
+    // For angular tests
+    useAllAngular2AppRoots: true,
+
+    // Base URL for application server
+    baseUrl: 'http://localhost:8080/nifi-registry',
+
+    // See https://github.com/angular/protractor/blob/master/docs/server-setup.md for the various protractor
+    // browser driver setup. Here we directly use Chrome or Firefox drivers. If Chrome or Firefox are not
+    // available an error will be thrown and a Selenium Server needs to be installed and started.
+    directConnect: true,
+
+    //OR
+
+    // The address of a running selenium server.
+    // seleniumAddress: 'http://localhost:4444/wd/hub',
+
+    // OR
+
+    // The location of the selenium standalone server .jar file, relative
+    // to the location of this config. If no other method of starting selenium
+    // is found, this will default to
+    // node_modules/protractor/selenium/selenium-server...
+    // seleniumServerJar: './node_modules/protractor/selenium/selenium-server-standalone-2.41.0.jar',
+
+    // The port to start the selenium server on, or null if the server should
+    // find its own unused port.
+    // seleniumPort: 4444,
+
+    onPrepare: function () {
+        // debugging
+        console.log('browser.params:' + JSON.stringify(browser.params));
+        jasmine.getEnv().addReporter(new Reporter(browser.params));
+
+        // Allow changing bootstrap mode to NG1 for upgrade tests
+        global.setProtractorToNg1Mode = function () {
+            browser.useAllAngular2AppRoots = false;
+            browser.rootEl = 'body';
+        };
+    },
+
+    jasmineNodeOpts: {
+        defaultTimeoutInterval: 10000,
+        showTiming: true,
+        print: function () {
+        }
+    }
+};
+
+// Custom reporter
+function Reporter(options) {
+    var _defaultOutputFile = path.resolve(process.cwd(), './_test-output', 'protractor-results.txt');
+    options.outputFile = options.outputFile || _defaultOutputFile;
+
+    initOutputFile(options.outputFile);
+    options.appDir = options.appDir || './';
+    var _root = {appDir: options.appDir, suites: []};
+    log('AppDir: ' + options.appDir, +1);
+    var _currentSuite;
+
+    this.suiteStarted = function (suite) {
+        _currentSuite = {description: suite.description, status: null, specs: []};
+        _root.suites.push(_currentSuite);
+        log('Suite: ' + suite.description, +1);
+    };
+
+    this.suiteDone = function (suite) {
+        var statuses = _currentSuite.specs.map(function (spec) {
+            return spec.status;
+        });
+        statuses = _.uniq(statuses);
+        var status = statuses.indexOf('failed') >= 0 ? 'failed' : statuses.join(', ');
+        _currentSuite.status = status;
+        log('Suite ' + _currentSuite.status + ': ' + suite.description, -1);
+    };
+
+    this.specStarted = function (spec) {
+
+    };
+
+    this.specDone = function (spec) {
+        var currentSpec = {
+            description: spec.description,
+            status: spec.status
+        };
+        if (spec.failedExpectations.length > 0) {
+            currentSpec.failedExpectations = spec.failedExpectations;
+        }
+
+        _currentSuite.specs.push(currentSpec);
+        log(spec.status + ' - ' + spec.description);
+    };
+
+    this.jasmineDone = function () {
+        outputFile = options.outputFile;
+        //// Alternate approach - just stringify the _root - not as pretty
+        //// but might be more useful for automation.
+        // var output = JSON.stringify(_root, null, 2);
+        var output = formatOutput(_root);
+        fs.appendFileSync(outputFile, output);
+    };
+
+    function ensureDirectoryExistence(filePath) {
+        var dirname = path.dirname(filePath);
+        if (directoryExists(dirname)) {
+            return true;
+        }
+        ensureDirectoryExistence(dirname);
+        fs.mkdirSync(dirname);
+    }
+
+    function directoryExists(path) {
+        try {
+            return fs.statSync(path).isDirectory();
+        }
+        catch (err) {
+            return false;
+        }
+    }
+
+    function initOutputFile(outputFile) {
+        ensureDirectoryExistence(outputFile);
+        var header = "Protractor results for: " + (new Date()).toLocaleString() + "\n\n";
+        fs.writeFileSync(outputFile, header);
+    }
+
+    // for output file output
+    function formatOutput(output) {
+        var indent = '  ';
+        var pad = '  ';
+        var results = [];
+        results.push('AppDir:' + output.appDir);
+        output.suites.forEach(function (suite) {
+            results.push(pad + 'Suite: ' + suite.description + ' -- ' + suite.status);
+            pad += indent;
+            suite.specs.forEach(function (spec) {
+                results.push(pad + spec.status + ' - ' + spec.description);
+                if (spec.failedExpectations) {
+                    pad += indent;
+                    spec.failedExpectations.forEach(function (fe) {
+                        results.push(pad + 'message: ' + fe.message);
+                    });
+                    pad = pad.substr(2);
+                }
+            });
+            pad = pad.substr(2);
+            results.push('');
+        });
+        results.push('');
+        return results.join('\n');
+    }
+
+    // for console output
+    var _pad;
+
+    function log(str, indent) {
+        _pad = _pad || '';
+        if (indent == -1) {
+            _pad = _pad.substr(2);
+        }
+        console.log(_pad + str);
+        if (indent == 1) {
+            _pad = _pad + '  ';
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/locale/messages.es.xlf
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/locale/messages.es.xlf b/nifi-registry-web-ui/src/main/locale/messages.es.xlf
new file mode 100644
index 0000000..91c9e1d
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/locale/messages.es.xlf
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+  <file source-language="es" datatype="plaintext" original="ng2.template">
+    <body>
+      <trans-unit id="nf-admin-general-tab-title" datatype="html">
+         <source>general</source>
+         <target state="new">general</target>
+        <note priority="1" from="description">A description of the type of administration options available.</note>
+        <note priority="1" from="meaning">General administration tab</note>
+       </trans-unit>
+      <trans-unit id="nf-admin-users-tab-title" datatype="html">
+         <source>users</source>
+         <target state="new">Usuarios</target>
+        <note priority="1" from="description">A description of the type of administration options available.</note>
+        <note priority="1" from="meaning">Users administration tab</note>
+       </trans-unit>
+      <trans-unit id="nf-admin-workflow-tab-title" datatype="html">
+         <source>Workflow</source>
+         <target state="new">Flujo de trabajo</target>
+        <note priority="1" from="description">A description of the type of administration options available.</note>
+        <note priority="1" from="meaning">Workflow administration tab</note>
+       </trans-unit>
+      <trans-unit id="nf-admin-workflow-create-bucket-button" datatype="html">
+         <source>Create</source>
+         <target state="new">Crear</target>
+        <note priority="1" from="description">A button for creating a new bucket in the registry.@@nf-admin-workflow-create-bucket-button.</note>
+        <note priority="1" from="meaning">Create new bucket button</note>
+       </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/README.md
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/README.md b/nifi-registry-web-ui/src/main/platform/core/README.md
new file mode 100644
index 0000000..2981e88
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/README.md
@@ -0,0 +1,33 @@
+# Fluid Design System (FDS)
+
+FDS NiFi Registry UI/UX Platform for layouts, icons, custom components and themes. This should be added as a dependency for any project that wants to use layouts, icons and themes for Angular Material or Teradata Covalent.
+
+The FDS will have custom components that enforce standards and best practices through built-in design patterns.
+
+## Setup
+
+Import the **[FluidDesignSystemModule]** in your NgModule:
+
+```javascript
+var fdsCore = require('@fluid-design-system/core');
+NfRegistryAppModule.prototype = {
+    constructor: NfRegistryAppModule
+};
+
+NfRegistryAppModule.annotations = [
+    new ngCore.NgModule({
+        imports: [
+            fdsCore,
+    ...
+  ],
+  ...
+})
+...
+```
+
+
+## Styles, Icons and Theming
+
+See [theming](https://github.com/apache/nifi-registry.github.io) in the docs for more info (TBD).
+
+FDS NiFi Registry UI/UX Platform comes with a base CSS file `@fluid-design-system/core/common/styles/css/fluid-design-system.css` (includes icons).

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_basicElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_basicElements.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_basicElements.scss
new file mode 100644
index 0000000..fc5808c
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_basicElements.scss
@@ -0,0 +1,130 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+@font-face {
+  font-family: 'Roboto';
+  font-style: normal;
+  font-weight: 300;
+  src: local("Roboto Light"), local("Roboto-Light"), url("../../../../../../../roboto-fontface/fonts/Roboto/Roboto-Light.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: 'Roboto';
+  font-style: italic;
+  font-weight: 300;
+  src: local("Roboto LightItalic"), local("Roboto-LightItalic"), url("../../../../../../../roboto-fontface/fonts/Roboto/Roboto-LightItalic.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: 'Roboto';
+  font-style: normal;
+  font-weight: normal;
+  src: local("Roboto Regular"), local("Roboto-Regular"), url("../../../../../../../roboto-fontface/fonts/Roboto/Roboto-Regular.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: 'Roboto';
+  font-style: normal;
+  font-weight: 500;
+  src: local("Roboto Medium"), local("Roboto-Medium"), url("../../../../../../../roboto-fontface/fonts/Roboto/Roboto-Medium.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: 'Roboto';
+  font-style: normal;
+  font-weight: bold;
+  src: local("Roboto Bold"), local("Roboto-Bold"), url("../../../../../../../roboto-fontface/fonts/Roboto/Roboto-Bold.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: 'Roboto';
+  font-style: italic;
+  font-weight: normal;
+  src: local("Roboto Italic"), local("Roboto-Italic"), url("../../../../../../../roboto-fontface/fonts/Roboto/Roboto-RegularItalic.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: 'Roboto Slab';
+  font-style: normal;
+  font-weight: normal;
+  src: local("RobotoSlab Regular"), local("RobotoSlab-Regular"), url("../../../../../../../roboto-fontface/fonts/Roboto-Slab/Roboto-Slab-Regular.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: 'Roboto Slab';
+  font-style: normal;
+  font-weight: bold;
+  src: local("RobotoSlab Bold"), local("RobotoSlab-Bold"), url("../../../../../../../roboto-fontface/fonts/Roboto-Slab/Roboto-Slab-Bold.ttf") format("truetype");
+}
+
+body,
+html {
+  height: 100%;
+}
+
+body,
+button,
+input,
+label,
+select,
+td,
+textarea {
+  font-family: $fontPrimary;
+  font-size: 14px;
+}
+
+body {
+  color: $bodyTextColor;
+}
+
+strong {
+  font-weight: bold;
+}
+
+pre {
+  overflow-x: auto;
+}
+
+em {
+  font-style: italic;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-family: $fontPrimary;
+  font-weight: normal;
+  font-style: normal;
+  background: #FFFFFF;
+}
+
+h1 {
+  color: $pageHeaderTextColor;
+}
+
+h2 {
+  color: $subHeaderTextColor;
+}
+
+table {
+  font-family: $fontPrimary;
+  font-size: 13px;
+  color: $grey2;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttonToggles.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttonToggles.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttonToggles.scss
new file mode 100644
index 0000000..73f4263
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttonToggles.scss
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+body[fds] .expansion-panel-filter-toggle-group {
+  box-shadow: none !important;
+}
+
+body[fds] .expansion-panel-filter-toggle-group .mat-button-toggle {
+  height: 75px;
+  width: 125px;
+  border: 1px solid $grey4;
+}
+
+body[fds] .expansion-panel-filter-toggle-group .mat-button-toggle-label-content {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  line-height: 63px;
+  text-align: center;
+}
+
+body[fds] .expansion-panel-filter-toggle-group .mat-button-toggle-checked {
+  background-color: $blue-grey1;
+  color: white;
+}
+
+body[fds] .expansion-panel-filter-toggle-group .mat-button-toggle-checked .md-display-1 {
+  color: white;
+}
+
+body[fds] .expansion-panel-filter-toggle-group .md-display-1 {
+  color: $blue-grey1;
+}
+
+body[fds] .expansion-panel-filter-toggle-group div {
+  line-height: normal;
+}
+
+body[fds] .tab-toggle-group {
+  box-shadow: none !important;
+}
+
+body[fds] .tab-toggle-group .mat-button-toggle-label-content {
+  border-bottom: 2px solid $grey5;
+}
+
+body[fds] .tab-toggle-group .mat-button-toggle-checked {
+  background: transparent;
+}
+
+body[fds] .tab-toggle-group .mat-button-toggle-checked .mat-button-toggle-label-content {
+  border-bottom: 2px solid $blue-grey1;
+  background: transparent;
+}
+
+body[fds] .on-off-toggle-group {
+  box-shadow: none !important;
+}
+
+body[fds] .on-off-toggle-group .mat-button-toggle {
+  height: 20px;
+  width: 35px;
+  border: 1px solid $grey4;
+}
+
+body[fds] .on-off-toggle-group .mat-button-toggle-label-content {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  line-height: 20px;
+  text-align: center;
+}
+
+body[fds] .on-off-toggle-group .mat-button-toggle-checked {
+  background-color: $blue-grey1;
+  color: white;
+  border: 1px solid $blue-grey1;
+}
+
+body[fds] .off-toggle.mat-button-toggle-checked {
+  background-color: $grey4;
+  color: $grey1;
+  border: 1px solid $grey4;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttons.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttons.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttons.scss
new file mode 100644
index 0000000..c8d4802
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_buttons.scss
@@ -0,0 +1,198 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Buttons */
+
+$buttonFontColor: #FFFFFF;
+$buttonFontColorDisabled: #D1E8D1;
+$buttonBgColorPrimary: $primaryColor;
+$buttonBgColorPrimaryHover: $primaryColorHover;
+$buttonBgColorPrimaryDisabled: $primaryColor;
+$buttonBgColorPrimarySelected: $primaryColor;
+$buttonBgColorSecondary: #FFFFFF;
+$buttonBgColorSecondaryHover: $primaryColorHover;
+$buttonBgColorSecondarySelected: #FFFFFF;
+$buttonBgColorRegular: #FFFFFF;
+$buttonBgColorRegularHover: #808793;
+$buttonBgColorRegularDisabled: #808793;
+$buttonBgColorRegularSelected: #FFFFFF;
+$buttonBorderColorSecondary: $primaryColor;
+$buttonBorderColorSecondaryHover: $buttonBgColorSecondaryHover;
+$buttonBorderColorSecondaryDisabled: $buttonBgColorSecondaryHover;
+$buttonBorderColorSecondarySelected: $primaryColor;
+$buttonBorderColorRegular: #CFD3D7;
+$buttonBorderColorRegularHover: $buttonBgColorRegularHover;
+$buttonBorderColorRegularDisabled: $buttonBgColorRegularHover;
+$buttonBorderColorRegularSelected: #CFD3D7;
+$buttonFontPrimaryColor: $buttonFontColor;
+$buttonFontPrimaryColorHover: $buttonFontColor;
+$buttonFontPrimaryColorDisabled: $buttonFontColorDisabled;
+$buttonFontPrimaryColorSelected: $buttonFontColor;
+$buttonFontSecondaryColor: $primaryColorHover;
+$buttonFontSecondaryColorHover: $buttonFontColor;
+$buttonFontSecondaryColorDisabled: $buttonFontColorDisabled;
+$buttonFontSecondaryColorSelected: $primaryColorHover;
+$buttonFontRegularColor: $descriptionTextColor;
+$buttonFontRegularColorHover: $buttonFontColor;
+$buttonFontRegularColorDisabled: $buttonFontColorDisabled;
+$buttonFontRegularColorSelected: $bodyTextColor;
+$buttonFontWarnColor: $buttonFontColor;
+$buttonFontWarnColorHover: $buttonFontColor;
+$buttonFontWarnColorDisabled: $buttonFontColorDisabled;
+$buttonFontWarnColorSelected: $buttonFontColor;
+$buttonFontCriticalColor: $buttonFontColor;
+$buttonFontCriticalColorHover: $buttonFontColor;
+$buttonFontCriticalColorDisabled: $buttonFontColorDisabled;
+$buttonFontCriticalColorSelected: $buttonFontColor;
+
+body[fds] .mat-raised-button {
+  height: 34px;
+  font-family: $fontPrimary;
+  font-weight: normal;
+  font-size: 14px;
+  text-transform: uppercase;
+  line-height: normal;
+  box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 0px rgba(0, 0, 0, 0.14), 0px 0px 0px 0px rgba(0, 0, 0, 0.12);
+}
+
+body[fds] .mat-raised-button.cdk-focused:focus {
+  box-shadow: 0px 0px 2px 0px rgba(19, 145, 193, 1);
+}
+
+body[fds] .mat-raised-button[disabled] {
+  opacity: .6;
+}
+
+body[fds] .mat-button-focus-overlay {
+  background-color: transparent;
+}
+
+body[fds] .mat-raised-button.mat-fds-primary {
+  border: 1px solid $buttonBgColorPrimary;
+  background-color: $buttonBgColorPrimary;
+  color: $buttonFontPrimaryColor;
+}
+
+body[fds] .mat-raised-button.mat-fds-primary:hover {
+  background-color: $buttonBgColorPrimaryHover;
+  color: $buttonFontPrimaryColorHover;
+}
+
+body[fds] .mat-raised-button.mat-fds-primary.mat-button-focus {
+  color: $buttonFontPrimaryColorSelected;
+  background-color: $buttonBgColorPrimarySelected;
+}
+
+body[fds] .mat-raised-button.mat-fds-primary[disabled] {
+  color: $buttonFontPrimaryColorDisabled;
+  background-color: $buttonBgColorPrimaryDisabled;
+  color: $buttonFontPrimaryColorDisabled;
+}
+
+body[fds] .mat-raised-button.mat-fds-secondary {
+  color: $buttonFontSecondaryColor;
+  border: 1px solid $buttonBorderColorSecondary;
+}
+
+body[fds] .mat-raised-button.mat-fds-secondary:hover:not([disabled]) {
+  color: $buttonFontSecondaryColorHover;
+  background-color: $buttonBgColorSecondaryHover;
+  border: 1px solid $buttonBorderColorSecondaryHover;
+}
+
+body[fds] .mat-raised-button.mat-fds-secondary.mat-button-focus {
+  color: $buttonFontSecondaryColorSelected;
+  background-color: $buttonBgColorSecondarySelected;
+  border: 1px solid $buttonBorderColorSecondarySelected;
+}
+
+body[fds] .mat-raised-button.mat-fds-secondary[disabled] {
+  color: $buttonFontPrimaryColorDisabled;
+  background-color: $buttonBgColorPrimaryDisabled;
+}
+
+body[fds] .mat-raised-button.mat-fds-regular {
+  color: $buttonFontRegularColor;
+  background-color: $buttonBgColorRegular;
+  border: 1px solid $buttonBorderColorRegular;
+}
+
+body[fds] .mat-raised-button.mat-fds-regular:hover {
+  color: $buttonFontRegularColorHover;
+  background-color: $buttonBgColorRegularHover;
+  border: 1px solid $buttonBorderColorRegularHover;
+}
+
+body[fds] .mat-raised-button.mat-fds-regular.mat-button-focus {
+  color: $buttonFontRegularColorSelected;
+  background-color: $buttonBgColorRegularSelected;
+  border: 1px solid $buttonBorderColorRegularSelected;
+}
+
+body[fds] .mat-raised-button.mat-fds-regular[disabled] {
+  color: $buttonFontRegularColorDisabled;
+  background-color: $buttonBgColorRegularDisabled;
+  border: 1px solid $buttonBorderColorRegularDisabled;
+}
+
+body[fds] .mat-raised-button.mat-fds-warn {
+  border: 1px solid $errorColor;
+  background-color: $errorColor;
+  color: $buttonFontWarnColor;
+}
+
+body[fds] .mat-raised-button.mat-fds-warn:hover {
+  color: $buttonFontWarnColorHover;
+  background-color: $red2;
+  border: 1px solid $errorColor;
+}
+
+body[fds] .mat-raised-button.mat-fds-warn.mat-button-focus {
+  color: $buttonFontWarnColorSelected;
+  background-color: $errorColor;
+  border: 1px solid $buttonBorderColorRegularSelected;
+}
+
+body[fds] .mat-raised-button.mat-fds-warn[disabled] {
+  color: $buttonFontWarnColorDisabled;
+  background-color: $errorColor;
+  border: 1px solid $errorColor;
+}
+
+body[fds] .mat-raised-button.mat-fds-critical {
+  color: $buttonFontCriticalColor;
+  background-color: $orange1;
+  border: 1px solid $orange1;
+}
+
+body[fds] .mat-raised-button.mat-fds-critical:hover {
+  color: $buttonFontCriticalColorHover;
+  background-color: $orange2;
+  border: 1px solid $orange2;
+}
+
+body[fds] .mat-raised-button.mat-fds-critical.mat-button-focus {
+  color: $buttonFontCriticalColorSelected;
+  background-color: $orange2;
+  border: 1px solid $buttonBorderColorRegularSelected;
+}
+
+body[fds] .mat-raised-button.mat-fds-critical[disabled] {
+  color: $buttonFontCriticalColorDisabled;
+  background-color: $orange1;
+  border: 1px solid $orange1;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_checkboxes.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_checkboxes.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_checkboxes.scss
new file mode 100644
index 0000000..319995e
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_checkboxes.scss
@@ -0,0 +1,85 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Checkboxes */
+
+body[fds] md-checkbox {
+  margin-bottom: 3px;
+}
+
+body[fds] .mat-checkbox-inner-container {
+  height: 10px !important;
+  width: 10px !important;
+}
+
+body[fds] .mat-checkbox-frame {
+  height: 10px;
+  width: 10px;
+  border-color: $grey7;
+}
+
+body[fds] .mat-checkbox-ripple {
+  left: -7px;
+  top: -7px;
+  right: -7px;
+  bottom: -7px;
+}
+
+body[fds] .mat-checkbox-indeterminate.mat-accent .mat-checkbox-background,
+body[fds] .mat-checkbox-checked.mat-accent .mat-checkbox-background {
+  background-color: $blue-grey1;
+}
+
+body[fds] .mat-checkbox-inner-container:hover {
+  background-color: $blue-grey1;
+  border-radius: 2px;
+}
+
+body[fds] .mat-checkbox-background {
+  height: 10px;
+  width: 10px;
+}
+
+/* Covalent TdDataTableComponent 'selectable' property checkboxes */
+
+body[fds] .mat-pseudo-checkbox {
+  height: 10px !important;
+  width: 10px !important;
+  border: 1px solid $grey7;
+}
+
+body[fds] .mat-pseudo-checkbox:hover {
+  background-color: $blue-grey1;
+  border: 1px solid $blue-grey1;
+}
+
+body[fds] .mat-pseudo-checkbox-checked::after {
+  content: '\f00c';
+  font-size: 8px;
+  font-family: fontawesome;
+  margin-top: -9px;
+  margin-left: -1px;
+  border: none;
+  transform: initial;
+}
+
+body[fds] .mat-pseudo-checkbox-checked, body[fds] .mat-pseudo-checkbox-indeterminate {
+  background-color: $blue-grey1;
+  border: 1px solid $blue-grey1;
+  height: 10px;
+  width: 10px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_chips.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_chips.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_chips.scss
new file mode 100644
index 0000000..755f678
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_chips.scss
@@ -0,0 +1,67 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Chips */
+
+body[fds] .mat-chip {
+  border-radius: 2px;
+  font-size: 10px;
+  font-family: $fontPrimary;
+  font-style: normal;
+  font-weight: normal;
+  padding: 4px 12px 4px 12px;
+}
+
+body[fds] .mat-chip i {
+  margin-left: 10px;
+  float: right;
+  margin-top: 2px;
+}
+
+body[fds] .mat-basic-chip {
+  color: $grey2;
+  height: 24px;
+  margin: 12px 8px 0 0;
+}
+
+body[fds] .mat-basic-chip i {
+  margin-left: 10px;
+  float: right;
+  margin-top: 2px;
+}
+
+body[fds] .mat-basic-chip .td-chip {
+  font-size: 10px;
+  padding: 0px 0px 7px 12px;
+  min-height: 24px;
+}
+
+body[fds] .td-chip span {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: 65px;
+}
+
+body[fds] .td-chip-disabled .td-chip {
+  padding: 0px 0px 0px 12px;
+}
+
+body[fds] .mat-basic-chip md-icon.td-chip-removal {
+  font-size: 15px;
+  margin-bottom: 7px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_expansionPanels.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_expansionPanels.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_expansionPanels.scss
new file mode 100644
index 0000000..937bce7
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_expansionPanels.scss
@@ -0,0 +1,59 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Expansion Panels */
+
+body[fds] td-expansion-panel:not(:last-of-type) .td-expanded {
+  margin-bottom: 0px;
+}
+
+body[fds] .td-expansion-panel-header-content {
+  height: 80px !important;
+  padding: 0px 30px !important;
+  border-bottom: 1px solid $grey7;
+}
+
+body[fds] .td-expansion-content form {
+  padding: 15px 10px 20px 20px;
+}
+
+body[fds] .md-subhead {
+  font-size: 18px;
+  color: $grey3;
+}
+
+body[fds] td-expansion-panel .td-expansion-panel-header .td-expansion-panel-header-content md-icon.td-expand-icon {
+  font-size: 28px;
+  color: $blue-grey1;
+  font-weight: bold;
+}
+
+body[fds] td-expansion-panel {
+  box-shadow: none;
+}
+
+body[fds] td-expansion-panel .td-expansion-panel-header:hover:not(.mat-disabled) {
+  background: $blue4;
+}
+
+body[fds] td-expansion-panel .td-expansion-panel-header:focus {
+  background: #FFFFFF;
+}
+
+body[fds] td-expansion-panel .td-expansion-panel-header:focus .td-expansion-panel-header-content {
+  border-bottom: 1px solid $primaryColor;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_globalVars.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_globalVars.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_globalVars.scss
new file mode 100644
index 0000000..acd7efb
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_globalVars.scss
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Begin Global Variables */
+
+/* Text */
+
+$fontPrimary: "Roboto",
+sans-serif;
+$fontSecondary: "Robot Slab",
+sans-serif;
+$fontMedium: "Roboto Medium",
+sans-serif;
+$fontLight: "Roboto Light",
+sans-serif;
+
+/* Define colors */
+
+$grey1: #333333;
+$grey2: #666666;
+$grey3: #999999;
+$grey4: #CCCCCC;
+$grey5: #EEEEEE;
+$grey6: #F5F5F5; // or #FAFAFA
+$grey7: #DDDDDD;
+$grey8: #CFD3D7;
+$grey9: #b2b8c1;
+$grey10: #dbdee2;
+$blue1: #1491C1;
+$blue2: #E7f6Fc;
+$blue3: #A7DFF2;
+$blue4: #F3FAFF;
+$blue5: #728E9B;
+$blue6: #004849;
+$blue7: #d0dbe0;
+$blue8: #1291c1;
+$red1: #EF6162;
+$red2: #D14A50;
+$orange1: #E98A40;
+$orange2: #D3702D;
+$green1: #1EB475;
+$green2: #3FAE2A;
+$green3: #429929;
+$rose1: #9E737D;
+$rose2: #915D69;
+$blue-grey1: #6B8791;
+$blue-grey2: #B2C1C6;
+
+/* Set FDS theme */
+
+$primaryColor: $rose1; //$green2 or $blue5
+$primaryColorHover: $rose2; //$green3 or $blue6
+$secondaryColor: $rose1; //$green1 or $blue5
+$accentColor: $blue7; //$orange1 or $blue7
+$accentColorHover: $grey4; //$orange2 or $grey4
+$warnColor: $red1;
+$errorColor: $warnColor;
+$warningColor: $accentColor;
+$bodyTextColor: $grey1;
+$pageHeaderTextColor: $grey1;
+$subHeaderTextColor: $grey2;
+$descriptionTextColor: $grey2;
+$linkColor: $blue-grey1;
+$linkColorDisabled: $grey1;
+
+/*End Global Variables */

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_helperClasses.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_helperClasses.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_helperClasses.scss
new file mode 100644
index 0000000..6ae237c
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_helperClasses.scss
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Text */
+
+.camel-case {
+  text-transform: capitalize;
+}
+
+.header {
+  font-family: $fontMedium;
+  font-size: 16px;
+  color: $pageHeaderTextColor;
+  padding-bottom: 10px;
+}
+
+.details-header {
+  height: 92px;
+}
+
+.details-header-container {
+  position: relative;
+  top: 22px;
+  left: 10px;
+}
+
+.description {
+  font-family: $fontLight;
+  font-size: 12px;
+  color: $descriptionTextColor;
+}
+
+.description i {
+  padding-right: 5px;
+}
+
+.label {
+  font-family: $fontMedium;
+  font-size: 14px;
+  color: $bodyTextColor;
+  text-transform: uppercase;
+}
+
+.units {
+  font-family: $fontLight;
+  font-size: 14px;
+  color: $bodyTextColor;
+}
+
+.align-vertical {
+  margin-top: auto;
+  margin-bottom: auto;
+}
+
+.align-horizontal {
+  margin-left: auto;
+  margin-right: auto;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_inputs.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_inputs.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_inputs.scss
new file mode 100644
index 0000000..7024363
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_inputs.scss
@@ -0,0 +1,124 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Inputs */
+
+body[fds] .mat-input-container {
+  min-width: 200px;
+}
+
+body[fds] .mat-input-wrapper {
+  margin: 0;
+  padding-bottom: 0;
+}
+
+body[fds] input.mat-input-element, body[fds] textarea.mat-input-element {
+  border-radius: 2px;
+  color: $grey2;
+  border: 1px solid $grey8;
+  height: 32px;
+  padding: 0px 10px;
+  width: calc(100% - 22px);
+}
+
+body[fds] textarea.mat-input-element {
+  padding: 10px 10px;
+}
+
+body[fds] input.mat-input-element[disabled], body[fds] textarea.mat-input-element[disabled] {
+  background: $grey9;
+  color: $grey10;
+  border: 1px solid $grey9;
+}
+
+body[fds] .mat-input-subscript-wrapper {
+  margin-top: 18px;
+  width: calc(100% - 23px);
+}
+
+body[fds] input.mat-input-element:focus, body[fds] textarea.mat-input-element:focus {
+  border-color: $blue-grey1;
+}
+
+body[fds] .mat-input-underline {
+  display: none;
+}
+
+body[fds] .mat-input-placeholder {
+  font-size: 14px;
+  color: $grey3;
+  font-weight: 300;
+}
+
+body[fds] .mat-input-placeholder-wrapper {
+  top: -20px;
+  padding-top: 33px;
+}
+
+body[fds] .mat-input-placeholder {
+  top: 20px;
+  left: 10px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: calc(100% - 44px);
+}
+
+body[fds] md-input-container.mat-focused .mat-input-placeholder {
+  transform: translateY(-26px) translateX(-10px) scale(0.75);
+}
+
+body[fds] .mat-input-placeholder.mat-float:not(.mat-empty) {
+  transform: translateY(-26px) translateX(-10px) scale(.75);
+}
+
+body[fds] .input-button {
+  top: 3.5px;
+  left: -24px;
+  z-index: 1;
+}
+
+body[fds] .input-button.mat-raised-button[disabled] {
+  opacity: 1;
+}
+
+body[fds] .mat-input-infix {
+  padding: 7px 23px 0px 0px;
+  border-top: 0;
+}
+
+body[fds] td-chips .mat-input-placeholder-wrapper::after {
+  content: '\f0b0';
+  display: inline-table;
+  font-family: FontAwesome;
+  float: right;
+  margin: 2px 10px 0px 0px;
+  color: $grey3;
+}
+
+body[fds] td-chips input.mat-input-element {
+  border-radius: 2px;
+  color: $grey2;
+  border: 1px solid $grey8;
+  height: 32px;
+  padding: 0px 10px;
+  width: 100%;
+}
+
+body[fds] .mat-hint {
+  color: $grey3;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_links.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_links.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_links.scss
new file mode 100644
index 0000000..c6973c3
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_links.scss
@@ -0,0 +1,33 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Links */
+
+body[fds] .link {
+  color: $linkColor;
+  font-size: 14px;
+  text-decoration: none;
+}
+
+body[fds] .link:hover {
+  text-decoration: underline;
+}
+
+body[fds] .link .disabled {
+  color: $linkColorDisabled;
+  text-decoration: none;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_menus.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_menus.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_menus.scss
new file mode 100644
index 0000000..26a2333
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_menus.scss
@@ -0,0 +1,120 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Menus */
+
+body[fds] .mat-menu-panel {
+  border-radius: 2px;
+}
+
+body[fds] .mat-menu-content {
+  padding-top: 0px;
+  padding-bottom: 0px;
+}
+
+body[fds] .mat-menu-item {
+  font-size: 14px;
+  color: $bodyTextColor;
+  min-width: 200px;
+  text-transform: none;
+  height: 24px;
+  line-height: 24px;
+}
+
+body[fds] .regular-button-menu .mat-menu-item:hover {
+  color: #ffffff;
+  background-color: #808793;
+}
+
+body[fds] .mat-menu-item[disabled] {
+  color: rgba(0, 0, 0, 0.38);
+  background-color: #ffffff;
+}
+
+body[fds] .mat-menu-item .mat-icon {
+  font-size: 14px;
+}
+
+body[fds] .mat-menu-item .fa {
+  font-size: 14px;
+  width: 1em;
+  height: 1em;
+}
+
+body[fds] .mat-menu-item[disabled] .mat-icon {
+  color: rgba(0, 0, 0, 0.38);
+}
+
+body[fds] .mat-menu-item[disabled] .fa {
+  color: rgba(0, 0, 0, 0.38);
+}
+
+body[fds] .mat-menu-item:hover:not([disabled]) .mat-icon {
+  color: #ffffff;
+}
+
+body[fds] .mat-menu-item:hover:not([disabled]) .fa {
+  color: #ffffff;
+}
+
+body[fds] .mat-menu-item:hover:not([disabled]),
+body[fds] .mat-menu-item:focus:not([disabled]) {
+  color: #ffffff;
+  background-color: #808793;
+}
+
+body[fds] .fds-primary-dropdown-button-menu .mat-menu-item:hover:not([disabled]),
+body[fds] .fds-primary-dropdown-button-menu .mat-menu-item:focus:not([disabled]) {
+  color: #FFFFFF;
+  background-color: $primaryColorHover;
+}
+
+.fds-primary-dropdown-button-menu .cdk-focused {
+  color: $buttonFontPrimaryColorSelected;
+  background-color: $buttonBgColorPrimarySelected;
+}
+
+.mat-raised-button .mat-button-wrapper i {
+  padding-left: 10px;
+}
+
+body[fds] .mat-option {
+  font-size: 14px;
+  color: $bodyTextColor;
+  text-transform: none;
+  height: 24px;
+  line-height: 24px;
+}
+
+body[fds] .mat-autocomplete-panel.mat-autocomplete-panel-below {
+  top: 0;
+}
+
+body[fds] .regular-button-menu .mat-option:hover {
+  color: #ffffff;
+  background-color: #808793;
+}
+
+body[fds] .mat-option:hover:not([disabled]),
+body[fds] .mat-option:focus:not([disabled]) {
+  color: #ffffff;
+  background-color: #808793;
+}
+
+body[fds] .mat-select-underline {
+  display: none;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_modals.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_modals.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_modals.scss
new file mode 100644
index 0000000..51c7bad
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_modals.scss
@@ -0,0 +1,23 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Modals */
+
+body[fds] .mat-dialog-container {
+  padding: 20px;
+  width: 400px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_panels.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_panels.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_panels.scss
new file mode 100644
index 0000000..f88a432
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_panels.scss
@@ -0,0 +1,54 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Panels */
+
+body[fds] .mat-card-title {
+  font-size: 20px;
+  color: $grey1;
+  margin-bottom: 0px;
+}
+
+body[fds] md-card-title {
+  padding-top: 20px;
+  padding-left: 20px;
+  padding-right: 20px;
+}
+
+body[fds] .mat-card-subtitle {
+  padding-left: 20px;
+  padding-right: 20px;
+  padding-top: 10px;
+  margin-bottom: 0px;
+}
+
+body[fds] .mat-card-content {
+  color: $grey2;
+  padding: 10px 20px 20px 20px;
+  margin: 0px;
+}
+
+body[fds] .mat-card .mat-card-actions:last-child, body[fds] .mat-card .mat-card .mat-card-actions:last-child {
+  padding: 0px 20px 20px 20px;
+  margin: 0px;
+}
+
+body[fds] .fds-panel-menu-button {
+  position: absolute;
+  right: 0px;
+  top: 20px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_radios.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_radios.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_radios.scss
new file mode 100644
index 0000000..2d9ca8e
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_radios.scss
@@ -0,0 +1,56 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Radios */
+
+body[fds] .mat-radio-container {
+  height: 12px;
+  width: 12px;
+}
+
+body[fds] .mat-radio-outer-circle {
+  height: 12px;
+  width: 12px;
+  background-color: #FFFFFF;
+  border: 1px solid $grey7;
+}
+
+body[fds] .mat-radio-outer-circle:hover {
+  background-color: $blue-grey1;
+  border-color: $blue-grey1;
+}
+
+body[fds] .mat-radio-checked .mat-radio-outer-circle {
+  border: 1px solid $blue-grey1;
+  background-color: $blue-grey1;
+}
+
+body[fds] .mat-radio-button.mat-accent.mat-radio-checked .mat-radio-outer-circle {
+  border-color: $blue-grey1;
+}
+
+body[fds] .mat-radio-inner-circle {
+  height: 10px;
+  width: 10px;
+  left: 1px;
+  top: 1px;
+  background-color: #FFFFFF;
+}
+
+body[fds] .mat-radio-checked .mat-radio-inner-circle {
+  background-color: #FFFFFF;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_sideNav.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_sideNav.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_sideNav.scss
new file mode 100644
index 0000000..af3514d
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_sideNav.scss
@@ -0,0 +1,20 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+body[fds] .mat-sidenav-container {
+  height: 100%;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_tables.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_tables.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_tables.scss
new file mode 100644
index 0000000..0dcdade
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_tables.scss
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Tables */
+
+body[fds] .td-data-table-cell {
+  font-size: 13px;
+  color: $grey2;
+  padding: 0 28px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+body[fds] .td-data-table-column {
+  color: $grey3;
+  font-weight: normal;
+}
+
+body[fds] .td-data-table-row {
+  height: 34px;
+  border-top: 1px solid #fff;
+  border-left: 1px solid #fff;
+  border-right: 1px solid #fff;
+  border-bottom: 1px solid $grey5;
+}
+
+body[fds] .td-data-table-row.selected {
+  background-color: $grey5;
+  border: 1px solid $grey5;
+}
+
+body[fds] .td-data-table-row:hover {
+  background-color: $grey6;
+  border: 1px solid $blue-grey2;
+}
+
+body[fds] .td-data-table-cell .mat-icon-button {
+  color: $linkColor;
+}
+
+body[fds] .td-data-table-cell .mat-button, body[fds] .td-data-table-cell .mat-icon-button, body[fds] .td-data-table-cell .mat-raised-button {
+  height: 24px;
+  width: 24px;
+  line-height: 0;
+}
+
+body[fds] .td-data-table-cell .mat-icon-button.badge {
+  border-top-left-radius: 0px;
+  border-top-right-radius: 0px;
+}
+
+body[fds] .td-data-table-cell .mat-icon-button.badge[disabled] {
+  opacity: .3;
+}
+
+// body[fds] .td-data-table-cell .mat-button-focus-overlay, body[fds] .td-data-table-cell .mat-button-ripple {
+//     display: none;
+// }
+
+body[fds] .td-data-table-column {
+  font-size: 12px;
+  color: $grey3;
+  height: 34px;
+  line-height: 34px;
+  padding: 0 28px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+body[fds] .td-data-table-column .fa-caret-up, body[fds] .td-data-table-column .fa-caret-down {
+  color: $blue-grey1;
+  font-size: 12px;
+  margin-bottom: 2px;
+}
+
+body[fds] td-paging-bar {
+  color: $grey3;
+}
+
+body[fds] td-paging-bar md-select .mat-select-value, body[fds] td-paging-bar md-select .mat-select-arrow {
+  color: $blue-grey1;
+}
+
+body[fds] .table-title {
+  font-size: 20px;
+  color: $grey1;
+  min-width: 250px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 50%;
+  margin-right: 10px;
+}
+
+body[fds] div .td-data-table {
+  border-bottom: 2px solid $grey7;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_tabs.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_tabs.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_tabs.scss
new file mode 100644
index 0000000..df5d653
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_tabs.scss
@@ -0,0 +1,41 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Tabs */
+
+body[fds] .mat-tab-label {
+  line-height: 72px;
+  text-transform: uppercase;
+  color: $grey2;
+}
+
+body[fds] .mat-tab-label:hover:not([disabled]) {
+  color: $grey1;
+}
+
+body[fds] .mat-tab-label:focus:not([disabled]) {
+  background-color: #FFFFFF;
+}
+
+body[fds] .mat-tab-label-active {
+  color: $grey1;
+}
+
+body[fds] .mat-tab-nav-bar,
+body[fds] .mat-tab-header {
+  border-bottom: 0px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/_tooltips.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/_tooltips.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/_tooltips.scss
new file mode 100644
index 0000000..563565f
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/_tooltips.scss
@@ -0,0 +1,24 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* Tooltips */
+
+body[fds] .mat-tooltip {
+  background: $grey2;
+  opacity: .9;
+  box-shadow: inset 0px 0px 3px 0px rgba(19, 145, 193, 1);
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/common/styles/fluid-design-system.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/common/styles/fluid-design-system.scss b/nifi-registry-web-ui/src/main/platform/core/common/styles/fluid-design-system.scss
new file mode 100644
index 0000000..0a29fe8
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/common/styles/fluid-design-system.scss
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the 'License'); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import '../../../..//node_modules/@angular/material/theming';
+@import '../../../../node_modules/@covalent/core/theming/all-theme';
+@import 'globalVars';
+@import 'basicElements';
+@import 'helperClasses';
+@import 'buttons';
+@import 'buttonToggles';
+@import 'checkboxes';
+@import 'radios';
+@import 'chips';
+@import 'modals';
+@import 'tabs';
+@import 'inputs';
+@import 'panels';
+@import 'links';
+@import 'sideNav';
+@import 'tooltips';
+@import 'expansionPanels';
+@import 'menus';
+@import 'tables';
+@import '../../dialogs/fds-dialog-component';
+// Include the base styles for Angular Material core. We include this here so that you only
+// have to load a single css file for Angular Material in your app.
+@include mat-core;
+// Define the palettes
+$fds-base-palette: (50: #89df79, 100: $primaryColorHover, 200: #65d550, 300: #53d03b, 400: #46c32f, 500: $primaryColor, 600: $primaryColor, 700: #89df79, 800: #29701b, 900: #215c16, A100: #9be48d, A200: #ade9a2, A400: #bfedb6, A700: #1a4711, contrast: (50: $black-87-opacity, 100: $black-87-opacity, 200: $black-87-opacity, 300: white, 400: white, 500: $white-87-opacity, 600: $white-87-opacity, 700: $white-87-opacity, 800: $white-87-opacity, 900: $white-87-opacity, A100: $black-87-opacity, A200: white, A400: white, A700: $white-87-opacity));
+$fds-accent-palette: (50: #89df79, 100: $accentColorHover, 200: #65d550, 300: #53d03b, 400: #46c32f, 500: $accentColor, 600: $accentColor, 700: #89df79, 800: #29701b, 900: #215c16, A100: #9be48d, A200: #ade9a2, A400: #bfedb6, A700: #1a4711, contrast: (50: $black-87-opacity, 100: $black-87-opacity, 200: $black-87-opacity, 300: white, 400: white, 500: $white-87-opacity, 600: $white-87-opacity, 700: $white-87-opacity, 800: $white-87-opacity, 900: $white-87-opacity, A100: $black-87-opacity, A200: white, A400: white, A700: $white-87-opacity));
+$fds-warn-palette: (50: #81410f, 100: #D14A50, 200: #af5814, 300: #c66317, 400: #dd6f19, 500: $warnColor, 600: $warnColor, 700: #eea66e, 800: #f1b485, 900: #f4c29b, A100: #ec9857, A200: #89df79, A400: #89df79, A700: #f6d0b2, contrast: (50: $black-87-opacity, 100: $black-87-opacity, 200: $black-87-opacity, 300: white, 400: white, 500: $white-87-opacity, 600: $white-87-opacity, 700: $white-87-opacity, 800: $white-87-opacity, 900: $white-87-opacity, A100: $black-87-opacity, A200: white, A400: white, A700: $white-87-opacity));
+$fds-primary: mat-palette($fds-base-palette, 500, 100, 500);
+$fds-accent: mat-palette($fds-accent-palette, 500, 100, 500);
+$fds-warn: mat-palette($fds-warn-palette, 500, 100, 500);
+// Optionally specify a default, lighter, and darker hue.
+$fds-theme: mat-light-theme($fds-primary, $fds-accent, $fds-warn);
+// Include theme styles for core and each component used in your app.
+// Alternatively, you can import and @include the theme mixins for each component
+// that you are using.
+@include angular-material-theme($fds-theme);
+@include covalent-theme($fds-theme);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/dialogs/_fds-dialog-component.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/dialogs/_fds-dialog-component.scss b/nifi-registry-web-ui/src/main/platform/core/dialogs/_fds-dialog-component.scss
new file mode 100644
index 0000000..f8784a4
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/dialogs/_fds-dialog-component.scss
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the 'License'); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.fds-dialog-title {
+  margin-top: 0;
+  margin-bottom: 20px;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.html b/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.html
new file mode 100644
index 0000000..101cd9e
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.html
@@ -0,0 +1,45 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<fds-dialog>
+    <fds-dialog-title *ngIf="title">
+        <div fxLayout="row" fxLayoutAlign="space-between center">
+            {{title}}
+            <button md-icon-button (click)="cancel()">
+                <md-icon color="primary">close</md-icon>
+            </button>
+        </div>
+    </fds-dialog-title>
+    <fds-dialog-content class="md-subhead tc-grey-700">
+        {{message}}
+    </fds-dialog-content>
+    <fds-dialog-actions>
+        <button md-raised-button
+                color="{{cancelButtonColor}}"
+                #closeBtn
+                (keydown.arrowright)="acceptBtn.focus()"
+                (click)="cancel()">{{cancelButton}}
+        </button>
+        <button md-raised-button
+                color="{{acceptButtonColor}}"
+                #acceptBtn
+                (keydown.arrowleft)="closeBtn.focus()"
+                (click)="accept()"
+                class="push-left-sm">{{acceptButton}}
+        </button>
+    </fds-dialog-actions>
+</fds-dialog>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.js b/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.js
new file mode 100644
index 0000000..4236b1a
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/dialogs/confirm-dialog/confirm-dialog.component.js
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngCore = require('@angular/core');
+
+/**
+ * FdsConfirmDialogComponent constructor.
+ *
+ * @constructor
+ */
+function FdsConfirmDialogComponent() {
+    this.title = '';
+    this.message = '';
+    this.acceptButton = 'ACCEPT';
+    this.acceptButtonColor = 'fds-primary';
+    this.cancelButton = 'CANCEL';
+    this.cancelButtonColor = 'fds-regular';
+    this.dialogRef = undefined;
+    this.viewContainerRef = undefined;
+    this.disableClose = true;
+};
+
+FdsConfirmDialogComponent.prototype = {
+    constructor: FdsConfirmDialogComponent,
+
+    /**
+     * Close the dialog and send a cancel response to any subscribers.
+     */
+    cancel: function () {
+        this.dialogRef.close(false);
+    },
+
+    /**
+     * Close the dialog and send an accept response to any subscribers.
+     */
+    accept: function () {
+        this.dialogRef.close(true);
+    }
+};
+
+FdsConfirmDialogComponent.annotations = [
+    new ngCore.Component({
+        selector: 'fds-confirm-dialog',
+        template: require('./confirm-dialog.component.html!text')
+    })
+];
+
+FdsConfirmDialogComponent.parameters = [];
+
+module.exports = FdsConfirmDialogComponent;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.html b/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.html
new file mode 100644
index 0000000..233bc64
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.html
@@ -0,0 +1,29 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div class="fds-dialog-wrapper">
+    <h3 class="fds-dialog-title md-title" *ngIf="dialogTitle.length > 0">
+        <ng-content select="fds-dialog-title"></ng-content>
+    </h3>
+    <div class="fds-dialog-content pad-bottom-md" *ngIf="dialogContent.length > 0">
+        <ng-content select="fds-dialog-content"></ng-content>
+    </div>
+    <div class="fds-dialog-actions" *ngIf="dialogActions.length > 0" layout="row">
+        <span flex></span>
+        <ng-content select="fds-dialog-actions"></ng-content>
+    </div>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.js b/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.js
new file mode 100644
index 0000000..d71a292
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialog.component.js
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngCore = require('@angular/core');
+
+var FdsDialogTitleDirective = new ngCore.Class({
+    extends: ngCore.Directive({selector: 'fds-dialog-title'}),
+    constructor: function () {
+    }
+});
+var FdsDialogContentDirective = new ngCore.Class({
+    extends: ngCore.Directive({selector: 'fds-dialog-content'}),
+    constructor: function () {
+    }
+});
+var FdsDialogActionsDirective = new ngCore.Class({
+    extends: ngCore.Directive({selector: 'fds-dialog-actions'}),
+    constructor: function () {
+    }
+});
+
+/**
+ * FdsDialogComponent constructor
+ *
+ * @constructor
+ */
+function FdsDialogComponent() {
+};
+
+FdsDialogComponent.prototype = {
+    constructor: FdsDialogComponent,
+
+    /**
+     * Respond after Angular projects external content into the component's view.
+     */
+    ngAfterContentInit: function () {
+        if (this.dialogTitle.length > 1) {
+            throw new Error('Duplicate fds-dialog-title component at in fds-dialog.');
+        }
+        if (this.dialogContent.length > 1) {
+            throw new Error('Duplicate fds-dialog-content component at in fds-dialog.');
+        }
+        if (this.dialogActions.length > 1) {
+            throw new Error('Duplicate fds-dialog-actions component at in fds-dialog.');
+        }
+    }
+}
+
+FdsDialogComponent.annotations = [
+    new ngCore.Component({
+        selector: 'fds-dialog',
+        template: require('./fds-dialog.component.html!text'),
+        queries: {
+            dialogTitle: new ngCore.ContentChildren(FdsDialogTitleDirective),
+            dialogContent: new ngCore.ContentChildren(FdsDialogContentDirective),
+            dialogActions: new ngCore.ContentChildren(FdsDialogActionsDirective)
+        }
+    })
+];
+
+FdsDialogComponent.parameters = [];
+
+module.exports = {
+    FdsDialogTitleDirective: FdsDialogTitleDirective,
+    FdsDialogContentDirective: FdsDialogContentDirective,
+    FdsDialogActionsDirective: FdsDialogActionsDirective,
+    FdsDialogComponent: FdsDialogComponent
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialogs.module.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialogs.module.js b/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialogs.module.js
new file mode 100644
index 0000000..3b9b610
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/dialogs/fds-dialogs.module.js
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngCore = require('@angular/core');
+var ngMaterial = require('@angular/material');
+var ngFlex = require('@angular/flex-layout');
+var ngCommon = require('@angular/common');
+var ngForms = require('@angular/forms');
+var fdsDialogComponentModule = require('@fluid-design-system/dialog-component');
+var fdsDialogServiceModule = require('@fluid-design-system/dialog-service');
+var FdsConfirmDialogComponent = require('@fluid-design-system/confirm-dialog-component');
+
+var FDS_DIALOGS = [
+    fdsDialogComponentModule.FdsDialogComponent,
+    fdsDialogComponentModule.FdsDialogTitleDirective,
+    fdsDialogComponentModule.FdsDialogActionsDirective,
+    fdsDialogComponentModule.FdsDialogContentDirective,
+    FdsConfirmDialogComponent
+];
+
+var FDS_DIALOGS_ENTRY_COMPONENTS = [
+    FdsConfirmDialogComponent
+];
+
+/**
+ * FdsDialogsModule constructor.
+ *
+ * @constructor
+ */
+function FdsDialogsModule() {
+
+};
+
+FdsDialogsModule.prototype = {
+    constructor: FdsDialogsModule
+};
+
+FdsDialogsModule.annotations = [
+    new ngCore.NgModule({
+        imports: [
+            ngFlex.FlexLayoutModule,
+            ngForms.FormsModule,
+            ngCommon.CommonModule,
+            ngMaterial.MdDialogModule,
+            ngMaterial.MdInputModule,
+            ngMaterial.MdButtonModule,
+            ngMaterial.MdIconModule
+        ],
+        declarations: [
+            FDS_DIALOGS
+        ],
+        exports: [
+            FDS_DIALOGS
+        ],
+        providers: [
+            fdsDialogServiceModule.FdsDialogService
+        ],
+        entryComponents: [
+            FDS_DIALOGS_ENTRY_COMPONENTS
+        ]
+    })
+];
+
+module.exports = {
+    FdsDialogsModule: FdsDialogsModule,
+    IConfirmConfig: fdsDialogServiceModule.IConfirmConfig,
+    FdsDialogService: fdsDialogServiceModule.FdsDialogService,
+    FdsDialogComponent: fdsDialogComponentModule.FdsDialogComponent,
+    FdsDialogTitleDirective: fdsDialogComponentModule.FdsDialogTitleDirective,
+    FdsDialogContentDirective: fdsDialogComponentModule.FdsDialogContentDirective,
+    FdsDialogActionsDirective: fdsDialogComponentModule.FdsDialogActionsDirective,
+    FdsConfirmDialogComponent: FdsConfirmDialogComponent
+};

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/dialogs/services/dialog.service.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/dialogs/services/dialog.service.js b/nifi-registry-web-ui/src/main/platform/core/dialogs/services/dialog.service.js
new file mode 100644
index 0000000..06ecf43
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/dialogs/services/dialog.service.js
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ngCore = require('@angular/core');
+var ngMaterial = require('@angular/material');
+var FdsConfirmDialogComponent = require('@fluid-design-system/confirm-dialog-component');
+
+var IDialogConfig = new ngCore.Class({
+    extends: ngMaterial.MdDialogConfig,
+    constructor: function () {
+        this.title = '';
+        this.message = '';
+        this.dialogRef = undefined;
+        this.viewContainerRef = undefined;
+        this.disableClose = true;
+    }
+});
+
+var IConfirmConfig = new ngCore.Class({
+    extends: IDialogConfig,
+    constructor: function () {
+        this.acceptButton = 'ACCEPT';
+        this.acceptButtonColor = 'fds-primary';
+        this.cancelButton = 'CANCEL';
+        this.cancelButtonColor = 'fds-secondary';
+    }
+});
+
+function createConfig(config) {
+    var dialogConfig = new IConfirmConfig();
+    dialogConfig.viewContainerRef = config.viewContainerRef;
+    dialogConfig.disableClose = config.disableClose;
+    return dialogConfig;
+}
+
+/**
+ * FdsDialogService constructor.
+ *
+ * @param MdDialog      The angular material MdDialog.
+ * @constructor
+ */
+function FdsDialogService(MdDialog) {
+    this.dialogService = MdDialog;
+}
+
+FdsDialogService.prototype = {
+    contstructor: FdsDialogService,
+
+    /**
+     * Wrapper function over the open() method in MdDialog.
+     * Opens a modal dialog containing the given component.
+     *
+     * @param component     The angular ComponentType<T>.
+     * @param config        The angular material MdDialogConfig.
+     *
+     */
+    open: function (component, config) {
+        return this.dialogService.open(component, config);
+    },
+
+    /**
+     * Wrapper function over the closeAll() method in MdDialog.
+     * Closes all of the currently-open dialogs.
+     */
+    closeAll: function () {
+        this.dialogService.closeAll();
+    },
+
+    /**
+     * Opens a confirm dialog with the provided config.
+     * Returns an MdDialogRef<TdConfirmDialogComponent> object.
+     *
+     * @param config     IConfirmConfig {
+     *                                      message: string;
+     *                                      title?: string;
+     *                                      viewContainerRef?: ViewContainerRef;
+     *                                      acceptButton?: string;
+     *                                      acceptButtonColor?: string;
+     *                                      cancelButton?: string;
+     *                                      cancelButtonColor?: string;
+     *                                   }
+     */
+    openConfirm: function (config) {
+        var dialogConfig = createConfig(config);
+        var dialogRef = this.dialogService.open(FdsConfirmDialogComponent, dialogConfig);
+        var confirmDialogComponent = dialogRef.componentInstance;
+        confirmDialogComponent.dialogRef = dialogRef;
+        if (config.title) {
+            confirmDialogComponent.title = config.title;
+        }
+        if (config.message) {
+            confirmDialogComponent.message = config.message;
+        }
+        if (config.acceptButton) {
+            confirmDialogComponent.acceptButton = config.acceptButton;
+        }
+        if (config.acceptButtonColor) {
+            confirmDialogComponent.acceptButtonColor = config.acceptButtonColor;
+        }
+        if (config.cancelButton) {
+            confirmDialogComponent.cancelButton = config.cancelButton;
+        }
+        if (config.cancelButtonColor) {
+            confirmDialogComponent.cancelButtonColor = config.cancelButtonColor;
+        }
+        return dialogRef;
+    },
+}
+
+FdsDialogService.parameters = [ngMaterial.MdDialog];
+
+module.exports = {
+    IDialogConfig: IDialogConfig,
+    IConfirmConfig: IConfirmConfig,
+    FdsDialogService: FdsDialogService
+};
\ No newline at end of file


[6/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/platform/core/fluid-design-system.module.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/platform/core/fluid-design-system.module.js b/nifi-registry-web-ui/src/main/platform/core/fluid-design-system.module.js
new file mode 100644
index 0000000..46cf881
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/platform/core/fluid-design-system.module.js
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var $ = require('jquery');
+var ngCore = require('@angular/core');
+var ngFlex = require('@angular/flex-layout');
+var ngMaterial = require('@angular/material');
+var ngCommon = require('@angular/common');
+var ngHttp = require('@angular/http');
+var ngPlatformBrowser = require('@angular/platform-browser');
+var ngAnimations = require('@angular/platform-browser/animations');
+var covalentCore = require('@covalent/core');
+var fdsDialogsModule = require('@fluid-design-system/dialogs');
+
+/**
+ * FluidDesignSystemModule constructor.
+ *
+ * @constructor
+ */
+function FluidDesignSystemModule() {
+    $(document).ready(function () {
+        //add fds attr to body tag to allow fine grain style overrides
+        document.body.setAttribute('fds', '');
+
+        //override the hover styles for checkbox borders
+        $(document.body).on('mouseenter', '.mat-checkbox-inner-container', function () {
+            $(this).find('.mat-checkbox-frame').css('border-color', '#1491C1');
+        });
+        $(document.body).on('mouseleave', '.mat-checkbox-inner-container', function () {
+            $(this).find('.mat-checkbox-frame').css('border-color', '#DDDDDD');
+        });
+    });
+};
+
+FluidDesignSystemModule.prototype = {
+    constructor: FluidDesignSystemModule
+};
+
+FluidDesignSystemModule.annotations = [
+    new ngCore.NgModule({
+        imports: [
+            ngFlex.FlexLayoutModule,
+            ngAnimations.BrowserAnimationsModule,
+            ngCommon.CommonModule,
+            ngPlatformBrowser.BrowserModule,
+            ngHttp.HttpModule,
+            ngHttp.JsonpModule,
+            ngMaterial.MaterialModule,
+            covalentCore.CovalentCommonModule,
+            covalentCore.CovalentChipsModule,
+            covalentCore.CovalentDataTableModule,
+            covalentCore.CovalentDialogsModule,
+            fdsDialogsModule.FdsDialogsModule,
+            covalentCore.CovalentExpansionPanelModule,
+            covalentCore.CovalentLoadingModule,
+            covalentCore.CovalentMenuModule,
+            covalentCore.CovalentNotificationsModule,
+            covalentCore.CovalentPagingModule,
+            covalentCore.CovalentSearchModule,
+            covalentCore.CovalentStepsModule
+        ],
+        exports: [
+            ngFlex.FlexLayoutModule,
+            ngAnimations.BrowserAnimationsModule,
+            ngCommon.CommonModule,
+            ngPlatformBrowser.BrowserModule,
+            ngHttp.HttpModule,
+            ngHttp.JsonpModule,
+            ngMaterial.MaterialModule,
+            covalentCore.CovalentCommonModule,
+            covalentCore.CovalentChipsModule,
+            covalentCore.CovalentDataTableModule,
+            covalentCore.CovalentDialogsModule,
+            fdsDialogsModule.FdsDialogsModule,
+            covalentCore.CovalentExpansionPanelModule,
+            covalentCore.CovalentLoadingModule,
+            covalentCore.CovalentMenuModule,
+            covalentCore.CovalentNotificationsModule,
+            covalentCore.CovalentPagingModule,
+            covalentCore.CovalentSearchModule,
+            covalentCore.CovalentStepsModule
+        ]
+    })
+];
+module.exports = FluidDesignSystemModule;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/resources/filters/registry-min.properties
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/resources/filters/registry-min.properties b/nifi-registry-web-ui/src/main/resources/filters/registry-min.properties
new file mode 100644
index 0000000..b0b7a26
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/resources/filters/registry-min.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nf.registry.script.tags=<script src="nifi-registry/nf-registry.bundle.min.js?${project.version}"></script>
+nf.registry.style.tags=<link rel="stylesheet" href="nifi-registry/node_modules/@covalent/core/common/platform.css?${project.version}">\n\
+<link rel="stylesheet" href='nifi-registry/node_modules/@fluid-design-system/dist/platform/core/common/styles/css/fluid-design-system.min.css?${project.version}'/>\n\
+<link rel="stylesheet" href='nifi-registry/css/nf-registry.min.css?${project.version}'/>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/resources/filters/registry.properties
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/resources/filters/registry.properties b/nifi-registry-web-ui/src/main/resources/filters/registry.properties
new file mode 100644
index 0000000..60ce777
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/resources/filters/registry.properties
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nf.registry.script.tags=<script src="nifi-registry/systemjs.spec.config.js?${project.version}"></script>\n\
+<script>\n\
+// bootstrap the app\n\
+System.import('nifi-registry/nf-registry-bootstrap.js?${project.version}');\n\
+</script>
+nf.registry.style.tags=<link rel="stylesheet" href="nifi-registry/node_modules/@covalent/core/common/platform.css?${project.version}">\n\
+<link rel="stylesheet" href='nifi-registry/node_modules/@fluid-design-system/dist/platform/core/common/styles/css/fluid-design-system.css?${project.version}'/>\n\
+<link rel="stylesheet" href='nifi-registry/css/nf-registry.css?${project.version}'/>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/WEB-INF/pages/index.jsp
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/WEB-INF/pages/index.jsp b/nifi-registry-web-ui/src/main/webapp/WEB-INF/pages/index.jsp
index c78f06a..0e8a950 100644
--- a/nifi-registry-web-ui/src/main/webapp/WEB-INF/pages/index.jsp
+++ b/nifi-registry-web-ui/src/main/webapp/WEB-INF/pages/index.jsp
@@ -17,14 +17,19 @@
 <%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
 <!DOCTYPE html>
 <html>
-    <head>
-        <title>NiFi Registry</title>
-        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-        <link rel="stylesheet" href="css/main.css" type="text/css" />
-    </head>
-    <body>
-        <div id="content">
-            <p>NiFi Registry</p>
-        </div>
-    </body>
+<head>
+    <title>NiFi Registry</title>
+    <base href="/">
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel=”shortcut icon” href=”nifi-registry/images/registry-favicon.png” type=”image/png>
+    <link rel=”icon” href=”nifi-registry/images/registry-favicon.png” type=”image/png>
+    ${nf.registry.style.tags}
+    <link rel="stylesheet" href='nifi-registry/node_modules/font-awesome/css/font-awesome.css'/>
+</head>
+<body>
+<nf-registry-app></nf-registry-app>
+</body>
+${nf.registry.script.tags}
 </html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/WEB-INF/web.xml b/nifi-registry-web-ui/src/main/webapp/WEB-INF/web.xml
index 442b4b3..2be7fff 100644
--- a/nifi-registry-web-ui/src/main/webapp/WEB-INF/web.xml
+++ b/nifi-registry-web-ui/src/main/webapp/WEB-INF/web.xml
@@ -16,17 +16,38 @@
 <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
     <display-name>nifi-registry</display-name>
 
-    <!-- servlet to map to search page -->
+    <!-- servlet to map to fluid design system page -->
     <servlet>
-        <servlet-name>index</servlet-name>
+        <servlet-name>FluidDesignSystem</servlet-name>
         <jsp-file>/WEB-INF/pages/index.jsp</jsp-file>
     </servlet>
     <servlet-mapping>
-        <servlet-name>index</servlet-name>
-        <url-pattern>/index</url-pattern>
+        <servlet-name>FluidDesignSystem</servlet-name>
+        <url-pattern>/fluid-design-system</url-pattern>
+    </servlet-mapping>
+
+    <!-- servlet to map to administration page -->
+    <servlet>
+        <servlet-name>Administration</servlet-name>
+        <jsp-file>/WEB-INF/pages/index.jsp</jsp-file>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>Administration</servlet-name>
+        <url-pattern>/administration/*</url-pattern>
+    </servlet-mapping>
+
+    <!-- servlet to map to explorer page -->
+    <servlet>
+        <servlet-name>Explorer</servlet-name>
+        <jsp-file>/WEB-INF/pages/index.jsp</jsp-file>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>Explorer</servlet-name>
+        <url-pattern>/explorer/*</url-pattern>
     </servlet-mapping>
 
     <welcome-file-list>
+        <welcome-file>index.jsp</welcome-file>
         <welcome-file>/WEB-INF/pages/index.jsp</welcome-file>
     </welcome-file-list>
 </web-app>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.html b/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.html
new file mode 100644
index 0000000..4246cab
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.html
@@ -0,0 +1,30 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div id="nifi-registry-general-administration-perspective" class="mat-elevation-z5">
+    <div fxFlex class="pad-top-md pad-bottom-sm pad-left-md pad-right-md">
+        <span class="mat-card-title">Settings</span>
+        <div layout="row" layout-align="space-between top" class="pad-top-md">
+            <md-input-container flex=100>
+                <input mdInput placeholder="Registry Name" value="{{nfRegistryService.registry.name}}">
+            </md-input-container>
+            <i class="info fa fa-question-circle align-vertical" aria-hidden="true"
+               mdTooltip="The name seen in NiFi to identify this registry."></i>
+        </div>
+    </div>
+</div>
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.js b/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.js
new file mode 100644
index 0000000..bdf9d51
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/general/nf-registry-general-administration.js
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+
+/**
+ * NfRegistryGeneralAdministration constructor
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @constructor
+ */
+function NfRegistryGeneralAdministration(nfRegistryService) {
+    this.nfRegistryService = nfRegistryService;
+};
+
+NfRegistryGeneralAdministration.prototype = {
+    constructor: NfRegistryGeneralAdministration,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        this.nfRegistryService.adminPerspective = 'general';
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.adminPerspective = '';
+    }
+};
+
+NfRegistryGeneralAdministration.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-general-administration.html!text'),
+        animations: [nfRegistryAnimations.slideInLeftAnimation],
+        host: {
+            '[@routeAnimation]': 'routeAnimation'
+        }
+    })
+];
+
+NfRegistryGeneralAdministration.parameters = [NfRegistryService];
+
+module.exports = NfRegistryGeneralAdministration;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html b/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html
new file mode 100644
index 0000000..8770fad
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.html
@@ -0,0 +1,39 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div id="nifi-registry-administration-perspective">
+    <md-button-toggle-group name="nifi-registry-administration-perspective" fxLayout="row"
+                            fxLayoutAlign="space-between center" class="tab-toggle-group">
+        <md-button-toggle [checked]="nfRegistryService.adminPerspective === 'general'" value="general" class="uppercase"
+                          routerLink="/nifi-registry/administration/{{nfRegistryService.registry.id}}/general"
+                          i18n="General administration tab|A description of the type of administration options available.@@nf-admin-general-tab-title">
+            general
+        </md-button-toggle>
+        <md-button-toggle [checked]="nfRegistryService.adminPerspective === 'users'" value="users" class="uppercase"
+                          routerLink="/nifi-registry/administration/{{nfRegistryService.registry.id}}/users"
+                          i18n="Users administration tab|A description of the type of administration options available.@@nf-admin-users-tab-title">
+            Users
+        </md-button-toggle>
+        <md-button-toggle [checked]="nfRegistryService.adminPerspective === 'workflow'" value="workflow"
+                          class="uppercase"
+                          routerLink="/nifi-registry/administration/{{nfRegistryService.registry.id}}/workflow"
+                          i18n="Workflow administration tab|A description of the type of administration options available.@@nf-admin-workflow-tab-title">
+            Workflow
+        </md-button-toggle>
+    </md-button-toggle-group>
+</div>
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js b/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js
new file mode 100644
index 0000000..6bf1a6b
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.js
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryAdministration constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param ActivatedRoute        The angular activated route module.
+ * @constructor
+ */
+function NfRegistryAdministration(nfRegistryService, ActivatedRoute) {
+    this.route = ActivatedRoute;
+    this.nfRegistryService = nfRegistryService;
+};
+
+NfRegistryAdministration.prototype = {
+    constructor: NfRegistryAdministration,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.nfRegistryService.perspective = 'administration';
+        this.route.params
+            .switchMap(function (params) {
+                self.nfRegistryService.setBreadcrumbState('out');
+                return self.nfRegistryService.getRegistry(params['registryId']);
+            })
+            .subscribe(function (registry) {
+                self.nfRegistryService.registry = registry;
+                self.nfRegistryService.setBreadcrumbState('in');
+            });
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.perspective = '';
+        this.nfRegistryService.registry = {};
+    },
+
+    /**
+     * Navigate to administer the registry.
+     *
+     * @param id     The registry id..
+     */
+    navigateToAdministration: function (id) {
+        this.route.navigateByUrl('nifi-registry/administration/' + id);
+    }
+};
+
+NfRegistryAdministration.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-administration.html!text'),
+        animations: [nfRegistryAnimations.slideInLeftAnimation],
+        host: {
+            '[@routeAnimation]': 'routeAnimation'
+        }
+    })
+];
+
+NfRegistryAdministration.parameters = [NfRegistryService, ngRouter.ActivatedRoute];
+
+module.exports = NfRegistryAdministration;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js b/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js
new file mode 100644
index 0000000..91a2da2
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/nf-registry-administration.spec.js
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var NfRegistryRoutes = require('nifi-registry/nf-registry.routes.js');
+var ngCoreTesting = require('@angular/core/testing');
+var ngCommon = require('@angular/common');
+var ngRouter = require('@angular/router');
+var ngPlatformBrowser = require('@angular/platform-browser');
+var FdsDemo = require('nifi-registry/components/fluid-design-system/fds-demo.js');
+var NfRegistry = require('nifi-registry/nf-registry.js');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var NfPageNotFoundComponent = require('nifi-registry/components/page-not-found/nf-registry-page-not-found.js');
+var NfRegistryExplorer = require('nifi-registry/components/explorer/nf-registry-explorer.js');
+var NfRegistryExplorerGridListViewer = require('nifi-registry/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js');
+var NfRegistryAdministration = require('nifi-registry/components/administration/nf-registry-administration.js');
+var NfRegistryGeneralAdministration = require('nifi-registry/components/administration/general/nf-registry-general-administration.js');
+var NfRegistryUsersAdministration = require('nifi-registry/components/administration/users/nf-registry-users-administration.js');
+var NfRegistryAddUser = require('nifi-registry/components/administration/users/add/nf-registry-add-user.js');
+var NfRegistryUserDetails = require('nifi-registry/components/administration/users/details/nf-registry-user-details.js');
+var NfRegistryUserPermissions = require('nifi-registry/components/administration/users/permissions/nf-registry-user-permissions.js');
+var NfRegistryBucketDetails = require('nifi-registry/components/administration/workflow/buckets/details/nf-registry-bucket-details.js');
+var NfRegistryBucketPermissions = require('nifi-registry/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js');
+var NfRegistryWorkflowAdministration = require('nifi-registry/components/administration/workflow/nf-registry-workflow-administration.js');
+var NfRegistryGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js');
+var NfRegistryBucketGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js');
+var NfRegistryDropletGridListViewer = require('nifi-registry/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js');
+var fdsCore = require('@fluid-design-system/core');
+var rxjs = require('rxjs/Rx');
+
+describe('NfRegistryAdministration Component', function () {
+    var comp;
+    var fixture;
+    var de;
+    var el;
+    var nfRegistryService;
+    var originalTimeout;
+
+    function ActivatedRouteStub() {
+        this._testParamMap = ngRouter.ParamMap;
+        this.subject = new rxjs.BehaviorSubject(ngRouter.convertToParamMap(this.testParamMap));
+        this.paramMap = this.subject.asObservable();
+
+        this.params = {
+            switchMap: function () {
+                return Observable.of({
+                    id: '1234',
+                    name: "Test Registry",
+                    certifications: [],
+                    users: [],
+                    buckets: []
+                });
+            }
+        };
+    };
+
+    ActivatedRouteStub.prototype = {
+        constructor: ActivatedRouteStub,
+        navigateByUrl: function (url) {
+            return url;
+        }
+    };
+
+    Object.defineProperty(ActivatedRouteStub.prototype, "testParamMap", {
+        get: function () {
+            return this._testParamMap;
+        },
+        set: function (params) {
+            this._testParamMap = ngRouter.convertToParamMap(params);
+            this.subject.next(this._testParamMap);
+        }
+    });
+
+    beforeEach(function () {
+        originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+        jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
+    });
+
+    afterEach(function () {
+        jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
+    });
+
+    beforeEach(ngCoreTesting.async(function () {
+        ngCoreTesting.TestBed.configureTestingModule({
+            imports: [
+                fdsCore,
+                NfRegistryRoutes
+            ],
+            declarations: [FdsDemo, NfRegistry, NfRegistryExplorer, NfRegistryExplorerGridListViewer, NfRegistryAdministration, NfRegistryGeneralAdministration, NfRegistryUsersAdministration, NfRegistryUserDetails, NfRegistryUserPermissions, NfRegistryBucketDetails, NfRegistryBucketPermissions, NfRegistryAddUser, NfRegistryWorkflowAdministration, NfRegistryGridListViewer, NfRegistryBucketGridListViewer, NfRegistryDropletGridListViewer, NfPageNotFoundComponent],
+            providers: [NfRegistryService, {
+                provide: ngCommon.APP_BASE_HREF,
+                useValue: '/'
+            }, {provide: ngRouter.ActivatedRoute, useClass: ActivatedRouteStub}]
+        });
+    }));
+
+    beforeEach(function () {
+        fixture = ngCoreTesting.TestBed.createComponent(NfRegistryAdministration);
+
+        // NfRegistryAdministration test instance
+        comp = fixture.componentInstance;
+
+        // NfRegistryService from the root injector
+        nfRegistryService = ngCoreTesting.TestBed.get(NfRegistryService);
+        // spyOn(nfRegistryService, 'getRegistries').and.returnValue(Promise.resolve([{
+        //     id: '1234',
+        //     name: "Test Registry",
+        //     certifications: [],
+        //     users: [],
+        //     buckets: []
+        // }]));
+
+        de = fixture.debugElement.query(ngPlatformBrowser.By.css('#nifi-registry-administration-perspective'));
+        el = de.nativeElement;
+    });
+
+    it('should have a defined component', function () {
+        fixture.detectChanges();
+        expect(comp).toBeDefined();
+        expect(de).toBeDefined();
+    });
+
+    it('should call Router.navigateByUrl("nifi-registry/administration/:registryId") with the ID of the registry', ngCoreTesting.inject([ngRouter.ActivatedRoute], function (router) {
+        fixture.detectChanges();
+        var spy = spyOn(router, 'navigateByUrl');
+        comp.navigateToAdministration('23f6cc59-0156-1000-06b4-2b0810089090');
+        var url = spy.calls.first().args[0];
+        expect(url).toBe('nifi-registry/administration/23f6cc59-0156-1000-06b4-2b0810089090');
+    }));
+
+    xit('should call `NfRegistryService.getRegistry` when the route ID changes', ngCoreTesting.inject([ngRouter.ActivatedRoute], function (activeRoute) {
+        spyOn(nfRegistryService, 'getRegistry');
+        activeRoute.testParamMap = {registryId: 1234};
+        fixture = ngCoreTesting.TestBed.createComponent(NfRegistryAdministration);
+        comp = fixture.componentInstance; // NfRegistryAdministration test instance
+        fixture.detectChanges();
+        expect(nfRegistryService.getRegistry).toHaveBeenCalledWith(1234);
+    }));
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.html b/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.html
new file mode 100644
index 0000000..6cdf631
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.html
@@ -0,0 +1,28 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div fxFill>
+    <div fxLayout="row" fxLayoutAlign="space-between center" class="pad-top-md pad-bottom-md pad-left-sm pad-right-sm">
+        <span class="mat-card-title">Add User</span>
+        <button md-icon-button (click)="closeSideNav()">
+            <md-icon color="primary">close</md-icon>
+        </button>
+    </div>
+    <button id="nf-registry-add-user-side-nav-container" md-raised-button color="fds-primary" (click)="closeSideNav()">
+        Close
+    </button>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.js b/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.js
new file mode 100644
index 0000000..2cbeb3b
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/add/nf-registry-add-user.js
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryAddUser constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param Router                The angular router module.
+ * @constructor
+ */
+function NfRegistryAddUser(nfRegistryService, Router) {
+    this.nfRegistryService = nfRegistryService;
+    this.router = Router;
+};
+
+NfRegistryAddUser.prototype = {
+    constructor: NfRegistryAddUser,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        this.nfRegistryService.sidenav.open();
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.sidenav.close();
+    },
+
+    /**
+     * Navigate to administer users for current registry.
+     */
+    closeSideNav: function () {
+        this.router.navigateByUrl('/nifi-registry/administration/' + this.nfRegistryService.registry.id + '/users');
+    }
+};
+
+NfRegistryAddUser.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-add-user.html!text')
+    })
+];
+
+NfRegistryAddUser.parameters = [NfRegistryService, ngRouter.Router];
+
+module.exports = NfRegistryAddUser;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.html b/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.html
new file mode 100644
index 0000000..49dd37e
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.html
@@ -0,0 +1,28 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div fxFill>
+    <div fxLayout="row" fxLayoutAlign="space-between center" class="pad-top-md pad-bottom-md pad-left-sm pad-right-sm">
+        <span class="mat-card-title">User Details</span>
+        <button md-icon-button (click)="closeSideNav()">
+            <md-icon color="primary">close</md-icon>
+        </button>
+    </div>
+    <button id="nf-registry-user-details-side-nav-container" md-raised-button color="fds-primary"
+            (click)="closeSideNav()">Close
+    </button>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.js b/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.js
new file mode 100644
index 0000000..5001586
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/details/nf-registry-user-details.js
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryUserDetails constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param Router                The angular router module.
+ * @constructor
+ */
+function NfRegistryUserDetails(nfRegistryService, Router) {
+    this.nfRegistryService = nfRegistryService;
+    this.router = Router;
+};
+
+NfRegistryUserDetails.prototype = {
+    constructor: NfRegistryUserDetails,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        this.nfRegistryService.sidenav.open();
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.sidenav.close();
+    },
+
+    /**
+     * Navigate to administer users for current registry.
+     */
+    closeSideNav: function () {
+        this.router.navigateByUrl('/nifi-registry/administration/' + this.nfRegistryService.registry.id + '/users');
+    }
+};
+
+NfRegistryUserDetails.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-user-details.html!text')
+    })
+];
+
+NfRegistryUserDetails.parameters = [NfRegistryService, ngRouter.Router];
+
+module.exports = NfRegistryUserDetails;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html
new file mode 100644
index 0000000..4dd9055
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html
@@ -0,0 +1,122 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div id="nifi-registry-users-administration-perspective" class="mat-elevation-z5">
+    <div layout="row" layout-align="space-between top" class="pad-top-md pad-bottom-sm pad-left-md pad-right-md">
+        <span class="mat-card-title">Authorized Users ({{usersPagingBar.range}}) <span hide-xs>of {{usersPagingBar.total}}</span></span>
+        <div flex class="push-right-sm" fxLayout="row" fxLayoutAlign="end center">
+            <td-chips [items]="nfRegistryService.autoCompleteUsers" (add)="nfRegistryService.usersSearchAdd($event)"
+                      (remove)="nfRegistryService.usersSearchRemove($event)"></td-chips>
+            <button class="push-right-sm" color="fds-secondary" md-raised-button (click)="execute({name: 'add'})">
+                Add User
+            </button>
+            <button color="fds-primary" md-raised-button [mdMenuTriggerFor]="userActionMenu">
+                Actions<i class="fa fa-caret-down" aria-hidden="true"></i>
+            </button>
+        </div>
+        <md-menu class="fds-primary-dropdown-button-menu" #userActionMenu="mdMenu" [overlapTrigger]="false">
+            <button md-menu-item> Need to loop over and provide list of all actions available to all currently selected
+                users
+            </button>
+        </md-menu>
+    </div>
+    <div class="pad-left-md pad-right-md">
+        <div id="nifi-registry-users-administration-list-container-column-header" fxLayout="row"
+             fxLayoutAlign="space-between center" class="td-data-table">
+            <div class="td-data-table-column" (click)="nfRegistryService.sortUsers($event, column)"
+                 [mdTooltip]="column.tooltip" *ngFor="let column of nfRegistryService.userColumns"
+                 fxFlex="{{column.width}}">
+                {{column.label}}
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'ASC'" class="fa fa-caret-up"
+                   aria-hidden="true"></i>
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'DESC'" class="fa fa-caret-down"
+                   aria-hidden="true"></i>
+            </div>
+            <div class="td-data-table-column">
+                <div fxLayout="row" fxLayoutAlign="end center">
+                    <md-checkbox class="pad-left-sm" [(ngModel)]="nfRegistryService.allUsersSelected"
+                                 (checked)="nfRegistryService.allUsersSelected"
+                                 (change)="nfRegistryService.toggleUsersSelectAll()"></md-checkbox>
+                </div>
+            </div>
+        </div>
+        <div id="nifi-registry-users-administration-list-container">
+            <div fxLayout="row" fxLayoutAlign="space-between center" class="td-data-table-row"
+                 [ngClass]="{'selected' : row.checked}" *ngFor="let row of nfRegistryService.filteredUsers"
+                 (click)="row.checked = !row.checked;nfRegistryService.toggleUserSelect(row)">
+                <div class="td-data-table-cell" *ngFor="let column of nfRegistryService.userColumns"
+                     fxFlex="{{column.width}}">
+                    <div *ngIf="column.name !== 'status'">
+                        {{column.format ? column.format(row[column.name]) : row[column.name]}}
+                    </div>
+                    <div *ngIf="column.name === 'status'">
+                        <i [ngClass]="(row[column.name] === 'authorized')?'fa fa-check-circle authorized':'fa fa-ban suspended'"
+                           aria-hidden="true"></i>
+                    </div>
+                </div>
+                <div class="td-data-table-cell">
+                    <div *ngIf="row.actions">
+                        <div *ngIf="row.actions.length <= 4" fxLayout="row" fxLayoutAlign="end center">
+                            <button (click)="execute(action, row)" *ngFor="let action of row.actions"
+                                    mdTooltip="{{action.tooltip}}" md-icon-button color="accent"
+                                    [disabled]="action.disabled ? '' : null">
+                                <i class="{{action.icon}}" aria-hidden="true"></i>
+                            </button>
+                            <md-checkbox class="pad-left-sm" [(ngModel)]="row.checked" [checked]="row.checked"
+                                         (change)="nfRegistryService.toggleUserSelect(row)"
+                                         (click)="row.checked = !row.checked;nfRegistryService.toggleUserSelect(row)"></md-checkbox>
+                        </div>
+                        <div *ngIf="row.actions.length > 4" fxLayout="row" fxLayoutAlign="end center">
+                            <button (click)="row.checked = !row.checked" mdTooltip="Actions" md-icon-button
+                                    [mdMenuTriggerFor]="userTableActionMenu">
+                                <i class="fa fa-ellipsis-h" aria-hidden="true"></i>
+                            </button>
+                            <md-menu #userTableActionMenu="mdMenu" [overlapTrigger]="false">
+                                <button (click)="execute(action, row)" *ngFor="let action of row.actions"
+                                        mdTooltip="{{action.tooltip}}" md-menu-item
+                                        [disabled]="action.disabled ? '' : null"
+                                        (click)="nfRegistryService.sidenav.toggle()">
+                                    <i class="{{action.icon}}" aria-hidden="true"></i>
+                                    <span>{{action.name}}</span>
+                                </button>
+                            </md-menu>
+                            <md-checkbox class="pad-left-sm" [(ngModel)]="row.checked" [checked]="row.checked"
+                                         (change)="nfRegistryService.toggleUserSelect(row)"
+                                         (click)="row.checked = !row.checked;nfRegistryService.toggleUserSelect(row)"></md-checkbox>
+                        </div>
+                    </div>
+                    <div *ngIf="!row.actions" fxLayout="row" fxLayoutAlign="end center">
+                        <md-checkbox class="pad-left-sm" [(ngModel)]="row.checked" [checked]="row.checked"
+                                     (change)="nfRegistryService.toggleUserSelect(row)"
+                                     (click)="row.checked = !row.checked;nfRegistryService.toggleUserSelect(row)"></md-checkbox>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="md-padding" *ngIf="!nfRegistryService.filteredUsers.length > 0" layout="row"
+             layout-align="center center">
+            <h3>No results to display.</h3>
+        </div>
+        <td-paging-bar id="nifi-registry-users-administration-list-paging-bar" #usersPagingBar [pageSizeAll]="true"
+                       [pageSizes]="[1, 2, 50, 100, 200, 500, 1000, 2000]"
+                       [initialPage]="1" [pageSize]="nfRegistryService.usersPageSize"
+                       [total]="nfRegistryService.usersPageCount" (change)="nfRegistryService.pageUsers($event)">
+            <span td-paging-bar-label hide-xs>Row per page:</span>
+        </td-paging-bar>
+    </div>
+</div>
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js
new file mode 100644
index 0000000..272df0c
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+var fdsDialogsModule = require('@fluid-design-system/dialogs');
+
+/**
+ * NfRegistryUsersAdministration constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param ActivatedRoute        The angular activated route module.
+ * @param Router                The angular router module.
+ * @param FdsDialogService      The FDS dialog service.
+ * @constructor
+ */
+function NfRegistryUsersAdministration(nfRegistryService, ActivatedRoute, Router, FdsDialogService) {
+    this.route = ActivatedRoute;
+    this.nfRegistryService = nfRegistryService;
+    this.router = Router;
+    this.dialogService = FdsDialogService;
+};
+
+NfRegistryUsersAdministration.prototype = {
+    constructor: NfRegistryUsersAdministration,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.route.params
+            .switchMap(function (params) {
+                self.nfRegistryService.adminPerspective = 'users';
+                return self.nfRegistryService.getUsers(self.nfRegistryService.registry.id);
+            })
+            .subscribe(function (users) {
+                self.nfRegistryService.users = self.nfRegistryService.filteredUsers = users;
+                self.nfRegistryService.filterUsers();
+            });
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.adminPerspective = '';
+        this.nfRegistryService.users = this.nfRegistryService.filteredUsers = [];
+    },
+
+    /**
+     * Execute the given user action.
+     *
+     * @param action        The action object.
+     * @param user          The user object the `action` will act upon.
+     */
+    execute: function (action, user) {
+        var self = this;
+        if (user) {
+            user.checked = !user.checked;
+        }
+        switch (action.name.toLowerCase()) {
+            case 'delete':
+                this.dialogService.openConfirm({
+                    title: 'Delete User',
+                    message: 'User will be deleted.',
+                    cancelButton: 'Cancel',
+                    acceptButton: 'Delete',
+                    acceptButtonColor: 'fds-warn'
+                }).afterClosed().subscribe(
+                    function (accept) {
+                        if (accept) {
+                            self.nfRegistryService.deleteUser(user.id);
+                        }
+                    });
+                break;
+            case 'suspend':
+                this.dialogService.openConfirm({
+                    title: 'Suspend User',
+                    message: 'User permissions will be suspended.',
+                    cancelButton: 'Cancel',
+                    acceptButton: 'Confirm',
+                    acceptButtonColor: 'fds-critical'
+                }).afterClosed().subscribe(
+                    function (accept) {
+                        if (accept) {
+                            self.nfRegistryService.suspendUser(user.id);
+                        }
+                    });
+                break;
+            case 'add':
+                this.router.navigateByUrl('/nifi-registry/administration/' + this.nfRegistryService.registry.id + '/users(sidenav:user/add)');
+                break;
+            default:
+                this.router.navigateByUrl('/nifi-registry/administration/' + this.nfRegistryService.registry.id + '/users(' + action.type + ':user/' + action.name + '/' + user.id + ')');
+                break;
+        }
+    }
+};
+
+NfRegistryUsersAdministration.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-users-administration.html!text'),
+        animations: [nfRegistryAnimations.slideInLeftAnimation],
+        host: {
+            '[@routeAnimation]': 'routeAnimation'
+        }
+    })
+];
+
+NfRegistryUsersAdministration.parameters = [NfRegistryService, ngRouter.ActivatedRoute, ngRouter.Router, fdsDialogsModule.FdsDialogService];
+
+module.exports = NfRegistryUsersAdministration;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.html b/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.html
new file mode 100644
index 0000000..1554821
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.html
@@ -0,0 +1,28 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div fxFill>
+    <div fxLayout="row" fxLayoutAlign="space-between center" class="pad-top-md pad-bottom-md pad-left-sm pad-right-sm">
+        <span class="mat-card-title">User Permissions</span>
+        <button md-icon-button (click)="closeSideNav()">
+            <md-icon color="primary">close</md-icon>
+        </button>
+    </div>
+    <button id="nf-registry-user-permissions-side-nav-container" md-raised-button color="fds-primary"
+            (click)="closeSideNav()">Close
+    </button>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.js b/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.js
new file mode 100644
index 0000000..0569409
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/permissions/nf-registry-user-permissions.js
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryUserPermissions constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param Router                The angular router module.
+ * @constructor
+ */
+function NfRegistryUserPermissions(nfRegistryService, Router) {
+    this.nfRegistryService = nfRegistryService;
+    this.router = Router;
+};
+
+NfRegistryUserPermissions.prototype = {
+    constructor: NfRegistryUserPermissions,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        this.nfRegistryService.sidenav.open();
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.sidenav.close();
+    },
+
+    /**
+     * Navigate to administer users for current registry.
+     */
+    closeSideNav: function () {
+        this.router.navigateByUrl('/nifi-registry/administration/' + this.nfRegistryService.registry.id + '/users');
+    }
+};
+
+NfRegistryUserPermissions.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-user-permissions.html!text')
+    })
+];
+
+NfRegistryUserPermissions.parameters = [NfRegistryService, ngRouter.Router];
+
+module.exports = NfRegistryUserPermissions;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.html b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.html
new file mode 100644
index 0000000..ff6bbdd
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.html
@@ -0,0 +1,28 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div fxFill>
+    <div fxLayout="row" fxLayoutAlign="space-between center" class="pad-top-md pad-bottom-md pad-left-sm pad-right-sm">
+        <span *ngIf="nfRegistryService.bucket.id" class="mat-card-title">{{nfRegistryService.bucket.name}}</span>
+        <button md-icon-button (click)="closeSideNav()">
+            <md-icon color="primary">close</md-icon>
+        </button>
+    </div>
+    <button id="nf-registry-workflow-bucket-permissions-side-nav-container" md-raised-button color="fds-primary"
+            (click)="closeSideNav()">Close
+    </button>
+</div>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js
new file mode 100644
index 0000000..98c5b0f
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+
+/**
+ * NfRegistryBucketPermissions constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param ActivatedRoute        The angular activated route module.
+ * @param Router                The angular router module.
+ * @constructor
+ */
+function NfRegistryBucketPermissions(nfRegistryService, ActivatedRoute, Router) {
+    this.nfRegistryService = nfRegistryService;
+    this.route = ActivatedRoute;
+    this.router = Router;
+};
+
+NfRegistryBucketPermissions.prototype = {
+    constructor: NfRegistryBucketPermissions,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.nfRegistryService.sidenav.open();
+        this.route.params
+            .switchMap(function (params) {
+                return self.nfRegistryService.getBucket(self.nfRegistryService.registry.id, params['bucketId']);
+            })
+            .subscribe(function (bucket) {
+                self.nfRegistryService.bucket = bucket;
+            });
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.sidenav.close();
+        this.nfRegistryService.bucket = {};
+    },
+
+    /**
+     * Navigate to administer the buckets of the current registry.
+     */
+    closeSideNav: function () {
+        this.router.navigateByUrl('/nifi-registry/administration/' + this.nfRegistryService.registry.id + '/workflow');
+    }
+};
+
+NfRegistryBucketPermissions.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-bucket-permissions.html!text')
+    })
+];
+
+NfRegistryBucketPermissions.parameters = [NfRegistryService, ngRouter.ActivatedRoute, ngRouter.Router];
+
+module.exports = NfRegistryBucketPermissions;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.html b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.html
new file mode 100644
index 0000000..a00c419
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.html
@@ -0,0 +1,170 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div id="nifi-registry-workflow-administration-perspective-buckets-container" class="mat-elevation-z5">
+    <div fxFlex class="pad-top-md pad-bottom-sm pad-left-md pad-right-md">
+        <span class="mat-card-title">Buckets ({{nfRegistryService.buckets.length}})</span>
+        <div flex fxLayoutAlign="start center" class="pad-top-md pad-bottom-sm">
+            <md-input-container flex>
+                <input mdInput placeholder="Create New Bucket">
+            </md-input-container>
+            <button class="input-button" color="fds-regular" md-raised-button
+                    i18n="Create new bucket button|A button for creating a new bucket in the registry.@@nf-admin-workflow-create-bucket-button">
+                Create
+            </button>
+        </div>
+        <div id="nifi-registry-workflow-administration-buckets-list-container-column-header" fxLayout="row"
+             fxLayoutAlign="space-between center" class="td-data-table">
+            <div class="td-data-table-column" (click)="nfRegistryService.sortBuckets($event, column)"
+                 [mdTooltip]="column.tooltip" *ngFor="let column of nfRegistryService.bucketColumns"
+                 fxFlex="{{column.width}}">
+                {{column.label}}
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'ASC'" class="fa fa-caret-up"
+                   aria-hidden="true"></i>
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'DESC'" class="fa fa-caret-down"
+                   aria-hidden="true"></i>
+            </div>
+            <div class="td-data-table-column"></div>
+        </div>
+        <div id="nifi-registry-workflow-administration-buckets-list-container">
+            <div fxLayout="row" fxLayoutAlign="space-between center" class="td-data-table-row"
+                 [ngClass]="{'selected' : row.checked}" *ngFor="let row of nfRegistryService.filteredBuckets"
+                 (click)="row.checked = !row.checked;nfRegistryService.toggleBucketSelect(row)">
+                <div class="td-data-table-cell" *ngFor="let column of nfRegistryService.bucketColumns"
+                     fxFlex="{{column.width}}">
+                    <div>
+                        {{column.format ? column.format(row[column.name]) : row[column.name]}}
+                    </div>
+                </div>
+                <div class="td-data-table-cell">
+                    <div *ngIf="row.actions">
+                        <div *ngIf="row.actions.length <= 4" fxLayout="row" fxLayoutAlign="end center">
+                            <button (click)="execute(action, row)" *ngFor="let action of row.actions"
+                                    mdTooltip="{{action.tooltip}}" md-icon-button color="accent"
+                                    [disabled]="action.disabled ? '' : null">
+                                <i class="{{action.icon}}" aria-hidden="true"></i>
+                            </button>
+                            <md-checkbox class="pad-left-sm" [(ngModel)]="row.checked" [checked]="row.checked"
+                                         (change)="nfRegistryService.toggleBucketSelect(row)"
+                                         (click)="row.checked = !row.checked;nfRegistryService.toggleBucketSelect(row)"></md-checkbox>
+                        </div>
+                        <div *ngIf="row.actions.length > 4" fxLayout="row" fxLayoutAlign="end center">
+                            <button (click)="row.checked = !row.checked" mdTooltip="Actions" md-icon-button
+                                    [mdMenuTriggerFor]="bucketTableActionMenu">
+                                <i class="fa fa-ellipsis-h" aria-hidden="true"></i>
+                            </button>
+                            <md-menu #bucketTableActionMenu="mdMenu" [overlapTrigger]="false">
+                                <button (click)="execute(action, row)" *ngFor="let action of row.actions"
+                                        mdTooltip="{{action.tooltip}}" md-menu-item
+                                        [disabled]="action.disabled ? '' : null">
+                                    <i class="{{action.icon}}" aria-hidden="true"></i>
+                                    <span>{{action.name}}</span>
+                                </button>
+                            </md-menu>
+                            <md-checkbox [(ngModel)]="row.checked" [checked]="row.checked"
+                                         (change)="nfRegistryService.toggleBucketSelect(row)"
+                                         (click)="row.checked = !row.checked;nfRegistryService.toggleBucketSelect(row)"></md-checkbox>
+                        </div>
+                    </div>
+                    <div *ngIf="!row.actions" fxLayout="row" fxLayoutAlign="end center">
+                        <md-checkbox [(ngModel)]="row.checked" [checked]="row.checked"
+                                     (change)="nfRegistryService.toggleBucketSelect(row)"
+                                     (click)="row.checked = !row.checked;nfRegistryService.toggleBucketSelect(row)"></md-checkbox>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div *ngIf="false" id="nifi-registry-workflow-administration-perspective-certifications-container"
+     class="mat-elevation-z5">
+    <div flex fxFill class="pad-top-md pad-bottom-sm pad-left-md pad-right-md">
+        <span class="mat-card-title">Certifications ({{nfRegistryService.certifications.length}})</span>
+        <div flex fxLayoutAlign="start center" class="pad-top-md pad-bottom-sm">
+            <md-input-container flex>
+                <input mdInput placeholder="Create New Certifications">
+            </md-input-container>
+            <button class="input-button" color="fds-regular" md-raised-button>
+                Create
+            </button>
+        </div>
+        <div id="nifi-registry-workflow-administration-certifications-list-container-column-header" flex
+             class="td-data-table">
+            <div class="td-data-table-column" (click)="nfRegistryService.sortCertifications($event, column)"
+                 [mdTooltip]="column.tooltip" *ngFor="let column of nfRegistryService.certificationColumns"
+                 fxFlex="{{column.width}}">
+                {{column.label}}
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'ASC'" class="fa fa-caret-up"
+                   aria-hidden="true"></i>
+                <i *ngIf="column.active && column.sortable && column.sortOrder === 'DESC'" class="fa fa-caret-down"
+                   aria-hidden="true"></i>
+            </div>
+            <div class="td-data-table-column" fxFlex="20"></div>
+        </div>
+        <div id="nifi-registry-workflow-administration-certifications-list-container">
+            <div fxLayout="row" fxLayoutAlign="space-between center" class="td-data-table-row"
+                 [ngClass]="{'selected' : row.checked}" *ngFor="let row of nfRegistryService.filteredCertifications">
+                <div class="td-data-table-cell" *ngFor="let column of nfRegistryService.certificationColumns"
+                     fxFlex="{{column.width}}">
+                    <div *ngIf="column.name !== 'usage' && column.name !== 'badge'">
+                        {{column.format ? column.format(row[column.name]) : row[column.name]}}
+                    </div>
+                    <div *ngIf="column.name === 'usage'">
+                        <md-button-toggle-group class="on-off-toggle-group" (change)="row['usage'] = !row['usage']">
+                            <md-button-toggle value="true" [checked]="row[column.name] === true">
+                                ON
+                            </md-button-toggle>
+                            <md-button-toggle value="false" [checked]="row[column.name] !== true" class="off-toggle">
+                                OFF
+                            </md-button-toggle>
+                        </md-button-toggle-group>
+                    </div>
+                    <div *ngIf="column.name === 'badge'" class="pad-left-md">
+                        <button [style.background]="row[column.name].background" [style.color]="row[column.name].color"
+                                mdTooltip="{{row[column.name].tooltip}}" md-icon-button class="badge"
+                                [disabled]="!row['usage'] ? '' : null">
+                            <i class="{{row[column.name].icon}}" aria-hidden="true"></i>
+                        </button>
+                    </div>
+                </div>
+                <div class="td-data-table-cell" fxFlex="20">
+                    <div *ngIf="row.actions">
+                        <div *ngIf="row.actions.length < 4" fxLayout="row" fxLayoutAlign="end center">
+                            <button *ngFor="let action of row.actions" mdTooltip="{{action.tooltip}}" md-icon-button
+                                    color="accent" [disabled]="action.disabled ? '' : null">
+                                <i class="{{action.icon}}" aria-hidden="true"></i>
+                            </button>
+                        </div>
+                        <div *ngIf="row.actions.length >= 4" fxLayout="row" fxLayoutAlign="end center">
+                            <button mdTooltip="Actions" md-icon-button [mdMenuTriggerFor]="tableActionMenu">
+                                <i class="fa fa-ellipsis-h" aria-hidden="true"></i>
+                            </button>
+                            <md-menu #tableActionMenu="mdMenu" [overlapTrigger]="false">
+                                <button *ngFor="let action of row.actions" mdTooltip="{{action.tooltip}}" md-menu-item
+                                        [disabled]="action.disabled ? '' : null">
+                                    <i class="{{action.icon}}" aria-hidden="true"></i>
+                                    <span>{{action.name}}</span>
+                                </button>
+                            </md-menu>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.js b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.js
new file mode 100644
index 0000000..eb64298
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/nf-registry-workflow-administration.js
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+var ngRouter = require('@angular/router');
+var nfRegistryAnimations = require('nifi-registry/nf-registry.animations.js');
+var fdsDialogsModule = require('@fluid-design-system/dialogs');
+
+/**
+ * NfRegistryWorkflowAdministration constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @param ActivatedRoute        The angular activated route module.
+ * @param Router                The angular router module.
+ * @param FdsDialogService      The FDS dialog service.
+ * @constructor
+ */
+function NfRegistryWorkflowAdministration(nfRegistryService, ActivatedRoute, Router, FdsDialogService) {
+    this.route = ActivatedRoute;
+    this.nfRegistryService = nfRegistryService;
+    this.router = Router;
+    this.dialogService = FdsDialogService;
+};
+
+NfRegistryWorkflowAdministration.prototype = {
+    constructor: NfRegistryWorkflowAdministration,
+
+    /**
+     * Initialize the component.
+     */
+    ngOnInit: function () {
+        var self = this;
+        this.route.params
+            .subscribe(function () {
+                self.nfRegistryService.adminPerspective = 'workflow';
+                // TODO: implement certifications
+                // self.nfRegistryService.getCertifications(self.nfRegistryService.registry.id).then(function(certifications) {
+                //     self.nfRegistryService.certifications = self.nfRegistryService.filteredCertifications = certifications;
+                //     self.nfRegistryService.filterCertifications();
+                // });
+
+                self.nfRegistryService.getBuckets(self.nfRegistryService.registry.id).then(function (buckets) {
+                    self.nfRegistryService.buckets = self.nfRegistryService.filteredBuckets = buckets;
+                    self.nfRegistryService.filterBuckets();
+                });
+
+            });
+
+    },
+
+    /**
+     * Destroy the component.
+     */
+    ngOnDestroy: function () {
+        this.nfRegistryService.adminPerspective = '';
+        this.nfRegistryService.certifications = this.nfRegistryService.filteredCertifications = [];
+        this.nfRegistryService.buckets = [];
+        this.nfRegistryService.filteredBuckets = [];
+        this.autoCompleteBuckets = [];
+    },
+
+    /**
+     * Execute the given bucket action.
+     *
+     * @param action        The action object.
+     * @param bucket        The bucket object the `action` will act upon.
+     */
+    execute: function (action, bucket) {
+        var self = this;
+        bucket.checked = !bucket.checked;
+        switch (action.name.toLowerCase()) {
+            case 'delete':
+                this.dialogService.openConfirm({
+                    title: 'Delete Bucket',
+                    message: 'All versions of all flows will be deleted.',
+                    cancelButton: 'Cancel',
+                    acceptButton: 'Delete',
+                    acceptButtonColor: 'fds-warn'
+                }).afterClosed().subscribe(
+                    function (accept) {
+                        if (accept) {
+                            self.nfRegistryService.deleteBucket(bucket.id);
+                        }
+                    });
+                break;
+            case 'permissions':
+                this.router.navigateByUrl('/nifi-registry/administration/' + this.nfRegistryService.registry.id + '/workflow(' + action.type + ':bucket/' + action.name + '/' + bucket.id + ')');
+                break;
+            default:
+                break;
+        }
+    }
+};
+
+NfRegistryWorkflowAdministration.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-workflow-administration.html!text'),
+        animations: [nfRegistryAnimations.slideInLeftAnimation],
+        host: {
+            '[@routeAnimation]': 'routeAnimation'
+        }
+    })
+];
+
+NfRegistryWorkflowAdministration.parameters = [NfRegistryService, ngRouter.ActivatedRoute, ngRouter.Router, fdsDialogsModule.FdsDialogService];
+
+module.exports = NfRegistryWorkflowAdministration;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.html b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.html
new file mode 100644
index 0000000..694065b
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.html
@@ -0,0 +1,18 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<router-outlet></router-outlet>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js
new file mode 100644
index 0000000..9aa528a
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var ngCore = require('@angular/core');
+var NfRegistryService = require('nifi-registry/services/nf-registry.service.js');
+
+/**
+ * NfRegistryExplorerGridListViewer constructor.
+ *
+ * @param nfRegistryService     The nf-registry.service module.
+ * @constructor
+ */
+
+function NfRegistryExplorerGridListViewer(nfRegistryService) {
+    this.nfRegistryService = nfRegistryService;
+};
+
+NfRegistryExplorerGridListViewer.prototype = {
+    constructor: NfRegistryExplorerGridListViewer
+};
+
+NfRegistryExplorerGridListViewer.annotations = [
+    new ngCore.Component({
+        template: require('./nf-registry-explorer-grid-list-viewer.html!text')
+    })
+];
+
+NfRegistryExplorerGridListViewer.parameters = [NfRegistryService];
+
+module.exports = NfRegistryExplorerGridListViewer;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.html
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.html b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.html
new file mode 100644
index 0000000..e277b48
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.html
@@ -0,0 +1,17 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+


[8/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

Posted by mc...@apache.org.
[NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8


Project: http://git-wip-us.apache.org/repos/asf/nifi-registry/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi-registry/commit/7fa56bea
Tree: http://git-wip-us.apache.org/repos/asf/nifi-registry/tree/7fa56bea
Diff: http://git-wip-us.apache.org/repos/asf/nifi-registry/diff/7fa56bea

Branch: refs/heads/master
Commit: 7fa56bea9ff73f096480bfb6d9b5ab85a09c0dcf
Parents: a1629c8
Author: Scott Aslan <sc...@gmail.com>
Authored: Tue Sep 5 17:02:14 2017 -0400
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Sep 6 13:45:00 2017 -0400

----------------------------------------------------------------------
 .travis.yml                                     |   21 +
 nifi-registry-jetty/pom.xml                     |   25 +-
 .../apache/nifi/registry/jetty/JettyServer.java |   23 +-
 .../org/apache/nifi-registry/web/webdefault.xml |  556 ++++
 nifi-registry-web-api/pom.xml                   |   10 +-
 nifi-registry-web-ui/pom.xml                    |  467 ++-
 .../src/main/frontend/Gruntfile.js              |   91 +
 .../src/main/frontend/karma-test-shim.js        |  113 +
 .../src/main/frontend/karma.conf.ci.js          |   39 +
 .../src/main/frontend/karma.conf.js             |  140 +
 .../src/main/frontend/package.json              |   65 +
 .../src/main/frontend/protractor.config.js      |  201 ++
 .../src/main/locale/messages.es.xlf             |   46 +
 .../src/main/platform/core/README.md            |   33 +
 .../core/common/styles/_basicElements.scss      |  130 +
 .../core/common/styles/_buttonToggles.scss      |   98 +
 .../platform/core/common/styles/_buttons.scss   |  198 ++
 .../core/common/styles/_checkboxes.scss         |   85 +
 .../platform/core/common/styles/_chips.scss     |   67 +
 .../core/common/styles/_expansionPanels.scss    |   59 +
 .../core/common/styles/_globalVars.scss         |   80 +
 .../core/common/styles/_helperClasses.scss      |   72 +
 .../platform/core/common/styles/_inputs.scss    |  124 +
 .../platform/core/common/styles/_links.scss     |   33 +
 .../platform/core/common/styles/_menus.scss     |  120 +
 .../platform/core/common/styles/_modals.scss    |   23 +
 .../platform/core/common/styles/_panels.scss    |   54 +
 .../platform/core/common/styles/_radios.scss    |   56 +
 .../platform/core/common/styles/_sideNav.scss   |   20 +
 .../platform/core/common/styles/_tables.scss    |  113 +
 .../main/platform/core/common/styles/_tabs.scss |   41 +
 .../platform/core/common/styles/_tooltips.scss  |   24 +
 .../core/common/styles/fluid-design-system.scss |   55 +
 .../core/dialogs/_fds-dialog-component.scss     |   21 +
 .../confirm-dialog.component.html               |   45 +
 .../confirm-dialog/confirm-dialog.component.js  |   64 +
 .../core/dialogs/fds-dialog.component.html      |   29 +
 .../core/dialogs/fds-dialog.component.js        |   82 +
 .../platform/core/dialogs/fds-dialogs.module.js |   87 +
 .../core/dialogs/services/dialog.service.js     |  130 +
 .../platform/core/fluid-design-system.module.js |   99 +
 .../resources/filters/registry-min.properties   |   19 +
 .../main/resources/filters/registry.properties  |   23 +
 .../src/main/webapp/WEB-INF/pages/index.jsp     |   25 +-
 .../src/main/webapp/WEB-INF/web.xml             |   29 +-
 .../nf-registry-general-administration.html     |   30 +
 .../nf-registry-general-administration.js       |   61 +
 .../nf-registry-administration.html             |   39 +
 .../nf-registry-administration.js               |   84 +
 .../nf-registry-administration.spec.js          |  152 +
 .../users/add/nf-registry-add-user.html         |   28 +
 .../users/add/nf-registry-add-user.js           |   66 +
 .../users/details/nf-registry-user-details.html |   28 +
 .../users/details/nf-registry-user-details.js   |   66 +
 .../users/nf-registry-users-administration.html |  122 +
 .../users/nf-registry-users-administration.js   |  128 +
 .../nf-registry-user-permissions.html           |   28 +
 .../permissions/nf-registry-user-permissions.js |   66 +
 .../nf-registry-bucket-permissions.html         |   28 +
 .../nf-registry-bucket-permissions.js           |   77 +
 .../nf-registry-workflow-administration.html    |  170 +
 .../nf-registry-workflow-administration.js      |  121 +
 .../nf-registry-explorer-grid-list-viewer.html  |   18 +
 .../nf-registry-explorer-grid-list-viewer.js    |   43 +
 .../nf-registry-droplet-grid-list-viewer.html   |   17 +
 .../nf-registry-droplet-grid-list-viewer.js     |   75 +
 .../nf-registry-bucket-grid-list-viewer.html    |   18 +
 .../nf-registry-bucket-grid-list-viewer.js      |   76 +
 .../registry/nf-registry-grid-list-viewer.html  |  186 ++
 .../registry/nf-registry-grid-list-viewer.js    |  106 +
 .../explorer/nf-registry-explorer.html          |   18 +
 .../components/explorer/nf-registry-explorer.js |   61 +
 .../fluid-design-system/fds-demo.html           | 3060 ++++++++++++++++++
 .../components/fluid-design-system/fds-demo.js  | 1029 ++++++
 .../nf-registry-page-not-found.js               |   36 +
 .../src/main/webapp/css/main.css                |   22 -
 .../src/main/webapp/images/registry-favicon.png |  Bin 0 -> 388 bytes
 .../webapp/images/registry-logo-web-app.svg     |   17 +
 .../src/main/webapp/nf-registry-bootstrap.js    |   54 +
 .../src/main/webapp/nf-registry.animations.js   |  119 +
 .../src/main/webapp/nf-registry.e2e-spec.js     |   30 +
 .../src/main/webapp/nf-registry.html            |   88 +
 .../src/main/webapp/nf-registry.js              |   59 +
 .../src/main/webapp/nf-registry.module.js       |   60 +
 .../src/main/webapp/nf-registry.routes.js       |  104 +
 .../src/main/webapp/nf-registry.spec.js         |   63 +
 .../main/webapp/services/nf-registry.service.js |  981 ++++++
 .../src/main/webapp/systemjs-angular-loader.js  |   66 +
 .../src/main/webapp/systemjs.builder.config.js  |  137 +
 .../src/main/webapp/systemjs.config.extras.js   |   27 +
 .../src/main/webapp/systemjs.spec.config.js     |   76 +
 .../src/main/webapp/theming/_helperClasses.scss |   63 +
 .../main/webapp/theming/_structureElements.scss |   82 +
 .../administration/_structureElements.scss      |   22 +
 .../general/_structureElements.scss             |   25 +
 .../users/_structureElements.scss               |   61 +
 .../workflow/_structureElements.scss            |   61 +
 .../explorer/grid-list/_structureElements.scss  |   44 +
 .../fluid-design-system/_structureElements.scss |   25 +
 .../src/main/webapp/theming/nf-registry.scss    |   30 +
 pom.xml                                         |  141 +-
 101 files changed, 12282 insertions(+), 147 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4bef783
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# before_install
+#   1. Configures pre-installed Chrome on Travis CI for karma testing
+before_install:
+  - export CHROME_BIN=chromium-browser
+  - export DISPLAY=:99.0
+  - sh -e /etc/init.d/xvfb start
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-jetty/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-jetty/pom.xml b/nifi-registry-jetty/pom.xml
index 3c75832..38e6442 100644
--- a/nifi-registry-jetty/pom.xml
+++ b/nifi-registry-jetty/pom.xml
@@ -13,7 +13,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.nifi.registry</groupId>
@@ -50,28 +51,22 @@
             <artifactId>jetty-servlets</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-jsp</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
         <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>javax.servlet.jsp</groupId>
-            <artifactId>javax.servlet.jsp-api</artifactId>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-annotations</artifactId>
         </dependency>
         <dependency>
-            <groupId>javax.el</groupId>
-            <artifactId>javax.el-api</artifactId>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>apache-jsp</artifactId>
+            <scope>compile</scope>
         </dependency>
         <dependency>
-            <groupId>javax.servlet.jsp.jstl</groupId>
-            <artifactId>javax.servlet.jsp.jstl-api</artifactId>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>apache-jstl</artifactId>
+            <scope>compile</scope>
         </dependency>
     </dependencies>
 </project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
----------------------------------------------------------------------
diff --git a/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java b/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
index 474f907..9f71e5f 100644
--- a/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
+++ b/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
@@ -20,6 +20,7 @@ import org.apache.nifi.registry.security.AuthorizationProvider;
 import org.apache.nifi.registry.security.AuthorizedUserFilter;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.HttpConfiguration;
@@ -32,6 +33,9 @@ import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,6 +44,7 @@ import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 import java.io.File;
 import java.io.FileFilter;
+import java.io.IOException;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -56,7 +61,7 @@ import java.util.Set;
 public class JettyServer {
 
     private static final Logger logger = LoggerFactory.getLogger(JettyServer.class);
-
+    private static final String WEB_DEFAULTS_XML = "org/apache/nifi-registry/web/webdefault.xml";
     private static final int HEADER_BUFFER_SIZE = 16 * 1024; // 16kb
 
     private static final FileFilter WAR_FILTER = new FileFilter() {
@@ -79,6 +84,11 @@ public class JettyServer {
 
         this.properties = properties;
         this.server = new Server(threadPool);
+
+        // enable the annotation based configuration to ensure the jsp container is initialized properly
+        final Configuration.ClassList classlist = Configuration.ClassList.setServerDefault(server);
+        classlist.addBefore(JettyWebXmlConfiguration.class.getName(), AnnotationConfiguration.class.getName());
+
         configureConnectors();
         loadWars();
     }
@@ -223,6 +233,7 @@ public class JettyServer {
         List<String> serverClasses = new ArrayList<>(Arrays.asList(webappContext.getServerClasses()));
         serverClasses.remove("org.slf4j.");
         webappContext.setServerClasses(serverClasses.toArray(new String[0]));
+        webappContext.setDefaultsDescriptor(WEB_DEFAULTS_XML);
 
         // get the temp directory for this webapp
         final File webWorkingDirectory = properties.getWebWorkingDirectory();
@@ -245,11 +256,11 @@ public class JettyServer {
         // configure the max form size (3x the default)
         webappContext.setMaxFormContentSize(600000);
 
-//        try {
-//            webappContext.setClassLoader(new WebAppClassLoader(ClassLoader.getSystemClassLoader(), webappContext));
-//        } catch (final IOException ioe) {
-//            throw new RuntimeException(ioe);
-//        }
+        try {
+            webappContext.setClassLoader(new WebAppClassLoader(ClassLoader.getSystemClassLoader(), webappContext));
+        } catch (final IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
         logger.info("Loading WAR: " + warFile.getAbsolutePath() + " with context path set to " + contextPath);
         return webappContext;
     }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-jetty/src/main/resources/org/apache/nifi-registry/web/webdefault.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-jetty/src/main/resources/org/apache/nifi-registry/web/webdefault.xml b/nifi-registry-jetty/src/main/resources/org/apache/nifi-registry/web/webdefault.xml
new file mode 100644
index 0000000..814dbd8
--- /dev/null
+++ b/nifi-registry-jetty/src/main/resources/org/apache/nifi-registry/web/webdefault.xml
@@ -0,0 +1,556 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<web-app 
+    xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+    metadata-complete="false"
+    version="3.1"> 
+
+    <!-- ===================================================================== -->
+    <!-- This file contains the default descriptor for web applications.       -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    <!-- The intent of this descriptor is to include jetty specific or common  -->
+    <!-- configuration for all webapps.   If a context has a webdefault.xml    -->
+    <!-- descriptor, it is applied before the contexts own web.xml file        -->
+    <!--                                                                       -->
+    <!-- A context may be assigned a default descriptor by:                    -->
+    <!--  + Calling WebApplicationContext.setDefaultsDescriptor                -->
+    <!--  + Passed an arg to addWebApplications                                -->
+    <!--                                                                       -->
+    <!-- This file is used both as the resource within the jetty.jar (which is -->
+    <!-- used as the default if no explicit defaults descriptor is set) and it -->
+    <!-- is copied to the etc directory of the Jetty distro and explicitly     -->
+    <!-- by the jetty.xml file.                                                -->
+    <!--                                                                       -->
+    <!-- ===================================================================== -->
+
+    <description>
+        Default web.xml file.  
+        This file is applied to a Web application before it's own WEB_INF/web.xml file
+    </description>
+
+    <!-- ==================================================================== -->
+    <!-- Removes static references to beans from javax.el.BeanELResolver to   -->
+    <!-- ensure webapp classloader can be released on undeploy                -->
+    <!-- ==================================================================== -->
+    <listener>
+        <listener-class>org.eclipse.jetty.servlet.listener.ELContextCleaner</listener-class>
+    </listener>
+  
+    <!-- ==================================================================== -->
+    <!-- Removes static cache of Methods from java.beans.Introspector to      -->
+    <!-- ensure webapp classloader can be released on undeploy                -->
+    <!-- ==================================================================== -->  
+    <listener>
+        <listener-class>org.eclipse.jetty.servlet.listener.IntrospectorCleaner</listener-class>
+    </listener>
+  
+
+    <!-- ==================================================================== -->
+    <!-- Context params to control Session Cookies                            -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+    <!--
+      UNCOMMENT TO ACTIVATE 
+      <context-param> 
+        <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name> 
+        <param-value>127.0.0.1</param-value> 
+      </context-param> 
+      <context-param>
+        <param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
+        <param-value>/</param-value>
+      </context-param>
+      <context-param>
+        <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
+        <param-value>-1</param-value>
+      </context-param>
+    -->
+
+    <!-- ==================================================================== -->
+    <!-- The default servlet.                                                 -->
+    <!-- This servlet, normally mapped to /, provides the handling for static -->
+    <!-- content, OPTIONS and TRACE methods for the context.                  -->
+    <!-- The following initParameters are supported:                          -->
+    <!--  
+    *  acceptRanges      If true, range requests and responses are
+    *                    supported
+    *
+    *  dirAllowed        If true, directory listings are returned if no
+    *                    welcome file is found. Else 403 Forbidden.
+    *
+    *  welcomeServlets   If true, attempt to dispatch to welcome files
+    *                    that are servlets, but only after no matching static
+    *                    resources could be found. If false, then a welcome
+    *                    file must exist on disk. If "exact", then exact
+    *                    servlet matches are supported without an existing file.
+    *                    Default is true.
+    *
+    *                    This must be false if you want directory listings,
+    *                    but have index.jsp in your welcome file list.
+    *
+    *  redirectWelcome   If true, welcome files are redirected rather than
+    *                    forwarded to.
+    *
+    *  gzip              If set to true, then static content will be served as
+    *                    gzip content encoded if a matching resource is
+    *                    found ending with ".gz"
+    *
+    *  resourceBase      Set to replace the context resource base
+    *
+    *  resourceCache     If set, this is a context attribute name, which the servlet
+    *                    will use to look for a shared ResourceCache instance.
+    *
+    *  relativeResourceBase
+    *                    Set with a pathname relative to the base of the
+    *                    servlet context root. Useful for only serving static content out
+    *                    of only specific subdirectories.
+    *
+    *  pathInfoOnly      If true, only the path info will be applied to the resourceBase
+    *
+    *  stylesheet        Set with the location of an optional stylesheet that will be used
+    *                    to decorate the directory listing html.
+    *
+    *  aliases           If True, aliases of resources are allowed (eg. symbolic
+    *                    links and caps variations). May bypass security constraints.
+    *                    
+    *  etags             If True, weak etags will be generated and handled.
+    *
+    *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
+    *  maxCachedFileSize The maximum size of a file to cache
+    *  maxCachedFiles    The maximum number of files to cache
+    *
+    *  useFileMappedBuffer
+    *                    If set to true, it will use mapped file buffer to serve static content
+    *                    when using NIO connector. Setting this value to false means that
+    *                    a direct buffer will be used instead of a mapped file buffer.
+    *                    By default, this is set to true.
+    *
+    *  cacheControl      If set, all static content will have this value set as the cache-control
+    *                    header.
+    *
+    -->
+ 
+ 
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+    <servlet>
+        <servlet-name>default</servlet-name>
+        <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
+        <init-param>
+            <param-name>aliases</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>acceptRanges</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>dirAllowed</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>welcomeServlets</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>redirectWelcome</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>maxCacheSize</param-name>
+            <param-value>256000000</param-value>
+        </init-param>
+        <init-param>
+            <param-name>maxCachedFileSize</param-name>
+            <param-value>200000000</param-value>
+        </init-param>
+        <init-param>
+            <param-name>maxCachedFiles</param-name>
+            <param-value>2048</param-value>
+        </init-param>
+        <init-param>
+            <param-name>gzip</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>etags</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>useFileMappedBuffer</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <!--
+        <init-param>
+          <param-name>resourceCache</param-name>
+          <param-value>resourceCache</param-value>
+        </init-param>
+        -->
+        <!--
+        <init-param>
+          <param-name>cacheControl</param-name>
+          <param-value>max-age=3600,public</param-value>
+        </init-param>
+        -->
+        <load-on-startup>0</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>default</servlet-name>
+        <url-pattern>/</url-pattern>
+    </servlet-mapping>
+
+
+    <!-- ==================================================================== -->
+    <!-- JSP Servlet                                                          -->
+    <!-- This is the jasper JSP servlet from the jakarta project              -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+    <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
+    <!-- used by Glassfish to support JSP pages.  Traditionally, this servlet -->
+    <!-- is mapped to URL patterh "*.jsp".  This servlet supports the         -->
+    <!-- following initialization parameters (default values are in square    -->
+    <!-- brackets):                                                           -->
+    <!--                                                                      -->
+    <!--   checkInterval       If development is false and reloading is true, -->
+    <!--                       background compiles are enabled. checkInterval -->
+    <!--                       is the time in seconds between checks to see   -->
+    <!--                       if a JSP page needs to be recompiled. [300]    -->
+    <!--                                                                      -->
+    <!--   compiler            Which compiler Ant should use to compile JSP   -->
+    <!--                       pages.  See the Ant documenation for more      -->
+    <!--                       information. [javac]                           -->
+    <!--                                                                      -->
+    <!--   classdebuginfo      Should the class file be compiled with         -->
+    <!--                       debugging information?  [true]                 -->
+    <!--                                                                      -->
+    <!--   classpath           What class path should I use while compiling   -->
+    <!--                       generated servlets?  [Created dynamically      -->
+    <!--                       based on the current web application]          -->
+    <!--                       Set to ? to make the container explicitly set  -->
+    <!--                       this parameter.                                -->
+    <!--                                                                      -->
+    <!--   development         Is Jasper used in development mode (will check -->
+    <!--                       for JSP modification on every access)?  [true] -->
+    <!--                                                                      -->
+    <!--   enablePooling       Determines whether tag handler pooling is      -->
+    <!--                       enabled  [true]                                -->
+    <!--                                                                      -->
+    <!--   fork                Tell Ant to fork compiles of JSP pages so that -->
+    <!--                       a separate JVM is used for JSP page compiles   -->
+    <!--                       from the one Tomcat is running in. [true]      -->
+    <!--                                                                      -->
+    <!--   ieClassId           The class-id value to be sent to Internet      -->
+    <!--                       Explorer when using <jsp:plugin> tags.         -->
+    <!--                       [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93]   -->
+    <!--                                                                      -->
+    <!--   javaEncoding        Java file encoding to use for generating java  -->
+    <!--                       source files. [UTF-8]                          -->
+    <!--                                                                      -->
+    <!--   keepgenerated       Should we keep the generated Java source code  -->
+    <!--                       for each page instead of deleting it? [true]   -->
+    <!--                                                                      -->
+    <!--   logVerbosityLevel   The level of detailed messages to be produced  -->
+    <!--                       by this servlet.  Increasing levels cause the  -->
+    <!--                       generation of more messages.  Valid values are -->
+    <!--                       FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
+    <!--                       [WARNING]                                      -->
+    <!--                                                                      -->
+    <!--   mappedfile          Should we generate static content with one     -->
+    <!--                       print statement per input line, to ease        -->
+    <!--                       debugging?  [false]                            -->
+    <!--                                                                      -->
+    <!--                                                                      -->
+    <!--   reloading           Should Jasper check for modified JSPs?  [true] -->
+    <!--                                                                      -->
+    <!--   suppressSmap        Should the generation of SMAP info for JSR45   -->
+    <!--                       debugging be suppressed?  [false]              -->
+    <!--                                                                      -->
+    <!--   dumpSmap            Should the SMAP info for JSR45 debugging be    -->
+    <!--                       dumped to a file? [false]                      -->
+    <!--                       False if suppressSmap is true                  -->
+    <!--                                                                      -->
+    <!--   scratchdir          What scratch directory should we use when      -->
+    <!--                       compiling JSP pages?  [default work directory  -->
+    <!--                       for the current web application]               -->
+    <!--                                                                      -->
+    <!--   tagpoolMaxSize      The maximum tag handler pool size  [5]         -->
+    <!--                                                                      -->
+    <!--   xpoweredBy          Determines whether X-Powered-By response       -->
+    <!--                       header is added by generated servlet  [false]  -->
+    <!--                                                                      -->
+    <!-- If you wish to use Jikes to compile JSP pages:                       -->
+    <!--   Set the init parameter "compiler" to "jikes".  Define              -->
+    <!--   the property "-Dbuild.compiler.emacs=true" when starting Jetty     -->
+    <!--   to cause Jikes to emit error messages in a format compatible with  -->
+    <!--   Jasper.                                                            -->
+    <!--   If you get an error reporting that jikes can't use UTF-8 encoding, -->
+    <!--   try setting the init parameter "javaEncoding" to "ISO-8859-1".     -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+    <servlet id="jsp">
+        <servlet-name>jsp</servlet-name>
+        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
+        <init-param>
+            <param-name>logVerbosityLevel</param-name>
+            <param-value>DEBUG</param-value>
+        </init-param>
+        <init-param>
+            <param-name>fork</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>keepgenerated</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>development</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>xpoweredBy</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>compilerTargetVM</param-name>
+            <param-value>1.7</param-value>
+        </init-param>
+        <init-param>
+            <param-name>compilerSourceVM</param-name>
+            <param-value>1.7</param-value>
+        </init-param>
+        <!--  
+        <init-param>
+            <param-name>classpath</param-name>
+            <param-value>?</param-value>
+        </init-param>
+        -->
+        <load-on-startup>0</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>jsp</servlet-name>
+        <url-pattern>*.jsp</url-pattern>
+        <url-pattern>*.jspf</url-pattern>
+        <url-pattern>*.jspx</url-pattern>
+        <url-pattern>*.xsp</url-pattern>
+        <url-pattern>*.JSP</url-pattern>
+        <url-pattern>*.JSPF</url-pattern>
+        <url-pattern>*.JSPX</url-pattern>
+        <url-pattern>*.XSP</url-pattern>
+    </servlet-mapping>
+
+
+    <!-- ==================================================================== -->
+    <session-config>
+        <session-timeout>30</session-timeout>
+    </session-config>
+
+    <!-- ==================================================================== -->
+    <!-- Default MIME mappings                                                -->
+    <!-- The default MIME mappings are provided by the mime.properties        -->
+    <!-- resource in the org.eclipse.jetty.server.jar file.  Additional or modified  -->
+    <!-- mappings may be specified here                                       -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+    <!-- UNCOMMENT TO ACTIVATE
+    <mime-mapping>
+      <extension>mysuffix</extension>
+      <mime-type>mymime/type</mime-type>
+    </mime-mapping>
+    -->
+
+    <!-- ==================================================================== -->
+    <welcome-file-list>
+        <welcome-file>index.html</welcome-file>
+        <welcome-file>index.htm</welcome-file>
+        <welcome-file>index.jsp</welcome-file>
+    </welcome-file-list>
+
+    <!-- ==================================================================== -->
+    <locale-encoding-mapping-list>
+        <locale-encoding-mapping>
+            <locale>ar</locale>
+            <encoding>ISO-8859-6</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>be</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>bg</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ca</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>cs</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>da</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>de</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>el</locale>
+            <encoding>ISO-8859-7</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>en</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>es</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>et</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>fi</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>fr</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>hr</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>hu</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>is</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>it</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>iw</locale>
+            <encoding>ISO-8859-8</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ja</locale>
+            <encoding>Shift_JIS</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ko</locale>
+            <encoding>EUC-KR</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>lt</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>lv</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>mk</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>nl</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>no</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>pl</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>pt</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ro</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ru</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sh</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sk</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sl</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sq</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sr</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sv</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>tr</locale>
+            <encoding>ISO-8859-9</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>uk</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>zh</locale>
+            <encoding>GB2312</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>zh_TW</locale>
+            <encoding>Big5</encoding>
+        </locale-encoding-mapping>
+    </locale-encoding-mapping-list>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Disable TRACE</web-resource-name>
+            <url-pattern>/</url-pattern>
+            <http-method>TRACE</http-method>
+        </web-resource-collection>
+        <auth-constraint/>
+    </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Enable everything but TRACE</web-resource-name>
+            <url-pattern>/</url-pattern>
+            <http-method-omission>TRACE</http-method-omission>
+        </web-resource-collection>
+    </security-constraint>
+
+</web-app>
+

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-api/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/pom.xml b/nifi-registry-web-api/pom.xml
index 74c6ba3..94478eb 100644
--- a/nifi-registry-web-api/pom.xml
+++ b/nifi-registry-web-api/pom.xml
@@ -14,7 +14,8 @@
   limitations under the License.
 -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.nifi.registry</groupId>
@@ -57,7 +58,8 @@
                                         <title>NiFi Registry REST API</title>
                                         <version>${project.version}</version>
                                         <description>
-                                            The Rest Api provides an interface to a registry with operations for saving, versioning, reading NiFi flows
+                                            The Rest Api provides an interface to a registry with operations for saving,
+                                            versioning, reading NiFi flows
                                             and components.
                                         </description>
                                         <contact>
@@ -71,7 +73,9 @@
                                         </license>
                                     </info>
                                     <templatePath>classpath:/templates/index.html.hbs</templatePath>
-                                    <outputPath>${project.build.directory}/${project.artifactId}-${project.version}/docs/rest-api/index.html</outputPath>
+                                    <outputPath>
+                                        ${project.build.directory}/${project.artifactId}-${project.version}/docs/rest-api/index.html
+                                    </outputPath>
                                     <swaggerDirectory>${project.build.directory}/swagger-ui</swaggerDirectory>
                                 </apiSource>
                             </apiSources>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/pom.xml b/nifi-registry-web-ui/pom.xml
index 270af29..7240345 100644
--- a/nifi-registry-web-ui/pom.xml
+++ b/nifi-registry-web-ui/pom.xml
@@ -14,7 +14,8 @@
   limitations under the License.
 -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.nifi.registry</groupId>
@@ -25,26 +26,446 @@
     <artifactId>nifi-registry-web-ui</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <packaging>war</packaging>
-    <dependencies>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>javax.servlet.jsp</groupId>
-            <artifactId>javax.servlet.jsp-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>javax.el</groupId>
-            <artifactId>javax.el-api</artifactId>
-            <scope>provided</scope>            
-        </dependency>
-        <dependency>
-            <groupId>javax.servlet.jsp.jstl</groupId>
-            <artifactId>javax.servlet.jsp.jstl-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-    </dependencies>
+    <properties>
+        <skipTests>true</skipTests>
+        <staging.dir>${project.build.directory}/tmp</staging.dir>
+        <registry.filter>registry-min.properties</registry.filter>
+        <frontend.source>${basedir}/src/main</frontend.source>
+        <frontend.dependency.configs>${basedir}/src/main/frontend</frontend.dependency.configs>
+        <frontend.working.dir>${project.build.directory}/frontend-working-directory</frontend.working.dir>
+        <frontend.assets>${project.build.directory}/${project.build.finalName}/node_modules</frontend.assets>
+    </properties>
+    <build>
+        <!--
+            These filters are used to populate the includes (css and js)
+            for each of the available pages. The property is the name of
+            the file which contains the properties that define which
+            css and js files get included. When running with minify and
+            compression (default) the filter properties will be overridden
+            in the profile. The JSPs that contain the HEAD portion of the
+            pages will not be pre-compiled and will instead be filtered
+            when the war is built.
+        -->
+        <filters>
+            <filter>src/main/resources/filters/${registry.filter}</filter>
+        </filters>
+        <plugins>
+            <!--
+                Precompile jsp's and add entries into the web.xml - the web.xml
+                is automatically places in ${project.build.directory}. Do not
+                precompile index.jsp, etc.
+                These jsp's need to have the artifacts version filtered in to
+                eliminate browser caching issues and set up the proper includes.
+                Since the webResource filter occurs after the precompilation we
+                must exclude them here.
+            -->
+            <plugin>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-jspc-maven-plugin</artifactId>
+                <version>${jetty.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>jspc</goal>
+                        </goals>
+                        <configuration>
+                            <keepSources>true</keepSources>
+                            <useProvidedScope>true</useProvidedScope>
+                            <excludes>
+                                **/index.jsp
+                            </excludes>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <!--
+                        Filter the web.xml that was generated from jspc to specify the
+                        NiFi Registry base directory. The plugin configuration is
+                        specified here while the execution's are defined below in the
+                        profiles to bind to the appropriate phase.
+                    -->
+                    <execution>
+                        <id>copy-web-xml</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${staging.dir}/WEB-INF</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${project.build.directory}</directory>
+                                    <filtering>true</filtering>
+                                    <includes>
+                                        <include>web.xml</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Copy build and test configs into frontend working directory.
+                    -->
+                    <execution>
+                        <id>copy-client-side-build-and-test-configs</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${frontend.working.dir}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${frontend.dependency.configs}</directory>
+                                    <filtering>false</filtering>
+                                    <includes>
+                                        <include>*</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Copy src into frontend working directory.
+                    -->
+                    <execution>
+                        <id>copy-source</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${frontend.working.dir}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${frontend.source}</directory>
+                                    <filtering>false</filtering>
+                                    <includes>
+                                        <include>locale/**/*</include>
+                                        <include>webapp/**/*</include>
+                                        <include>platform/**/*</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Simulate an npm installed FDS.
+                    -->
+                    <execution>
+                        <id>copy-fluid-design-system</id>
+                        <phase>process-sources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${frontend.working.dir}/node_modules/@fluid-design-system/dist
+                            </outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${frontend.working.dir}</directory>
+                                    <filtering>false</filtering>
+                                    <includes>
+                                        <include>platform/**/*</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Stage client side node_modules dependencies for inclusion in .war.
+                    -->
+                    <execution>
+                        <id>copy-client-side-deps</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${frontend.assets}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${frontend.working.dir}/node_modules</directory>
+                                    <filtering>false</filtering>
+                                    <includes>
+                                        <!-- roboto -->
+                                        <include>roboto-fontface/fonts/Roboto/Roboto-Regular.ttf</include>
+                                        <include>roboto-fontface/fonts/Roboto/Roboto-Medium.ttf</include>
+                                        <include>roboto-fontface/fonts/Roboto/Roboto-Light.ttf</include>
+                                        <include>roboto-fontface/fonts/Roboto/Roboto-Bold.ttf</include>
+                                        <include>roboto-fontface/LICENSE*</include>
+                                        <!-- covalent -->
+                                        <include>@covalent/core/common/platform.css</include>
+                                        <include>@covalent/core/common/styles/font/MaterialIcons-Regular.woff2</include>
+                                        <include>@covalent/core/common/styles/font/MaterialIcons-Regular.ttf</include>
+                                        <include>@covalent/core/README.md</include>
+                                        <!-- FDS -->
+                                        <include>
+                                            @fluid-design-system/dist/platform/core/common/styles/css/*
+                                        </include>
+                                        <include>@fluid-design-system/dist/platform/core/dialogs/**/*</include>
+                                        <include>@fluid-design-system/dist/platform/core/LICENSE.md</include>
+                                        <!-- font-awesome -->
+                                        <include>font-awesome/css/font-awesome.css</include>
+                                        <include>font-awesome/fonts/fontawesome-webfont.woff2</include>
+                                        <include>font-awesome/fonts/fontawesome-webfont.ttf</include>
+                                        <include>font-awesome/README.md</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Stage client side styles.
+                    -->
+                    <execution>
+                        <id>copy-webapp-client-side-styles</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/${project.build.finalName}/css</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${frontend.working.dir}/webapp/css</directory>
+                                    <filtering>false</filtering>
+                                    <includes>
+                                        <include>*</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <!--
+                Tell the war plugin where to find the filtered web.xml and
+                filter the head portion of the pages. The correct includes and
+                project version is filtered into these jsp's as a browser cache
+                buster.
+            -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <configuration>
+                    <webXml>${staging.dir}/WEB-INF/web.xml</webXml>
+                    <webResources>
+                        <resource>
+                            <directory>src/main/webapp/WEB-INF/pages</directory>
+                            <targetPath>WEB-INF/pages</targetPath>
+                            <includes>
+                                <include>index.jsp</include>
+                            </includes>
+                            <filtering>true</filtering>
+                        </resource>
+                    </webResources>
+                </configuration>
+            </plugin>
+            <!--
+                Skip tests by default
+            -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.12.4</version>
+                <configuration>
+                    <skipTests>${skipTests}</skipTests>
+                </configuration>
+            </plugin>
+            <!--
+                Speed up build time by excluding node, npm, and any node_modules from `mvn clean` since the front-end-maven plugin uses these
+                directories as cache.
+            -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-clean-plugin</artifactId>
+                <version>3.0.0</version>
+                <configuration>
+                    <excludeDefaultDirectories>true</excludeDefaultDirectories>
+                    <filesets>
+                        <fileset>
+                            <directory>${project.build.directory}</directory>
+                            <includes>
+                                <include>**</include>
+                            </includes>
+                            <excludes>
+                                <exclude>frontend-working-directory/node/**/*</exclude>
+                                <exclude>frontend-working-directory/node_modules/**/*</exclude>
+                            </excludes>
+                        </fileset>
+                    </filesets>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.github.eirslett</groupId>
+                <artifactId>frontend-maven-plugin</artifactId>
+                <version>1.5</version>
+                <configuration>
+                    <installDirectory>${frontend.working.dir}</installDirectory>
+                </configuration>
+                <executions>
+                    <!--
+                        Install node and npm.
+                    -->
+                    <execution>
+                        <id>install-node-and-npm</id>
+                        <goals>
+                            <goal>install-node-and-npm</goal>
+                        </goals>
+                        <phase>initialize</phase>
+                        <configuration>
+                            <nodeVersion>v6.11.1</nodeVersion>
+                            <npmVersion>3.10.10</npmVersion>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Install node_modules (build, test, AND client side dependencies).
+                    -->
+                    <execution>
+                        <id>npm-install</id>
+                        <goals>
+                            <goal>npm</goal>
+                        </goals>
+                        <phase>initialize</phase>
+                        <configuration>
+                            <arguments>--silent --cache-min Infinity install</arguments>
+                            <workingDirectory>${frontend.working.dir}</workingDirectory>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Compile FDS SASS into css and gzip compress it.
+                    -->
+                    <execution>
+                        <id>grunt-compile-fds-sass</id>
+                        <goals>
+                            <goal>grunt</goal>
+                        </goals>
+                        <phase>generate-sources</phase>
+                        <configuration>
+                            <arguments>compile-fds-styles</arguments>
+                            <workingDirectory>${frontend.working.dir}</workingDirectory>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Selenium, Karma/Jasmine JS unit tests.
+                    -->
+                    <execution>
+                        <id>javascript-tests</id>
+                        <goals>
+                            <goal>npm</goal>
+                        </goals>
+                        <phase>test</phase>
+                        <configuration>
+                            <arguments>run test:ci</arguments>
+                            <workingDirectory>${frontend.working.dir}</workingDirectory>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Compile nifi registry web ui SASS into css and gzip compress it.
+                    -->
+                    <execution>
+                        <id>grunt-compile-web-ui-sass</id>
+                        <goals>
+                            <goal>grunt</goal>
+                        </goals>
+                        <phase>generate-resources</phase>
+                        <configuration>
+                            <arguments>compile-web-ui-styles</arguments>
+                            <workingDirectory>${frontend.working.dir}</workingDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.github.eirslett</groupId>
+                <artifactId>frontend-maven-plugin</artifactId>
+                <version>1.5</version>
+                <executions>
+                    <!--
+                        Bundle, minify, and gzip compress all the javascript.
+                    -->
+                    <execution>
+                        <id>grunt-package-web-ui</id>
+                        <goals>
+                            <goal>grunt</goal>
+                        </goals>
+                        <phase>generate-resources</phase>
+                        <configuration>
+                            <arguments>bundle-web-ui</arguments>
+                            <workingDirectory>${frontend.working.dir}</workingDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <!--
+                        Stage the final bundle of JS to be included in the .war
+                    -->
+                    <execution>
+                        <id>copy-web-ui-bundle</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/${project.build.finalName}
+                            </outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${frontend.working.dir}/webapp</directory>
+                                    <filtering>false</filtering>
+                                    <includes>
+                                        <include>nf-registry.bundle.*</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <!--
+                        Stage the localization files to be included in the .war
+                    -->
+                    <execution>
+                        <id>copy-localization</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/${project.build.finalName}
+                            </outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${frontend.working.dir}/locale</directory>
+                                    <filtering>false</filtering>
+                                    <includes>
+                                        <include>*</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes combine.children="append">
+                        <exclude>nbactions.xml</exclude>
+                        <exclude>src/main/frontend/package.json</exclude>
+                        <exclude>src/main/platform/core/package.json</exclude>
+                        <exclude>src/main/platform/core/README.md</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/frontend/Gruntfile.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/frontend/Gruntfile.js b/nifi-registry-web-ui/src/main/frontend/Gruntfile.js
new file mode 100644
index 0000000..5ec5c61
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/frontend/Gruntfile.js
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module.exports = function (grunt) {
+    // load all grunt tasks matching the ['grunt-*', '@*/grunt-*'] patterns
+    require('load-grunt-tasks')(grunt);
+
+    grunt.initConfig({
+        sass: {
+            options: {
+                outputStyle: 'compressed',
+                sourceMap: true
+            },
+            minifyFds: {
+                files: [{
+                    './platform/core/common/styles/css/fluid-design-system.min.css': ['./platform/core/common/styles/fluid-design-system.scss']
+                }]
+            },
+            minifyWebUi: {
+                files: [{
+                    './webapp/css/nf-registry.min.css': ['./webapp/theming/nf-registry.scss']
+                }]
+            }
+        },
+        systemjs: {
+            options: {
+                sfx: true,
+                minify: true, // Comment out this line when developing
+                sourceMaps: true,
+                build: {
+                    lowResSourceMaps: true
+                }
+            },
+            bundleWebUi: {
+                options: {
+                    configFile: "./webapp/systemjs.builder.config.js"
+                },
+                files: [{
+                    "src": "./webapp/nf-registry-bootstrap.js",
+                    "dest": "./webapp/nf-registry.bundle.min.js"
+                }]
+            }
+        },
+        compress: {
+            options: {
+                mode: 'gzip'
+            },
+            webUi: {
+                files: [{
+                    expand: true,
+                    src: ['./webapp/nf-registry.bundle.min.js'],
+                    dest: './',
+                    ext: '.bundle.min.js.gz'
+                }]
+            },
+            webUiStyles: {
+                files: [{
+                    expand: true,
+                    src: ['./webapp/css/nf-registry.min.css'],
+                    dest: './',
+                    ext: '.min.css.gz'
+                }]
+            },
+            fdsStyles: {
+                files: [{
+                    expand: true,
+                    src: ['./platform/core/common/styles/css/fluid-design-system.min.css'],
+                    dest: './',
+                    ext: '.min.css.gz'
+                }]
+            }
+        }
+    });
+    grunt.registerTask('compile-fds-styles', ['sass:minifyFds', 'compress:fdsStyles']);
+    grunt.registerTask('compile-web-ui-styles', ['sass:minifyWebUi', 'compress:webUiStyles']);
+    grunt.registerTask('bundle-web-ui', ['systemjs:bundleWebUi', 'compress:webUi']);
+};

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/frontend/karma-test-shim.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/frontend/karma-test-shim.js b/nifi-registry-web-ui/src/main/frontend/karma-test-shim.js
new file mode 100644
index 0000000..d0a524d
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/frontend/karma-test-shim.js
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// /*global jasmine, __karma__, window*/
+Error.stackTraceLimit = 0; // "No stacktrace"" is usually best for app testing.
+
+// Uncomment to get full stacktrace output. Sometimes helpful, usually not.
+// Error.stackTraceLimit = Infinity; //
+
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
+
+// builtPaths: root paths for output ("built") files
+// get from karma.config.js, then prefix with '/base/'
+var builtPaths = (__karma__.config.builtPaths)
+    .map(function (p) {
+        return '/base/' + p;
+    });
+
+__karma__.loaded = function () {
+};
+
+function isJsFile(path) {
+    return path.slice(-3) == '.js';
+}
+
+function isSpecFile(path) {
+    return /\.spec\.(.*\.)?js$/.test(path);
+}
+
+// Is a "built" file if is JavaScript file in one of the "built" folders
+function isBuiltFile(path) {
+    return isJsFile(path) &&
+        builtPaths.reduce(function (keep, bp) {
+            return keep || (path.substr(0, bp.length) === bp);
+        }, false);
+}
+
+var allSpecFiles = Object.keys(window.__karma__.files)
+    .filter(isSpecFile)
+    .filter(isBuiltFile);
+
+System.config({
+    // Base URL for System.js calls. 'base/' is where Karma serves files from.
+    baseURL: 'base',
+
+    // Map the angular testing umd bundles
+    map: {
+        '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
+        '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
+        '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
+        '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
+        '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
+        '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
+        '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
+        '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js'
+    }
+});
+
+System.import('webapp/systemjs.spec.config.js')
+    .then(importSystemJsExtras)
+    .then(initTestBed)
+    .then(initTesting);
+
+/** Optional SystemJS configuration extras. Keep going w/o it */
+function importSystemJsExtras() {
+    return System.import('webapp/systemjs.config.extras.js')
+        .catch(function (reason) {
+            console.log(
+                'Warning: System.import could not load the optional "systemjs.config.extras.js". Did you omit it by accident? Continuing without it.'
+            );
+            console.log(reason);
+        });
+}
+
+function initTestBed() {
+    return Promise.all([
+        System.import('@angular/core/testing'),
+        System.import('@angular/platform-browser-dynamic/testing')
+    ])
+
+        .then(function (providers) {
+            var coreTesting = providers[0];
+            var browserTesting = providers[1];
+
+            coreTesting.TestBed.initTestEnvironment(
+                browserTesting.BrowserDynamicTestingModule,
+                browserTesting.platformBrowserDynamicTesting());
+        })
+}
+
+// Import all spec files and start karma
+function initTesting() {
+    return Promise.all(
+        allSpecFiles.map(function (moduleName) {
+            return System.import(moduleName);
+        })
+    )
+        .then(__karma__.start, __karma__.error);
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/frontend/karma.conf.ci.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/frontend/karma.conf.ci.js b/nifi-registry-web-ui/src/main/frontend/karma.conf.ci.js
new file mode 100644
index 0000000..c03db0d
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/frontend/karma.conf.ci.js
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var baseConfig = require('./karma.conf.js');
+
+module.exports = function (config) {
+    // Load base config
+    baseConfig(config);
+
+    if (process.env.TRAVIS) {
+        config.set({
+            browsers: ['Chrome_travis_ci']
+        });
+    }
+
+    // Override base config
+    config.set({
+        singleRun: true,
+        autoWatch: false,
+        reporters: ['progress', 'spec', 'coverage'],
+        specReporter: {
+            failFast: true
+        }
+    });
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/frontend/karma.conf.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/frontend/karma.conf.js b/nifi-registry-web-ui/src/main/frontend/karma.conf.js
new file mode 100644
index 0000000..f3946b7
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/frontend/karma.conf.js
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module.exports = function (config) {
+
+    var appBase = 'webapp/';       // app JS and map files
+
+    config.set({
+        basePath: '',
+        frameworks: ['jasmine'],
+
+        plugins: [
+            require('karma-jasmine'),
+            require('karma-chrome-launcher'),
+            require('karma-jasmine-html-reporter'),
+            require('karma-spec-reporter'),
+            require('karma-coverage')
+        ],
+
+        client: {
+            builtPaths: [appBase], // add more spec base paths as needed
+            clearContext: false // leave Jasmine Spec Runner output visible in browser
+        },
+
+        files: [
+            // System.js for module loading
+            'node_modules/systemjs/dist/system.src.js',
+
+            // Polyfills
+            'node_modules/core-js/client/shim.js',
+
+            // zone.js
+            'node_modules/zone.js/dist/zone.js',
+            'node_modules/zone.js/dist/long-stack-trace-zone.js',
+            'node_modules/zone.js/dist/proxy.js',
+            'node_modules/zone.js/dist/sync-test.js',
+            'node_modules/zone.js/dist/jasmine-patch.js',
+            'node_modules/zone.js/dist/async-test.js',
+            'node_modules/zone.js/dist/fake-async-test.js',
+            'node_modules/hammerjs/hammer.js',
+
+            // RxJs
+            {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false},
+            {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false},
+
+            // Paths loaded via module imports:
+            {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
+            {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false},
+            {pattern: 'node_modules/@covalent/**/*.js', included: false, watched: false},
+            {pattern: 'node_modules/@covalent/**/*.js.map', included: false, watched: false},
+            {pattern: 'node_modules/@fluid-design-system/**/*.js', included: false, watched: false},
+            {pattern: 'node_modules/jquery/**/*.js', included: false, watched: false},
+            {pattern: 'node_modules/roboto-fontface/**/*.ttf', included: false, watched: false},
+            {pattern: 'node_modules/systemjs-plugin-text/text.js', included: false, watched: false},
+
+            {pattern: appBase + 'systemjs.spec.config.js', included: false, watched: false},
+            'karma-test-shim.js', // optionally extend SystemJS mapping e.g., with barrels
+
+            // Include the Fluid Design System (which includes the Teradata Covalent and
+            // Angular Material themes) in the test suite.
+            {
+                pattern: 'node_modules/@fluid-design-system/dist/platform/core/common/styles/css/fluid-design-system.min.css',
+                included: true,
+                watched: true,
+                served: true
+            },
+            {
+                pattern: 'node_modules/@fluid-design-system/dist/platform/core/common/styles/css/fluid-design-system.min.css.map',
+                included: false,
+                watched: false
+            },
+
+            // Include the Nifi Registry styles (currently built based off of the
+            // @fluid-design-system/dist/platform/core/common/styles/_globalVars.scss)
+            {
+                pattern: 'webapp/css/nf-registry.min.css',
+                included: true,
+                watched: true
+            },
+            {
+                pattern: 'webapp/css/nf-registry.min.css.map',
+                included: false,
+                watched: false
+            },
+
+            // Asset (HTML) paths loaded via Angular's component compiler
+            // (these paths need to be rewritten, see proxies section)
+            // {pattern: appBase + '**/*.html', included: false, watched: true},
+
+            // Images
+            {pattern: '**/*.svg', watched: false, included: false, served: true},
+
+            // Paths for debugging with source maps in dev tools
+            {pattern: appBase + '**/*.js', included: false, watched: false}
+        ],
+
+        // Proxied base paths for loading assets
+        proxies: {
+            // required for modules fetched by SystemJS
+            '/base/nifi-registry/node_modules/': '/base/node_modules/',
+            '/base/systemjs-angular-loader.js': '/base/webapp/systemjs-angular-loader.js',
+            '/base/nifi-registry/': '/base/webapp/',
+            '/nifi-registry/images/': '/base/webapp/images/'
+        },
+
+        exclude: [],
+        preprocessors: {
+            'webapp/**/!(*spec|*mock).js': 'coverage',
+            'platform/**/!(*spec|*mock).js': 'coverage'
+        },
+        reporters: ['progress', 'kjhtml', 'spec', 'coverage'],
+        coverageReporter: {
+            type: 'html',
+            dir: 'coverage/'
+        },
+        specReporter: {
+            failFast: false
+        },
+        port: 9876,
+        colors: true,
+        logLevel: config.LOG_INFO,
+        autoWatch: true,
+        browsers: ['Chrome'],
+        singleRun: false
+    })
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/frontend/package.json
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/frontend/package.json b/nifi-registry-web-ui/src/main/frontend/package.json
new file mode 100644
index 0000000..7317475
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/frontend/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "nifi-registry",
+  "version": "0.0.1",
+  "description": "",
+  "scripts": {
+    "protractor": "protractor protractor.config.js",
+    "test:dev": "./node_modules/protractor/bin/webdriver-manager update --gecko false && karma start karma.conf.js",
+    "test:ci": "./node_modules/protractor/bin/webdriver-manager update --gecko false && karma start karma.conf.ci.js",
+    "test:once": "karma start karma.conf.js --single-run"
+  },
+  "keywords": [],
+  "author": "",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/apache/nifi-registry"
+  },
+  "dependencies": {
+    "@covalent/core": "1.0.0-beta.6",
+    "angular2-moment": "1.6.0",
+    "font-awesome": "4.7.0",
+    "moment": "2.18.1",
+    "roboto-fontface": "0.7.0"
+  },
+  "devDependencies": {
+    "@angular/animations": "4.2.0",
+    "@angular/cdk": "2.0.0-beta.8",
+    "@angular/common": "4.2.0",
+    "@angular/compiler": "4.2.0",
+    "@angular/core": "4.2.0",
+    "@angular/flex-layout": "2.0.0-beta.8",
+    "@angular/forms": "4.2.0",
+    "@angular/http": "4.2.0",
+    "@angular/material": "2.0.0-beta.6",
+    "@angular/platform-browser": "4.2.0",
+    "@angular/platform-browser-dynamic": "4.2.0",
+    "@angular/router": "4.2.0",
+    "canonical-path": "0.0.2",
+    "grunt": "0.4.5",
+    "grunt-cli": "1.2.0",
+    "grunt-contrib-compress": "1.4.3",
+    "grunt-sass": "2.0.0",
+    "grunt-systemjs-builder": "1.0.0",
+    "hammerjs": "2.0.8",
+    "jasmine-core": "2.4.1",
+    "jquery": "3.2.1",
+    "karma": "1.7.0",
+    "karma-chrome-launcher": "2.0.0",
+    "karma-cli": "1.0.1",
+    "karma-coverage": "1.1.1",
+    "karma-jasmine": "1.0.2",
+    "karma-jasmine-html-reporter": "0.2.2",
+    "karma-spec-reporter": "0.0.31",
+    "load-grunt-tasks": "3.5.2",
+    "lodash": "4.16.2",
+    "material-design-icons": "3.0.1",
+    "protractor": "4.0.14",
+    "reset-css": "2.2.0",
+    "rxjs": "5.4.3",
+    "systemjs": "0.20.17",
+    "systemjs-plugin-text": "0.0.11",
+    "zone.js": "0.8.4"
+  },
+  "bundleDependencies": [],
+  "private": true
+}