Merge pull request #26 from hrfee/dynamic-settings

Add live reloading to some options, email fix
This commit is contained in:
Harvey Tindall 2020-06-30 21:28:00 +01:00 committed by GitHub
commit ac500e14cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 76 deletions

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
__version__ = "0.2.5" __version__ = "0.2.6"
import secrets import secrets
import configparser import configparser
@ -66,14 +66,14 @@ if data_dir.exists() is False or (data_dir / "config.ini").exists() is False:
else: else:
config_path = data_dir / "config.ini" config_path = data_dir / "config.ini"
config = configparser.RawConfigParser()
config.read(config_path)
temp_config = configparser.RawConfigParser()
temp_config.read(config_path)
def create_log(name): def create_log(name):
log = logging.getLogger(name) log = logging.getLogger(name)
handler = logging.StreamHandler(sys.stdout) handler = logging.StreamHandler(sys.stdout)
if config.getboolean("ui", "debug"): if temp_config.getboolean('ui', 'debug'):
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
handler.setLevel(logging.DEBUG) handler.setLevel(logging.DEBUG)
else: else:
@ -88,18 +88,11 @@ def create_log(name):
log = create_log("main") log = create_log("main")
web_log = create_log("waitress")
if not first_run:
email_log = create_log("emails")
auth_log = create_log("auth")
if args.host is not None:
log.debug(f"Using specified host {args.host}")
config["ui"]["host"] = args.host
if args.port is not None:
log.debug(f"Using specified port {args.port}")
config["ui"]["port"] = args.port
def load_config(config_path, data_dir):
config = configparser.RawConfigParser()
config.read(config_path)
global log
for key in config["files"]: for key in config["files"]:
if config["files"][key] == "": if config["files"][key] == "":
if key != "custom_css": if key != "custom_css":
@ -114,6 +107,52 @@ for key in ["user_configuration", "user_displayprefs"]:
if "no_username" not in config["email"]: if "no_username" not in config["email"]:
config["email"]["no_username"] = "false" config["email"]["no_username"] = "false"
log.debug("Set no_username to false") log.debug("Set no_username to false")
if (
"email_html" not in config["password_resets"]
or config["password_resets"]["email_html"] == ""
):
log.debug("Using default password reset email HTML template")
config["password_resets"]["email_html"] = str(local_dir / "email.html")
if (
"email_text" not in config["password_resets"]
or config["password_resets"]["email_text"] == ""
):
log.debug("Using default password reset email plaintext template")
config["password_resets"]["email_text"] = str(local_dir / "email.txt")
if (
"email_html" not in config["invite_emails"]
or config["invite_emails"]["email_html"] == ""
):
log.debug("Using default invite email HTML template")
config["invite_emails"]["email_html"] = str(local_dir / "invite-email.html")
if (
"email_text" not in config["invite_emails"]
or config["invite_emails"]["email_text"] == ""
):
log.debug("Using default invite email plaintext template")
config["invite_emails"]["email_text"] = str(local_dir / "invite-email.txt")
if (
"public_server" not in config["jellyfin"]
or config["jellyfin"]["public_server"] == ""
):
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
return config
config = load_config(config_path, data_dir)
web_log = create_log("waitress")
if not first_run:
email_log = create_log("emails")
auth_log = create_log("auth")
if args.host is not None:
log.debug(f"Using specified host {args.host}")
config["ui"]["host"] = args.host
if args.port is not None:
log.debug(f"Using specified port {args.port}")
config["ui"]["port"] = args.port
try: try:
with open(config["files"]["invites"], "r") as f: with open(config["files"]["invites"], "r") as f:
@ -171,36 +210,6 @@ if "custom_css" in config["files"]:
) )
if (
"email_html" not in config["password_resets"]
or config["password_resets"]["email_html"] == ""
):
log.debug("Using default password reset email HTML template")
config["password_resets"]["email_html"] = str(local_dir / "email.html")
if (
"email_text" not in config["password_resets"]
or config["password_resets"]["email_text"] == ""
):
log.debug("Using default password reset email plaintext template")
config["password_resets"]["email_text"] = str(local_dir / "email.txt")
if (
"email_html" not in config["invite_emails"]
or config["invite_emails"]["email_html"] == ""
):
log.debug("Using default invite email HTML template")
config["invite_emails"]["email_html"] = str(local_dir / "invite-email.html")
if (
"email_text" not in config["invite_emails"]
or config["invite_emails"]["email_text"] == ""
):
log.debug("Using default invite email plaintext template")
config["invite_emails"]["email_text"] = str(local_dir / "invite-email.txt")
if (
"public_server" not in config["jellyfin"]
or config["jellyfin"]["public_server"] == ""
):
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
def resp(success=True, code=500): def resp(success=True, code=500):

View File

@ -87,7 +87,7 @@
}, },
"jellyfin_login": { "jellyfin_login": {
"name": "Use Jellyfin for authentication", "name": "Use Jellyfin for authentication",
"required": true, "required": false,
"requires_restart": true, "requires_restart": true,
"type": "bool", "type": "bool",
"value": true, "value": true,
@ -122,7 +122,7 @@
}, },
"debug": { "debug": {
"name": "Debug logging", "name": "Debug logging",
"required": true, "required": false,
"requires_restart": true, "requires_restart": true,
"type": "bool", "type": "bool",
"value": false "value": false
@ -130,7 +130,7 @@
"contact_message": { "contact_message": {
"name": "Contact message", "name": "Contact message",
"required": false, "required": false,
"requires_restart": false, "requires_restart": true,
"type": "text", "type": "text",
"value": "Need help? contact me.", "value": "Need help? contact me.",
"description": "Displayed at bottom of all pages except admin" "description": "Displayed at bottom of all pages except admin"
@ -138,7 +138,7 @@
"help_message": { "help_message": {
"name": "Help message", "name": "Help message",
"required": false, "required": false,
"requires_restart": false, "requires_restart": true,
"type": "text", "type": "text",
"value": "Enter your details to create an account.", "value": "Enter your details to create an account.",
"description": "Display at top of invite form." "description": "Display at top of invite form."
@ -146,7 +146,7 @@
"success_message": { "success_message": {
"name": "Success message", "name": "Success message",
"required": false, "required": false,
"requires_restart": false, "requires_restart": true,
"type": "text", "type": "text",
"value": "Your account has been created. Click below to continue to Jellyfin.", "value": "Your account has been created. Click below to continue to Jellyfin.",
"description": "Displayed when a user creates an account" "description": "Displayed when a user creates an account"
@ -159,7 +159,7 @@
}, },
"enabled": { "enabled": {
"name": "Enabled", "name": "Enabled",
"required": true, "required": false,
"requires_restart": true, "requires_restart": true,
"type": "bool", "type": "bool",
"value": true "value": true
@ -195,6 +195,7 @@
"special": { "special": {
"name": "Minimum number of special characters", "name": "Minimum number of special characters",
"requires_restart": true, "requires_restart": true,
"depends_true": "enabled",
"type": "text", "type": "text",
"value": "0" "value": "0"
} }
@ -277,7 +278,7 @@
}, },
"enabled": { "enabled": {
"name": "Enabled", "name": "Enabled",
"required": true, "required": false,
"requires_restart": true, "requires_restart": true,
"type": "bool", "type": "bool",
"value": true, "value": true,
@ -327,7 +328,7 @@
}, },
"enabled": { "enabled": {
"name": "Enabled", "name": "Enabled",
"required": true, "required": false,
"requires_restart": true, "requires_restart": true,
"type": "bool", "type": "bool",
"value": true "value": true

View File

@ -498,6 +498,9 @@ document.getElementById('openSettings').onclick = function () {
entryName += ' <sup class="text-danger">*</sup>'; entryName += ' <sup class="text-danger">*</sup>';
required = true; required = true;
}; };
if (config[section][entry]['requires_restart']) {
entryName += ' <sup class="text-danger">R</sup>';
};
if (config[section][entry].hasOwnProperty('description')) { if (config[section][entry].hasOwnProperty('description')) {
var tooltip = ` var tooltip = `
<a class="text-muted" href="#" data-toggle="tooltip" data-placement="right" title="${config[section][entry]['description']}"><i class="fa fa-question-circle-o"></i></a> <a class="text-muted" href="#" data-toggle="tooltip" data-placement="right" title="${config[section][entry]['description']}"><i class="fa fa-question-circle-o"></i></a>
@ -505,15 +508,12 @@ document.getElementById('openSettings').onclick = function () {
entryName += ' '; entryName += ' ';
entryName += tooltip; entryName += tooltip;
}; };
// if (config[section][entry]['requires_restart']) {
// entryName += ' <sup class="text-danger">R</sup>';
// };
var entryValue = config[section][entry]['value']; var entryValue = config[section][entry]['value'];
var entryType = config[section][entry]['type']; var entryType = config[section][entry]['type'];
var entryGroup = document.createElement('div'); var entryGroup = document.createElement('div');
if (entryType == 'bool') { if (entryType == 'bool') {
entryGroup.classList.add('form-check'); entryGroup.classList.add('form-check');
if (entryValue) { if (entryValue.toString() == 'true') {
var checked = true; var checked = true;
} else { } else {
var checked = false; var checked = false;
@ -604,8 +604,8 @@ function sendConfig(modalId) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); xhr.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
}, },
success: function() { success: function() {
if (modalId != 'settingsMenu') {
$('#' + modalId).modal('hide'); $('#' + modalId).modal('hide');
if (modalId != 'settingsMenu') {
$('#settingsMenu').modal('hide'); $('#settingsMenu').modal('hide');
}; };
}, },
@ -618,13 +618,11 @@ function sendConfig(modalId) {
footer.appendChild(alert); footer.appendChild(alert);
}, },
}); });
// placeholder
}; };
document.getElementById('settingsSave').onclick = function() { document.getElementById('settingsSave').onclick = function() {
modifiedConfig = {}; modifiedConfig = {};
// Live config changes have not yet been implemented, so restart always required. var restart_setting_changed = false;
// var restart_setting_changed = false;
var settings_changed = false; var settings_changed = false;
for (var section of Object.keys(config)) { for (var section of Object.keys(config)) {
@ -643,23 +641,24 @@ document.getElementById('settingsSave').onclick = function() {
}; };
modifiedConfig[section][entry] = value; modifiedConfig[section][entry] = value;
settings_changed = true; settings_changed = true;
// if (config[section][entry]['requires_restart']) { if (config[section][entry]['requires_restart']) {
// restart_setting_changed = true; restart_setting_changed = true;
// }; };
}; };
}; };
}; };
}; };
// if (restart_setting_changed) { // if (restart_setting_changed) {
if (settings_changed) { if (restart_setting_changed) {
document.getElementById('applyRestarts').onclick = function(){sendConfig('restartModal');}; document.getElementById('applyRestarts').onclick = function(){sendConfig('restartModal');};
$('#settingsMenu').modal('hide'); $('#settingsMenu').modal('hide');
$('#restartModal').modal({ $('#restartModal').modal({
backdrop: 'static', backdrop: 'static',
show: true show: true
}); });
} else if (settings_changed) {
sendConfig('settingsMenu');
} else { } else {
// sendConfig('settingsMenu');
$('#settingsMenu').modal('hide'); $('#settingsMenu').modal('hide');
}; };
}; };

View File

@ -93,7 +93,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<p>Note: <sup class="text-danger">*</sup> Indicates required field.</p> <p>Note: <sup class="text-danger">*</sup> Indicates required field, <sup class="text-danger">R</sup> Indicates changes require a restart.</p>
<button type="button" class="list-group-item list-group-item-action" id="openUsers"> <button type="button" class="list-group-item list-group-item-action" id="openUsers">
Users <i class="fa fa-user"></i> Users <i class="fa fa-user"></i>
</button> </button>
@ -160,7 +160,7 @@
<h5 class="modal-title">Warning</h5> <h5 class="modal-title">Warning</h5>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>A restart is needed to apply settings. This must be done manually. Apply now?</p> <p>A restart is needed to apply some settings. This must be done manually. Apply now?</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>

View File

@ -1,8 +1,9 @@
from pathlib import Path from pathlib import Path
from flask import Flask, send_from_directory, render_template from flask import Flask, send_from_directory, render_template
from jellyfin_accounts import config, app, g, css, data_store
from jellyfin_accounts import app, g, css, data_store
from jellyfin_accounts import web_log as log from jellyfin_accounts import web_log as log
from jellyfin_accounts.web_api import checkInvite, validator from jellyfin_accounts.web_api import config, checkInvite, validator
@app.errorhandler(404) @app.errorhandler(404)

View File

@ -7,6 +7,8 @@ import time
from jellyfin_accounts import ( from jellyfin_accounts import (
config, config,
config_path, config_path,
load_config,
data_dir,
app, app,
g, g,
data_store, data_store,
@ -326,6 +328,7 @@ def setDefaults():
@app.route("/modifyConfig", methods=["POST"]) @app.route("/modifyConfig", methods=["POST"])
@auth.login_required @auth.login_required
def modifyConfig(): def modifyConfig():
global config
log.info("Config modification requested") log.info("Config modification requested")
data = request.get_json() data = request.get_json()
temp_config = configparser.RawConfigParser( temp_config = configparser.RawConfigParser(
@ -344,7 +347,8 @@ def modifyConfig():
log.debug(f"{section}/{item} does not exist in config") log.debug(f"{section}/{item} does not exist in config")
with open(config_path, "w") as config_file: with open(config_path, "w") as config_file:
temp_config.write(config_file) temp_config.write(config_file)
log.info("Config written. Restart is needed to load settings.") config = load_config(config_path, data_dir)
log.info("Config written. Restart may be needed to load settings.")
return resp() return resp()
@ -361,7 +365,7 @@ def getConfig():
log.debug("Config requested") log.debug("Config requested")
with open(config_base_path, "r") as f: with open(config_base_path, "r") as f:
config_base = json.load(f) config_base = json.load(f)
config.read(config_path) # config.read(config_path)
response_config = config_base response_config = config_base
for section in config_base: for section in config_base:
for entry in config_base[section]: for entry in config_base[section]:

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "jellyfin-accounts" name = "jellyfin-accounts"
version = "0.2.5" version = "0.2.6"
readme = "README.md" readme = "README.md"
description = "A simple account management system for Jellyfin" description = "A simple account management system for Jellyfin"
authors = ["Harvey Tindall <harveyltindall@gmail.com>"] authors = ["Harvey Tindall <harveyltindall@gmail.com>"]