Added hosting settings to admin panel

This commit is contained in:
CasVT
2024-02-28 22:11:12 +01:00
parent ae56927d0f
commit 4073f60539
9 changed files with 299 additions and 62 deletions

View File

@@ -24,25 +24,28 @@ from backend.custom_exceptions import (AccessUnauthorized, APIKeyExpired,
UsernameInvalid, UsernameTaken,
UserNotFound)
from backend.db import get_db, import_db, revert_db_import
from backend.helpers import folder_path
from backend.helpers import RestartVars, folder_path
from backend.notification_service import get_apprise_services
from backend.settings import get_admin_settings, get_setting, set_setting
from backend.settings import (backup_hosting_settings, get_admin_settings,
get_setting, set_setting)
from backend.users import Users
from frontend.input_validation import (AllowNewAccountsVariable, ColorVariable,
DatabaseFileVariable,
EditNotificationServicesVariable,
EditTimeVariable, EditTitleVariable,
EditURLVariable, LoginTimeResetVariable,
EditURLVariable, HostVariable,
LoginTimeResetVariable,
LoginTimeVariable, Method, Methods,
NewPasswordVariable,
NotificationServicesVariable,
PasswordCreateVariable,
PasswordVariable, QueryVariable,
RepeatIntervalVariable,
PasswordVariable, PortVariable,
QueryVariable, RepeatIntervalVariable,
RepeatQuantityVariable, SortByVariable,
TextVariable, TimelessSortByVariable,
TimeVariable, TitleVariable,
URLVariable, UsernameCreateVariable,
UrlPrefixVariable, URLVariable,
UsernameCreateVariable,
UsernameVariable, WeekDaysVariable,
admin_api, admin_api_prefix, api,
api_prefix, get_api_docs,
@@ -93,12 +96,22 @@ def revert_db() -> None:
restart_server()
return
def revert_hosting() -> None:
"""Revert the hosting changes.
"""
logging.warning(f'Timer for hosting changes expired; reverting back to original settings')
APIVariables.handle_flags = True
restart_server()
return
shutdown_server_thread = Timer(1.0, shutdown_server)
shutdown_server_thread.name = "InternalStateHandler"
restart_server_thread = Timer(1.0, restart_server)
restart_server_thread.name = "InternalStateHandler"
revert_db_thread = Timer(60.0, revert_db)
revert_db_thread.name = "DatabaseImportHandler"
revert_hosting_thread = Timer(60.0, revert_hosting)
revert_hosting_thread.name = "HostingHandler"
@dataclass
class ApiKeyEntry:
@@ -211,6 +224,10 @@ def api_login(inputs: Dict[str, str]):
revert_db_thread.cancel()
revert_db_import(swap=False)
elif user.admin and revert_hosting_thread.is_alive():
logging.info('Timer for hosting changes diffused')
revert_hosting_thread.cancel()
# Generate an API key until one
# is generated that isn't used already
while True:
@@ -737,7 +754,8 @@ def api_settings():
),
put=Method(
vars=[AllowNewAccountsVariable, LoginTimeVariable,
LoginTimeResetVariable],
LoginTimeResetVariable, HostVariable, PortVariable,
UrlPrefixVariable],
description='Edit the admin settings'
)
),
@@ -749,14 +767,24 @@ def api_admin_settings(inputs: Dict[str, Any]):
return return_api(get_admin_settings())
elif request.method == 'PUT':
values = {
'allow_new_accounts': inputs['allow_new_accounts'],
'login_time': inputs['login_time'],
'login_time_reset': inputs['login_time_reset']
}
logging.info(f'Submitting admin settings: {values}')
for k, v in values.items():
set_setting(k, v)
logging.info(f'Submitting admin settings: {inputs}')
hosting_changes = any(
inputs[s] is not None
for s in ('host', 'port', 'url_prefix')
)
if hosting_changes:
backup_hosting_settings()
for k, v in inputs.items():
if v is not None:
set_setting(k, v)
if hosting_changes:
APIVariables.restart_args = [RestartVars.HOST_CHANGE.value]
restart_server_thread.start()
return return_api({})
@admin_api.route(

View File

@@ -378,23 +378,38 @@ class AdminSettingsVariable(BaseInputVariable):
return True
class AllowNewAccountsVariable(AdminSettingsVariable):
class AllowNewAccountsVariable(NonRequiredVersion, AdminSettingsVariable):
name = 'allow_new_accounts'
description = ('Whether or not to allow users to register a new account. '
+ 'The admin can always add a new account.')
class LoginTimeVariable(AdminSettingsVariable):
class LoginTimeVariable(NonRequiredVersion, AdminSettingsVariable):
name = 'login_time'
description = ('How long a user stays logged in, in seconds. '
+ 'Between 1 min and 1 month (60 <= sec <= 2592000)')
class LoginTimeResetVariable(AdminSettingsVariable):
class LoginTimeResetVariable(NonRequiredVersion, AdminSettingsVariable):
name = 'login_time_reset'
description = 'If the Login Time timer should reset with each API request.'
class HostVariable(NonRequiredVersion, AdminSettingsVariable):
name = 'host'
description = 'The IP to bind to. Use 0.0.0.0 to bind to all addresses.'
class PortVariable(NonRequiredVersion, AdminSettingsVariable):
name = 'port'
description = 'The port to listen on.'
class UrlPrefixVariable(NonRequiredVersion, AdminSettingsVariable):
name = 'url_prefix'
description = 'The base url to run on. Useful for reverse proxies. Empty string to disable.'
class DatabaseFileVariable(BaseInputVariable):
name = 'file'
description = 'The MIND database file'

View File

@@ -52,7 +52,8 @@ main {
padding-top: var(--nav-width);
}
#settings-form {
#settings-form,
#hosting-form {
display: flex;
flex-direction: column;
align-items: center;
@@ -74,7 +75,8 @@ h2 {
overflow-x: auto;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.settings-table {
@@ -96,6 +98,7 @@ h2 {
.settings-table td:first-child {
width: 50%;
padding-right: var(--middle-spacing);
padding-top: .55rem;
text-align: right;
}
@@ -155,14 +158,14 @@ h2 {
flex-wrap: wrap;
}
#save-hosting-button,
#add-user-button,
#download-db-button,
#upload-db-button,
#restart-button,
#shutdown-button {
width: min(15rem, 100%);
height: 2rem;
padding: .5rem 1rem;
border-radius: 4px;
background-color: var(--color-gray);
@@ -170,11 +173,12 @@ h2 {
box-shadow: var(--default-shadow);
}
#download-db-button,
#upload-db-button,
#restart-button,
#shutdown-button {
height: unset;
#save-hosting-button {
align-self: center;
}
#add-user-button {
height: 2rem;
}
#add-user-button > svg {
@@ -230,6 +234,7 @@ h2 {
padding: .25rem;
}
#hosting-form > p,
#upload-database-form p {
max-width: 50rem;
margin-inline: auto;
@@ -238,10 +243,6 @@ h2 {
}
@media (max-width: 40rem) {
#settings-form {
justify-content: flex-start;
}
h2 {
text-align: center;
padding-inline: 0;
@@ -249,7 +250,7 @@ h2 {
.settings-table-container,
.user-table-container {
justify-content: left;
align-items: flex-start;
}
.settings-table tbody {

View File

@@ -4,6 +4,14 @@ const setting_inputs = {
login_time_reset: document.querySelector('#login-time-reset-input')
};
const hosting_inputs = {
form: document.querySelector('#hosting-form'),
host: document.querySelector('#host-input'),
port: document.querySelector('#port-input'),
url_prefix: document.querySelector('#url-prefix-input'),
submit: document.querySelector('#save-hosting-button')
};
const user_inputs = {
username: document.querySelector('#new-username-input'),
password: document.querySelector('#new-password-input')
@@ -44,6 +52,9 @@ function loadSettings() {
setting_inputs.allow_new_accounts.checked = json.result.allow_new_accounts;
setting_inputs.login_time.value = Math.round(json.result.login_time / 60);
setting_inputs.login_time_reset.value = json.result.login_time_reset.toString();
hosting_inputs.host.value = json.result.host;
hosting_inputs.port.value = json.result.port;
hosting_inputs.url_prefix.value = json.result.url_prefix;
});
};
@@ -69,6 +80,34 @@ function submitSettings() {
});
};
function submitHostingSettings() {
hosting_inputs.submit.innerText = 'Restarting';
const data = {
host: hosting_inputs.host.value,
port: parseInt(hosting_inputs.port.value),
url_prefix: hosting_inputs.url_prefix.value
};
fetch(`${url_prefix}/api/admin/settings?api_key=${api_key}`, {
'method': 'PUT',
'headers': {'Content-Type': 'application/json'},
'body': JSON.stringify(data)
})
.then(response => response.json())
.then(json => {
if (json.error !== null)
return Promise.reject(json)
setTimeout(
() => window.location.reload(),
1000
)
})
.catch(json => {
if (['ApiKeyInvalid', 'ApiKeyExpired'].includes(json.error))
window.location.href = `${url_prefix}/`;
});
};
function toggleAddUser() {
const el = document.querySelector('#add-user-row');
if (el.classList.contains('hidden')) {
@@ -244,6 +283,7 @@ loadUsers();
document.querySelector('#logout-button').onclick = e => logout();
document.querySelector('#settings-form').action = 'javascript:submitSettings();';
hosting_inputs.form.action = 'javascript:submitHostingSettings();';
document.querySelector('#add-user-button').onclick = e => toggleAddUser();
document.querySelector('#add-user-form').action = 'javascript:addUser()';
document.querySelector('#download-db-button').onclick = e =>

View File

@@ -53,7 +53,7 @@
<td><label for="login-time-input">Login Time</label></td>
<td>
<div class="number-input">
<input type="number" id="login-time-input" min="1" max="43200">
<input type="number" id="login-time-input" min="1" max="43200" required>
<p>Min</p>
</div>
<p>For how long users stay logged in before having to authenticate again. Between 1 minute and 1 month.</p>
@@ -74,6 +74,38 @@
</table>
</div>
</form>
<form id="hosting-form">
<h2>Hosting</h2>
<div class="settings-table-container">
<table class="settings-table">
<tbody>
<tr>
<td><label for="host-input">Host</label></td>
<td>
<input type="text" id="host-input" required>
<p>Valid IPv4 address (default is '0.0.0.0' for all available interfaces)</p>
</td>
</tr>
<tr>
<td><label for="port-input">Port</label></td>
<td>
<input type="number" id="port-input" min="1" max="65535" required>
<p>The port used to access the web UI (default is '8080')</p>
</td>
</tr>
<tr>
<td><label for="url-prefix-input">URL Prefix</label></td>
<td>
<input type="text" id="url-prefix-input">
<p>For reverse proxy support (default is empty).</p>
</td>
</tr>
</tbody>
</table>
<button type="submit" id="save-hosting-button">Save and Restart</button>
</div>
<p>IMPORTANT: After saving the hosting settings, it is required to log into the admin panel within 1 minute (60 seconds) in order to keep the new hosting settings. Otherwise, MIND will revert the changes and go back to the old hosting settings.</p>
</form>
<h2>User Management</h2>
<div class="add-user-container">
<button id="add-user-button">