From b38b988da4bf88992ded0effcbb2430496560b90 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 20 Jun 2018 08:48:14 +1000 Subject: [PATCH] Frontend --- src/frontend/app-images/default-avatar.jpg | Bin 0 -> 2506 bytes .../favicons/android-chrome-192x192.png | Bin 0 -> 15638 bytes .../favicons/android-chrome-512x512.png | Bin 0 -> 35095 bytes .../app-images/favicons/apple-touch-icon.png | Bin 0 -> 11338 bytes .../app-images/favicons/browserconfig.xml | 9 + .../app-images/favicons/favicon-16x16.png | Bin 0 -> 958 bytes .../app-images/favicons/favicon-32x32.png | Bin 0 -> 1993 bytes src/frontend/app-images/favicons/favicon.ico | Bin 0 -> 15086 bytes .../app-images/favicons/manifest.json | 18 ++ .../app-images/favicons/mstile-150x150.png | Bin 0 -> 10368 bytes .../app-images/favicons/safari-pinned-tab.svg | 32 +++ src/frontend/fonts | 1 + src/frontend/images | 1 + src/frontend/js/app/api.js | 224 ++++++++++++++++++ src/frontend/js/app/cache.js | 10 + src/frontend/js/app/controller.js | 107 +++++++++ src/frontend/js/app/dashboard/main.ejs | 1 + src/frontend/js/app/dashboard/main.js | 10 + src/frontend/js/app/main.js | 167 +++++++++++++ src/frontend/js/app/profile/main.ejs | 33 +++ src/frontend/js/app/profile/main.js | 21 ++ src/frontend/js/app/router.js | 17 ++ src/frontend/js/app/tokens.js | 128 ++++++++++ src/frontend/js/app/ui/footer/main.ejs | 14 ++ src/frontend/js/app/ui/footer/main.js | 16 ++ src/frontend/js/app/ui/header/main.ejs | 28 +++ src/frontend/js/app/ui/header/main.js | 55 +++++ src/frontend/js/app/ui/main.ejs | 18 ++ src/frontend/js/app/ui/main.js | 44 ++++ src/frontend/js/app/ui/menu/main.ejs | 56 +++++ src/frontend/js/app/ui/menu/main.js | 10 + src/frontend/js/index.js | 112 +++++++++ src/frontend/js/lib/helpers.js | 36 +++ src/frontend/js/lib/marionette.js | 117 +++++++++ src/frontend/js/login.js | 5 + src/frontend/js/login/main.js | 17 ++ src/frontend/js/login/ui/login.ejs | 28 +++ src/frontend/js/login/ui/login.js | 42 ++++ src/frontend/js/models/user.js | 29 +++ src/frontend/scss/styles.scss | 13 + 40 files changed, 1419 insertions(+) create mode 100644 src/frontend/app-images/default-avatar.jpg create mode 100644 src/frontend/app-images/favicons/android-chrome-192x192.png create mode 100644 src/frontend/app-images/favicons/android-chrome-512x512.png create mode 100644 src/frontend/app-images/favicons/apple-touch-icon.png create mode 100644 src/frontend/app-images/favicons/browserconfig.xml create mode 100644 src/frontend/app-images/favicons/favicon-16x16.png create mode 100644 src/frontend/app-images/favicons/favicon-32x32.png create mode 100644 src/frontend/app-images/favicons/favicon.ico create mode 100644 src/frontend/app-images/favicons/manifest.json create mode 100644 src/frontend/app-images/favicons/mstile-150x150.png create mode 100644 src/frontend/app-images/favicons/safari-pinned-tab.svg create mode 120000 src/frontend/fonts create mode 120000 src/frontend/images create mode 100644 src/frontend/js/app/api.js create mode 100644 src/frontend/js/app/cache.js create mode 100644 src/frontend/js/app/controller.js create mode 100644 src/frontend/js/app/dashboard/main.ejs create mode 100644 src/frontend/js/app/dashboard/main.js create mode 100644 src/frontend/js/app/main.js create mode 100644 src/frontend/js/app/profile/main.ejs create mode 100644 src/frontend/js/app/profile/main.js create mode 100644 src/frontend/js/app/router.js create mode 100644 src/frontend/js/app/tokens.js create mode 100644 src/frontend/js/app/ui/footer/main.ejs create mode 100644 src/frontend/js/app/ui/footer/main.js create mode 100644 src/frontend/js/app/ui/header/main.ejs create mode 100644 src/frontend/js/app/ui/header/main.js create mode 100644 src/frontend/js/app/ui/main.ejs create mode 100644 src/frontend/js/app/ui/main.js create mode 100644 src/frontend/js/app/ui/menu/main.ejs create mode 100644 src/frontend/js/app/ui/menu/main.js create mode 100644 src/frontend/js/index.js create mode 100644 src/frontend/js/lib/helpers.js create mode 100644 src/frontend/js/lib/marionette.js create mode 100644 src/frontend/js/login.js create mode 100644 src/frontend/js/login/main.js create mode 100644 src/frontend/js/login/ui/login.ejs create mode 100644 src/frontend/js/login/ui/login.js create mode 100644 src/frontend/js/models/user.js create mode 100644 src/frontend/scss/styles.scss diff --git a/src/frontend/app-images/default-avatar.jpg b/src/frontend/app-images/default-avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a0e507681fce1e48acc015055ef117fae0fefdf GIT binary patch literal 2506 zcmex=?SXe|vSXf3{T3SXP4nT%5va_@E^6*Ls2uMhY3W`d>C5dJH zKfoZ!0df;FqaXv5AS1INU8#4<#Bh&vQ43+|n3`~qn%q;9| ztSro6c@U3PP|=Z1$S_e@MA<2@aiWruapA#>&Ou2<8-ph;d|1-7=>HK05fmfAT0x?M zj9?}b@&6VB4>KbJlOVGogFVCR1|+G0VKPR{_UAfsEB%lXPP_Qjlj(Nd z!??0bPdk2`RguUQTXQB#ZL^$V#shvVj%53Hd(-Q!pO4F0&!~E~Ut;~~#|j%??AF|# z*fHl%sigApy{GnT9Z=oya?6kGyGOfrJboC_!4|;SuQ#79>wD4TbzXmT1?`^*TAe%f zcITtqE_MG&RhQp={Jf@V_s$%vzdUEx#(Ova%_^KH_&4x^-~lxI8P6ZGJfX8&JuT^C ztlz$U6<8_jPBkea!W1+Iwf> zxgW}h?%)0}A#|CVT2fL{QiX5&lp|;n!gTs(?<6(t^;fR{XONdU{+}WE7t;l(8?@&x zyIHH6yY6!MtuWiY%J=%8{dpi70F{JLZ2wMg_uC)qQ!ICEe!j_1mmf6;Yu9E+bX~J> zap&lG$?yfDj6uz!seAw1d5hQ0ds1-u+2fiRtD5XjED2Cq2(_~;*j2*peZ-c6lPl(Z z|F`kf2F?u-83qPH*<}~qmgnq!EmM^6ZR(%B?|-nWz~n$QpKxZ^k;0kt1m$N~xy|ND zy!_~>oWyI<{wd5FAZZZh@0sy>vh2#d(mlRuHp?uS=cFFD*qn0Q&f;y>U-bq%kP;Y9 zzx`6|`peaJVy8WxKUa8xh!f`d`6gX?yLS9|GV|AGwiz(>3=9lBziW<`^Xe`?v@_G* zlidv=BlEj*OLXbg+YcutzdRv@>|@(*-(0c2;K;{9d=rzZ0x%>tSGX8zvu)gnKhtd4%I@B^j>M6;gXwClY8wqug>GndbMay;(M z=QT|kAPEp=3C}d0xa`&^7k{3ad*>>zx0nCHDg%;$V~;o1Q(l;U-Main+C!gxfybxr zbJ*u{9d0{N{;IUKd@ortZP^oZD%uox69=4z*l; zxV7-ZZ__`^Dw}*j8r<`K@uqF(UwrvhI=g0fc)NC8vCKjL>$y^Yd4AW#CohZoyynlZ znuE(gY7v-OZ?^A>E6;i?G|f-uO}u40Zn|Du-JZzUQ_k5?k-h#PnT$6W4 zbaZ^^cq+sA0<4)|S1{weEp16UuFxO3ZVuixX@7au+C+uu2@Mq7SO)BEjjcg3!>fgr$GU4&VtTx+sdhom%2FGP)s<+nc zd47tq?snN5D_-4#x$LK?Kpl1%9$;XU!eH_s)UobKkw_?TEknmy>|EFPg&JG5x>#V(a{$uvk0mJN^y3~ zm@%)=W*MW?eY5w0*YC?F-?;zw(VykZPdUm#mFUf#cH!5)yOOzbmji8o{y18DgXIKN z3QBS0UCF3T&YQEfKXduKKQ(FlzA#OLNb&rVHF>plM#;xN-Y))6uhliYn7TCEV`*}0 zwxFO~06%JGWRN#on(MiCac{1$uv`#d`MS$~(<5j5+aKO?r1H4uFG%Ij!1?Y(=3cek zdX;?<*@yPu&Hu%^2Vxh346%-1_ND4g<2h6As?Foj)zP<=IJN z;g4&I8vNh^4pzXxz@c~j!TYycP759@c+xrZM_8o?BurU;c8~E}C5C36cVEBI%06Tw9!2kdN literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/android-chrome-192x192.png b/src/frontend/app-images/favicons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..7bf92ccd886e5ed4e8e6eba9cd1c7dda571ff505 GIT binary patch literal 15638 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%0AGa@n{~4sM^`Y3+uUm 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 F2>|D6m5~4d literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/apple-touch-icon.png b/src/frontend/app-images/favicons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..31e8c9d17a520783d9174e4498c11896262b3740 GIT binary patch literal 11338 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 literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/browserconfig.xml b/src/frontend/app-images/favicons/browserconfig.xml new file mode 100644 index 00000000..e781b887 --- /dev/null +++ b/src/frontend/app-images/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #f0ad00 + + + diff --git a/src/frontend/app-images/favicons/favicon-16x16.png b/src/frontend/app-images/favicons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..0072a798abd7b4b32510b692ebcf28402945827f GIT binary patch literal 958 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 literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/favicon.ico b/src/frontend/app-images/favicons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6090f0f1fb880f35603f892df0e5bb63a4499540 GIT binary patch literal 15086 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 literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/manifest.json b/src/frontend/app-images/favicons/manifest.json new file mode 100644 index 00000000..c0a1e161 --- /dev/null +++ b/src/frontend/app-images/favicons/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "", + "icons": [ + { + "src": "/images/favicon/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/images/favicon/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/src/frontend/app-images/favicons/mstile-150x150.png b/src/frontend/app-images/favicons/mstile-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..14687cce15c46bb9bf20f3143b4cff0576fa5a5e GIT binary patch literal 10368 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 Qz`(%Z>FVdQ&MBb@0P{Gm&Hw-a literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/safari-pinned-tab.svg b/src/frontend/app-images/favicons/safari-pinned-tab.svg new file mode 100644 index 00000000..8333c048 --- /dev/null +++ b/src/frontend/app-images/favicons/safari-pinned-tab.svg @@ -0,0 +1,32 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/src/frontend/fonts b/src/frontend/fonts new file mode 120000 index 00000000..84b6a8e0 --- /dev/null +++ b/src/frontend/fonts @@ -0,0 +1 @@ +../../node_modules/tabler-ui/dist/assets/fonts \ No newline at end of file diff --git a/src/frontend/images b/src/frontend/images new file mode 120000 index 00000000..6f1cb6a6 --- /dev/null +++ b/src/frontend/images @@ -0,0 +1 @@ +../../node_modules/tabler-ui/dist/assets/images \ No newline at end of file diff --git a/src/frontend/js/app/api.js b/src/frontend/js/app/api.js new file mode 100644 index 00000000..d6c47643 --- /dev/null +++ b/src/frontend/js/app/api.js @@ -0,0 +1,224 @@ +'use strict'; + +const $ = require('jquery'); +const _ = require('underscore'); +const Tokens = require('./tokens'); + +/** + * @param {String} message + * @param {*} debug + * @param {Integer} code + * @constructor + */ +const ApiError = function (message, debug, code) { + let temp = Error.call(this, message); + temp.name = this.name = 'ApiError'; + this.stack = temp.stack; + this.message = temp.message; + this.debug = debug; + this.code = code; +}; + +ApiError.prototype = Object.create(Error.prototype, { + constructor: { + value: ApiError, + writable: true, + configurable: true + } +}); + +/** + * + * @param {String} verb + * @param {String} path + * @param {Object} [data] + * @param {Object} [options] + * @returns {Promise} + */ +function fetch (verb, path, data, options) { + options = options || {}; + + return new Promise(function (resolve, reject) { + let api_url = '/api/'; + let url = api_url + path; + let token = Tokens.getTopToken(); + + $.ajax({ + url: url, + data: typeof data === 'object' ? JSON.stringify(data) : data, + type: verb, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + crossDomain: true, + timeout: (options.timeout ? options.timeout : 15000), + xhrFields: { + withCredentials: true + }, + + beforeSend: function (xhr) { + xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); + }, + + success: function (data, textStatus, response) { + let total = response.getResponseHeader('X-Dataset-Total'); + if (total !== null) { + resolve({ + data: data, + pagination: { + total: parseInt(total, 10), + offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10), + limit: parseInt(response.getResponseHeader('X-Dataset-Limit'), 10) + } + }); + } else { + resolve(response); + } + }, + + error: function (xhr, status, error_thrown) { + let code = 400; + + if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { + error_thrown = xhr.responseJSON.error.message; + code = xhr.responseJSON.error.code || 500; + } + + reject(new ApiError(error_thrown, xhr.responseText, code)); + } + }); + }); +} + +/** + * + * @param {Array} expand + * @returns {String} + */ +function makeExpansionString (expand) { + let items = []; + _.forEach(expand, function (exp) { + items.push(encodeURIComponent(exp)); + }); + + return items.join(','); +} + +module.exports = { + status: function () { + return fetch('get', ''); + }, + + Tokens: { + + /** + * @param {String} identity + * @param {String} secret + * @param {Boolean} [wipe] Will wipe the stack before adding to it again if login was successful + * @returns {Promise} + */ + login: function (identity, secret, wipe) { + return fetch('post', 'tokens', {identity: identity, secret: secret}) + .then(response => { + if (response.token) { + if (wipe) { + Tokens.clearTokens(); + } + + // Set storage token + Tokens.addToken(response.token); + return response.token; + } else { + Tokens.clearTokens(); + throw(new Error('No token returned')); + } + }); + }, + + /** + * @returns {Promise} + */ + refresh: function () { + return fetch('get', 'tokens') + .then(response => { + if (response.token) { + Tokens.setCurrentToken(response.token); + return response.token; + } else { + Tokens.clearTokens(); + throw(new Error('No token returned')); + } + }); + } + }, + + Users: { + + /** + * @param {Integer|String} user_id + * @param {Array} [expand] + * @returns {Promise} + */ + getById: function (user_id, expand) { + return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : '')); + }, + + /** + * @param {Integer} [offset] + * @param {Integer} [limit] + * @param {String} [sort] + * @param {Array} [expand] + * @param {String} [query] + * @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 : '')); + }, + + /** + * @param {Object} data + * @returns {Promise} + */ + create: function (data) { + return fetch('post', 'users', data); + }, + + /** + * @param {Object} data + * @param {Integer} data.id + * @returns {Promise} + */ + update: function (data) { + let id = data.id; + delete data.id; + return fetch('put', 'users/' + id, data); + }, + + /** + * @param {Integer} id + * @returns {Promise} + */ + delete: function (id) { + return fetch('delete', 'users/' + id); + }, + + /** + * + * @param {Integer} id + * @param {Object} auth + * @returns {Promise} + */ + setPassword: function (id, auth) { + return fetch('put', 'users/' + id + '/auth', auth); + }, + + /** + * @param {Integer} id + * @returns {Promise} + */ + loginAs: function (id) { + return fetch('post', 'users/' + id + '/login'); + } + } +}; diff --git a/src/frontend/js/app/cache.js b/src/frontend/js/app/cache.js new file mode 100644 index 00000000..c34d6745 --- /dev/null +++ b/src/frontend/js/app/cache.js @@ -0,0 +1,10 @@ +'use strict'; + +const UserModel = require('../models/user'); + +let cache = { + User: new UserModel.Model() +}; + +module.exports = cache; + diff --git a/src/frontend/js/app/controller.js b/src/frontend/js/app/controller.js new file mode 100644 index 00000000..e217d1de --- /dev/null +++ b/src/frontend/js/app/controller.js @@ -0,0 +1,107 @@ +'use strict'; + +const Backbone = require('backbone'); +const Cache = require('./cache'); +const Tokens = require('./tokens'); + +module.exports = { + + /** + * @param {String} route + * @param {Object} [options] + * @returns {Boolean} + */ + navigate: function (route, options) { + options = options || {}; + Backbone.history.navigate(route.toString(), options); + return true; + }, + + /** + * Login + */ + showLogin: function () { + window.location = '/login'; + }, + + /** + * Users + * + * @param {Number} [offset] + * @param {Number} [limit] + * @param {String} [sort] + */ + showUsers: function (offset, limit, sort) { + /* + let controller = this; + if (Cache.User.isAdmin()) { + require(['./main', './users/main'], (App, View) => { + 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); + }); + } else { + this.showRules(); + } + */ + }, + + /** + * Error + * + * @param {Error} err + * @param {String} nice_msg + */ + /* + showError: function (err, nice_msg) { + require(['./main', './error/main'], (App, View) => { + App.UI.showAppContent(new View({ + err: err, + nice_msg: nice_msg + })); + }); + }, + */ + + /** + * Dashboard + */ + showDashboard: function () { + let controller = this; + + require(['./main', './dashboard/main'], (App, View) => { + controller.navigate('/'); + App.UI.showAppContent(new View()); + }); + }, + + /** + * Dashboard + */ + showProfile: function () { + let controller = this; + + require(['./main', './profile/main'], (App, View) => { + controller.navigate('/profile'); + App.UI.showAppContent(new View()); + }); + }, + + /** + * Logout + */ + logout: function () { + Tokens.dropTopToken(); + this.showLogin(); + } +}; diff --git a/src/frontend/js/app/dashboard/main.ejs b/src/frontend/js/app/dashboard/main.ejs new file mode 100644 index 00000000..40816a2b --- /dev/null +++ b/src/frontend/js/app/dashboard/main.ejs @@ -0,0 +1 @@ +Hi \ No newline at end of file diff --git a/src/frontend/js/app/dashboard/main.js b/src/frontend/js/app/dashboard/main.js new file mode 100644 index 00000000..6694281f --- /dev/null +++ b/src/frontend/js/app/dashboard/main.js @@ -0,0 +1,10 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + template: template, + id: 'dashboard' +}); + diff --git a/src/frontend/js/app/main.js b/src/frontend/js/app/main.js new file mode 100644 index 00000000..4ef25c61 --- /dev/null +++ b/src/frontend/js/app/main.js @@ -0,0 +1,167 @@ +'use strict'; + +const _ = require('underscore'); +const Backbone = require('backbone'); +const Mn = require('../lib/marionette'); +const Cache = require('./cache'); +const Controller = require('./controller'); +const Router = require('./router'); +const Api = require('./api'); +const Tokens = require('./tokens'); +const UI = require('./ui/main'); + +const App = Mn.Application.extend({ + + region: '#app', + Cache: Cache, + Api: Api, + UI: null, + Controller: Controller, + version: null, + + onStart: function (app, options) { + console.log('Welcome to Nginx Proxy Manager'); + + // Check if token is coming through + if (this.getParam('token')) { + Tokens.addToken(this.getParam('token')); + } + + // Check if we are still logged in by refreshing the token + Api.status() + .then(result => { + this.version = [result.version.major, result.version.minor, result.version.revision].join('.'); + }) + .then(Api.Tokens.refresh) + .then(this.bootstrap) + .then(() => { + console.info('You are logged in'); + this.bootstrapTimer(); + this.refreshTokenTimer(); + + this.UI = new UI(); + this.UI.on('render', () => { + new Router(options); + Backbone.history.start({pushState: true}); + }); + + this.getRegion().show(this.UI); + }) + .catch(err => { + console.warn('Not logged in:', err.message); + Controller.showLogin(); + }); + + }, + + History: { + replace: function (data) { + window.history.replaceState(_.extend(window.history.state || {}, data), document.title); + }, + + get: function (attr) { + return window.history.state ? window.history.state[attr] : undefined; + } + }, + + Error: function (code, message, debug) { + let temp = Error.call(this, message); + temp.name = this.name = 'AppError'; + this.stack = temp.stack; + this.message = temp.message; + this.code = code; + this.debug = debug; + }, + + showError: function () { + let ErrorView = Mn.View.extend({ + tagName: 'section', + id: 'error', + template: _.template('Error loading stuff. Please reload the app.') + }); + + this.getRegion().show(new ErrorView()); + }, + + getParam: function (name) { + name = name.replace(/[\[\]]/g, '\\$&'); + let url = window.location.href; + let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); + let results = regex.exec(url); + + if (!results) { + return null; + } + + if (!results[2]) { + return ''; + } + + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + }, + + /** + * Get user and other base info to start prime the cache and the application + * + * @returns {Promise} + */ + bootstrap: function () { + return Api.Users.getById('me') + .then(response => { + Cache.User.set(response); + Tokens.setCurrentName(response.nickname || response.name); + }); + }, + + /** + * Bootstraps the user from time to time + */ + bootstrapTimer: function () { + setTimeout(() => { + Api.status() + .then(result => { + let version = [result.version.major, result.version.minor, result.version.revision].join('.'); + if (version !== this.version) { + document.location.reload(); + } + }) + .then(this.bootstrap) + .then(() => { + this.bootstrapTimer(); + }) + .catch(err => { + if (err.message !== 'timeout' && err.code && err.code !== 400) { + console.log(err); + console.error(err.message); + console.info('Not logged in?'); + Controller.showLogin(); + } else { + this.bootstrapTimer(); + } + }); + }, 30 * 1000); // 30 seconds + }, + + refreshTokenTimer: function () { + setTimeout(() => { + return Api.Tokens.refresh() + .then(this.bootstrap) + .then(() => { + this.refreshTokenTimer(); + }) + .catch(err => { + if (err.message !== 'timeout' && err.code && err.code !== 400) { + console.log(err); + console.error(err.message); + console.info('Not logged in?'); + Controller.showLogin(); + } else { + this.refreshTokenTimer(); + } + }); + }, 10 * 60 * 1000); + } +}); + +const app = new App(); +module.exports = app; diff --git a/src/frontend/js/app/profile/main.ejs b/src/frontend/js/app/profile/main.ejs new file mode 100644 index 00000000..f1e5d5cf --- /dev/null +++ b/src/frontend/js/app/profile/main.ejs @@ -0,0 +1,33 @@ +
+ +
+
+
+

My Profile

+
+
+
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+ +
+
+
+
+ +
\ No newline at end of file diff --git a/src/frontend/js/app/profile/main.js b/src/frontend/js/app/profile/main.js new file mode 100644 index 00000000..5f4ea1a8 --- /dev/null +++ b/src/frontend/js/app/profile/main.js @@ -0,0 +1,21 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const Cache = require('../cache'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + template: template, + id: 'profile', + + templateContext: { + getUserField: function (field, default_val) { + return Cache.User.get(field) || default_val; + } + }, + + initialize: function () { + this.model = Cache.User; + } +}); + diff --git a/src/frontend/js/app/router.js b/src/frontend/js/app/router.js new file mode 100644 index 00000000..91825e77 --- /dev/null +++ b/src/frontend/js/app/router.js @@ -0,0 +1,17 @@ +'use strict'; + +const Mn = require('../lib/marionette'); +const Controller = require('./controller'); + +module.exports = Mn.AppRouter.extend({ + appRoutes: { + users: 'showUsers', + profile: 'showProfile', + logout: 'logout', + '*default': 'showDashboard' + }, + + initialize: function () { + this.controller = Controller; + } +}); diff --git a/src/frontend/js/app/tokens.js b/src/frontend/js/app/tokens.js new file mode 100644 index 00000000..fb85e9f6 --- /dev/null +++ b/src/frontend/js/app/tokens.js @@ -0,0 +1,128 @@ +'use strict'; + +const STORAGE_NAME = 'nginx-proxy-manager-tokens'; + +/** + * @returns {Array} + */ +const getStorageTokens = function () { + let json = window.localStorage.getItem(STORAGE_NAME); + if (json) { + try { + return JSON.parse(json); + } catch (err) { + return []; + } + } + + return []; +}; + +/** + * @param {Array} tokens + */ +const setStorageTokens = function (tokens) { + window.localStorage.setItem(STORAGE_NAME, JSON.stringify(tokens)); +}; + +const Tokens = { + + /** + * @returns {Integer} + */ + getTokenCount: () => { + return getStorageTokens().length; + }, + + /** + * @returns {Object} t,n + */ + getTopToken: () => { + let tokens = getStorageTokens(); + if (tokens && tokens.length) { + return tokens[0]; + } + + return null; + }, + + /** + * @returns {String} + */ + getNextTokenName: () => { + let tokens = getStorageTokens(); + if (tokens && tokens.length > 1 && typeof tokens[1] !== 'undefined' && typeof tokens[1].n !== 'undefined') { + return tokens[1].n; + } + + return null; + }, + + /** + * + * @param {String} token + * @param {String} [name] + * @returns {Integer} + */ + addToken: (token, name) => { + // Get top token and if it's the same, ignore this call + let top = Tokens.getTopToken(); + if (!top || top.t !== token) { + let tokens = getStorageTokens(); + tokens.unshift({t: token, n: name || null}); + setStorageTokens(tokens); + } + + return Tokens.getTokenCount(); + }, + + /** + * @param {String} token + * @returns {Boolean} + */ + setCurrentToken: token => { + let tokens = getStorageTokens(); + if (tokens.length) { + tokens[0].t = token; + setStorageTokens(tokens); + return true; + } + + return false; + }, + + /** + * @param {String} name + * @returns {Boolean} + */ + setCurrentName: name => { + let tokens = getStorageTokens(); + if (tokens.length) { + tokens[0].n = name; + setStorageTokens(tokens); + return true; + } + + return false; + }, + + /** + * @returns {Integer} + */ + dropTopToken: () => { + let tokens = getStorageTokens(); + tokens.shift(); + setStorageTokens(tokens); + return tokens.length; + }, + + /** + * + */ + clearTokens: () => { + window.localStorage.removeItem(STORAGE_NAME); + } + +}; + +module.exports = Tokens; diff --git a/src/frontend/js/app/ui/footer/main.ejs b/src/frontend/js/app/ui/footer/main.ejs new file mode 100644 index 00000000..eeea09e2 --- /dev/null +++ b/src/frontend/js/app/ui/footer/main.ejs @@ -0,0 +1,14 @@ +
+
+
+ +
+
+
+ v<%- getVersion() %> © 2018 jc21.com. Theme by Tabler +
+
diff --git a/src/frontend/js/app/ui/footer/main.js b/src/frontend/js/app/ui/footer/main.js new file mode 100644 index 00000000..531ad816 --- /dev/null +++ b/src/frontend/js/app/ui/footer/main.js @@ -0,0 +1,16 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); +const App = require('../../main'); + +module.exports = Mn.View.extend({ + className: 'container', + template: template, + + templateContext: { + getVersion: function () { + return App.version; + } + } +}); diff --git a/src/frontend/js/app/ui/header/main.ejs b/src/frontend/js/app/ui/header/main.ejs new file mode 100644 index 00000000..eba23e36 --- /dev/null +++ b/src/frontend/js/app/ui/header/main.ejs @@ -0,0 +1,28 @@ + diff --git a/src/frontend/js/app/ui/header/main.js b/src/frontend/js/app/ui/header/main.js new file mode 100644 index 00000000..74f492f1 --- /dev/null +++ b/src/frontend/js/app/ui/header/main.js @@ -0,0 +1,55 @@ +'use strict'; + +const $ = require('jquery'); +const Mn = require('backbone.marionette'); +const Cache = require('../../cache'); +const Controller = require('../../controller'); +const Tokens = require('../../tokens'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + id: 'header', + className: 'header', + 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 '/profile': + Controller.showProfile(); + break; + case '/logout': + Controller.logout(); + break; + } + } + }, + + templateContext: { + getUserField: function (field, default_val) { + return Cache.User.get(field) || default_val; + }, + + getRole: function () { + return Cache.User.isAdmin() ? 'Administrator' : 'Apache Helicopter'; + }, + + getLogoutText: function () { + if (Tokens.getTokenCount() > 1) { + return 'Sign back in as ' + Tokens.getNextTokenName(); + } + + return 'Sign out'; + } + } +}); diff --git a/src/frontend/js/app/ui/main.ejs b/src/frontend/js/app/ui/main.ejs new file mode 100644 index 00000000..5d8b656b --- /dev/null +++ b/src/frontend/js/app/ui/main.ejs @@ -0,0 +1,18 @@ +
+ + + +
+
+ +
+
+
+ +
+ +
\ No newline at end of file diff --git a/src/frontend/js/app/ui/main.js b/src/frontend/js/app/ui/main.js new file mode 100644 index 00000000..b013cd48 --- /dev/null +++ b/src/frontend/js/app/ui/main.js @@ -0,0 +1,44 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); +const HeaderView = require('./header/main'); +const MenuView = require('./menu/main'); +const FooterView = require('./footer/main'); +const Cache = require('../cache'); + +module.exports = Mn.View.extend({ + className: 'page', + template: template, + + regions: { + header_region: { + el: '#header', + replaceElement: true + }, + menu_region: { + el: '#menu', + replaceElement: true + }, + footer_region: '.footer', + app_content_region: '#app-content' + }, + + showAppContent: function (view) { + this.showChildView('app_content_region', view); + }, + + onRender: function () { + this.showChildView('header_region', new HeaderView({ + model: Cache.User + })); + + this.showChildView('menu_region', new MenuView()); + this.showChildView('footer_region', new FooterView()); + }, + + reset: function () { + this.getRegion('header_region').reset(); + this.getRegion('footer_region').reset(); + } +}); diff --git a/src/frontend/js/app/ui/menu/main.ejs b/src/frontend/js/app/ui/menu/main.ejs new file mode 100644 index 00000000..d4b975c4 --- /dev/null +++ b/src/frontend/js/app/ui/menu/main.ejs @@ -0,0 +1,56 @@ + \ No newline at end of file diff --git a/src/frontend/js/app/ui/menu/main.js b/src/frontend/js/app/ui/menu/main.js new file mode 100644 index 00000000..b9fdf170 --- /dev/null +++ b/src/frontend/js/app/ui/menu/main.js @@ -0,0 +1,10 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + id: 'menu', + className: 'header collapse d-lg-flex p-0', + template: template +}); diff --git a/src/frontend/js/index.js b/src/frontend/js/index.js new file mode 100644 index 00000000..bfaa0175 --- /dev/null +++ b/src/frontend/js/index.js @@ -0,0 +1,112 @@ +// This has to exist here so that Webpack picks it up +import '../scss/styles.scss'; + +window.tabler = { + colors: { + 'blue': '#467fcf', + 'blue-darkest': '#0e1929', + 'blue-darker': '#1c3353', + 'blue-dark': '#3866a6', + 'blue-light': '#7ea5dd', + 'blue-lighter': '#c8d9f1', + 'blue-lightest': '#edf2fa', + 'azure': '#45aaf2', + 'azure-darkest': '#0e2230', + 'azure-darker': '#1c4461', + 'azure-dark': '#3788c2', + 'azure-light': '#7dc4f6', + 'azure-lighter': '#c7e6fb', + 'azure-lightest': '#ecf7fe', + 'indigo': '#6574cd', + 'indigo-darkest': '#141729', + 'indigo-darker': '#282e52', + 'indigo-dark': '#515da4', + 'indigo-light': '#939edc', + 'indigo-lighter': '#d1d5f0', + 'indigo-lightest': '#f0f1fa', + 'purple': '#a55eea', + 'purple-darkest': '#21132f', + 'purple-darker': '#42265e', + 'purple-dark': '#844bbb', + 'purple-light': '#c08ef0', + 'purple-lighter': '#e4cff9', + 'purple-lightest': '#f6effd', + 'pink': '#f66d9b', + 'pink-darkest': '#31161f', + 'pink-darker': '#622c3e', + 'pink-dark': '#c5577c', + 'pink-light': '#f999b9', + 'pink-lighter': '#fcd3e1', + 'pink-lightest': '#fef0f5', + 'red': '#e74c3c', + 'red-darkest': '#2e0f0c', + 'red-darker': '#5c1e18', + 'red-dark': '#b93d30', + 'red-light': '#ee8277', + 'red-lighter': '#f8c9c5', + 'red-lightest': '#fdedec', + 'orange': '#fd9644', + 'orange-darkest': '#331e0e', + 'orange-darker': '#653c1b', + 'orange-dark': '#ca7836', + 'orange-light': '#feb67c', + 'orange-lighter': '#fee0c7', + 'orange-lightest': '#fff5ec', + 'yellow': '#f1c40f', + 'yellow-darkest': '#302703', + 'yellow-darker': '#604e06', + 'yellow-dark': '#c19d0c', + 'yellow-light': '#f5d657', + 'yellow-lighter': '#fbedb7', + 'yellow-lightest': '#fef9e7', + 'lime': '#7bd235', + 'lime-darkest': '#192a0b', + 'lime-darker': '#315415', + 'lime-dark': '#62a82a', + 'lime-light': '#a3e072', + 'lime-lighter': '#d7f2c2', + 'lime-lightest': '#f2fbeb', + 'green': '#5eba00', + 'green-darkest': '#132500', + 'green-darker': '#264a00', + 'green-dark': '#4b9500', + 'green-light': '#8ecf4d', + 'green-lighter': '#cfeab3', + 'green-lightest': '#eff8e6', + 'teal': '#2bcbba', + 'teal-darkest': '#092925', + 'teal-darker': '#11514a', + 'teal-dark': '#22a295', + 'teal-light': '#6bdbcf', + 'teal-lighter': '#bfefea', + 'teal-lightest': '#eafaf8', + 'cyan': '#17a2b8', + 'cyan-darkest': '#052025', + 'cyan-darker': '#09414a', + 'cyan-dark': '#128293', + 'cyan-light': '#5dbecd', + 'cyan-lighter': '#b9e3ea', + 'cyan-lightest': '#e8f6f8', + 'gray': '#868e96', + 'gray-darkest': '#1b1c1e', + 'gray-darker': '#36393c', + 'gray-light': '#aab0b6', + 'gray-lighter': '#dbdde0', + 'gray-lightest': '#f3f4f5', + 'gray-dark': '#343a40', + 'gray-dark-darkest': '#0a0c0d', + 'gray-dark-darker': '#15171a', + 'gray-dark-dark': '#2a2e33', + 'gray-dark-light': '#717579', + 'gray-dark-lighter': '#c2c4c6', + 'gray-dark-lightest': '#ebebec' + } +}; + +require('tabler-core'); + +const App = require('./app/main'); + +$(document).ready(() => { + App.start(); +}); diff --git a/src/frontend/js/lib/helpers.js b/src/frontend/js/lib/helpers.js new file mode 100644 index 00000000..984b513f --- /dev/null +++ b/src/frontend/js/lib/helpers.js @@ -0,0 +1,36 @@ +'use strict'; + +const numeral = require('numeral'); +const moment = require('moment'); + +module.exports = { + + /** + * @param {Integer} number + * @returns {String} + */ + niceNumber: function (number) { + return numeral(number).format('0,0'); + }, + + /** + * @param {String|Integer} date + * @returns {String} + */ + shortTime: function (date) { + let shorttime = ''; + + if (typeof date === 'number') { + shorttime = moment.unix(date).format('H:mm A'); + } else { + shorttime = moment(date).format('H:mm A'); + } + + return shorttime; + }, + + replaceSlackLinks: function (content) { + return content.replace(/<(http[^|>]+)\|([^>]+)>/gi, '$2'); + } + +}; diff --git a/src/frontend/js/lib/marionette.js b/src/frontend/js/lib/marionette.js new file mode 100644 index 00000000..810defe4 --- /dev/null +++ b/src/frontend/js/lib/marionette.js @@ -0,0 +1,117 @@ +'use strict'; + +const _ = require('underscore'); +const Mn = require('backbone.marionette'); +const moment = require('moment'); +const numeral = require('numeral'); + +let render = Mn.Renderer.render; + +Mn.Renderer.render = function (template, data, view) { + + data = _.clone(data); + + /** + * @param {Integer} number + * @returns {String} + */ + data.niceNumber = function (number) { + return numeral(number).format('0,0'); + }; + + /** + * @param {Integer} seconds + * @returns {String} + */ + data.secondsToTime = function (seconds) { + let sec_num = parseInt(seconds, 10); + let minutes = Math.floor(sec_num / 60); + let sec = sec_num - (minutes * 60); + + if (sec < 10) { + sec = '0' + sec; + } + + return minutes + ':' + sec; + }; + + /** + * @param {String} date + * @returns {String} + */ + data.shortDate = function (date) { + let shortdate = ''; + + if (typeof date === 'number') { + shortdate = moment.unix(date).format('YYYY-MM-DD'); + } else { + shortdate = moment(date).format('YYYY-MM-DD'); + } + + return moment().format('YYYY-MM-DD') === shortdate ? 'Today' : shortdate; + }; + + /** + * @param {String} date + * @returns {String} + */ + data.shortTime = function (date) { + let shorttime = ''; + + if (typeof date === 'number') { + shorttime = moment.unix(date).format('H:mm A'); + } else { + shorttime = moment(date).format('H:mm A'); + } + + return shorttime; + }; + + /** + * @param {String} string + * @returns {String} + */ + data.escape = function (string) { + let entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }; + + return String(string).replace(/[&<>"'\/]/g, function (s) { + return entityMap[s]; + }); + }; + + /** + * @param {String} string + * @param {Integer} length + * @returns {String} + */ + data.trim = function (string, length) { + if (string.length > length) { + let trimmedString = string.substr(0, length); + return trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(' '))) + '...'; + } + + return string; + }; + + /** + * @param {String} name + * @returns {String} + */ + data.niceVarName = function (name) { + return name.replace('_', ' ') + .replace(/^(.)|\s+(.)/g, function ($1) { + return $1.toUpperCase(); + }); + }; + + return render.call(this, template, data, view); +}; + +module.exports = Mn; diff --git a/src/frontend/js/login.js b/src/frontend/js/login.js new file mode 100644 index 00000000..0094e2a2 --- /dev/null +++ b/src/frontend/js/login.js @@ -0,0 +1,5 @@ +const App = require('./login/main'); + +$(document).ready(() => { + App.start(); +}); diff --git a/src/frontend/js/login/main.js b/src/frontend/js/login/main.js new file mode 100644 index 00000000..80d28660 --- /dev/null +++ b/src/frontend/js/login/main.js @@ -0,0 +1,17 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const LoginView = require('./ui/login'); + +const App = Mn.Application.extend({ + + region: '#login', + UI: null, + + onStart: function (/*app, options*/) { + this.getRegion().show(new LoginView()); + } +}); + +const app = new App(); +module.exports = app; diff --git a/src/frontend/js/login/ui/login.ejs b/src/frontend/js/login/ui/login.ejs new file mode 100644 index 00000000..991d42f2 --- /dev/null +++ b/src/frontend/js/login/ui/login.ejs @@ -0,0 +1,28 @@ +
+
+ +
+
diff --git a/src/frontend/js/login/ui/login.js b/src/frontend/js/login/ui/login.js new file mode 100644 index 00000000..07ee8ebf --- /dev/null +++ b/src/frontend/js/login/ui/login.js @@ -0,0 +1,42 @@ +'use strict'; + +const $ = require('jquery'); +const Mn = require('backbone.marionette'); +const template = require('./login.ejs'); +const Api = require('../../app/api'); + +module.exports = Mn.View.extend({ + template: template, + className: 'page-single', + + ui: { + form: 'form', + identity: 'input[name="identity"]', + secret: 'input[name="secret"]', + error: '.secret-error', + button: 'button' + }, + + events: { + 'submit @ui.form': function (e) { + e.preventDefault(); + this.ui.button.addClass('btn-loading').prop('disabled', true); + this.ui.error.hide(); + + Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true) + .then(() => { + window.location = '/'; + }) + .catch(err => { + this.ui.error.text(err.message).show(); + this.ui.button.removeClass('btn-loading').prop('disabled', false); + }); + } + }, + + templateContext: { + getVersion: function () { + return $('#login').data('version'); + } + } +}); diff --git a/src/frontend/js/models/user.js b/src/frontend/js/models/user.js new file mode 100644 index 00000000..41250d3a --- /dev/null +++ b/src/frontend/js/models/user.js @@ -0,0 +1,29 @@ +'use strict'; + +const _ = require('underscore'); +const Backbone = require('backbone'); + +const model = Backbone.Model.extend({ + idAttribute: 'id', + + defaults: function () { + return { + name: '', + nickname: '', + email: '', + is_disabled: false, + roles: [] + }; + }, + + isAdmin: function () { + return _.indexOf(this.get('roles'), 'admin') !== -1; + } +}); + +module.exports = { + Model: model, + Collection: Backbone.Collection.extend({ + model: model + }) +}; diff --git a/src/frontend/scss/styles.scss b/src/frontend/scss/styles.scss new file mode 100644 index 00000000..ac3bf728 --- /dev/null +++ b/src/frontend/scss/styles.scss @@ -0,0 +1,13 @@ +@import "~tabler-ui/dist/assets/css/dashboard"; + +/* Before any JS content is loaded */ +#app > .loader, #login > .loader, .container > .loader { + position: absolute; + left: 49%; + top: 40%; + display: block; +} + +.no-js-warning { + margin-top: 100px; +} \ No newline at end of file