client: fetch configurations from server at runtime

Permissions, regex filters, app title, email info,
and safety now fetched using server's Info API
This commit is contained in:
Shyam Sunder 2018-06-25 10:47:20 -04:00 committed by Marcin Kurczewski
parent 2bf361c64a
commit 3972b902d8
21 changed files with 137 additions and 91 deletions

View file

@ -8,6 +8,7 @@ const progress = require('./util/progress.js');
const uri = require('./util/uri.js');
let fileTokens = {};
let remoteConfig = null;
class Api extends events.EventTarget {
constructor() {
@ -65,14 +66,53 @@ class Api extends events.EventTarget {
return this._wrappedRequest(url, request.delete, data, {}, options);
}
fetchConfig() {
if (remoteConfig === null) {
return this.get(uri.formatApiLink('info'))
.then(response => {
remoteConfig = response.config;
});
} else {
return Promise.resolve();
}
}
getName() {
return remoteConfig.name;
}
getTagNameRegex() {
return remoteConfig.tagNameRegex;
}
getPasswordRegex() {
return remoteConfig.passwordRegex;
}
getUserNameRegex() {
return remoteConfig.userNameRegex;
}
getContactEmail() {
return remoteConfig.contactEmail;
}
canSendMails() {
return !!remoteConfig.canSendMails;
}
safetyEnabled() {
return !!remoteConfig.enableSafety;
}
hasPrivilege(lookup) {
let minViableRank = null;
for (let privilege of Object.keys(config.privileges)) {
if (!privilege.startsWith(lookup)) {
for (let p of Object.keys(remoteConfig.privileges)) {
if (!p.startsWith(lookup)) {
continue;
}
const rankName = config.privileges[privilege];
const rankIndex = this.allRanks.indexOf(rankName);
const rankIndex = this.allRanks.indexOf(
remoteConfig.privileges[p]);
if (minViableRank === null || rankIndex < minViableRank) {
minViableRank = rankIndex;
}

View file

@ -12,7 +12,7 @@ class HomeController {
topNavigation.setTitle('Home');
this._homeView = new HomeView({
name: config.name,
name: api.getName(),
version: config.meta.version,
buildDate: config.meta.buildDate,
canListSnapshots: api.hasPrivilege('snapshots:list'),

View file

@ -1,6 +1,5 @@
'use strict';
const config = require('../config.js');
const router = require('../router.js');
const api = require('../api.js');
const settings = require('../models/settings.js');
@ -33,9 +32,9 @@ class PostListController {
this._headerView = new PostsHeaderView({
hostNode: this._pageController.view.pageHeaderHolderNode,
parameters: ctx.parameters,
enableSafety: config.enableSafety,
canBulkEditTags: api.hasPrivilege('posts:bulkEdit:tags'),
canBulkEditSafety: api.hasPrivilege('posts:bulkEdit:safety'),
enableSafety: api.safetyEnabled(),
canBulkEditTags: api.hasPrivilege('posts:bulk-edit:tags'),
canBulkEditSafety: api.hasPrivilege('posts:bulk-edit:safety'),
bulkEdit: {
tags: this._bulkEditTags
},
@ -97,9 +96,9 @@ class PostListController {
pageRenderer: pageCtx => {
Object.assign(pageCtx, {
canViewPosts: api.hasPrivilege('posts:view'),
canBulkEditTags: api.hasPrivilege('posts:bulkEdit:tags'),
canBulkEditTags: api.hasPrivilege('posts:bulk-edit:tags'),
canBulkEditSafety:
api.hasPrivilege('posts:bulkEdit:safety'),
api.hasPrivilege('posts:bulk-edit:safety'),
bulkEdit: {
tags: this._bulkEditTags,
},

View file

@ -1,7 +1,6 @@
'use strict';
const api = require('../api.js');
const config = require('../config.js');
const router = require('../router.js');
const uri = require('../util/uri.js');
const misc = require('../util/misc.js');
@ -31,7 +30,7 @@ class PostUploadController {
this._view = new PostUploadView({
canUploadAnonymously: api.hasPrivilege('posts:create:anonymous'),
canViewPosts: api.hasPrivilege('posts:view'),
enableSafety: config.enableSafety,
enableSafety: api.safetyEnabled(),
});
this._view.addEventListener('change', e => this._evtChange(e));
this._view.addEventListener('submit', e => this._evtSubmit(e));

View file

@ -1,12 +1,12 @@
'use strict';
const api = require('../api.js');
const config = require('../config.js');
const topNavigation = require('../models/top_navigation.js');
const TopNavigationView = require('../views/top_navigation_view.js');
class TopNavigationController {
constructor() {
api.fetchConfig().then(() => {
this._topNavigationView = new TopNavigationView();
topNavigation.addEventListener(
@ -16,6 +16,7 @@ class TopNavigationController {
api.addEventListener('logout', e => this._evtAuthChange(e));
this._render();
});
}
_evtAuthChange(e) {
@ -65,7 +66,7 @@ class TopNavigationController {
this._updateNavigationFromPrivileges();
this._topNavigationView.render({
items: topNavigation.getAll(),
name: config.name
name: api.getName()
});
this._topNavigationView.activate(
topNavigation.activeItem ? topNavigation.activeItem.key : '');

View file

@ -4,7 +4,6 @@ const router = require('../router.js');
const api = require('../api.js');
const uri = require('../util/uri.js');
const misc = require('../util/misc.js');
const config = require('../config.js');
const views = require('../util/views.js');
const User = require('../models/user.js');
const UserToken = require('../models/user_token.js');

View file

@ -1,7 +1,6 @@
'use strict';
const api = require('../api.js');
const config = require('../config.js');
const events = require('../events.js');
const misc = require('../util/misc.js');
const views = require('../util/views.js');
@ -26,7 +25,7 @@ class PostEditSidebarControl extends events.EventTarget {
views.replaceContent(this._hostNode, template({
post: this._post,
enableSafety: config.enableSafety,
enableSafety: api.safetyEnabled(),
hasClipboard: document.queryCommandSupported('copy'),
canEditPostSafety: api.hasPrivilege('posts:edit:safety'),
canEditPostSource: api.hasPrivilege('posts:edit:source'),

View file

@ -21,7 +21,7 @@ class PostReadonlySidebarControl extends events.EventTarget {
views.replaceContent(this._hostNode, template({
post: this._post,
enableSafety: config.enableSafety,
enableSafety: api.safetyEnabled(),
canListPosts: api.hasPrivilege('posts:list'),
canEditPosts: api.hasPrivilege('posts:edit'),
canViewTags: api.hasPrivilege('tags:view'),

View file

@ -26,6 +26,11 @@ router.enter(
next();
});
const tags = require('./tags.js');
const api = require('./api.js');
tags.refreshCategoryColorMap(); // we don't care about errors
api.fetchConfig().then(() => {
// register controller routes
let controllers = [];
controllers.push(require('./controllers/home_controller.js'));
@ -52,10 +57,9 @@ controllers.push(require('./controllers/not_found_controller.js'));
for (let controller of controllers) {
controller(router);
}
const tags = require('./tags.js');
const api = require('./api.js');
tags.refreshCategoryColorMap(); // we don't care about errors
}, error => {
window.alert('Could not fetch basic configuration from server');
}).then(() => {
api.loginFromCookies().then(() => {
router.start();
}, error => {
@ -69,3 +73,4 @@ api.loginFromCookies().then(() => {
error.message);
}
});
});

View file

@ -37,7 +37,7 @@ class PostList extends AbstractList {
static _decorateSearchQuery(text) {
const browsingSettings = settings.get();
const disabledSafety = [];
if (config.enableSafety) {
if (api.safetyEnabled()) {
for (let key of Object.keys(browsingSettings.listPosts)) {
if (browsingSettings.listPosts[key] === false) {
disabledSafety.push(key);

View file

@ -1,7 +1,7 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const api = require('../api.js');
class TopNavigationItem {
constructor(accessKey, title, url, available, imageUrl) {
@ -53,8 +53,10 @@ class TopNavigation extends events.EventTarget {
}
setTitle(title) {
api.fetchConfig().then(() => {
document.oldTitle = null;
document.title = config.name + (title ? (' ' + title) : '');
document.title = api.getName() + (title ? (' ' + title) : '');
});
}
showAll() {

View file

@ -1,6 +1,6 @@
'use strict';
const config = require('../config.js');
const api = require('../api.js');
const views = require('../util/views.js');
const template = views.getTemplate('help');
@ -26,7 +26,7 @@ class HelpView {
const sourceNode = template();
const ctx = {
name: config.name,
name: api.getName(),
};
section = section || 'about';

View file

@ -1,7 +1,7 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const api = require('../api.js');
const views = require('../util/views.js');
const template = views.getTemplate('login');
@ -12,15 +12,15 @@ class LoginView extends events.EventTarget {
this._hostNode = document.getElementById('content-holder');
views.replaceContent(this._hostNode, template({
userNamePattern: config.userNameRegex,
passwordPattern: config.passwordRegex,
canSendMails: config.canSendMails,
userNamePattern: api.getUserNameRegex(),
passwordPattern: api.getPasswordRegex(),
canSendMails: api.canSendMails(),
}));
views.syncScrollPosition();
views.decorateValidator(this._formNode);
this._userNameInputNode.setAttribute('pattern', config.userNameRegex);
this._passwordInputNode.setAttribute('pattern', config.passwordRegex);
this._userNameInputNode.setAttribute('pattern', api.getUserNameRegex());
this._passwordInputNode.setAttribute('pattern', api.getPasswordRegex());
this._formNode.addEventListener('submit', e => {
e.preventDefault();
this.dispatchEvent(new CustomEvent('submit', {

View file

@ -1,6 +1,5 @@
'use strict';
const config = require('../config.js');
const views = require('../util/views.js');
const template = views.getTemplate('not-found');

View file

@ -1,7 +1,7 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const api = require('../api.js');
const views = require('../util/views.js');
const template = views.getTemplate('password-reset');
@ -12,8 +12,8 @@ class PasswordResetView extends events.EventTarget {
this._hostNode = document.getElementById('content-holder');
views.replaceContent(this._hostNode, template({
canSendMails: config.canSendMails,
contactEmail: config.contactEmail,
canSendMails: api.canSendMails(),
contactEmail: api.getContactEmail(),
}));
views.syncScrollPosition();

View file

@ -1,6 +1,5 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const views = require('../util/views.js');

View file

@ -1,7 +1,7 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const api = require('../api.js');
const views = require('../util/views.js');
const template = views.getTemplate('user-registration');
@ -11,8 +11,8 @@ class RegistrationView extends events.EventTarget {
super();
this._hostNode = document.getElementById('content-holder');
views.replaceContent(this._hostNode, template({
userNamePattern: config.userNameRegex,
passwordPattern: config.passwordRegex,
userNamePattern: api.getUserNameRegex(),
passwordPattern: api.getPasswordRegex(),
}));
views.syncScrollPosition();
views.decorateValidator(this._formNode);

View file

@ -1,7 +1,7 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const api = require('../api.js');
const misc = require('../util/misc.js');
const views = require('../util/views.js');
const TagInputControl = require('../controls/tag_input_control.js');
@ -64,7 +64,7 @@ class TagEditView extends events.EventTarget {
}
_evtNameInput(e) {
const regex = new RegExp(config.tagNameRegex);
const regex = new RegExp(api.getTagNameRegex());
const list = misc.splitByWhitespace(this._namesFieldNode.value);
if (!list.length) {

View file

@ -1,7 +1,7 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const api = require('../api.js');
const views = require('../util/views.js');
const TagAutoCompleteControl =
require('../controls/tag_auto_complete_control.js');
@ -14,7 +14,7 @@ class TagMergeView extends events.EventTarget {
this._tag = ctx.tag;
this._hostNode = ctx.hostNode;
ctx.tagNamePattern = config.tagNameRegex;
ctx.tagNamePattern = api.getTagNameRegex();
views.replaceContent(this._hostNode, template(ctx));
views.decorateValidator(this._formNode);

View file

@ -1,7 +1,7 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const api = require('../api.js');
const views = require('../util/views.js');
const FileDropperControl = require('../controls/file_dropper_control.js');
@ -11,8 +11,8 @@ class UserEditView extends events.EventTarget {
constructor(ctx) {
super();
ctx.userNamePattern = config.userNameRegex + /|^$/.source;
ctx.passwordPattern = config.passwordRegex + /|^$/.source;
ctx.userNamePattern = api.getUserNameRegex() + /|^$/.source;
ctx.passwordPattern = api.getPasswordRegex() + /|^$/.source;
this._user = ctx.user;
this._hostNode = ctx.hostNode;

View file

@ -35,11 +35,15 @@ def get_info(
'diskUsage': _get_disk_usage(),
'serverTime': datetime.utcnow(),
'config': {
'name': config.config['name'],
'userNameRegex': config.config['user_name_regex'],
'passwordRegex': config.config['password_regex'],
'tagNameRegex': config.config['tag_name_regex'],
'tagCategoryNameRegex': config.config['tag_category_name_regex'],
'defaultUserRank': config.config['default_rank'],
'enableSafety': config.config['enable_safety'],
'contactEmail': config.config['contactEmail'],
'canSendMails': bool(config.config['smtp']['host']),
'privileges':
util.snake_case_to_lower_camel_case_keys(
config.config['privileges']),