Browse Source

Frontend user list and modal dialog fixes

Jamie Curnow 7 years ago
parent
commit
bfc319cff9

+ 12 - 7
src/frontend/js/app/api.js

@@ -163,17 +163,22 @@ module.exports = {
         },
         },
 
 
         /**
         /**
-         * @param   {Integer}  [offset]
-         * @param   {Integer}  [limit]
-         * @param   {String}   [sort]
          * @param   {Array}    [expand]
          * @param   {Array}    [expand]
          * @param   {String}   [query]
          * @param   {String}   [query]
          * @returns {Promise}
          * @returns {Promise}
          */
          */
-        getAll: function (offset, limit, sort, expand, query) {
-            return fetch('get', 'users?offset=' + (offset ? offset : 0) + '&limit=' + (limit ? limit : 20) + (sort ? '&sort=' + sort : '') +
-                (typeof expand === 'object' && expand !== null && expand.length ? '&expand=' + makeExpansionString(expand) : '') +
-                (typeof query === 'string' ? '&query=' + query : ''));
+        getAll: function (expand, query) {
+            let params = [];
+
+            if (typeof expand === 'object' && expand !== null && expand.length) {
+                params.push('expand=' + makeExpansionString(expand));
+            }
+
+            if (typeof query === 'string') {
+                params.push('query=' + query);
+            }
+
+            return fetch('get', 'users' + (params.length ? '?' + params.join('&') : ''));
         },
         },
 
 
         /**
         /**

+ 16 - 20
src/frontend/js/app/controller.js

@@ -26,34 +26,30 @@ module.exports = {
 
 
     /**
     /**
      * Users
      * Users
-     *
-     * @param {Number}  [offset]
-     * @param {Number}  [limit]
-     * @param {String}  [sort]
      */
      */
-    showUsers: function (offset, limit, sort) {
-        /*
+    showUsers: function () {
         let controller = this;
         let controller = this;
         if (Cache.User.isAdmin()) {
         if (Cache.User.isAdmin()) {
             require(['./main', './users/main'], (App, View) => {
             require(['./main', './users/main'], (App, View) => {
                 controller.navigate('/users');
                 controller.navigate('/users');
-                App.UI.showMainLoading();
-                let view = new View({
-                    sort:   (typeof sort !== 'undefined' && sort ? sort : Cache.Session.Users.sort),
-                    offset: (typeof offset !== 'undefined' ? offset : Cache.Session.Users.offset),
-                    limit:  (typeof limit !== 'undefined' && limit ? limit : Cache.Session.Users.limit)
-                });
-
-                view.on('loaded', function () {
-                    App.UI.hideMainLoading();
-                });
-
-                App.UI.showAppContent(view);
+                App.UI.showAppContent(new View());
             });
             });
         } else {
         } else {
-            this.showRules();
+            this.showDashboard();
+        }
+    },
+
+    /**
+     * User Form
+     *
+     * @param model
+     */
+    showUserForm: function (model) {
+        if (Cache.User.isAdmin()) {
+            require(['./main', './user/form'], function (App, View) {
+                App.UI.showModalDialog(new View({model: model}));
+            });
         }
         }
-        */
     },
     },
 
 
     /**
     /**

+ 5 - 1
src/frontend/js/app/main.js

@@ -12,13 +12,17 @@ const UI         = require('./ui/main');
 
 
 const App = Mn.Application.extend({
 const App = Mn.Application.extend({
 
 
-    region:     '#app',
     Cache:      Cache,
     Cache:      Cache,
     Api:        Api,
     Api:        Api,
     UI:         null,
     UI:         null,
     Controller: Controller,
     Controller: Controller,
     version:    null,
     version:    null,
 
 
+    region: {
+        el:             '#app',
+        replaceElement: true
+    },
+
     onStart: function (app, options) {
     onStart: function (app, options) {
         console.log('Welcome to Nginx Proxy Manager');
         console.log('Welcome to Nginx Proxy Manager');
 
 

+ 1 - 1
src/frontend/js/app/ui/footer/main.ejs

@@ -9,6 +9,6 @@
         </div>
         </div>
     </div>
     </div>
     <div class="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
     <div class="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
-        v<%- getVersion() %> &copy; 2018 <a href="https://jc21.com?utm_source=docker-registry-ui" target="_blank">jc21.com</a>. Theme by <a href="https://github.com/tabler/tabler?utm_source=docker-registry-ui" target="_blank">Tabler</a>
+        v<%- getVersion() %> &copy; 2018 <a href="https://jc21.com?utm_source=nginx-proxy-manager" target="_blank">jc21.com</a>. Theme by <a href="https://tabler.github.io/?utm_source=nginx-proxy-manager" target="_blank">Tabler</a>
     </div>
     </div>
 </div>
 </div>

+ 2 - 2
src/frontend/js/app/ui/header/main.ejs

@@ -1,7 +1,7 @@
 <div class="container">
 <div class="container">
     <div class="d-flex">
     <div class="d-flex">
         <a class="navbar-brand" href="/">
         <a class="navbar-brand" href="/">
-            <img src="/images/favicons/favicon-32x32.png" border="0"> &nbsp; Docker Registry
+            <img src="/images/favicons/favicon-32x32.png" border="0"> &nbsp; Nginx Proxy Manager
         </a>
         </a>
 
 
         <div class="d-flex order-lg-2 ml-auto">
         <div class="d-flex order-lg-2 ml-auto">
@@ -9,7 +9,7 @@
                 <a href="#" class="nav-link pr-0 leading-none" data-toggle="dropdown">
                 <a href="#" class="nav-link pr-0 leading-none" data-toggle="dropdown">
                     <span class="avatar" style="background-image: url(<%- getUserField('avatar', '/images/default-avatar.jpg') %>)"></span>
                     <span class="avatar" style="background-image: url(<%- getUserField('avatar', '/images/default-avatar.jpg') %>)"></span>
                     <span class="ml-2 d-none d-lg-block">
                     <span class="ml-2 d-none d-lg-block">
-                      <span class="text-default"><%- getUserField('name', 'Unknown User') %></span>
+                      <span class="text-default"><%- getUserField('nickname', null) || getUserField('name', 'Unknown User') %></span>
                       <small class="text-muted d-block mt-1"><%- getRole() %></small>
                       <small class="text-muted d-block mt-1"><%- getRole() %></small>
                     </span>
                     </span>
                 </a>
                 </a>

+ 3 - 2
src/frontend/js/app/ui/main.ejs

@@ -1,5 +1,4 @@
 <div class="page-main">
 <div class="page-main">
-
     <div class="header" id="header">
     <div class="header" id="header">
         <!-- Header View -->
         <!-- Header View -->
     </div>
     </div>
@@ -15,4 +14,6 @@
 
 
 <footer class="footer">
 <footer class="footer">
     <!-- Footer View -->
     <!-- Footer View -->
-</footer>
+</footer>
+
+<div class="modal fade" id="modal-dialog" tabindex="-1" role="dialog" aria-hidden="true"></div>

+ 59 - 3
src/frontend/js/app/ui/main.js

@@ -8,8 +8,16 @@ const FooterView = require('./footer/main');
 const Cache      = require('../cache');
 const Cache      = require('../cache');
 
 
 module.exports = Mn.View.extend({
 module.exports = Mn.View.extend({
-    className: 'page',
-    template:  template,
+    id:          'app',
+    className:   'page',
+    template:    template,
+    modal_setup: false,
+
+    modal: null,
+
+    ui: {
+        modal: '#modal-dialog'
+    },
 
 
     regions: {
     regions: {
         header_region:      {
         header_region:      {
@@ -21,13 +29,60 @@ module.exports = Mn.View.extend({
             replaceElement: true
             replaceElement: true
         },
         },
         footer_region:      '.footer',
         footer_region:      '.footer',
-        app_content_region: '#app-content'
+        app_content_region: '#app-content',
+        modal_region:       '#modal-dialog'
     },
     },
 
 
+    /**
+     * @param {Object}  view
+     */
     showAppContent: function (view) {
     showAppContent: function (view) {
         this.showChildView('app_content_region', view);
         this.showChildView('app_content_region', view);
     },
     },
 
 
+    /**
+     * @param {Object}    view
+     * @param {Function}  [show_callback]
+     * @param {Function}  [shown_callback]
+     */
+    showModalDialog: function (view, show_callback, shown_callback) {
+        this.showChildView('modal_region', view);
+        let modal = this.getRegion('modal_region').$el.modal('show');
+
+        modal.on('hidden.bs.modal', function (/*e*/) {
+            if (show_callback) {
+                modal.off('show.bs.modal', show_callback);
+            }
+
+            if (shown_callback) {
+                modal.off('shown.bs.modal', shown_callback);
+            }
+
+            modal.off('hidden.bs.modal');
+            view.destroy();
+        });
+
+        if (show_callback) {
+            modal.on('show.bs.modal', show_callback);
+        }
+
+        if (shown_callback) {
+            modal.on('shown.bs.modal', shown_callback);
+        }
+    },
+
+    /**
+     *
+     * @param {Function}  [hidden_callback]
+     */
+    closeModal: function (hidden_callback) {
+        let modal = this.getRegion('modal_region').$el.modal('hide');
+
+        if (hidden_callback) {
+            modal.on('hidden.bs.modal', hidden_callback);
+        }
+    },
+
     onRender: function () {
     onRender: function () {
         this.showChildView('header_region', new HeaderView({
         this.showChildView('header_region', new HeaderView({
             model: Cache.User
             model: Cache.User
@@ -40,5 +95,6 @@ module.exports = Mn.View.extend({
     reset: function () {
     reset: function () {
         this.getRegion('header_region').reset();
         this.getRegion('header_region').reset();
         this.getRegion('footer_region').reset();
         this.getRegion('footer_region').reset();
+        this.getRegion('modal_region').reset();
     }
     }
 });
 });

+ 9 - 4
src/frontend/js/app/ui/menu/main.ejs

@@ -3,10 +3,15 @@
         <div class="col-lg order-lg-first">
         <div class="col-lg order-lg-first">
             <ul class="nav nav-tabs border-0 flex-column flex-lg-row">
             <ul class="nav nav-tabs border-0 flex-column flex-lg-row">
                 <li class="nav-item">
                 <li class="nav-item">
-                    <a href="../index.html" class="nav-link"><i class="fe fe-home"></i> Home</a>
+                    <a href="/" class="nav-link"><i class="fe fe-home"></i> Home</a>
                 </li>
                 </li>
+                <% if (showUsers()) { %>
                 <li class="nav-item">
                 <li class="nav-item">
-                    <a href="javascript:void(0)" class="nav-link" data-toggle="dropdown"><i class="fe fe-box"></i> Interface</a>
+                    <a href="/users" class="nav-link"><i class="fe fe-users"></i> Users</a>
+                </li>
+                <% } %>
+                <li class="nav-item">
+                    <a href="#" class="nav-link" data-toggle="dropdown"><i class="fe fe-box"></i> Interface</a>
                     <div class="dropdown-menu dropdown-menu-arrow">
                     <div class="dropdown-menu dropdown-menu-arrow">
                         <a href="../cards.html" class="dropdown-item ">Cards design</a>
                         <a href="../cards.html" class="dropdown-item ">Cards design</a>
                         <a href="../charts.html" class="dropdown-item ">Charts</a>
                         <a href="../charts.html" class="dropdown-item ">Charts</a>
@@ -14,7 +19,7 @@
                     </div>
                     </div>
                 </li>
                 </li>
                 <li class="nav-item dropdown">
                 <li class="nav-item dropdown">
-                    <a href="javascript:void(0)" class="nav-link" data-toggle="dropdown"><i class="fe fe-calendar"></i> Components</a>
+                    <a href="#" class="nav-link" data-toggle="dropdown"><i class="fe fe-calendar"></i> Components</a>
                     <div class="dropdown-menu dropdown-menu-arrow">
                     <div class="dropdown-menu dropdown-menu-arrow">
                         <a href="../maps.html" class="dropdown-item ">Maps</a>
                         <a href="../maps.html" class="dropdown-item ">Maps</a>
                         <a href="../icons.html" class="dropdown-item ">Icons</a>
                         <a href="../icons.html" class="dropdown-item ">Icons</a>
@@ -24,7 +29,7 @@
                     </div>
                     </div>
                 </li>
                 </li>
                 <li class="nav-item dropdown">
                 <li class="nav-item dropdown">
-                    <a href="javascript:void(0)" class="nav-link" data-toggle="dropdown"><i class="fe fe-file"></i> Pages</a>
+                    <a href="#" class="nav-link" data-toggle="dropdown"><i class="fe fe-file"></i> Pages</a>
                     <div class="dropdown-menu dropdown-menu-arrow">
                     <div class="dropdown-menu dropdown-menu-arrow">
                         <a href="../profile.html" class="dropdown-item ">Profile</a>
                         <a href="../profile.html" class="dropdown-item ">Profile</a>
                         <a href="../login.html" class="dropdown-item ">Login</a>
                         <a href="../login.html" class="dropdown-item ">Login</a>

+ 32 - 3
src/frontend/js/app/ui/menu/main.js

@@ -1,10 +1,39 @@
 'use strict';
 'use strict';
 
 
-const Mn       = require('backbone.marionette');
-const template = require('./main.ejs');
+const $          = require('jquery');
+const Mn         = require('backbone.marionette');
+const Controller = require('../../controller');
+const Cache      = require('../../cache');
+const template   = require('./main.ejs');
 
 
 module.exports = Mn.View.extend({
 module.exports = Mn.View.extend({
     id:        'menu',
     id:        'menu',
     className: 'header collapse d-lg-flex p-0',
     className: 'header collapse d-lg-flex p-0',
-    template:  template
+    template:  template,
+
+    ui: {
+        link: 'a'
+    },
+
+    events: {
+        'click @ui.link': function (e) {
+            e.preventDefault();
+            let href = $(e.currentTarget).attr('href');
+
+            switch (href) {
+                case '/':
+                    Controller.showDashboard();
+                    break;
+                case '/users':
+                    Controller.showUsers();
+                    break;
+            }
+        }
+    },
+
+    templateContext: {
+        showUsers: function () {
+            return Cache.User.isAdmin();
+        }
+    }
 });
 });

+ 53 - 0
src/frontend/js/app/user/form.ejs

@@ -0,0 +1,53 @@
+<div class="modal-content">
+    <div class="modal-header">
+        <h5 class="modal-title"><% if (typeof id !== 'undefined') { %>Edit<% } else { %>New<% } %> User</h5>
+        <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
+    </div>
+    <div class="modal-body">
+        <form>
+            <div class="row">
+                <div class="col-sm-6 col-md-6">
+                    <div class="form-group">
+                        <label class="form-label">Full Name <span class="form-required">*</span></label>
+                        <input name="name" type="text" class="form-control" placeholder="Joe Citizen" value="<%- name %>" required>
+                    </div>
+                </div>
+                <div class="col-sm-6 col-md-6">
+                    <div class="form-group">
+                        <label class="form-label">Nickname</label>
+                        <input name="nickname" type="text" class="form-control" placeholder="Joe" value="<%- nickname %>">
+                    </div>
+                </div>
+                <div class="col-sm-12 col-md-12">
+                    <div class="form-group">
+                        <label class="form-label">Email <span class="form-required">*</span></label>
+                        <input name="email" type="email" class="form-control" placeholder="[email protected]" value="<%- email %>" required>
+                    </div>
+                </div>
+                <% if (!isSelf()) { %>
+                <div class="col-sm-12 col-md-12">
+                    <div class="form-group">
+                        <div class="form-label">Switches</div>
+                        <div class="custom-switches-stacked">
+                            <label class="custom-switch">
+                                <input type="checkbox" class="custom-switch-input" name="is_admin" value="1"<%- isAdmin() ? ' checked' : '' %>>
+                                <span class="custom-switch-indicator"></span>
+                                <span class="custom-switch-description">Administrator</span>
+                            </label>
+                            <label class="custom-switch">
+                                <input type="checkbox" class="custom-switch-input" name="is_disabled" value="1"<%- is_disabled ? ' checked' : '' %>>
+                                <span class="custom-switch-indicator"></span>
+                                <span class="custom-switch-description">Disabled</span>
+                            </label>
+                        </div>
+                    </div>
+                </div>
+                <% } %>
+            </div>
+        </form>
+    </div>
+    <div class="modal-footer">
+        <button type="button" class="btn btn-secondary cancel" data-dismiss="modal">Cancel</button>
+        <button type="button" class="btn btn-teal save">Save</button>
+    </div>
+</div>

+ 99 - 0
src/frontend/js/app/user/form.js

@@ -0,0 +1,99 @@
+'use strict';
+
+const Mn         = require('backbone.marionette');
+const template   = require('./form.ejs');
+const Controller = require('../controller');
+const Cache      = require('../cache');
+const Api        = require('../api');
+const App        = require('../main');
+const UserModel  = require('../../models/user');
+
+require('jquery-serializejson');
+
+module.exports = Mn.View.extend({
+    template:  template,
+    className: 'modal-dialog',
+
+    ui: {
+        form:    'form',
+        buttons: '.modal-footer button',
+        cancel:  'button.cancel',
+        save:    'button.save'
+    },
+
+    events: {
+        /*
+        'click @ui.cancel': function (e) {
+            e.preventDefault();
+            App.UI.closeModal();
+        },
+        */
+
+        'submit @ui.form': function (e) {
+            e.preventDefault();
+            let view = this;
+            let data = this.ui.form.serializeJSON();
+
+            // Manipulate
+            data.roles = [];
+            if (
+                (this.model.get('id') === Cache.User.get('id') && this.model.isAdmin()) ||
+                (typeof data.is_admin !== 'undefined' && data.is_admin)) {
+                data.roles.push('admin');
+                delete data.is_admin;
+            }
+
+            data.is_disabled = typeof data.is_disabled !== 'undefined' ? !!data.is_disabled : false;
+            this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
+            let method = Api.Users.create;
+
+            if (this.model.get('id')) {
+                // edit
+                method  = Api.Users.update;
+                data.id = this.model.get('id');
+            }
+
+            method(data)
+                .then(result => {
+                    if (result.id === Cache.User.get('id')) {
+                        Cache.User.set(result);
+                    }
+
+                    view.model.set(result);
+                    App.UI.closeModal();
+
+                    if (view.model.get('id') !== Cache.User.get('id')) {
+                        Controller.showUsers();
+                    }
+                })
+                .catch(err => {
+                    alert(err.message);
+                    this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
+                });
+        }
+    },
+
+    templateContext: function () {
+        let view = this;
+
+        return {
+            isSelf: function () {
+                return view.model.get('id') === Cache.User.get('id');
+            },
+
+            isAdmin: function () {
+                return view.model.isAdmin();
+            },
+
+            isDisabled: function () {
+                return view.model.isDisabled();
+            }
+        };
+    },
+
+    initialize: function (options) {
+        if (typeof options.model === 'undefined' || !options.model) {
+            this.model = new UserModel.Model();
+        }
+    }
+});

+ 31 - 0
src/frontend/js/app/users/list/item.ejs

@@ -0,0 +1,31 @@
+<td class="text-center">
+    <div class="avatar d-block" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)">
+        <span class="avatar-status <%- is_disabled ? 'bg-red' : 'bg-green' %>"></span>
+    </div>
+</td>
+<td>
+    <div><%- name %></div>
+    <div class="small text-muted">
+        Created: Mar 19, 2018
+    </div>
+</td>
+<td>
+    <div><%- email %></div>
+</td>
+<td>
+    <div><%- roles.join(', ') %></div>
+</td>
+<td class="text-center">
+    <div class="item-action dropdown">
+        <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
+        <div class="dropdown-menu dropdown-menu-right">
+            <a href="#" class="edit-user dropdown-item"><i class="dropdown-icon fe fe-edit"></i> Edit User</a>
+            <a href="#" class="set-password dropdown-item"><i class="dropdown-icon fe fe-shield"></i> Set Password</a>
+            <% if (!isSelf()) { %>
+            <a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> Sign in as User</a>
+            <div class="dropdown-divider"></div>
+            <a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> Delete User</a>
+            <% } %>
+        </div>
+    </div>
+</td>

+ 66 - 0
src/frontend/js/app/users/list/item.js

@@ -0,0 +1,66 @@
+'use strict';
+
+const Mn         = require('backbone.marionette');
+const Controller = require('../../controller');
+const Api        = require('../../api');
+const Cache      = require('../../cache');
+const Tokens     = require('../../tokens');
+const template   = require('./item.ejs');
+
+module.exports = Mn.View.extend({
+    template: template,
+    tagName:  'tr',
+
+    ui: {
+        edit:             'a.edit-user',
+        password:         'a.set-password',
+        login:            'a.login',
+        delete:           'a.delete-user'
+    },
+
+    events: {
+        'click @ui.edit': function (e) {
+            e.preventDefault();
+            Controller.showUserForm(this.model);
+        },
+
+        'click @ui.password': function (e) {
+            e.preventDefault();
+            //Controller.showUserPasswordForm(this.model);
+        },
+
+        'click @ui.delete': function (e) {
+            e.preventDefault();
+            //Controller.showUserDeleteConfirm(this.model);
+        },
+
+        'click @ui.login': function (e) {
+            e.preventDefault();
+
+            if (Cache.User.get('id') !== this.model.get('id')) {
+                this.ui.login.prop('disabled', true).addClass('btn-disabled');
+
+                Api.Users.loginAs(this.model.get('id'))
+                    .then(res => {
+                        Tokens.addToken(res.token, res.user.nickname || res.user.name);
+                        window.location = '/';
+                        window.location.reload();
+                    })
+                    .catch(err => {
+                        alert(err.message);
+                        this.ui.login.prop('disabled', false).removeClass('btn-disabled');
+                    });
+            }
+        }
+    },
+
+    templateContext: {
+        isSelf: function () {
+            return Cache.User.get('id') === this.id;
+        }
+    },
+
+    initialize: function () {
+        this.listenTo(this.model, 'change', this.render);
+    }
+});

+ 10 - 0
src/frontend/js/app/users/list/main.ejs

@@ -0,0 +1,10 @@
+<thead>
+    <th width="30">&nbsp;</th>
+    <th>Name</th>
+    <th>Email</th>
+    <th>Roles</th>
+    <th>&nbsp;</th>
+</thead>
+<tbody>
+    <!-- items -->
+</tbody>

+ 29 - 0
src/frontend/js/app/users/list/main.js

@@ -0,0 +1,29 @@
+'use strict';
+
+const Mn         = require('backbone.marionette');
+const ItemView   = require('./item');
+const template   = require('./main.ejs');
+
+const TableBody = Mn.CollectionView.extend({
+    tagName:   'tbody',
+    childView: ItemView
+});
+
+module.exports = Mn.View.extend({
+    tagName:   'table',
+    className: 'table table-hover table-outline table-vcenter text-nowrap card-table',
+    template:  template,
+
+    regions: {
+        body: {
+            el:             'tbody',
+            replaceElement: true
+        }
+    },
+
+    onRender: function () {
+        this.showChildView('body', new TableBody({
+            collection: this.collection
+        }));
+    }
+});

+ 17 - 0
src/frontend/js/app/users/main.ejs

@@ -0,0 +1,17 @@
+<div class="card">
+    <div class="card-header">
+        <h3 class="card-title">Users</h3>
+        <div class="card-options">
+            <a href="#" class="btn btn-outline-teal btn-sm ml-2 add-user">Add User</a>
+        </div>
+    </div>
+    <div class="card-body no-padding min-100">
+        <div class="dimmer active">
+            <div class="loader"></div>
+            <div class="dimmer-content list-region">
+                <!-- List Region -->
+            </div>
+        </div>
+
+    </div>
+</div>

+ 51 - 0
src/frontend/js/app/users/main.js

@@ -0,0 +1,51 @@
+'use strict';
+
+const Mn         = require('backbone.marionette');
+const UserModel  = require('../../models/user');
+const Api        = require('../api');
+const Controller = require('../controller');
+const ListView   = require('./list/main');
+const template   = require('./main.ejs');
+
+module.exports = Mn.View.extend({
+    id:        'users',
+    template:  template,
+
+    ui: {
+        list_region: '.list-region',
+        add_user:    '.add-user',
+        dimmer:      '.dimmer'
+    },
+
+    regions: {
+        list_region: '@ui.list_region'
+    },
+
+    events: {
+        'click @ui.add_user': function (e) {
+            e.preventDefault();
+            Controller.showUserForm(new UserModel.Model());
+        }
+    },
+
+    onRender: function () {
+        let view = this;
+
+        Api.Users.getAll()
+            .then(response => {
+                if (!view.isDestroyed() && response && response.length) {
+                    view.showChildView('list_region', new ListView({
+                        collection: new UserModel.Collection(response)
+                    }));
+
+                    // Remove loader
+                    view.ui.dimmer.removeClass('active');
+                }
+            })
+            .catch(err => {
+                console.log(err);
+                //Controller.showError(err, 'Could not fetch Users');
+                //view.trigger('loaded');
+            });
+    }
+});

+ 25 - 0
src/frontend/scss/custom.scss

@@ -0,0 +1,25 @@
+$primary-color: #2bcbba;
+
+.loader {
+    color: $primary-color;
+}
+
+a {
+    color: $primary-color;
+}
+
+a:hover {
+    color: darken($primary-color, 10%);
+}
+
+.dropdown-item.active, .dropdown-item:active {
+    background-color: $primary-color;
+}
+
+.custom-switch-input:checked ~ .custom-switch-indicator {
+    background: $primary-color;
+}
+
+.min-100 {
+    min-height: 100px;
+}

+ 3 - 1
src/frontend/scss/styles.scss

@@ -1,4 +1,6 @@
 @import "~tabler-ui/dist/assets/css/dashboard";
 @import "~tabler-ui/dist/assets/css/dashboard";
+@import "tabler-extra";
+@import "custom";
 
 
 /* Before any JS content is loaded */
 /* Before any JS content is loaded */
 #app > .loader, #login > .loader, .container > .loader {
 #app > .loader, #login > .loader, .container > .loader {
@@ -10,4 +12,4 @@
 
 
 .no-js-warning {
 .no-js-warning {
     margin-top: 100px;
     margin-top: 100px;
-}
+}

+ 26 - 0
src/frontend/scss/tabler-extra.scss

@@ -0,0 +1,26 @@
+$teal: #2bcbba;
+
+/* For Card bodies where I don't want padding */
+.card-body.no-padding {
+    padding: 0;
+}
+
+/* Teal Outline Buttons */
+.btn-outline-teal {
+    color: $teal;
+    background-color: transparent;
+    background-image: none;
+    border-color: $teal;
+}
+
+.btn-outline-teal:hover {
+    color: #fff;
+    background-color: $teal;
+    border-color: $teal;
+}
+
+.btn-outline-teal:not(:disabled):not(.disabled):active, .btn-outline-teal:not(:disabled):not(.disabled).active, .show > .btn-outline-teal.dropdown-toggle {
+    color: #fff;
+    background-color: $teal;
+    border-color: $teal;
+}