From 3972b902d840e54bd4f8dd47a9d13bc20ee22c65 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Mon, 25 Jun 2018 10:47:20 -0400 Subject: [PATCH] client: fetch configurations from server at runtime Permissions, regex filters, app title, email info, and safety now fetched using server's Info API --- client/js/api.js | 48 ++++++++++- client/js/controllers/home_controller.js | 2 +- client/js/controllers/post_list_controller.js | 11 ++- .../js/controllers/post_upload_controller.js | 3 +- .../controllers/top_navigation_controller.js | 17 ++-- client/js/controllers/user_controller.js | 1 - .../js/controls/post_edit_sidebar_control.js | 3 +- .../controls/post_readonly_sidebar_control.js | 2 +- client/js/main.js | 83 ++++++++++--------- client/js/models/post_list.js | 2 +- client/js/models/top_navigation.js | 8 +- client/js/views/help_view.js | 4 +- client/js/views/login_view.js | 12 +-- client/js/views/not_found_view.js | 1 - client/js/views/password_reset_view.js | 6 +- client/js/views/post_merge_view.js | 1 - client/js/views/registration_view.js | 6 +- client/js/views/tag_edit_view.js | 4 +- client/js/views/tag_merge_view.js | 4 +- client/js/views/user_edit_view.js | 6 +- server/szurubooru/api/info_api.py | 4 + 21 files changed, 137 insertions(+), 91 deletions(-) diff --git a/client/js/api.js b/client/js/api.js index 3623045b..ba06f199 100644 --- a/client/js/api.js +++ b/client/js/api.js @@ -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; } diff --git a/client/js/controllers/home_controller.js b/client/js/controllers/home_controller.js index b7590a00..cc22ca95 100644 --- a/client/js/controllers/home_controller.js +++ b/client/js/controllers/home_controller.js @@ -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'), diff --git a/client/js/controllers/post_list_controller.js b/client/js/controllers/post_list_controller.js index f60cb8b7..fd1adfea 100644 --- a/client/js/controllers/post_list_controller.js +++ b/client/js/controllers/post_list_controller.js @@ -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, }, diff --git a/client/js/controllers/post_upload_controller.js b/client/js/controllers/post_upload_controller.js index 9dfdb4af..ccd6f94c 100644 --- a/client/js/controllers/post_upload_controller.js +++ b/client/js/controllers/post_upload_controller.js @@ -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)); diff --git a/client/js/controllers/top_navigation_controller.js b/client/js/controllers/top_navigation_controller.js index 0f56ddc0..fdf8117a 100644 --- a/client/js/controllers/top_navigation_controller.js +++ b/client/js/controllers/top_navigation_controller.js @@ -1,21 +1,22 @@ '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() { - this._topNavigationView = new TopNavigationView(); + api.fetchConfig().then(() => { + this._topNavigationView = new TopNavigationView(); - topNavigation.addEventListener( - 'activate', e => this._evtActivate(e)); + topNavigation.addEventListener( + 'activate', e => this._evtActivate(e)); - api.addEventListener('login', e => this._evtAuthChange(e)); - api.addEventListener('logout', e => this._evtAuthChange(e)); + api.addEventListener('login', e => this._evtAuthChange(e)); + api.addEventListener('logout', e => this._evtAuthChange(e)); - this._render(); + 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 : ''); diff --git a/client/js/controllers/user_controller.js b/client/js/controllers/user_controller.js index d042e41f..48989af3 100644 --- a/client/js/controllers/user_controller.js +++ b/client/js/controllers/user_controller.js @@ -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'); diff --git a/client/js/controls/post_edit_sidebar_control.js b/client/js/controls/post_edit_sidebar_control.js index 38dded28..dd8b66da 100644 --- a/client/js/controls/post_edit_sidebar_control.js +++ b/client/js/controls/post_edit_sidebar_control.js @@ -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'), diff --git a/client/js/controls/post_readonly_sidebar_control.js b/client/js/controls/post_readonly_sidebar_control.js index bb51e69d..579a38e6 100644 --- a/client/js/controls/post_readonly_sidebar_control.js +++ b/client/js/controls/post_readonly_sidebar_control.js @@ -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'), diff --git a/client/js/main.js b/client/js/main.js index 71284f8e..5ddf26fe 100644 --- a/client/js/main.js +++ b/client/js/main.js @@ -26,46 +26,51 @@ router.enter( next(); }); -// register controller routes -let controllers = []; -controllers.push(require('./controllers/home_controller.js')); -controllers.push(require('./controllers/help_controller.js')); -controllers.push(require('./controllers/auth_controller.js')); -controllers.push(require('./controllers/password_reset_controller.js')); -controllers.push(require('./controllers/comments_controller.js')); -controllers.push(require('./controllers/snapshots_controller.js')); -controllers.push(require('./controllers/post_detail_controller.js')); -controllers.push(require('./controllers/post_main_controller.js')); -controllers.push(require('./controllers/post_list_controller.js')); -controllers.push(require('./controllers/post_upload_controller.js')); -controllers.push(require('./controllers/tag_controller.js')); -controllers.push(require('./controllers/tag_list_controller.js')); -controllers.push(require('./controllers/tag_categories_controller.js')); -controllers.push(require('./controllers/settings_controller.js')); -controllers.push(require('./controllers/user_controller.js')); -controllers.push(require('./controllers/user_list_controller.js')); -controllers.push(require('./controllers/user_registration_controller.js')); - -// 404 controller needs to be registered last -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 -api.loginFromCookies().then(() => { - router.start(); - }, error => { - if (window.location.href.indexOf('login') !== -1) { - api.forget(); + +api.fetchConfig().then(() => { + // register controller routes + let controllers = []; + controllers.push(require('./controllers/home_controller.js')); + controllers.push(require('./controllers/help_controller.js')); + controllers.push(require('./controllers/auth_controller.js')); + controllers.push(require('./controllers/password_reset_controller.js')); + controllers.push(require('./controllers/comments_controller.js')); + controllers.push(require('./controllers/snapshots_controller.js')); + controllers.push(require('./controllers/post_detail_controller.js')); + controllers.push(require('./controllers/post_main_controller.js')); + controllers.push(require('./controllers/post_list_controller.js')); + controllers.push(require('./controllers/post_upload_controller.js')); + controllers.push(require('./controllers/tag_controller.js')); + controllers.push(require('./controllers/tag_list_controller.js')); + controllers.push(require('./controllers/tag_categories_controller.js')); + controllers.push(require('./controllers/settings_controller.js')); + controllers.push(require('./controllers/user_controller.js')); + controllers.push(require('./controllers/user_list_controller.js')); + controllers.push(require('./controllers/user_registration_controller.js')); + + // 404 controller needs to be registered last + controllers.push(require('./controllers/not_found_controller.js')); + + for (let controller of controllers) { + controller(router); + } +}, error => { + window.alert('Could not fetch basic configuration from server'); +}).then(() => { + api.loginFromCookies().then(() => { router.start(); - } else { - const ctx = router.start('/'); - ctx.controller.showError( - 'An error happened while trying to log you in: ' + - error.message); - } - }); + }, error => { + if (window.location.href.indexOf('login') !== -1) { + api.forget(); + router.start(); + } else { + const ctx = router.start('/'); + ctx.controller.showError( + 'An error happened while trying to log you in: ' + + error.message); + } + }); +}); diff --git a/client/js/models/post_list.js b/client/js/models/post_list.js index 2e3c6fca..2bfd056b 100644 --- a/client/js/models/post_list.js +++ b/client/js/models/post_list.js @@ -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); diff --git a/client/js/models/top_navigation.js b/client/js/models/top_navigation.js index ebb3753e..a7c726db 100644 --- a/client/js/models/top_navigation.js +++ b/client/js/models/top_navigation.js @@ -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) { - document.oldTitle = null; - document.title = config.name + (title ? (' – ' + title) : ''); + api.fetchConfig().then(() => { + document.oldTitle = null; + document.title = api.getName() + (title ? (' – ' + title) : ''); + }); } showAll() { diff --git a/client/js/views/help_view.js b/client/js/views/help_view.js index 4938b231..11f6a39a 100644 --- a/client/js/views/help_view.js +++ b/client/js/views/help_view.js @@ -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'; diff --git a/client/js/views/login_view.js b/client/js/views/login_view.js index 7d97982c..2c05332c 100644 --- a/client/js/views/login_view.js +++ b/client/js/views/login_view.js @@ -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', { diff --git a/client/js/views/not_found_view.js b/client/js/views/not_found_view.js index 8b5a1039..487613b5 100644 --- a/client/js/views/not_found_view.js +++ b/client/js/views/not_found_view.js @@ -1,6 +1,5 @@ 'use strict'; -const config = require('../config.js'); const views = require('../util/views.js'); const template = views.getTemplate('not-found'); diff --git a/client/js/views/password_reset_view.js b/client/js/views/password_reset_view.js index 25409c77..685fe5a0 100644 --- a/client/js/views/password_reset_view.js +++ b/client/js/views/password_reset_view.js @@ -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(); diff --git a/client/js/views/post_merge_view.js b/client/js/views/post_merge_view.js index 04ed21da..3e987b36 100644 --- a/client/js/views/post_merge_view.js +++ b/client/js/views/post_merge_view.js @@ -1,6 +1,5 @@ 'use strict'; -const config = require('../config.js'); const events = require('../events.js'); const views = require('../util/views.js'); diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index fb924c2f..48034ddf 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -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); diff --git a/client/js/views/tag_edit_view.js b/client/js/views/tag_edit_view.js index 77c7cefc..5b517d46 100644 --- a/client/js/views/tag_edit_view.js +++ b/client/js/views/tag_edit_view.js @@ -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) { diff --git a/client/js/views/tag_merge_view.js b/client/js/views/tag_merge_view.js index 12286800..c975a50e 100644 --- a/client/js/views/tag_merge_view.js +++ b/client/js/views/tag_merge_view.js @@ -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); diff --git a/client/js/views/user_edit_view.js b/client/js/views/user_edit_view.js index 28208ead..8a6f1a4a 100644 --- a/client/js/views/user_edit_view.js +++ b/client/js/views/user_edit_view.js @@ -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; diff --git a/server/szurubooru/api/info_api.py b/server/szurubooru/api/info_api.py index 19b42cb8..8f8c9ea4 100644 --- a/server/szurubooru/api/info_api.py +++ b/server/szurubooru/api/info_api.py @@ -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']),