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

[3/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/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