Frontend
This commit is contained in:
224
src/frontend/js/app/api.js
Normal file
224
src/frontend/js/app/api.js
Normal file
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
10
src/frontend/js/app/cache.js
Normal file
10
src/frontend/js/app/cache.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const UserModel = require('../models/user');
|
||||
|
||||
let cache = {
|
||||
User: new UserModel.Model()
|
||||
};
|
||||
|
||||
module.exports = cache;
|
||||
|
107
src/frontend/js/app/controller.js
Normal file
107
src/frontend/js/app/controller.js
Normal file
@ -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();
|
||||
}
|
||||
};
|
1
src/frontend/js/app/dashboard/main.ejs
Normal file
1
src/frontend/js/app/dashboard/main.ejs
Normal file
@ -0,0 +1 @@
|
||||
Hi
|
10
src/frontend/js/app/dashboard/main.js
Normal file
10
src/frontend/js/app/dashboard/main.js
Normal file
@ -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'
|
||||
});
|
||||
|
167
src/frontend/js/app/main.js
Normal file
167
src/frontend/js/app/main.js
Normal file
@ -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;
|
33
src/frontend/js/app/profile/main.ejs
Normal file
33
src/frontend/js/app/profile/main.ejs
Normal file
@ -0,0 +1,33 @@
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-4 col-md-6 col-xs-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">My Profile</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<span class="avatar avatar-xl" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)"></span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name</label>
|
||||
<input name="name" class="form-control" value="<%- name %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email-Address</label>
|
||||
<input name="email" class="form-control" value="<%- email %>">
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button class="btn btn-primary btn-block">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
21
src/frontend/js/app/profile/main.js
Normal file
21
src/frontend/js/app/profile/main.js
Normal file
@ -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;
|
||||
}
|
||||
});
|
||||
|
17
src/frontend/js/app/router.js
Normal file
17
src/frontend/js/app/router.js
Normal file
@ -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;
|
||||
}
|
||||
});
|
128
src/frontend/js/app/tokens.js
Normal file
128
src/frontend/js/app/tokens.js
Normal file
@ -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;
|
14
src/frontend/js/app/ui/footer/main.ejs
Normal file
14
src/frontend/js/app/ui/footer/main.ejs
Normal file
@ -0,0 +1,14 @@
|
||||
<div class="row align-items-center flex-row-reverse">
|
||||
<div class="col-auto ml-auto">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<ul class="list-inline list-inline-dots mb-0">
|
||||
<li class="list-inline-item"><a href="https://github.com/jc21/docker-registry-ui?utm_source=docker-registry-ui">Fork me on Github</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
|
||||
v<%- getVersion() %> © 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>
|
||||
</div>
|
||||
</div>
|
16
src/frontend/js/app/ui/footer/main.js
Normal file
16
src/frontend/js/app/ui/footer/main.js
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
28
src/frontend/js/app/ui/header/main.ejs
Normal file
28
src/frontend/js/app/ui/header/main.ejs
Normal file
@ -0,0 +1,28 @@
|
||||
<div class="container">
|
||||
<div class="d-flex">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="/images/favicons/favicon-32x32.png" border="0"> Docker Registry
|
||||
</a>
|
||||
|
||||
<div class="d-flex order-lg-2 ml-auto">
|
||||
<div class="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="ml-2 d-none d-lg-block">
|
||||
<span class="text-default"><%- getUserField('name', 'Unknown User') %></span>
|
||||
<small class="text-muted d-block mt-1"><%- getRole() %></small>
|
||||
</span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right dropdown-menu-arrow">
|
||||
<a class="dropdown-item profile" href="/profile">
|
||||
<i class="dropdown-icon fe fe-user"></i> Profile
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item logout" href="/logout">
|
||||
<i class="dropdown-icon fe fe-log-out"></i> <%- getLogoutText() %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
55
src/frontend/js/app/ui/header/main.js
Normal file
55
src/frontend/js/app/ui/header/main.js
Normal file
@ -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';
|
||||
}
|
||||
}
|
||||
});
|
18
src/frontend/js/app/ui/main.ejs
Normal file
18
src/frontend/js/app/ui/main.ejs
Normal file
@ -0,0 +1,18 @@
|
||||
<div class="page-main">
|
||||
|
||||
<div class="header" id="header">
|
||||
<!-- Header View -->
|
||||
</div>
|
||||
<div id="menu">
|
||||
<!-- Menu View -->
|
||||
</div>
|
||||
<div class="my-3 my-md-5">
|
||||
<div id="app-content" class="container">
|
||||
<!-- App View -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<!-- Footer View -->
|
||||
</footer>
|
44
src/frontend/js/app/ui/main.js
Normal file
44
src/frontend/js/app/ui/main.js
Normal file
@ -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();
|
||||
}
|
||||
});
|
56
src/frontend/js/app/ui/menu/main.ejs
Normal file
56
src/frontend/js/app/ui/menu/main.ejs
Normal file
@ -0,0 +1,56 @@
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg order-lg-first">
|
||||
<ul class="nav nav-tabs border-0 flex-column flex-lg-row">
|
||||
<li class="nav-item">
|
||||
<a href="../index.html" class="nav-link"><i class="fe fe-home"></i> Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="javascript:void(0)" class="nav-link" data-toggle="dropdown"><i class="fe fe-box"></i> Interface</a>
|
||||
<div class="dropdown-menu dropdown-menu-arrow">
|
||||
<a href="../cards.html" class="dropdown-item ">Cards design</a>
|
||||
<a href="../charts.html" class="dropdown-item ">Charts</a>
|
||||
<a href="../pricing-cards.html" class="dropdown-item ">Pricing cards</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="javascript:void(0)" class="nav-link" data-toggle="dropdown"><i class="fe fe-calendar"></i> Components</a>
|
||||
<div class="dropdown-menu dropdown-menu-arrow">
|
||||
<a href="../maps.html" class="dropdown-item ">Maps</a>
|
||||
<a href="../icons.html" class="dropdown-item ">Icons</a>
|
||||
<a href="../store.html" class="dropdown-item ">Store</a>
|
||||
<a href="../blog.html" class="dropdown-item ">Blog</a>
|
||||
<a href="../carousel.html" class="dropdown-item ">Carousel</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="javascript:void(0)" class="nav-link" data-toggle="dropdown"><i class="fe fe-file"></i> Pages</a>
|
||||
<div class="dropdown-menu dropdown-menu-arrow">
|
||||
<a href="../profile.html" class="dropdown-item ">Profile</a>
|
||||
<a href="../login.html" class="dropdown-item ">Login</a>
|
||||
<a href="../register.html" class="dropdown-item ">Register</a>
|
||||
<a href="../forgot-password.html" class="dropdown-item ">Forgot password</a>
|
||||
<a href="../400.html" class="dropdown-item ">400 error</a>
|
||||
<a href="../401.html" class="dropdown-item ">401 error</a>
|
||||
<a href="../403.html" class="dropdown-item ">403 error</a>
|
||||
<a href="../404.html" class="dropdown-item ">404 error</a>
|
||||
<a href="../500.html" class="dropdown-item ">500 error</a>
|
||||
<a href="../503.html" class="dropdown-item ">503 error</a>
|
||||
<a href="../email.html" class="dropdown-item ">Email</a>
|
||||
<a href="../empty.html" class="dropdown-item ">Empty page</a>
|
||||
<a href="../rtl.html" class="dropdown-item ">RTL mode</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="../form-elements.html" class="nav-link"><i class="fe fe-check-square"></i> Forms</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="../gallery.html" class="nav-link"><i class="fe fe-image"></i> Gallery</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="../docs/index.html" class="nav-link"><i class="fe fe-file-text"></i> Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
10
src/frontend/js/app/ui/menu/main.js
Normal file
10
src/frontend/js/app/ui/menu/main.js
Normal file
@ -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
|
||||
});
|
Reference in New Issue
Block a user