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:26 UTC

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

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.
+-->
+