Development
This commit is contained in:
parent
563d82daf3
commit
70ccfe2c29
48 changed files with 549 additions and 578 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
from flask import Blueprint, session, redirect
|
from flask import Blueprint, session, redirect
|
||||||
from auth import require_level
|
import auth
|
||||||
|
|
||||||
bp = Blueprint('accountlogout', __name__)
|
bp = Blueprint('accountlogout', __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/accountlogout/logout', methods=['POST'])
|
@bp.route('/action/accountlogout/logout', methods=['POST'])
|
||||||
@require_level('viewer')
|
@auth.require_level('viewer')
|
||||||
def logout():
|
def logout():
|
||||||
session.clear()
|
session.clear()
|
||||||
return redirect('/overview')
|
return redirect('/overview')
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,25 @@
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import (
|
import config_utils
|
||||||
_load_done_set, _is_locked, _lock_mtime,
|
|
||||||
_seconds_until_next_run, _entry_ts_from_queue,
|
|
||||||
)
|
|
||||||
|
|
||||||
bp = Blueprint('api_apply_health', __name__)
|
bp = Blueprint('api_apply_health', __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/api/apply-health')
|
@bp.route('/api/apply-health')
|
||||||
@require_level('viewer')
|
@auth.require_level('viewer')
|
||||||
def apply_health():
|
def apply_health():
|
||||||
entry_uuid = request.args.get('uuid', '')
|
entry_uuid = request.args.get('uuid', '')
|
||||||
if not entry_uuid:
|
if not entry_uuid:
|
||||||
return jsonify({'status': 'unknown'})
|
return jsonify({'status': 'unknown'})
|
||||||
|
|
||||||
if entry_uuid in _load_done_set():
|
if entry_uuid in config_utils._load_done_set():
|
||||||
return jsonify({'status': 'complete'})
|
return jsonify({'status': 'complete'})
|
||||||
|
|
||||||
if _is_locked():
|
if config_utils._is_locked():
|
||||||
mtime = _lock_mtime()
|
mtime = config_utils._lock_mtime()
|
||||||
entry_ts = _entry_ts_from_queue(entry_uuid)
|
entry_ts = config_utils._entry_ts_from_queue(entry_uuid)
|
||||||
if mtime and entry_ts is not None and entry_ts < mtime:
|
if mtime and entry_ts is not None and entry_ts < mtime:
|
||||||
return jsonify({'status': 'running'})
|
return jsonify({'status': 'running'})
|
||||||
return jsonify({'status': 'pending', 'next_in': None})
|
return jsonify({'status': 'pending', 'next_in': None})
|
||||||
|
|
||||||
return jsonify({'status': 'pending', 'next_in': _seconds_until_next_run()})
|
return jsonify({'status': 'pending', 'next_in': config_utils._seconds_until_next_run()})
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,13 @@
|
||||||
from flask import session
|
from flask import session
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
import json, re, sys, html as html_mod, os, subprocess
|
import json, re, sys, html as html_mod, os, subprocess
|
||||||
from config_utils import (
|
import config_utils
|
||||||
config_hash, load_config, CONFIGS_DIR, WWW_DIR, APP_DIR,
|
import settings
|
||||||
ACCOUNTS_FILE, HEALTH_FILE, BLOCKLISTS_DIR,
|
|
||||||
fmt_timestamp, relative_time, fmt_bytes, resolve_iface,
|
|
||||||
WEB_APP_DISPLAY_NAME,
|
|
||||||
)
|
|
||||||
from config_utils import (
|
|
||||||
get_pending_entries, get_dashboard_pending, _find_cmd_in_queues,
|
|
||||||
_apply_changes_immediately, _seconds_until_next_run, _format_timing,
|
|
||||||
_is_locked, _lock_mtime, _entry_ts_from_queue,
|
|
||||||
)
|
|
||||||
|
|
||||||
import settings as settings
|
PAGES_DIR = os.path.join(config_utils.APP_DIR, 'pages')
|
||||||
|
NAVBAR_FILE = os.path.join(config_utils.APP_DIR, 'navbar.json')
|
||||||
PAGES_DIR = os.path.join(APP_DIR, 'pages')
|
CSS_FILE = os.path.join(config_utils.WWW_DIR, 'styles.css')
|
||||||
NAVBAR_FILE = os.path.join(APP_DIR, 'navbar.json')
|
COMMON_JS_FILE = os.path.join(config_utils.WWW_DIR, 'common.js')
|
||||||
CSS_FILE = os.path.join(WWW_DIR, 'styles.css')
|
|
||||||
COMMON_JS_FILE = os.path.join(WWW_DIR, 'common.js')
|
|
||||||
|
|
||||||
|
|
||||||
def _file_version(path):
|
def _file_version(path):
|
||||||
|
|
@ -55,7 +44,7 @@ VALIDATION_FLAGS = {
|
||||||
|
|
||||||
def _restricted_vlan_subnets():
|
def _restricted_vlan_subnets():
|
||||||
"""Return list of 'subnet/prefix' strings for all restricted VLANs."""
|
"""Return list of 'subnet/prefix' strings for all restricted VLANs."""
|
||||||
vlans = load_config().get('vlans', [])
|
vlans = config_utils.load_config().get('vlans', [])
|
||||||
result = []
|
result = []
|
||||||
for v in vlans:
|
for v in vlans:
|
||||||
if v.get('restricted_vlan') in ('q', 'c') and v.get('subnet') and v.get('subnet_mask') is not None:
|
if v.get('restricted_vlan') in ('q', 'c') and v.get('subnet') and v.get('subnet_mask') is not None:
|
||||||
|
|
@ -73,10 +62,10 @@ def load_json(path):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def load_ddns():
|
def load_ddns():
|
||||||
return load_config().get('ddns', {})
|
return config_utils.load_config().get('ddns', {})
|
||||||
|
|
||||||
def load_accounts():
|
def load_accounts():
|
||||||
return load_json(ACCOUNTS_FILE)
|
return load_json(config_utils.ACCOUNTS_FILE)
|
||||||
|
|
||||||
def run(cmd):
|
def run(cmd):
|
||||||
try:
|
try:
|
||||||
|
|
@ -94,7 +83,7 @@ def load_css():
|
||||||
|
|
||||||
def load_icon(name):
|
def load_icon(name):
|
||||||
try:
|
try:
|
||||||
with open(f'{WWW_DIR}/icons/{name}.svg') as f:
|
with open(f'{config_utils.WWW_DIR}/icons/{name}.svg') as f:
|
||||||
return f.read().strip()
|
return f.read().strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -1036,7 +1025,7 @@ def build_table(item, tokens, rows, inherited_req=None):
|
||||||
columns = item.get('columns', [])
|
columns = item.get('columns', [])
|
||||||
empty = e(item.get('empty_message', 'No data.'))
|
empty = e(item.get('empty_message', 'No data.'))
|
||||||
row_actions = item.get('row_actions', [])
|
row_actions = item.get('row_actions', [])
|
||||||
hash_val = config_hash()
|
hash_val = config_utils.config_hash()
|
||||||
|
|
||||||
toolbar_html = ''
|
toolbar_html = ''
|
||||||
toolbar = item.get('toolbar')
|
toolbar = item.get('toolbar')
|
||||||
|
|
@ -1245,7 +1234,7 @@ def build_item(item, tokens, inherited_req=None):
|
||||||
'<button type="button" class="btn btn-ghost btn-sm stat-card-edit-btn">Edit</button>'
|
'<button type="button" class="btn btn-ghost btn-sm stat-card-edit-btn">Edit</button>'
|
||||||
'</div>'
|
'</div>'
|
||||||
f'<form class="stat-card-edit-form hidden" action="{e(edit_action)}" method="post">'
|
f'<form class="stat-card-edit-form hidden" action="{e(edit_action)}" method="post">'
|
||||||
f'<input type="hidden" name="config_hash" value="{e(config_hash())}"/>'
|
f'<input type="hidden" name="config_hash" value="{e(config_utils.config_hash())}"/>'
|
||||||
f'{input_wrap}'
|
f'{input_wrap}'
|
||||||
'<div class="stat-card-edit-actions">'
|
'<div class="stat-card-edit-actions">'
|
||||||
'<button type="submit" class="btn btn-primary btn-sm" disabled>Save</button>'
|
'<button type="submit" class="btn btn-primary btn-sm" disabled>Save</button>'
|
||||||
|
|
@ -1331,7 +1320,7 @@ def build_item(item, tokens, inherited_req=None):
|
||||||
action = e(apply_tokens(item.get('action', ''), tokens))
|
action = e(apply_tokens(item.get('action', ''), tokens))
|
||||||
method = e(item.get('method', 'post'))
|
method = e(item.get('method', 'post'))
|
||||||
inner = build_items(item.get('items', []), tokens, req)
|
inner = build_items(item.get('items', []), tokens, req)
|
||||||
hash_field = f'<input type="hidden" name="config_hash" value="{e(config_hash())}"/>'
|
hash_field = f'<input type="hidden" name="config_hash" value="{e(config_utils.config_hash())}"/>'
|
||||||
originals = collect_form_originals(item.get('items', []), tokens)
|
originals = collect_form_originals(item.get('items', []), tokens)
|
||||||
orig_field = (
|
orig_field = (
|
||||||
f'<input type="hidden" name="original_values" value="{e(json.dumps(originals))}"/>'
|
f'<input type="hidden" name="original_values" value="{e(json.dumps(originals))}"/>'
|
||||||
|
|
@ -1530,21 +1519,21 @@ def build_item(item, tokens, inherited_req=None):
|
||||||
|
|
||||||
def render_layout(view_id, content_html, tokens, page_name=None):
|
def render_layout(view_id, content_html, tokens, page_name=None):
|
||||||
level = client_level()
|
level = client_level()
|
||||||
has_pending_alert = not _apply_changes_immediately() and bool(get_dashboard_pending())
|
has_pending_alert = not config_utils._apply_changes_immediately() and bool(config_utils.get_dashboard_pending())
|
||||||
titlebar_html = f'<div class="titlebar"><span class="titlebar-brand">{WEB_APP_DISPLAY_NAME}</span></div>'
|
titlebar_html = f'<div class="titlebar"><span class="titlebar-brand">{config_utils.WEB_APP_DISPLAY_NAME}</span></div>'
|
||||||
navbar_html = build_navbar(view_id, level, tokens, pending_alert=has_pending_alert)
|
navbar_html = build_navbar(view_id, level, tokens, pending_alert=has_pending_alert)
|
||||||
footer_html = f'<footer class="footer">{WEB_APP_DISPLAY_NAME}</footer>'
|
footer_html = f'<footer class="footer">{config_utils.WEB_APP_DISPLAY_NAME}</footer>'
|
||||||
|
|
||||||
page_hash = config_hash()
|
page_hash = config_utils.config_hash()
|
||||||
lan_iface = e(tokens.get('GENERAL_LAN_INTERFACE', ''))
|
lan_iface = e(tokens.get('GENERAL_LAN_INTERFACE', ''))
|
||||||
vpn_count = tokens.get('VPN_VLAN_COUNT', '0')
|
vpn_count = tokens.get('VPN_VLAN_COUNT', '0')
|
||||||
current_user = session.get('email_address', '')
|
current_user = session.get('email_address', '')
|
||||||
pending = get_pending_entries()
|
pending = config_utils.get_pending_entries()
|
||||||
my_uuid = next((u for u, t, c, usr in pending if usr == current_user and c != 'fix problems'), None)
|
my_uuid = next((u for u, t, c, usr in pending if usr == current_user and c != 'fix problems'), None)
|
||||||
|
|
||||||
secs = _seconds_until_next_run()
|
secs = config_utils._seconds_until_next_run()
|
||||||
locked = _is_locked()
|
locked = config_utils._is_locked()
|
||||||
lock_mtime = _lock_mtime()
|
lock_mtime = config_utils._lock_mtime()
|
||||||
other_bars = ''
|
other_bars = ''
|
||||||
seen_other_users = set()
|
seen_other_users = set()
|
||||||
for o_uuid, o_ts, o_cmd, o_user in pending:
|
for o_uuid, o_ts, o_cmd, o_user in pending:
|
||||||
|
|
@ -1558,7 +1547,7 @@ def render_layout(view_id, content_html, tokens, page_name=None):
|
||||||
text = f'{display_user}\'s changes are being applied now...'
|
text = f'{display_user}\'s changes are being applied now...'
|
||||||
cls = 'info-bar-warning info-bar-running'
|
cls = 'info-bar-warning info-bar-running'
|
||||||
else:
|
else:
|
||||||
timing = _format_timing(secs)
|
timing = config_utils._format_timing(secs)
|
||||||
text = (
|
text = (
|
||||||
f'{display_user} has pending changes which will be applied {timing}.'
|
f'{display_user} has pending changes which will be applied {timing}.'
|
||||||
if timing else
|
if timing else
|
||||||
|
|
@ -1570,7 +1559,7 @@ def render_layout(view_id, content_html, tokens, page_name=None):
|
||||||
problem_bars = ''
|
problem_bars = ''
|
||||||
if level >= LEVEL_RANK['viewer']:
|
if level >= LEVEL_RANK['viewer']:
|
||||||
try:
|
try:
|
||||||
st = json.load(open(HEALTH_FILE))
|
st = json.load(open(config_utils.HEALTH_FILE))
|
||||||
problems = []
|
problems = []
|
||||||
for section in ('configurations', 'logs'):
|
for section in ('configurations', 'logs'):
|
||||||
for item in st.get(section, []):
|
for item in st.get(section, []):
|
||||||
|
|
@ -1598,17 +1587,17 @@ def render_layout(view_id, content_html, tokens, page_name=None):
|
||||||
if level < LEVEL_RANK['administrator']:
|
if level < LEVEL_RANK['administrator']:
|
||||||
fix_suffix = 'Please contact an administrator.'
|
fix_suffix = 'Please contact an administrator.'
|
||||||
else:
|
else:
|
||||||
fix_uuid, fix_ts = _find_cmd_in_queues('fix problems')
|
fix_uuid, fix_ts = config_utils._find_cmd_in_queues('fix problems')
|
||||||
if _apply_changes_immediately():
|
if config_utils._apply_changes_immediately():
|
||||||
if _is_locked():
|
if config_utils._is_locked():
|
||||||
mtime = _lock_mtime()
|
mtime = config_utils._lock_mtime()
|
||||||
fix_suffix = (
|
fix_suffix = (
|
||||||
'Fix is being applied now...'
|
'Fix is being applied now...'
|
||||||
if fix_ts and mtime and fix_ts < mtime
|
if fix_ts and mtime and fix_ts < mtime
|
||||||
else 'Fix will be applied on the next run.'
|
else 'Fix will be applied on the next run.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
timing = _format_timing(_seconds_until_next_run())
|
timing = config_utils._format_timing(config_utils._seconds_until_next_run())
|
||||||
fix_suffix = (
|
fix_suffix = (
|
||||||
f'Fix will be applied {timing}.'
|
f'Fix will be applied {timing}.'
|
||||||
if timing else
|
if timing else
|
||||||
|
|
@ -1628,7 +1617,7 @@ def render_layout(view_id, content_html, tokens, page_name=None):
|
||||||
)
|
)
|
||||||
uuid_attr = (
|
uuid_attr = (
|
||||||
f' data-health-uuid="{e(fix_uuid)}"'
|
f' data-health-uuid="{e(fix_uuid)}"'
|
||||||
if fix_uuid and _entry_ts_from_queue(fix_uuid) is not None else ''
|
if fix_uuid and config_utils._entry_ts_from_queue(fix_uuid) is not None else ''
|
||||||
)
|
)
|
||||||
fix_html = (
|
fix_html = (
|
||||||
f'<div style="margin-top:0.5em"{uuid_attr}>{fix_suffix}</div>'
|
f'<div style="margin-top:0.5em"{uuid_attr}>{fix_suffix}</div>'
|
||||||
|
|
@ -1665,7 +1654,7 @@ def render_layout(view_id, content_html, tokens, page_name=None):
|
||||||
'<!DOCTYPE html>\n<html lang="en">\n<head>\n'
|
'<!DOCTYPE html>\n<html lang="en">\n<head>\n'
|
||||||
' <meta charset="UTF-8"/>\n'
|
' <meta charset="UTF-8"/>\n'
|
||||||
' <meta name="viewport" content="width=device-width, initial-scale=1.0"/>\n'
|
' <meta name="viewport" content="width=device-width, initial-scale=1.0"/>\n'
|
||||||
f' <title>{WEB_APP_DISPLAY_NAME}</title>\n'
|
f' <title>{config_utils.WEB_APP_DISPLAY_NAME}</title>\n'
|
||||||
f'{css_tag}'
|
f'{css_tag}'
|
||||||
'</head>\n<body>\n'
|
'</head>\n<body>\n'
|
||||||
f'{titlebar_html}\n'
|
f'{titlebar_html}\n'
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
import os, json, sys, importlib.util as _importlib_util
|
import os, json, sys, importlib.util as _importlib_util
|
||||||
from flask import Flask, Blueprint, session, redirect, get_flashed_messages, send_from_directory
|
from flask import Flask, Blueprint, session, redirect, get_flashed_messages, send_from_directory
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
from config_utils import (
|
import config_utils
|
||||||
ACCOUNTS_FILE, APP_DIR, CONFIGS_DIR, HEALTH_FILE, WWW_DIR,
|
import factory
|
||||||
load_config, queue_command, _find_cmd_in_queues,
|
import settings
|
||||||
)
|
|
||||||
from factory import (
|
|
||||||
LEVEL_RANK, PAGES_DIR, e, client_level, passes, build_items,
|
|
||||||
load_json, render_layout,
|
|
||||||
)
|
|
||||||
import settings as settings
|
|
||||||
from pages.actions.action import bp as actions_bp
|
from pages.actions.action import bp as actions_bp
|
||||||
from pages.bannedips.action import bp as bannedips_bp
|
from pages.bannedips.action import bp as bannedips_bp
|
||||||
from pages.ddns.action import bp as ddns_bp
|
from pages.ddns.action import bp as ddns_bp
|
||||||
|
|
@ -41,7 +35,7 @@ app.secret_key = os.environ.get('SECRET_KEY', os.urandom(24))
|
||||||
|
|
||||||
@app.route('/www/<path:filename>')
|
@app.route('/www/<path:filename>')
|
||||||
def serve_www(filename):
|
def serve_www(filename):
|
||||||
response = send_from_directory(WWW_DIR, filename)
|
response = send_from_directory(config_utils.WWW_DIR, filename)
|
||||||
if settings.is_production():
|
if settings.is_production():
|
||||||
response.cache_control.max_age = 86400
|
response.cache_control.max_age = 86400
|
||||||
response.cache_control.public = True
|
response.cache_control.public = True
|
||||||
|
|
@ -55,7 +49,7 @@ page_view_cache = {}
|
||||||
|
|
||||||
def load_page_view(page_name):
|
def load_page_view(page_name):
|
||||||
if page_name not in page_view_cache:
|
if page_name not in page_view_cache:
|
||||||
path = os.path.join(PAGES_DIR, page_name, 'view.py')
|
path = os.path.join(factory.PAGES_DIR, page_name, 'view.py')
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
page_view_cache[page_name] = None
|
page_view_cache[page_name] = None
|
||||||
else:
|
else:
|
||||||
|
|
@ -75,30 +69,30 @@ def view(page_name):
|
||||||
return serve_view(page_name)
|
return serve_view(page_name)
|
||||||
|
|
||||||
def serve_view(page_name):
|
def serve_view(page_name):
|
||||||
view_def = load_json(os.path.join(PAGES_DIR, page_name, 'content.json'))
|
view_def = factory.load_json(os.path.join(factory.PAGES_DIR, page_name, 'content.json'))
|
||||||
if not view_def:
|
if not view_def:
|
||||||
from flask import abort
|
from flask import abort
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
view_req = view_def.get('client_requirement')
|
view_req = view_def.get('client_requirement')
|
||||||
level = client_level()
|
level = factory.client_level()
|
||||||
if not passes(view_req, level):
|
if not factory.passes(view_req, level):
|
||||||
return redirect('/overview' if level > 0 else '/accountlogin')
|
return redirect('/overview' if level > 0 else '/accountlogin')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
|
|
||||||
if level >= LEVEL_RANK['administrator']:
|
if level >= factory.LEVEL_RANK['administrator']:
|
||||||
try:
|
try:
|
||||||
st = json.load(open(HEALTH_FILE))
|
st = json.load(open(config_utils.HEALTH_FILE))
|
||||||
has_problems = any(
|
has_problems = any(
|
||||||
item.get('status') == 'problem'
|
item.get('status') == 'problem'
|
||||||
for section in ('configurations', 'logs', 'services')
|
for section in ('configurations', 'logs', 'services')
|
||||||
for item in st.get(section, [])
|
for item in st.get(section, [])
|
||||||
)
|
)
|
||||||
if has_problems:
|
if has_problems:
|
||||||
fix_uuid, _ = _find_cmd_in_queues('fix problems')
|
fix_uuid, _ = config_utils._find_cmd_in_queues('fix problems')
|
||||||
if fix_uuid is None:
|
if fix_uuid is None:
|
||||||
queue_command('fix problems', user=session.get('email_address', ''))
|
config_utils.queue_command('fix problems', user=session.get('email_address', ''))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -107,17 +101,17 @@ def serve_view(page_name):
|
||||||
if page_view and hasattr(page_view, 'collect_tokens'):
|
if page_view and hasattr(page_view, 'collect_tokens'):
|
||||||
tokens.update(page_view.collect_tokens(cfg))
|
tokens.update(page_view.collect_tokens(cfg))
|
||||||
|
|
||||||
if page_name == 'radius' and not os.path.exists(f'{CONFIGS_DIR}/.radius-secret'):
|
if page_name == 'radius' and not os.path.exists(f'{config_utils.CONFIGS_DIR}/.radius-secret'):
|
||||||
queue_command('gen radius')
|
config_utils.queue_command('gen radius')
|
||||||
|
|
||||||
flash_html = ''
|
flash_html = ''
|
||||||
for category, message in get_flashed_messages(with_categories=True):
|
for category, message in get_flashed_messages(with_categories=True):
|
||||||
variant = {'error': 'danger', 'warning': 'warning', 'success': 'success'}.get(category, 'info')
|
variant = {'error': 'danger', 'warning': 'warning', 'success': 'success'}.get(category, 'info')
|
||||||
msg_html = message if isinstance(message, Markup) else e(message)
|
msg_html = message if isinstance(message, Markup) else factory.e(message)
|
||||||
flash_html += f'<div class="info-bar info-bar-{variant} info-bar-flash"><span>{msg_html}</span></div>'
|
flash_html += f'<div class="info-bar info-bar-{variant} info-bar-flash"><span>{msg_html}</span></div>'
|
||||||
|
|
||||||
content_html = flash_html + build_items(view_def.get('items', []), tokens, view_req)
|
content_html = flash_html + factory.build_items(view_def.get('items', []), tokens, view_req)
|
||||||
return render_layout(page_name, content_html, tokens, page_name=page_name)
|
return factory.render_layout(page_name, content_html, tokens, page_name=page_name)
|
||||||
|
|
||||||
# Register blueprints =================================================
|
# Register blueprints =================================================
|
||||||
|
|
||||||
|
|
@ -151,7 +145,7 @@ def _seed_initial_account():
|
||||||
email = os.environ.get('INITIAL_MANAGER_EMAIL', '').strip().lower()
|
email = os.environ.get('INITIAL_MANAGER_EMAIL', '').strip().lower()
|
||||||
if not email:
|
if not email:
|
||||||
try:
|
try:
|
||||||
with open(ACCOUNTS_FILE) as f:
|
with open(config_utils.ACCOUNTS_FILE) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
data = {'accounts': []}
|
data = {'accounts': []}
|
||||||
|
|
@ -160,7 +154,7 @@ def _seed_initial_account():
|
||||||
'Set it in docker-compose.yml to seed the initial manager account.', file=sys.stderr)
|
'Set it in docker-compose.yml to seed the initial manager account.', file=sys.stderr)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
with open(ACCOUNTS_FILE) as f:
|
with open(config_utils.ACCOUNTS_FILE) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
data = {'accounts': []}
|
data = {'accounts': []}
|
||||||
|
|
@ -172,7 +166,7 @@ def _seed_initial_account():
|
||||||
'hashed_password': '',
|
'hashed_password': '',
|
||||||
'timezone': '',
|
'timezone': '',
|
||||||
}]
|
}]
|
||||||
with open(ACCOUNTS_FILE, 'w') as f:
|
with open(config_utils.ACCOUNTS_FILE, 'w') as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2)
|
||||||
print(f'[main] Seeded initial manager account: {email}', file=sys.stderr)
|
print(f'[main] Seeded initial manager account: {email}', file=sys.stderr)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ from flask import Blueprint, request, session, redirect, flash
|
||||||
import json, os, bcrypt, secrets, smtplib
|
import json, os, bcrypt, secrets, smtplib
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import WEB_APP_DISPLAY_NAME, ACCOUNTS_FILE
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
@ -16,7 +16,7 @@ CODE_TTL_MIN = 15
|
||||||
|
|
||||||
def _load_accounts():
|
def _load_accounts():
|
||||||
try:
|
try:
|
||||||
with open(ACCOUNTS_FILE) as f:
|
with open(config_utils.ACCOUNTS_FILE) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {'accounts': []}
|
return {'accounts': []}
|
||||||
|
|
@ -33,7 +33,7 @@ def _send_verification_email(to_address, code):
|
||||||
raise RuntimeError('SMTP_HOST is not configured.')
|
raise RuntimeError('SMTP_HOST is not configured.')
|
||||||
|
|
||||||
msg = EmailMessage()
|
msg = EmailMessage()
|
||||||
msg['Subject'] = f'{WEB_APP_DISPLAY_NAME} - Email Verification'
|
msg['Subject'] = f'{config_utils.WEB_APP_DISPLAY_NAME} - Email Verification'
|
||||||
msg['From'] = from_addr
|
msg['From'] = from_addr
|
||||||
msg['To'] = to_address
|
msg['To'] = to_address
|
||||||
msg.set_content(
|
msg.set_content(
|
||||||
|
|
@ -52,7 +52,7 @@ def _send_verification_email(to_address, code):
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/accountcreate/form_create', methods=['POST'])
|
@bp.route('/action/accountcreate/form_create', methods=['POST'])
|
||||||
@require_level('nothing')
|
@auth.require_level('nothing')
|
||||||
def form_create():
|
def form_create():
|
||||||
# Abort if already logged in
|
# Abort if already logged in
|
||||||
if session.get('access_level', 'nothing') != 'nothing':
|
if session.get('access_level', 'nothing') != 'nothing':
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import json
|
import json
|
||||||
import sanitize
|
import sanitize
|
||||||
from config_utils import collect_layout_tokens
|
import config_utils
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
blank = [{'value': '', 'label': '-- Select timezone --'}]
|
blank = [{'value': '', 'label': '-- Select timezone --'}]
|
||||||
tokens['TIMEZONE_OPTIONS'] = json.dumps(blank + [{'value': tz, 'label': tz} for tz in sanitize.VALID_TIMEZONES])
|
tokens['TIMEZONE_OPTIONS'] = json.dumps(blank + [{'value': tz, 'label': tz} for tz in sanitize.VALID_TIMEZONES])
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request, session, redirect, flash
|
from flask import Blueprint, request, session, redirect, flash
|
||||||
import json, bcrypt
|
import json, bcrypt
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import ACCOUNTS_FILE
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
@ -13,14 +13,14 @@ bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
def _load_accounts():
|
def _load_accounts():
|
||||||
try:
|
try:
|
||||||
with open(ACCOUNTS_FILE) as f:
|
with open(config_utils.ACCOUNTS_FILE) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {'accounts': []}
|
return {'accounts': []}
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/accountlogin/form_login', methods=['POST'])
|
@bp.route('/action/accountlogin/form_login', methods=['POST'])
|
||||||
@require_level('nothing')
|
@auth.require_level('nothing')
|
||||||
def form_login():
|
def form_login():
|
||||||
# Abort if already logged in
|
# Abort if already logged in
|
||||||
if session.get('access_level', 'nothing') != 'nothing':
|
if session.get('access_level', 'nothing') != 'nothing':
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from config_utils import collect_layout_tokens
|
import config_utils
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
return collect_layout_tokens(cfg)
|
return config_utils.collect_layout_tokens(cfg)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
from flask import Blueprint, request, session, redirect, flash
|
from flask import Blueprint, request, session, redirect, flash
|
||||||
import json, re
|
import json, re
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import ACCOUNTS_FILE
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
@ -15,18 +15,18 @@ VALID_LEVELS = {'viewer', 'administrator', 'manager'}
|
||||||
|
|
||||||
def _load_accounts():
|
def _load_accounts():
|
||||||
try:
|
try:
|
||||||
with open(ACCOUNTS_FILE) as f:
|
with open(config_utils.ACCOUNTS_FILE) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {'accounts': []}
|
return {'accounts': []}
|
||||||
|
|
||||||
def _save_accounts(data):
|
def _save_accounts(data):
|
||||||
with open(ACCOUNTS_FILE, 'w') as f:
|
with open(config_utils.ACCOUNTS_FILE, 'w') as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/accountmanage/accounts_add', methods=['POST'])
|
@bp.route('/action/accountmanage/accounts_add', methods=['POST'])
|
||||||
@require_level('manager')
|
@auth.require_level('manager')
|
||||||
def accounts_add():
|
def accounts_add():
|
||||||
email = sanitize.email(request.form.get('email_address', ''))
|
email = sanitize.email(request.form.get('email_address', ''))
|
||||||
access_level = request.form.get('access_level', '').strip()
|
access_level = request.form.get('access_level', '').strip()
|
||||||
|
|
@ -67,7 +67,7 @@ def accounts_add():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/accountmanage/accounts_delete', methods=['POST'])
|
@bp.route('/action/accountmanage/accounts_delete', methods=['POST'])
|
||||||
@require_level('manager')
|
@auth.require_level('manager')
|
||||||
def accounts_delete():
|
def accounts_delete():
|
||||||
try:
|
try:
|
||||||
row_index = int(request.form.get('row_index', ''))
|
row_index = int(request.form.get('row_index', ''))
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
tokens['ACCOUNT_LEVEL_OPTIONS'] = json.dumps([
|
tokens['ACCOUNT_LEVEL_OPTIONS'] = json.dumps([
|
||||||
{'value': 'viewer', 'label': 'Viewer (read-only access to live data)'},
|
{'value': 'viewer', 'label': 'Viewer (read-only access to live data)'},
|
||||||
{'value': 'administrator', 'label': 'Administrator (can modify configuration)'},
|
{'value': 'administrator', 'label': 'Administrator (can modify configuration)'},
|
||||||
{'value': 'manager', 'label': 'Manager (full access including account management)'},
|
{'value': 'manager', 'label': 'Manager (full access including account management)'},
|
||||||
])
|
])
|
||||||
content = load_json(f'{PAGES_DIR}/accountmanage/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/accountmanage/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
from flask import Blueprint, request, session, redirect, flash
|
from flask import Blueprint, request, session, redirect, flash
|
||||||
import json, os, secrets
|
import json, os, secrets
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import ACCOUNTS_FILE
|
import config_utils
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
||||||
|
|
@ -13,18 +13,18 @@ bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
def _load_accounts():
|
def _load_accounts():
|
||||||
try:
|
try:
|
||||||
with open(ACCOUNTS_FILE) as f:
|
with open(config_utils.ACCOUNTS_FILE) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {'accounts': []}
|
return {'accounts': []}
|
||||||
|
|
||||||
def _save_accounts(data):
|
def _save_accounts(data):
|
||||||
with open(ACCOUNTS_FILE, 'w') as f:
|
with open(config_utils.ACCOUNTS_FILE, 'w') as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/accountverifyemail/email_verify', methods=['POST'])
|
@bp.route('/action/accountverifyemail/email_verify', methods=['POST'])
|
||||||
@require_level('nothing')
|
@auth.require_level('nothing')
|
||||||
def email_verify():
|
def email_verify():
|
||||||
# Abort if already logged in
|
# Abort if already logged in
|
||||||
if session.get('access_level', 'nothing') != 'nothing':
|
if session.get('access_level', 'nothing') != 'nothing':
|
||||||
|
|
@ -84,7 +84,7 @@ def email_verify():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/accountverifyemail/email_resend')
|
@bp.route('/action/accountverifyemail/email_resend')
|
||||||
@require_level('nothing')
|
@auth.require_level('nothing')
|
||||||
def email_resend():
|
def email_resend():
|
||||||
# Abort if already logged in
|
# Abort if already logged in
|
||||||
if session.get('access_level', 'nothing') != 'nothing':
|
if session.get('access_level', 'nothing') != 'nothing':
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from config_utils import collect_layout_tokens
|
import config_utils
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
return collect_layout_tokens(cfg)
|
return config_utils.collect_layout_tokens(cfg)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request, redirect, flash, session
|
from flask import Blueprint, request, redirect, flash, session
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import (flush_pending_to_queue, get_dashboard_pending,
|
import config_utils
|
||||||
revert_group, revert_group_chain, queued_msg,
|
|
||||||
DASHBOARD_PENDING, _db)
|
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
||||||
bp = Blueprint(_PAGE, __name__)
|
bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
@bp.route('/action/actions/pending_save', methods=['POST'])
|
@bp.route('/action/actions/pending_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def pending_save():
|
def pending_save():
|
||||||
session['apply_changes_immediately'] = 'apply_changes_immediately' in request.form
|
session['apply_changes_immediately'] = 'apply_changes_immediately' in request.form
|
||||||
flash('Preference saved.', 'success')
|
flash('Preference saved.', 'success')
|
||||||
|
|
@ -18,20 +16,20 @@ def pending_save():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/actions/pending_apply', methods=['POST'])
|
@bp.route('/action/actions/pending_apply', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def pending_apply():
|
def pending_apply():
|
||||||
pending = get_dashboard_pending()
|
pending = config_utils.get_dashboard_pending()
|
||||||
if not pending:
|
if not pending:
|
||||||
flash('No pending changes to apply.', 'info')
|
flash('No pending changes to apply.', 'info')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
flush_pending_to_queue()
|
config_utils.flush_pending_to_queue()
|
||||||
if any(cmd != 'fix problems' for _, _, cmd, _ in pending):
|
if any(cmd != 'fix problems' for _, _, cmd, _ in pending):
|
||||||
flash('Changes queued.', 'success')
|
flash('Changes queued.', 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/actions/history_revert', methods=['POST'])
|
@bp.route('/action/actions/history_revert', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def history_revert():
|
def history_revert():
|
||||||
selected_uuids = request.form.getlist('selected_uuids')
|
selected_uuids = request.form.getlist('selected_uuids')
|
||||||
if not selected_uuids:
|
if not selected_uuids:
|
||||||
|
|
@ -42,7 +40,7 @@ def history_revert():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
behavior = request.form.get('revert_behavior', 'revert_subsequent')
|
behavior = request.form.get('revert_behavior', 'revert_subsequent')
|
||||||
errors, succeeded, failed = revert_group_chain(selected_uuids[0])
|
errors, succeeded, failed = config_utils.revert_group_chain(selected_uuids[0])
|
||||||
|
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
|
|
@ -56,14 +54,14 @@ def history_revert():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/actions/history_clear', methods=['POST'])
|
@bp.route('/action/actions/history_clear', methods=['POST'])
|
||||||
@require_level('manager')
|
@auth.require_level('manager')
|
||||||
def history_clear():
|
def history_clear():
|
||||||
selected_uuids = request.form.getlist('selected_uuids')
|
selected_uuids = request.form.getlist('selected_uuids')
|
||||||
if not selected_uuids:
|
if not selected_uuids:
|
||||||
flash('No items selected.', 'info')
|
flash('No items selected.', 'info')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
count = 0
|
count = 0
|
||||||
conn = _db()
|
conn = config_utils._db()
|
||||||
try:
|
try:
|
||||||
for uid in selected_uuids:
|
for uid in selected_uuids:
|
||||||
conn.execute('DELETE FROM changes WHERE group_id=?', (uid,))
|
conn.execute('DELETE FROM changes WHERE group_id=?', (uid,))
|
||||||
|
|
@ -78,15 +76,15 @@ def history_clear():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/actions/pending_dismiss', methods=['POST'])
|
@bp.route('/action/actions/pending_dismiss', methods=['POST'])
|
||||||
@require_level('manager')
|
@auth.require_level('manager')
|
||||||
def pending_dismiss():
|
def pending_dismiss():
|
||||||
pending = get_dashboard_pending()
|
pending = config_utils.get_dashboard_pending()
|
||||||
dismissible = [(u, t, c, usr) for u, t, c, usr in pending if c != 'fix problems']
|
dismissible = [(u, t, c, usr) for u, t, c, usr in pending if c != 'fix problems']
|
||||||
if not dismissible:
|
if not dismissible:
|
||||||
flash('No pending changes to dismiss.', 'info')
|
flash('No pending changes to dismiss.', 'info')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
keep = [(u, t, c, usr) for u, t, c, usr in pending if c == 'fix problems']
|
keep = [(u, t, c, usr) for u, t, c, usr in pending if c == 'fix problems']
|
||||||
with open(DASHBOARD_PENDING, 'w') as f:
|
with open(config_utils.DASHBOARD_PENDING, 'w') as f:
|
||||||
for u, t, c, usr in keep:
|
for u, t, c, usr in keep:
|
||||||
f.write(f'{u} {t} [{c}] ({usr})\n')
|
f.write(f'{u} {t} [{c}] ({usr})\n')
|
||||||
flash('Pending changes dismissed.', 'success')
|
flash('Pending changes dismissed.', 'success')
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,17 @@ import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import session
|
from flask import session
|
||||||
from config_utils import (
|
import config_utils
|
||||||
collect_layout_tokens, get_dashboard_pending, load_all_groups, get_done_timestamps,
|
import factory
|
||||||
_apply_changes_immediately, _find_cmd_in_queues, WEB_APP_DISPLAY_NAME,
|
|
||||||
)
|
|
||||||
from factory import LEVEL_RANK, e, client_level, build_snap_val, snap_expand_row, load_icon
|
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
tokens['GENERAL_APPLY_ON_SAVE'] = 'true' if session.get('apply_changes_immediately', False) else 'false'
|
tokens['GENERAL_APPLY_ON_SAVE'] = 'true' if session.get('apply_changes_immediately', False) else 'false'
|
||||||
|
|
||||||
all_groups = load_all_groups()
|
all_groups = config_utils.load_all_groups()
|
||||||
group_uuid_set = {g['uuid'] for g, _ in all_groups}
|
group_uuid_set = {g['uuid'] for g, _ in all_groups}
|
||||||
pending_items = get_dashboard_pending()
|
pending_items = config_utils.get_dashboard_pending()
|
||||||
|
|
||||||
if pending_items:
|
if pending_items:
|
||||||
pgroups = defaultdict(list)
|
pgroups = defaultdict(list)
|
||||||
|
|
@ -39,8 +36,8 @@ def collect_tokens(cfg):
|
||||||
req_cell = '<td class="table-cell">-</td>'
|
req_cell = '<td class="table-cell">-</td>'
|
||||||
rows += (
|
rows += (
|
||||||
'<tr>'
|
'<tr>'
|
||||||
f'<td class="table-cell">{e(cmd)}</td>'
|
f'<td class="table-cell">{factory.e(cmd)}</td>'
|
||||||
f'<td class="table-cell">{e(users)}</td>'
|
f'<td class="table-cell">{factory.e(users)}</td>'
|
||||||
f'{req_cell}'
|
f'{req_cell}'
|
||||||
'</tr>'
|
'</tr>'
|
||||||
)
|
)
|
||||||
|
|
@ -59,13 +56,13 @@ def collect_tokens(cfg):
|
||||||
tokens['NO_PENDING'] = 'true' if not pending_items else ''
|
tokens['NO_PENDING'] = 'true' if not pending_items else ''
|
||||||
tokens['NO_DISMISSIBLE_PENDING'] = 'true' if not any(c != 'fix problems' for _, _, c, _ in pending_items) else ''
|
tokens['NO_DISMISSIBLE_PENDING'] = 'true' if not any(c != 'fix problems' for _, _, c, _ in pending_items) else ''
|
||||||
tokens['APPLY_WARNING'] = (
|
tokens['APPLY_WARNING'] = (
|
||||||
f'<span style="color:var(--warning)"><p>{load_icon("arrow-left")} <strong>Applying actions will briefly disrupt connections as network services are restarted.</strong></p></span>'
|
f'<span style="color:var(--warning)"><p>{factory.load_icon("arrow-left")} <strong>Applying actions will briefly disrupt connections as network services are restarted.</strong></p></span>'
|
||||||
if pending_items else ''
|
if pending_items else ''
|
||||||
)
|
)
|
||||||
|
|
||||||
done_ts_map = get_done_timestamps()
|
done_ts_map = config_utils.get_done_timestamps()
|
||||||
if all_groups:
|
if all_groups:
|
||||||
is_manager = client_level() >= LEVEL_RANK['manager']
|
is_manager = factory.client_level() >= factory.LEVEL_RANK['manager']
|
||||||
no_revert = {g['uuid'] for g, _ in all_groups if g['reverted'] or g['reverts_group']}
|
no_revert = {g['uuid'] for g, _ in all_groups if g['reverted'] or g['reverts_group']}
|
||||||
hist_rows = ''
|
hist_rows = ''
|
||||||
hist_onclick = (
|
hist_onclick = (
|
||||||
|
|
@ -89,28 +86,28 @@ def collect_tokens(cfg):
|
||||||
item = g.get('item_value') or ''
|
item = g.get('item_value') or ''
|
||||||
summary_text = f'{verb} {g["parent_path"]}: {item}' if item else f'{verb} {g["parent_path"]}'
|
summary_text = f'{verb} {g["parent_path"]}: {item}' if item else f'{verb} {g["parent_path"]}'
|
||||||
if g['reverted']:
|
if g['reverted']:
|
||||||
summary = f'<span style="text-decoration:line-through;opacity:0.5">{e(summary_text)}</span> <span class="badge badge-disabled">Superseded</span>'
|
summary = f'<span style="text-decoration:line-through;opacity:0.5">{factory.e(summary_text)}</span> <span class="badge badge-disabled">Superseded</span>'
|
||||||
else:
|
else:
|
||||||
summary = e(summary_text)
|
summary = factory.e(summary_text)
|
||||||
snap_tag = (
|
snap_tag = (
|
||||||
f'<div class="tag-list"><span class="tag" data-tooltip="{e(uuid)}" data-uuid="{e(uuid)}">'
|
f'<div class="tag-list"><span class="tag" data-tooltip="{factory.e(uuid)}" data-uuid="{factory.e(uuid)}">'
|
||||||
f'<span class="tl-full">{e(uuid[:8])}</span>'
|
f'<span class="tl-full">{factory.e(uuid[:8])}</span>'
|
||||||
f'<span class="tl-short">{e(uuid[:8])}</span>'
|
f'<span class="tl-short">{factory.e(uuid[:8])}</span>'
|
||||||
f'<span class="tl-min">{e(uuid[:8])}</span>'
|
f'<span class="tl-min">{factory.e(uuid[:8])}</span>'
|
||||||
'</span></div>'
|
'</span></div>'
|
||||||
)
|
)
|
||||||
snap_user = e(g.get('user', ''))
|
snap_user = factory.e(g.get('user', ''))
|
||||||
cb_attrs = '' if is_manager else ('disabled title="Cannot revert"' if uuid in no_revert else '')
|
cb_attrs = '' if is_manager else ('disabled title="Cannot revert"' if uuid in no_revert else '')
|
||||||
hist_rows += (
|
hist_rows += (
|
||||||
f'<tr class="row-expandable" data-uuid="{e(uuid)}" {hist_onclick}>'
|
f'<tr class="row-expandable" data-uuid="{factory.e(uuid)}" {hist_onclick}>'
|
||||||
f'<td class="table-cell"><input type="checkbox" name="selected_uuids" value="{e(uuid)}" {cb_attrs}/></td>'
|
f'<td class="table-cell"><input type="checkbox" name="selected_uuids" value="{factory.e(uuid)}" {cb_attrs}/></td>'
|
||||||
f'<td class="table-cell">{e(dt_str)}</td>'
|
f'<td class="table-cell">{factory.e(dt_str)}</td>'
|
||||||
f'<td class="table-cell">{summary}</td>'
|
f'<td class="table-cell">{summary}</td>'
|
||||||
f'<td class="table-cell">{build_snap_val(changes)}</td>'
|
f'<td class="table-cell">{factory.build_snap_val(changes)}</td>'
|
||||||
f'<td class="table-cell">{snap_tag}</td>'
|
f'<td class="table-cell">{snap_tag}</td>'
|
||||||
f'<td class="table-cell">{snap_user}</td>'
|
f'<td class="table-cell">{snap_user}</td>'
|
||||||
'</tr>'
|
'</tr>'
|
||||||
f'{snap_expand_row(changes, 6)}'
|
f'{factory.snap_expand_row(changes, 6)}'
|
||||||
)
|
)
|
||||||
select_all = (
|
select_all = (
|
||||||
'<input type="checkbox" '
|
'<input type="checkbox" '
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -38,7 +38,7 @@ def _parse_ip():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/bannedips/addip_add', methods=['POST'])
|
@bp.route('/action/bannedips/addip_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addip_add():
|
def addip_add():
|
||||||
description = sanitize.text(request.form.get('description', ''))
|
description = sanitize.text(request.form.get('description', ''))
|
||||||
ip = _parse_ip()
|
ip = _parse_ip()
|
||||||
|
|
@ -47,7 +47,7 @@ def addip_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
entry = {'description': description, 'ip': ip, 'enabled': True}
|
entry = {'description': description, 'ip': ip, 'enabled': True}
|
||||||
cfg.setdefault('banned_ips', []).append(entry)
|
cfg.setdefault('banned_ips', []).append(entry)
|
||||||
errors = validate.validate_config(cfg)
|
errors = validate.validate_config(cfg)
|
||||||
|
|
@ -56,13 +56,13 @@ def addip_add():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'banned_ips', 'ip', ip, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'banned_ips', 'ip', ip, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/bannedips/table_toggle', methods=['POST'])
|
@bp.route('/action/bannedips/table_toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_toggle():
|
def table_toggle():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -71,7 +71,7 @@ def table_toggle():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('banned_ips', [])
|
items = cfg.get('banned_ips', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -87,13 +87,13 @@ def table_toggle():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
ip = items[idx]['ip']
|
ip = items[idx]['ip']
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'banned_ips', 'ip', ip, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'banned_ips', 'ip', ip, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/bannedips/table_edit', methods=['POST'])
|
@bp.route('/action/bannedips/table_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_edit():
|
def table_edit():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -109,7 +109,7 @@ def table_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('banned_ips', [])
|
items = cfg.get('banned_ips', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -123,13 +123,13 @@ def table_edit():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'banned_ips', 'ip', ip, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'banned_ips', 'ip', ip, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/bannedips/table_delete', methods=['POST'])
|
@bp.route('/action/bannedips/table_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_delete():
|
def table_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -138,7 +138,7 @@ def table_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('banned_ips', [])
|
items = cfg.get('banned_ips', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -151,6 +151,6 @@ def table_delete():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, 'banned_ips', 'ip', removed['ip'], changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'banned_ips', 'ip', removed['ip'], changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
content = load_json(f'{PAGES_DIR}/bannedips/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/bannedips/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import bcrypt
|
import bcrypt
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
import config_utils
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import settings
|
import settings
|
||||||
|
|
@ -130,7 +130,7 @@ def _row_index():
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
@bp.route('/action/clientcredentials/addedit', methods=['POST'])
|
@bp.route('/action/clientcredentials/addedit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addedit():
|
def addedit():
|
||||||
if not PRO_LICENSE:
|
if not PRO_LICENSE:
|
||||||
flash('Client Credentials requires a Routlin Pro license.', 'error')
|
flash('Client Credentials requires a Routlin Pro license.', 'error')
|
||||||
|
|
@ -259,7 +259,7 @@ def addedit():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/clientcredentials/delete', methods=['POST'])
|
@bp.route('/action/clientcredentials/delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def delete():
|
def delete():
|
||||||
if not PRO_LICENSE:
|
if not PRO_LICENSE:
|
||||||
flash('Client Credentials requires a Routlin Pro license.', 'error')
|
flash('Client Credentials requires a Routlin Pro license.', 'error')
|
||||||
|
|
@ -285,7 +285,7 @@ def delete():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/clientcredentials/toggle', methods=['POST'])
|
@bp.route('/action/clientcredentials/toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def toggle():
|
def toggle():
|
||||||
if not PRO_LICENSE:
|
if not PRO_LICENSE:
|
||||||
flash('Client Credentials requires a Routlin Pro license.', 'error')
|
flash('Client Credentials requires a Routlin Pro license.', 'error')
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import sqlite3
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from config_utils import collect_layout_tokens, CREDENTIALS_DB
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
import settings as settings
|
import settings
|
||||||
|
|
||||||
PRO_LICENSE = settings.is_pro()
|
PRO_LICENSE = settings.is_pro()
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ HASH_TYPE_LABELS = {0: 'Cleartext', 1: 'NT-Password', 2: 'Bcrypt'}
|
||||||
|
|
||||||
def _load_credentials():
|
def _load_credentials():
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(CREDENTIALS_DB)
|
conn = sqlite3.connect(config_utils.CREDENTIALS_DB)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS credentials (
|
CREATE TABLE IF NOT EXISTS credentials (
|
||||||
|
|
@ -61,7 +61,7 @@ def _format_expiry(date_set, valid_for):
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
|
|
||||||
tokens['PRO_NOTE'] = (
|
tokens['PRO_NOTE'] = (
|
||||||
'' if PRO_LICENSE else
|
'' if PRO_LICENSE else
|
||||||
|
|
@ -92,10 +92,10 @@ def collect_tokens(cfg):
|
||||||
r['expires_label'] = _format_expiry(r.get('date_set', 0), r.get('valid_for'))
|
r['expires_label'] = _format_expiry(r.get('date_set', 0), r.get('valid_for'))
|
||||||
display_rows.append(r)
|
display_rows.append(r)
|
||||||
|
|
||||||
content = load_json(f'{PAGES_DIR}/clientcredentials/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/clientcredentials/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
data = display_rows if ds == 'sqlite:client_credentials' else []
|
data = display_rows if ds == 'sqlite:client_credentials' else []
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, data)
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, data)
|
||||||
|
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
import os
|
import os
|
||||||
from flask import Blueprint, request, redirect, flash, send_file, abort
|
from flask import Blueprint, request, redirect, flash, send_file, abort
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, verify_config_hash, record_group, diff_fields, CONFIGS_DIR
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -11,11 +11,11 @@ _PAGE = Path(__file__).parent.name
|
||||||
|
|
||||||
bp = Blueprint(_PAGE, __name__)
|
bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
LOG_FILE = f'{CONFIGS_DIR}/ddns.log'
|
LOG_FILE = f'{config_utils.CONFIGS_DIR}/ddns.log'
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/addaccount_add', methods=['POST'])
|
@bp.route('/action/ddns/addaccount_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addaccount_add():
|
def addaccount_add():
|
||||||
provider_type = sanitize.filtervalue(request.form.get('provider', ''), validate.VALID_DDNS_PROVIDERS)
|
provider_type = sanitize.filtervalue(request.form.get('provider', ''), validate.VALID_DDNS_PROVIDERS)
|
||||||
description = sanitize.description(request.form.get('description', ''))
|
description = sanitize.description(request.form.get('description', ''))
|
||||||
|
|
@ -31,7 +31,7 @@ def addaccount_add():
|
||||||
flash('Unknown provider type.', 'error')
|
flash('Unknown provider type.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
@ -47,15 +47,15 @@ def addaccount_add():
|
||||||
else:
|
else:
|
||||||
entry['api_token'] = request.form.get('api_token', '').strip()
|
entry['api_token'] = request.form.get('api_token', '').strip()
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
cfg.setdefault('ddns', {}).setdefault('providers', []).append(entry)
|
cfg.setdefault('ddns', {}).setdefault('providers', []).append(entry)
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'ddns.providers', 'description', description, changes, 'ddns update', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'ddns.providers', 'description', description, changes, 'ddns update', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/accounts_edit', methods=['POST'])
|
@bp.route('/action/ddns/accounts_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def accounts_edit():
|
def accounts_edit():
|
||||||
try:
|
try:
|
||||||
row_index = int(request.form.get('row_index', -1))
|
row_index = int(request.form.get('row_index', -1))
|
||||||
|
|
@ -72,11 +72,11 @@ def accounts_edit():
|
||||||
flash('Unknown provider type.', 'error')
|
flash('Unknown provider type.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
providers = cfg.setdefault('ddns', {}).setdefault('providers', [])
|
providers = cfg.setdefault('ddns', {}).setdefault('providers', [])
|
||||||
if row_index < 0 or row_index >= len(providers):
|
if row_index < 0 or row_index >= len(providers):
|
||||||
flash('Invalid provider index.', 'error')
|
flash('Invalid provider index.', 'error')
|
||||||
|
|
@ -96,13 +96,13 @@ def accounts_edit():
|
||||||
entry['api_token'] = request.form.get('api_token', '').strip()
|
entry['api_token'] = request.form.get('api_token', '').strip()
|
||||||
|
|
||||||
providers[row_index] = entry
|
providers[row_index] = entry
|
||||||
changes = diff_fields(before, entry)
|
changes = config_utils.diff_fields(before, entry)
|
||||||
flash(record_group(cfg, 'ddns.providers', 'description', description, changes, 'ddns update', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'ddns.providers', 'description', description, changes, 'ddns update', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/accounts_delete', methods=['POST'])
|
@bp.route('/action/ddns/accounts_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def accounts_delete():
|
def accounts_delete():
|
||||||
try:
|
try:
|
||||||
row_index = int(request.form.get('row_index', -1))
|
row_index = int(request.form.get('row_index', -1))
|
||||||
|
|
@ -110,11 +110,11 @@ def accounts_delete():
|
||||||
flash('Invalid row index.', 'error')
|
flash('Invalid row index.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
providers = cfg.setdefault('ddns', {}).setdefault('providers', [])
|
providers = cfg.setdefault('ddns', {}).setdefault('providers', [])
|
||||||
if row_index < 0 or row_index >= len(providers):
|
if row_index < 0 or row_index >= len(providers):
|
||||||
flash('Invalid provider index.', 'error')
|
flash('Invalid provider index.', 'error')
|
||||||
|
|
@ -123,13 +123,13 @@ def accounts_delete():
|
||||||
before = copy.deepcopy(providers[row_index])
|
before = copy.deepcopy(providers[row_index])
|
||||||
description = before.get('description', str(row_index))
|
description = before.get('description', str(row_index))
|
||||||
del providers[row_index]
|
del providers[row_index]
|
||||||
changes = diff_fields(before, None)
|
changes = config_utils.diff_fields(before, None)
|
||||||
flash(record_group(cfg, 'ddns.providers', 'description', description, changes, 'ddns update', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'ddns.providers', 'description', description, changes, 'ddns update', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/ipcheckinterval_save', methods=['POST'])
|
@bp.route('/action/ddns/ipcheckinterval_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def ipcheckinterval_save():
|
def ipcheckinterval_save():
|
||||||
raw = request.form.get('timer_interval', '').strip()
|
raw = request.form.get('timer_interval', '').strip()
|
||||||
try:
|
try:
|
||||||
|
|
@ -141,22 +141,22 @@ def ipcheckinterval_save():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
timer_interval = f'{mins}m'
|
timer_interval = f'{mins}m'
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('ddns', {}).get('general', {}))
|
before = copy.deepcopy(cfg.get('ddns', {}).get('general', {}))
|
||||||
cfg.setdefault('ddns', {}).setdefault('general', {})['timer_interval'] = timer_interval
|
cfg.setdefault('ddns', {}).setdefault('general', {})['timer_interval'] = timer_interval
|
||||||
changes = diff_fields(before, cfg['ddns']['general'])
|
changes = config_utils.diff_fields(before, cfg['ddns']['general'])
|
||||||
flash(record_group(cfg, 'ddns.general', None, None, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'ddns.general', None, None, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/ipcheckservices_save', methods=['POST'])
|
@bp.route('/action/ddns/ipcheckservices_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def ipcheckservices_save():
|
def ipcheckservices_save():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
@ -167,18 +167,18 @@ def ipcheckservices_save():
|
||||||
flash('At least one IP check service is required.', 'error')
|
flash('At least one IP check service is required.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('ddns', {}).get('ip_check_services', []))
|
before = copy.deepcopy(cfg.get('ddns', {}).get('ip_check_services', []))
|
||||||
services = [{'type': 'http', 'url': u} for u in http_services]
|
services = [{'type': 'http', 'url': u} for u in http_services]
|
||||||
services += [{'type': 'dig', 'url': u} for u in dig_services]
|
services += [{'type': 'dig', 'url': u} for u in dig_services]
|
||||||
cfg.setdefault('ddns', {})['ip_check_services'] = services
|
cfg.setdefault('ddns', {})['ip_check_services'] = services
|
||||||
changes = diff_fields({'ip_check_services': before}, {'ip_check_services': services})
|
changes = config_utils.diff_fields({'ip_check_services': before}, {'ip_check_services': services})
|
||||||
flash(record_group(cfg, 'ddns', None, None, changes, 'ddns update', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'ddns', None, None, changes, 'ddns update', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/logging_save', methods=['POST'])
|
@bp.route('/action/ddns/logging_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_save():
|
def logging_save():
|
||||||
log_max_kb = validate.int_range(request.form.get('log_max_kb', '').strip(), 64, None)
|
log_max_kb = validate.int_range(request.form.get('log_max_kb', '').strip(), 64, None)
|
||||||
if log_max_kb is None:
|
if log_max_kb is None:
|
||||||
|
|
@ -186,23 +186,23 @@ def logging_save():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
log_errors_only = 'log_errors_only' in request.form
|
log_errors_only = 'log_errors_only' in request.form
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('ddns', {}).get('general', {}))
|
before = copy.deepcopy(cfg.get('ddns', {}).get('general', {}))
|
||||||
cfg.setdefault('ddns', {}).setdefault('general', {}).update({
|
cfg.setdefault('ddns', {}).setdefault('general', {}).update({
|
||||||
'log_max_kb': log_max_kb,
|
'log_max_kb': log_max_kb,
|
||||||
'log_errors_only': log_errors_only,
|
'log_errors_only': log_errors_only,
|
||||||
})
|
})
|
||||||
changes = diff_fields(before, cfg['ddns']['general'])
|
changes = config_utils.diff_fields(before, cfg['ddns']['general'])
|
||||||
flash(record_group(cfg, 'ddns.general', None, None, changes, 'ddns update', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'ddns.general', None, None, changes, 'ddns update', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/logging_clear', methods=['POST'])
|
@bp.route('/action/ddns/logging_clear', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_clear():
|
def logging_clear():
|
||||||
try:
|
try:
|
||||||
open(LOG_FILE, 'w').close()
|
open(LOG_FILE, 'w').close()
|
||||||
|
|
@ -213,7 +213,7 @@ def logging_clear():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/ddns/logging_download', methods=['GET'])
|
@bp.route('/action/ddns/logging_download', methods=['GET'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_download():
|
def logging_download():
|
||||||
if not os.path.isfile(LOG_FILE):
|
if not os.path.isfile(LOG_FILE):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
from config_utils import (
|
import config_utils
|
||||||
collect_layout_tokens, load_datasource, CONFIGS_DIR, relative_time,
|
import factory
|
||||||
)
|
|
||||||
from factory import load_ddns, load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,9 +18,9 @@ def _parse_interval_to_seconds(s):
|
||||||
|
|
||||||
|
|
||||||
def _ddns_log_tail():
|
def _ddns_log_tail():
|
||||||
log_path = f'{CONFIGS_DIR}/ddns.log'
|
log_path = f'{config_utils.CONFIGS_DIR}/ddns.log'
|
||||||
try:
|
try:
|
||||||
log_max_kb = load_ddns().get('general', {}).get('log_max_kb', 1024)
|
log_max_kb = factory.load_ddns().get('general', {}).get('log_max_kb', 1024)
|
||||||
size_kb = os.path.getsize(log_path) / 1024
|
size_kb = os.path.getsize(log_path) / 1024
|
||||||
with open(log_path) as f:
|
with open(log_path) as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
@ -49,9 +47,9 @@ def _ddns_log_tail():
|
||||||
def _read_cached_ip():
|
def _read_cached_ip():
|
||||||
try:
|
try:
|
||||||
best_ip, best_mtime = '', 0.0
|
best_ip, best_mtime = '', 0.0
|
||||||
for fname in os.listdir(CONFIGS_DIR):
|
for fname in os.listdir(config_utils.CONFIGS_DIR):
|
||||||
if fname.startswith('.ddns-last-ip-'):
|
if fname.startswith('.ddns-last-ip-'):
|
||||||
path = f'{CONFIGS_DIR}/{fname}'
|
path = f'{config_utils.CONFIGS_DIR}/{fname}'
|
||||||
mtime = os.path.getmtime(path)
|
mtime = os.path.getmtime(path)
|
||||||
if mtime > best_mtime:
|
if mtime > best_mtime:
|
||||||
ip = open(path).read().strip()
|
ip = open(path).read().strip()
|
||||||
|
|
@ -70,7 +68,7 @@ def public_ip_info(ddns_cfg):
|
||||||
all_hosts.extend(p.get('hostnames', p.get('subdomains', [])))
|
all_hosts.extend(p.get('hostnames', p.get('subdomains', [])))
|
||||||
domains_sub = ', '.join(all_hosts)
|
domains_sub = ', '.join(all_hosts)
|
||||||
ip, mtime = _read_cached_ip()
|
ip, mtime = _read_cached_ip()
|
||||||
last_obtained = f'Obtained: {relative_time(mtime, datetime.now(tz=timezone.utc).timestamp())} ago' if mtime else ''
|
last_obtained = f'Obtained: {config_utils.relative_time(mtime, datetime.now(tz=timezone.utc).timestamp())} ago' if mtime else ''
|
||||||
if ip:
|
if ip:
|
||||||
return ip, domains_sub, last_obtained
|
return ip, domains_sub, last_obtained
|
||||||
return 'Offline', domains_sub, ''
|
return 'Offline', domains_sub, ''
|
||||||
|
|
@ -79,15 +77,15 @@ def public_ip_info(ddns_cfg):
|
||||||
def ddns_last_checked():
|
def ddns_last_checked():
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
try:
|
try:
|
||||||
mtime = os.path.getmtime(f'{CONFIGS_DIR}/.ddns-last-service')
|
mtime = os.path.getmtime(f'{config_utils.CONFIGS_DIR}/.ddns-last-service')
|
||||||
return f'Last checked: {relative_time(mtime, datetime.now(tz=timezone.utc).timestamp())} ago'
|
return f'Last checked: {config_utils.relative_time(mtime, datetime.now(tz=timezone.utc).timestamp())} ago'
|
||||||
except OSError:
|
except OSError:
|
||||||
return 'Last checked: ---'
|
return 'Last checked: ---'
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
ddns = load_ddns()
|
ddns = factory.load_ddns()
|
||||||
ddns_gen = ddns.get('general', {})
|
ddns_gen = ddns.get('general', {})
|
||||||
tokens['DDNS_TIMER_INTERVAL'] = ddns_gen.get('timer_interval', '-')
|
tokens['DDNS_TIMER_INTERVAL'] = ddns_gen.get('timer_interval', '-')
|
||||||
interval_secs = _parse_interval_to_seconds(ddns_gen.get('timer_interval', '')) or 600
|
interval_secs = _parse_interval_to_seconds(ddns_gen.get('timer_interval', '')) or 600
|
||||||
|
|
@ -111,8 +109,8 @@ def collect_tokens(cfg):
|
||||||
tokens['STAT_PUBLIC_IP_LAST_OBTAINED'] = last_obtained
|
tokens['STAT_PUBLIC_IP_LAST_OBTAINED'] = last_obtained
|
||||||
tokens['STAT_PUBLIC_IP_LAST_CHECKED'] = ddns_last_checked()
|
tokens['STAT_PUBLIC_IP_LAST_CHECKED'] = ddns_last_checked()
|
||||||
tokens['DDNS_LOG_TAIL'], tokens['DDNS_LOG_SUMMARY'] = _ddns_log_tail()
|
tokens['DDNS_LOG_TAIL'], tokens['DDNS_LOG_SUMMARY'] = _ddns_log_tail()
|
||||||
content = load_json(f'{PAGES_DIR}/ddns/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/ddns/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,8 @@ import json
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from config_utils import collect_layout_tokens, load_config, relative_time
|
import config_utils
|
||||||
from factory import (
|
import factory
|
||||||
load_json, build_table, table_token_key, iter_table_items, PAGES_DIR, e,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import manuf as _manuf_mod
|
import manuf as _manuf_mod
|
||||||
|
|
@ -42,8 +40,8 @@ def _vendor_cell(vendor):
|
||||||
if not display:
|
if not display:
|
||||||
return '-'
|
return '-'
|
||||||
if long:
|
if long:
|
||||||
return f'<span data-vendor-long="{e(long)}">{e(display)}</span>'
|
return f'<span data-vendor-long="{factory.e(long)}">{factory.e(display)}</span>'
|
||||||
return e(display)
|
return factory.e(display)
|
||||||
|
|
||||||
|
|
||||||
def _get_arp_table():
|
def _get_arp_table():
|
||||||
|
|
@ -93,7 +91,7 @@ def _parse_lease_secs(s):
|
||||||
def live_dhcp_leases():
|
def live_dhcp_leases():
|
||||||
rows = []
|
rows = []
|
||||||
now = int(datetime.now(tz=timezone.utc).timestamp())
|
now = int(datetime.now(tz=timezone.utc).timestamp())
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
arp_table = _get_arp_table()
|
arp_table = _get_arp_table()
|
||||||
lease_macs = set()
|
lease_macs = set()
|
||||||
|
|
@ -130,9 +128,9 @@ def live_dhcp_leases():
|
||||||
if obtained_ts is None:
|
if obtained_ts is None:
|
||||||
lease_renewed = '-'
|
lease_renewed = '-'
|
||||||
elif obtained_ts <= now:
|
elif obtained_ts <= now:
|
||||||
lease_renewed = relative_time(obtained_ts, now, short=True) + ' ago'
|
lease_renewed = config_utils.relative_time(obtained_ts, now, short=True) + ' ago'
|
||||||
elif renews_ts and renews_ts > now:
|
elif renews_ts and renews_ts > now:
|
||||||
lease_renewed = 'ETA ' + relative_time(renews_ts, now, short=True)
|
lease_renewed = 'ETA ' + config_utils.relative_time(renews_ts, now, short=True)
|
||||||
else:
|
else:
|
||||||
lease_renewed = 'ETA soon'
|
lease_renewed = 'ETA soon'
|
||||||
mac_norm = parts[1].lower()
|
mac_norm = parts[1].lower()
|
||||||
|
|
@ -141,8 +139,8 @@ def live_dhcp_leases():
|
||||||
desc = mac_to_desc.get(mac_norm)
|
desc = mac_to_desc.get(mac_norm)
|
||||||
name = res_h or device_h
|
name = res_h or device_h
|
||||||
if name:
|
if name:
|
||||||
desc_attr = f' data-hostname-desc="{e(desc)}"' if desc else ''
|
desc_attr = f' data-hostname-desc="{factory.e(desc)}"' if desc else ''
|
||||||
hostname_html = f'<span{desc_attr}>{e(name)}</span>' if desc_attr else e(name)
|
hostname_html = f'<span{desc_attr}>{factory.e(name)}</span>' if desc_attr else factory.e(name)
|
||||||
else:
|
else:
|
||||||
hostname_html = '-'
|
hostname_html = '-'
|
||||||
arp_entry = arp_table.get(mac_norm, {})
|
arp_entry = arp_table.get(mac_norm, {})
|
||||||
|
|
@ -154,7 +152,7 @@ def live_dhcp_leases():
|
||||||
'vendor': _vendor_cell(_get_vendor(parts[1])),
|
'vendor': _vendor_cell(_get_vendor(parts[1])),
|
||||||
'vlan_name': vlan_name,
|
'vlan_name': vlan_name,
|
||||||
'lease_renewed': lease_renewed,
|
'lease_renewed': lease_renewed,
|
||||||
'renews': 'in ' + relative_time(renews_ts or expiry, now, short=True),
|
'renews': 'in ' + config_utils.relative_time(renews_ts or expiry, now, short=True),
|
||||||
'status': _status_badge(arp_entry.get('state', '')),
|
'status': _status_badge(arp_entry.get('state', '')),
|
||||||
})
|
})
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -178,16 +176,16 @@ def live_dhcp_leases():
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
vlan_names = [v.get('name', '') for v in vlans]
|
vlan_names = [v.get('name', '') for v in vlans]
|
||||||
filter_opts = '<option value="all">All VLANs</option>' + ''.join(
|
filter_opts = '<option value="all">All VLANs</option>' + ''.join(
|
||||||
f'<option value="{n}">{n}</option>' for n in vlan_names
|
f'<option value="{n}">{n}</option>' for n in vlan_names
|
||||||
)
|
)
|
||||||
tokens['VLAN_FILTER_OPTIONS'] = filter_opts
|
tokens['VLAN_FILTER_OPTIONS'] = filter_opts
|
||||||
content = load_json(f'{PAGES_DIR}/dhcpleases/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/dhcpleases/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
rows = live_dhcp_leases() if ds == 'live:dhcp_leases' else []
|
rows = live_dhcp_leases() if ds == 'live:dhcp_leases' else []
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, rows)
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, rows)
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import copy
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -59,7 +59,7 @@ def _check_ip_in_vlan_subnet(ip, vlan):
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dhcpreservations/addreservation_add', methods=['POST'])
|
@bp.route('/action/dhcpreservations/addreservation_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addreservation_add():
|
def addreservation_add():
|
||||||
vlan_name = sanitize.name(request.form.get('vlan_name', ''))
|
vlan_name = sanitize.name(request.form.get('vlan_name', ''))
|
||||||
description = sanitize.text(request.form.get('description', ''))
|
description = sanitize.text(request.form.get('description', ''))
|
||||||
|
|
@ -79,7 +79,7 @@ def addreservation_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
vlan = next((v for v in vlans if v.get('name') == vlan_name), None)
|
vlan = next((v for v in vlans if v.get('name') == vlan_name), None)
|
||||||
if vlan is None:
|
if vlan is None:
|
||||||
|
|
@ -113,13 +113,13 @@ def addreservation_add():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'dhcp_reservations', 'mac', mac, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'dhcp_reservations', 'mac', mac, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dhcpreservations/reservations_toggle', methods=['POST'])
|
@bp.route('/action/dhcpreservations/reservations_toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def reservations_toggle():
|
def reservations_toggle():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -128,7 +128,7 @@ def reservations_toggle():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('dhcp_reservations', [])
|
items = cfg.get('dhcp_reservations', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -144,13 +144,13 @@ def reservations_toggle():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, res)
|
changes = config_utils.diff_fields(before, res)
|
||||||
flash(record_group(cfg, 'dhcp_reservations', 'mac', res['mac'], changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'dhcp_reservations', 'mac', res['mac'], changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dhcpreservations/reservations_edit', methods=['POST'])
|
@bp.route('/action/dhcpreservations/reservations_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def reservations_edit():
|
def reservations_edit():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -171,7 +171,7 @@ def reservations_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('dhcp_reservations', [])
|
items = cfg.get('dhcp_reservations', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -208,13 +208,13 @@ def reservations_edit():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, res)
|
changes = config_utils.diff_fields(before, res)
|
||||||
flash(record_group(cfg, 'dhcp_reservations', 'mac', mac, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'dhcp_reservations', 'mac', mac, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dhcpreservations/reservations_delete', methods=['POST'])
|
@bp.route('/action/dhcpreservations/reservations_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def reservations_delete():
|
def reservations_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -223,7 +223,7 @@ def reservations_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('dhcp_reservations', [])
|
items = cfg.get('dhcp_reservations', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -236,6 +236,6 @@ def reservations_delete():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, 'dhcp_reservations', 'mac', removed['mac'], changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'dhcp_reservations', 'mac', removed['mac'], changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
vlan_names = [v.get('name', '') for v in vlans]
|
vlan_names = [v.get('name', '') for v in vlans]
|
||||||
res_ips_by_vlan, res_hosts_by_vlan = {}, {}
|
res_ips_by_vlan, res_hosts_by_vlan = {}, {}
|
||||||
|
|
@ -26,8 +26,8 @@ def collect_tokens(cfg):
|
||||||
})
|
})
|
||||||
tokens['RESERVATION_IPS_BY_VLAN_JSON'] = json.dumps(res_ips_by_vlan)
|
tokens['RESERVATION_IPS_BY_VLAN_JSON'] = json.dumps(res_ips_by_vlan)
|
||||||
tokens['RESERVATION_HOSTNAMES_BY_VLAN_JSON'] = json.dumps(res_hosts_by_vlan)
|
tokens['RESERVATION_HOSTNAMES_BY_VLAN_JSON'] = json.dumps(res_hosts_by_vlan)
|
||||||
content = load_json(f'{PAGES_DIR}/dhcpreservations/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/dhcpreservations/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
import re
|
import re
|
||||||
from flask import Blueprint, request, redirect, flash, send_file
|
from flask import Blueprint, request, redirect, flash, send_file
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash, queued_msg, CONFIGS_DIR
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
DNS_LOG_FILE = Path(CONFIGS_DIR) / 'dns-blocklists.log'
|
DNS_LOG_FILE = Path(config_utils.CONFIGS_DIR) / 'dns-blocklists.log'
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -56,7 +56,7 @@ def _parse_fields():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/blocklists_delete', methods=['POST'])
|
@bp.route('/action/dnsblocking/blocklists_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def blocklists_delete():
|
def blocklists_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -66,7 +66,7 @@ def blocklists_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('dns_blocking', {}).get('blocklists', [])
|
items = cfg.get('dns_blocking', {}).get('blocklists', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -80,13 +80,13 @@ def blocklists_delete():
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
changes = diff_fields(before, None)
|
changes = config_utils.diff_fields(before, None)
|
||||||
flash(record_group(cfg, 'dns_blocking.blocklists', 'name', name, changes, 'core apply', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'dns_blocking.blocklists', 'name', name, changes, 'core apply', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/blocklists_edit', methods=['POST'])
|
@bp.route('/action/dnsblocking/blocklists_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def blocklists_edit():
|
def blocklists_edit():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -100,7 +100,7 @@ def blocklists_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('dns_blocking', {}).get('blocklists', [])
|
items = cfg.get('dns_blocking', {}).get('blocklists', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -125,13 +125,13 @@ def blocklists_edit():
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'dns_blocking.blocklists', 'name', fields['name'], changes, 'core apply', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'dns_blocking.blocklists', 'name', fields['name'], changes, 'core apply', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/addblocklist_add', methods=['POST'])
|
@bp.route('/action/dnsblocking/addblocklist_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addblocklist_add():
|
def addblocklist_add():
|
||||||
fields, err = _parse_fields()
|
fields, err = _parse_fields()
|
||||||
if err:
|
if err:
|
||||||
|
|
@ -140,7 +140,7 @@ def addblocklist_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
blocklists = cfg.setdefault('dns_blocking', {}).setdefault('blocklists', [])
|
blocklists = cfg.setdefault('dns_blocking', {}).setdefault('blocklists', [])
|
||||||
|
|
||||||
# Blocklist name must be unique - it is the lookup key for VLAN use_blocklists references
|
# Blocklist name must be unique - it is the lookup key for VLAN use_blocklists references
|
||||||
|
|
@ -162,13 +162,13 @@ def addblocklist_add():
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'dns_blocking.blocklists', 'name', fields['name'], changes, 'core apply', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'dns_blocking.blocklists', 'name', fields['name'], changes, 'core apply', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/blocklistrefresh_save', methods=['POST'])
|
@bp.route('/action/dnsblocking/blocklistrefresh_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def blocklistrefresh_save():
|
def blocklistrefresh_save():
|
||||||
daily_execute_time = validate.time_24h(sanitize.time_24h(request.form.get('daily_execute_time_24hr_local', '')))
|
daily_execute_time = validate.time_24h(sanitize.time_24h(request.form.get('daily_execute_time_24hr_local', '')))
|
||||||
|
|
||||||
|
|
@ -176,27 +176,27 @@ def blocklistrefresh_save():
|
||||||
flash('Daily Refresh Time must be a valid 24-hour time (e.g. 02:30).', 'error')
|
flash('Daily Refresh Time must be a valid 24-hour time (e.g. 02:30).', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('dns_blocking', {}).get('general', {}))
|
before = copy.deepcopy(cfg.get('dns_blocking', {}).get('general', {}))
|
||||||
cfg.setdefault('dns_blocking', {}).setdefault('general', {})['daily_execute_time_24hr_local'] = daily_execute_time
|
cfg.setdefault('dns_blocking', {}).setdefault('general', {})['daily_execute_time_24hr_local'] = daily_execute_time
|
||||||
changes = diff_fields(before, cfg['dns_blocking']['general'])
|
changes = config_utils.diff_fields(before, cfg['dns_blocking']['general'])
|
||||||
flash(record_group(cfg, 'dns_blocking.general', None, None, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'dns_blocking.general', None, None, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/blocklistrefresh_refresh', methods=['POST'])
|
@bp.route('/action/dnsblocking/blocklistrefresh_refresh', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def blocklistrefresh_refresh():
|
def blocklistrefresh_refresh():
|
||||||
flash(queued_msg('core update-blocklists', action_label='Blocklist refresh queued'), 'success')
|
flash(config_utils.queued_msg('core update-blocklists', action_label='Blocklist refresh queued'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/logging_save', methods=['POST'])
|
@bp.route('/action/dnsblocking/logging_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_save():
|
def logging_save():
|
||||||
log_max_kb_raw = request.form.get('log_max_kb', '').strip()
|
log_max_kb_raw = request.form.get('log_max_kb', '').strip()
|
||||||
log_errors_only = 'log_errors_only' in request.form
|
log_errors_only = 'log_errors_only' in request.form
|
||||||
|
|
@ -206,11 +206,11 @@ def logging_save():
|
||||||
flash('Max Log Size must be a number >= 64.', 'error')
|
flash('Max Log Size must be a number >= 64.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('dns_blocking', {}).get('general', {}))
|
before = copy.deepcopy(cfg.get('dns_blocking', {}).get('general', {}))
|
||||||
cfg.setdefault('dns_blocking', {}).setdefault('general', {}).update({
|
cfg.setdefault('dns_blocking', {}).setdefault('general', {}).update({
|
||||||
'log_max_kb': log_max_kb,
|
'log_max_kb': log_max_kb,
|
||||||
|
|
@ -221,13 +221,13 @@ def logging_save():
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
changes = diff_fields(before, cfg['dns_blocking']['general'])
|
changes = config_utils.diff_fields(before, cfg['dns_blocking']['general'])
|
||||||
flash(record_group(cfg, 'dns_blocking.general', None, None, changes, 'core apply', queue=False), 'success')
|
flash(config_utils.record_group(cfg, 'dns_blocking.general', None, None, changes, 'core apply', queue=False), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/logging_clear', methods=['POST'])
|
@bp.route('/action/dnsblocking/logging_clear', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_clear():
|
def logging_clear():
|
||||||
try:
|
try:
|
||||||
DNS_LOG_FILE.write_text('')
|
DNS_LOG_FILE.write_text('')
|
||||||
|
|
@ -238,7 +238,7 @@ def logging_clear():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsblocking/logging_download', methods=['GET'])
|
@bp.route('/action/dnsblocking/logging_download', methods=['GET'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_download():
|
def logging_download():
|
||||||
if not DNS_LOG_FILE.is_file():
|
if not DNS_LOG_FILE.is_file():
|
||||||
flash('Log file not found.', 'error')
|
flash('Log file not found.', 'error')
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from config_utils import collect_layout_tokens, load_datasource, fmt_bytes, relative_time, BLOCKLISTS_DIR, CONFIGS_DIR
|
import config_utils
|
||||||
from factory import e, load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
DNS_LOG_FILE = f'{CONFIGS_DIR}/dns-blocklists.log'
|
DNS_LOG_FILE = f'{config_utils.CONFIGS_DIR}/dns-blocklists.log'
|
||||||
DNS_LOG_MAX = 50
|
DNS_LOG_MAX = 50
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,17 +37,17 @@ def _dnsblocking_log_tail(cfg):
|
||||||
def blocklist_stats_html(cfg):
|
def blocklist_stats_html(cfg):
|
||||||
rows = ''
|
rows = ''
|
||||||
for bl in cfg.get('dns_blocking', {}).get('blocklists', []):
|
for bl in cfg.get('dns_blocking', {}).get('blocklists', []):
|
||||||
name = e(bl.get('name', ''))
|
name = factory.e(bl.get('name', ''))
|
||||||
save_as = bl.get('save_as', '')
|
save_as = bl.get('save_as', '')
|
||||||
bl_path = f'{BLOCKLISTS_DIR}/{save_as}' if save_as else ''
|
bl_path = f'{config_utils.BLOCKLISTS_DIR}/{save_as}' if save_as else ''
|
||||||
try:
|
try:
|
||||||
with open(bl_path) as f:
|
with open(bl_path) as f:
|
||||||
entries = sum(1 for _ in f)
|
entries = sum(1 for _ in f)
|
||||||
mtime = int(os.path.getmtime(bl_path))
|
mtime = int(os.path.getmtime(bl_path))
|
||||||
size_str = fmt_bytes(os.path.getsize(bl_path))
|
size_str = config_utils.fmt_bytes(os.path.getsize(bl_path))
|
||||||
last_refreshed = (
|
last_refreshed = (
|
||||||
f'{datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M")}'
|
f'{datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M")}'
|
||||||
f' ({relative_time(mtime, datetime.now(tz=timezone.utc).timestamp())} ago)'
|
f' ({config_utils.relative_time(mtime, datetime.now(tz=timezone.utc).timestamp())} ago)'
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
entries, size_str, last_refreshed = '-', '-', 'Never'
|
entries, size_str, last_refreshed = '-', '-', 'Never'
|
||||||
|
|
@ -56,7 +56,7 @@ def blocklist_stats_html(cfg):
|
||||||
f'<td class="table-cell">{name}</td>'
|
f'<td class="table-cell">{name}</td>'
|
||||||
f'<td class="table-cell">{entries}</td>'
|
f'<td class="table-cell">{entries}</td>'
|
||||||
f'<td class="table-cell">{size_str}</td>'
|
f'<td class="table-cell">{size_str}</td>'
|
||||||
f'<td class="table-cell">{e(last_refreshed)}</td>'
|
f'<td class="table-cell">{factory.e(last_refreshed)}</td>'
|
||||||
'</tr>'
|
'</tr>'
|
||||||
)
|
)
|
||||||
if not rows:
|
if not rows:
|
||||||
|
|
@ -73,7 +73,7 @@ def blocklist_stats_html(cfg):
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
dns_blk_gen = cfg.get('dns_blocking', {}).get('general', {})
|
dns_blk_gen = cfg.get('dns_blocking', {}).get('general', {})
|
||||||
tokens['GENERAL_LOG_MAX_KB'] = str(dns_blk_gen.get('log_max_kb', '-'))
|
tokens['GENERAL_LOG_MAX_KB'] = str(dns_blk_gen.get('log_max_kb', '-'))
|
||||||
tokens['GENERAL_LOG_ERRORS_ONLY'] = 'true' if dns_blk_gen.get('log_errors_only') else 'false'
|
tokens['GENERAL_LOG_ERRORS_ONLY'] = 'true' if dns_blk_gen.get('log_errors_only') else 'false'
|
||||||
|
|
@ -84,8 +84,8 @@ def collect_tokens(cfg):
|
||||||
{'value': 'hosts', 'label': 'hosts (hosts file format)'},
|
{'value': 'hosts', 'label': 'hosts (hosts file format)'},
|
||||||
{'value': 'dnsmasq', 'label': 'dnsmasq (local=/ syntax)'},
|
{'value': 'dnsmasq', 'label': 'dnsmasq (local=/ syntax)'},
|
||||||
])
|
])
|
||||||
content = load_json(f'{PAGES_DIR}/dnsblocking/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/dnsblocking/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ _PAGE = Path(__file__).parent.name
|
||||||
bp = Blueprint(_PAGE, __name__)
|
bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
@bp.route('/action/dnsserver/upstreamdns_save', methods=['POST'])
|
@bp.route('/action/dnsserver/upstreamdns_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def upstreamdns_save():
|
def upstreamdns_save():
|
||||||
strict_order = 'strict_order' in request.form
|
strict_order = 'strict_order' in request.form
|
||||||
submitted = request.form.getlist('upstream_servers')
|
submitted = request.form.getlist('upstream_servers')
|
||||||
|
|
@ -29,11 +29,11 @@ def upstreamdns_save():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
upstream_servers.append(clean)
|
upstream_servers.append(clean)
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('upstream_dns', {}))
|
before = copy.deepcopy(cfg.get('upstream_dns', {}))
|
||||||
current = cfg.get('upstream_dns', {})
|
current = cfg.get('upstream_dns', {})
|
||||||
if (strict_order == bool(current.get('strict_order', False)) and
|
if (strict_order == bool(current.get('strict_order', False)) and
|
||||||
|
|
@ -50,24 +50,24 @@ def upstreamdns_save():
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
changes = diff_fields(before, cfg['upstream_dns'])
|
changes = config_utils.diff_fields(before, cfg['upstream_dns'])
|
||||||
flash(record_group(cfg, 'upstream_dns', None, None, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'upstream_dns', None, None, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/dnsserver/dnsforwarding_save', methods=['POST'])
|
@bp.route('/action/dnsserver/dnsforwarding_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def dnsforwarding_save():
|
def dnsforwarding_save():
|
||||||
cache_size = validate.int_range(request.form.get('cache_size', '').strip(), 0, None)
|
cache_size = validate.int_range(request.form.get('cache_size', '').strip(), 0, None)
|
||||||
if cache_size is None:
|
if cache_size is None:
|
||||||
flash('Cache Size must be a non-negative integer.', 'error')
|
flash('Cache Size must be a non-negative integer.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('upstream_dns', {}))
|
before = copy.deepcopy(cfg.get('upstream_dns', {}))
|
||||||
current = cfg.get('upstream_dns', {})
|
current = cfg.get('upstream_dns', {})
|
||||||
if cache_size == int(current.get('cache_size', 0)):
|
if cache_size == int(current.get('cache_size', 0)):
|
||||||
|
|
@ -80,6 +80,6 @@ def dnsforwarding_save():
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
changes = diff_fields(before, cfg['upstream_dns'])
|
changes = config_utils.diff_fields(before, cfg['upstream_dns'])
|
||||||
flash(record_group(cfg, 'upstream_dns', None, None, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'upstream_dns', None, None, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens
|
import config_utils
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
dns = cfg.get('upstream_dns', {})
|
dns = cfg.get('upstream_dns', {})
|
||||||
servers = dns.get('upstream_servers', [])
|
servers = dns.get('upstream_servers', [])
|
||||||
tokens['DNS_STRICT_ORDER'] = 'true' if dns.get('strict_order') else 'false'
|
tokens['DNS_STRICT_ORDER'] = 'true' if dns.get('strict_order') else 'false'
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -19,14 +19,14 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/hostoverrides/addoverride_add', methods=['POST'])
|
@bp.route('/action/hostoverrides/addoverride_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addoverride_add():
|
def addoverride_add():
|
||||||
description = sanitize.text(request.form.get('description', ''))
|
description = sanitize.text(request.form.get('description', ''))
|
||||||
host = validate.domainname(request.form.get('host', ''))
|
host = validate.domainname(request.form.get('host', ''))
|
||||||
|
|
@ -38,7 +38,7 @@ def addoverride_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
err = validate.check_host_override_ip_in_vlans(ip, cfg)
|
err = validate.check_host_override_ip_in_vlans(ip, cfg)
|
||||||
if err:
|
if err:
|
||||||
flash(err, 'error')
|
flash(err, 'error')
|
||||||
|
|
@ -52,13 +52,13 @@ def addoverride_add():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'host_overrides', 'host', host, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'host_overrides', 'host', host, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/hostoverrides/table_toggle', methods=['POST'])
|
@bp.route('/action/hostoverrides/table_toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_toggle():
|
def table_toggle():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -67,7 +67,7 @@ def table_toggle():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('host_overrides', [])
|
items = cfg.get('host_overrides', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -83,13 +83,13 @@ def table_toggle():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
host = items[idx]['host']
|
host = items[idx]['host']
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'host_overrides', 'host', host, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'host_overrides', 'host', host, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/hostoverrides/table_edit', methods=['POST'])
|
@bp.route('/action/hostoverrides/table_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_edit():
|
def table_edit():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -107,7 +107,7 @@ def table_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
err = validate.check_host_override_ip_in_vlans(ip, cfg)
|
err = validate.check_host_override_ip_in_vlans(ip, cfg)
|
||||||
if err:
|
if err:
|
||||||
flash(err, 'error')
|
flash(err, 'error')
|
||||||
|
|
@ -126,13 +126,13 @@ def table_edit():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'host_overrides', 'host', host, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'host_overrides', 'host', host, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/hostoverrides/table_delete', methods=['POST'])
|
@bp.route('/action/hostoverrides/table_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_delete():
|
def table_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -141,7 +141,7 @@ def table_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('host_overrides', [])
|
items = cfg.get('host_overrides', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -154,6 +154,6 @@ def table_delete():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, 'host_overrides', 'host', removed['host'], changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'host_overrides', 'host', removed['host'], changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
content = load_json(f'{PAGES_DIR}/hostoverrides/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/hostoverrides/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -88,7 +88,7 @@ def _parse_entry():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/intervlan/addexception_add', methods=['POST'])
|
@bp.route('/action/intervlan/addexception_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addexception_add():
|
def addexception_add():
|
||||||
entry, err = _parse_entry()
|
entry, err = _parse_entry()
|
||||||
if err:
|
if err:
|
||||||
|
|
@ -96,7 +96,7 @@ def addexception_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
cfg.setdefault('inter_vlan_exceptions', []).append(entry)
|
cfg.setdefault('inter_vlan_exceptions', []).append(entry)
|
||||||
errors = validate.validate_config(cfg)
|
errors = validate.validate_config(cfg)
|
||||||
if errors:
|
if errors:
|
||||||
|
|
@ -105,13 +105,13 @@ def addexception_add():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
src = entry.get('src_ip_or_subnet', '')
|
src = entry.get('src_ip_or_subnet', '')
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/intervlan/table_toggle', methods=['POST'])
|
@bp.route('/action/intervlan/table_toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_toggle():
|
def table_toggle():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -120,7 +120,7 @@ def table_toggle():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('inter_vlan_exceptions', [])
|
items = cfg.get('inter_vlan_exceptions', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -136,13 +136,13 @@ def table_toggle():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
src = items[idx].get('src_ip_or_subnet', '')
|
src = items[idx].get('src_ip_or_subnet', '')
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/intervlan/table_edit', methods=['POST'])
|
@bp.route('/action/intervlan/table_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_edit():
|
def table_edit():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -155,7 +155,7 @@ def table_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('inter_vlan_exceptions', [])
|
items = cfg.get('inter_vlan_exceptions', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -171,13 +171,13 @@ def table_edit():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
src = items[idx].get('src_ip_or_subnet', '')
|
src = items[idx].get('src_ip_or_subnet', '')
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/intervlan/table_delete', methods=['POST'])
|
@bp.route('/action/intervlan/table_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def table_delete():
|
def table_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -186,7 +186,7 @@ def table_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('inter_vlan_exceptions', [])
|
items = cfg.get('inter_vlan_exceptions', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -200,6 +200,6 @@ def table_delete():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
src = removed.get('src_ip_or_subnet', '')
|
src = removed.get('src_ip_or_subnet', '')
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'inter_vlan_exceptions', 'src_ip_or_subnet', src, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
tokens['PROTOCOL_OPTIONS'] = json.dumps([
|
tokens['PROTOCOL_OPTIONS'] = json.dumps([
|
||||||
{'value': 'tcp', 'label': 'TCP'},
|
{'value': 'tcp', 'label': 'TCP'},
|
||||||
{'value': 'udp', 'label': 'UDP'},
|
{'value': 'udp', 'label': 'UDP'},
|
||||||
{'value': 'both', 'label': 'TCP/UDP'},
|
{'value': 'both', 'label': 'TCP/UDP'},
|
||||||
])
|
])
|
||||||
content = load_json(f'{PAGES_DIR}/intervlan/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/intervlan/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -13,15 +13,15 @@ bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/mdns/settings_apply', methods=['POST'])
|
@bp.route('/action/mdns/settings_apply', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def settings_apply():
|
def settings_apply():
|
||||||
mdns_enabled = 'mdns_enabled' in request.form
|
mdns_enabled = 'mdns_enabled' in request.form
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
mdns_reflect_vlans = sanitize.filterlist(
|
mdns_reflect_vlans = sanitize.filterlist(
|
||||||
request.form.getlist('mdns_reflect_vlans'),
|
request.form.getlist('mdns_reflect_vlans'),
|
||||||
{v.get('name') for v in cfg.get('vlans', [])},
|
{v.get('name') for v in cfg.get('vlans', [])},
|
||||||
|
|
@ -38,6 +38,6 @@ def settings_apply():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, cfg['mdns_reflection'])
|
changes = config_utils.diff_fields(before, cfg['mdns_reflection'])
|
||||||
flash(record_group(cfg, 'mdns_reflection', None, None, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'mdns_reflection', None, None, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from config_utils import collect_layout_tokens
|
import config_utils
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
return collect_layout_tokens(cfg)
|
return config_utils.collect_layout_tokens(cfg)
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import ipaddress
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
import settings as settings
|
import settings
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
||||||
|
|
@ -25,14 +25,14 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/networklayout/vlans_addedit', methods=['POST'])
|
@bp.route('/action/networklayout/vlans_addedit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def vlans_addedit():
|
def vlans_addedit():
|
||||||
_ri = request.form.get('row_index', '').strip()
|
_ri = request.form.get('row_index', '').strip()
|
||||||
try:
|
try:
|
||||||
|
|
@ -53,7 +53,7 @@ def vlans_addedit():
|
||||||
restricted_vlan = restricted_vlan_raw if restricted_vlan_raw in ('q', 'c') else ''
|
restricted_vlan = restricted_vlan_raw if restricted_vlan_raw in ('q', 'c') else ''
|
||||||
use_blocklists = sanitize.filterlist(
|
use_blocklists = sanitize.filterlist(
|
||||||
request.form.getlist('use_blocklists'),
|
request.form.getlist('use_blocklists'),
|
||||||
{b.get('name') for b in load_config().get('dns_blocking', {}).get('blocklists', [])},
|
{b.get('name') for b in config_utils.load_config().get('dns_blocking', {}).get('blocklists', [])},
|
||||||
)
|
)
|
||||||
|
|
||||||
if restricted_vlan and not PRO_LICENSE:
|
if restricted_vlan and not PRO_LICENSE:
|
||||||
|
|
@ -216,7 +216,7 @@ def vlans_addedit():
|
||||||
if dhcp_overrides:
|
if dhcp_overrides:
|
||||||
dhcp_info['explicit_overrides'] = dhcp_overrides
|
dhcp_info['explicit_overrides'] = dhcp_overrides
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlans = cfg.setdefault('vlans', [])
|
vlans = cfg.setdefault('vlans', [])
|
||||||
|
|
||||||
if is_edit:
|
if is_edit:
|
||||||
|
|
@ -284,11 +284,11 @@ def vlans_addedit():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, existing)
|
changes = config_utils.diff_fields(before, existing)
|
||||||
if not changes:
|
if not changes:
|
||||||
flash('No changes were made.', 'info')
|
flash('No changes were made.', 'info')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
flash(record_group(cfg, 'vlans', 'name', name, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'vlans', 'name', name, changes, 'core apply'), 'success')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
is_vpn = 'is_vpn' in request.form
|
is_vpn = 'is_vpn' in request.form
|
||||||
|
|
@ -347,14 +347,14 @@ def vlans_addedit():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'vlans', 'name', name, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'vlans', 'name', name, changes, 'core apply'), 'success')
|
||||||
|
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/networklayout/vlans_delete', methods=['POST'])
|
@bp.route('/action/networklayout/vlans_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def vlans_delete():
|
def vlans_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -363,7 +363,7 @@ def vlans_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
if idx < 0 or idx >= len(vlans):
|
if idx < 0 or idx >= len(vlans):
|
||||||
flash('VLAN not found.', 'error')
|
flash('VLAN not found.', 'error')
|
||||||
|
|
@ -376,6 +376,6 @@ def vlans_delete():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, 'vlans', 'name', removed['name'], changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'vlans', 'name', removed['name'], changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
import settings as settings
|
import settings
|
||||||
|
|
||||||
PRO_LICENSE = settings.is_pro()
|
PRO_LICENSE = settings.is_pro()
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
dv = next((v for v in vlans if v.get('radius_default')), None)
|
dv = next((v for v in vlans if v.get('radius_default')), None)
|
||||||
tokens['EXISTING_VLAN_IDS_JSON'] = json.dumps([v.get('vlan_id') for v in vlans])
|
tokens['EXISTING_VLAN_IDS_JSON'] = json.dumps([v.get('vlan_id') for v in vlans])
|
||||||
|
|
@ -30,8 +30,8 @@ def collect_tokens(cfg):
|
||||||
{'value': bl.get('name', ''), 'label': bl.get('description', bl.get('name', ''))}
|
{'value': bl.get('name', ''), 'label': bl.get('description', bl.get('name', ''))}
|
||||||
for bl in cfg.get('dns_blocking', {}).get('blocklists', [])
|
for bl in cfg.get('dns_blocking', {}).get('blocklists', [])
|
||||||
])
|
])
|
||||||
content = load_json(f'{PAGES_DIR}/networklayout/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/networklayout/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
from config_utils import collect_layout_tokens, fmt_timestamp, BLOCKLISTS_DIR
|
import config_utils
|
||||||
from factory import run, load_ddns
|
import factory
|
||||||
from pages.ddns.view import public_ip_info
|
from pages.ddns.view import public_ip_info
|
||||||
from pages.dhcpleases.view import live_dhcp_leases
|
from pages.dhcpleases.view import live_dhcp_leases
|
||||||
|
|
||||||
|
|
||||||
def get_dnsmasq_stats():
|
def get_dnsmasq_stats():
|
||||||
stats = {'queries': '-', 'hits': '-', 'hit_rate': '-', 'forwarded': '-', 'auth': '-', 'tcp_peak': '-'}
|
stats = {'queries': '-', 'hits': '-', 'hit_rate': '-', 'forwarded': '-', 'auth': '-', 'tcp_peak': '-'}
|
||||||
out = run('journalctl -u dnsmasq -n 200 --no-pager 2>/dev/null')
|
out = factory.run('journalctl -u dnsmasq -n 200 --no-pager 2>/dev/null')
|
||||||
for line in reversed(out.splitlines()):
|
for line in reversed(out.splitlines()):
|
||||||
if 'queries forwarded' in line:
|
if 'queries forwarded' in line:
|
||||||
m = re.search(r'queries forwarded (\d+)', line)
|
m = re.search(r'queries forwarded (\d+)', line)
|
||||||
|
|
@ -36,15 +36,15 @@ def get_dnsmasq_stats():
|
||||||
|
|
||||||
|
|
||||||
def count_blocked_today():
|
def count_blocked_today():
|
||||||
out = run("journalctl -u dnsmasq --since today --no-pager 2>/dev/null | grep -c 'is NXDOMAIN'")
|
out = factory.run("journalctl -u dnsmasq --since today --no-pager 2>/dev/null | grep -c 'is NXDOMAIN'")
|
||||||
return out or '0'
|
return out or '0'
|
||||||
|
|
||||||
|
|
||||||
def count_blocked_domains():
|
def count_blocked_domains():
|
||||||
try:
|
try:
|
||||||
total = sum(
|
total = sum(
|
||||||
int(run(f'wc -l < "{BLOCKLISTS_DIR}/{f}"') or 0)
|
int(factory.run(f'wc -l < "{config_utils.BLOCKLISTS_DIR}/{f}"') or 0)
|
||||||
for f in os.listdir(BLOCKLISTS_DIR) if f.endswith('.con')
|
for f in os.listdir(config_utils.BLOCKLISTS_DIR) if f.endswith('.con')
|
||||||
)
|
)
|
||||||
return str(total)
|
return str(total)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -54,23 +54,23 @@ def count_blocked_domains():
|
||||||
def bl_last_update():
|
def bl_last_update():
|
||||||
try:
|
try:
|
||||||
mtime = max(
|
mtime = max(
|
||||||
os.path.getmtime(f'{BLOCKLISTS_DIR}/{f}')
|
os.path.getmtime(f'{config_utils.BLOCKLISTS_DIR}/{f}')
|
||||||
for f in os.listdir(BLOCKLISTS_DIR) if f.endswith('.con')
|
for f in os.listdir(config_utils.BLOCKLISTS_DIR) if f.endswith('.con')
|
||||||
)
|
)
|
||||||
return fmt_timestamp(int(mtime))
|
return config_utils.fmt_timestamp(int(mtime))
|
||||||
except Exception:
|
except Exception:
|
||||||
return '-'
|
return '-'
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
non_vpn_vlans = [v for v in vlans if not v.get('is_vpn')]
|
non_vpn_vlans = [v for v in vlans if not v.get('is_vpn')]
|
||||||
vlan_names = [v.get('name', '') for v in vlans]
|
vlan_names = [v.get('name', '') for v in vlans]
|
||||||
net = cfg.get('network_interfaces', {})
|
net = cfg.get('network_interfaces', {})
|
||||||
dns = cfg.get('upstream_dns', {})
|
dns = cfg.get('upstream_dns', {})
|
||||||
dns_stats = get_dnsmasq_stats()
|
dns_stats = get_dnsmasq_stats()
|
||||||
ddns = load_ddns()
|
ddns = factory.load_ddns()
|
||||||
ip_str, domains_sub, last_obtained = public_ip_info(ddns)
|
ip_str, domains_sub, last_obtained = public_ip_info(ddns)
|
||||||
|
|
||||||
tokens['GENERAL_WAN_INTERFACE'] = str(net.get('wan_interface', '-'))
|
tokens['GENERAL_WAN_INTERFACE'] = str(net.get('wan_interface', '-'))
|
||||||
|
|
@ -82,8 +82,8 @@ def collect_tokens(cfg):
|
||||||
tokens['STAT_BLOCKED_TODAY'] = count_blocked_today()
|
tokens['STAT_BLOCKED_TODAY'] = count_blocked_today()
|
||||||
tokens['STAT_BLOCKED_DOMAINS'] = count_blocked_domains()
|
tokens['STAT_BLOCKED_DOMAINS'] = count_blocked_domains()
|
||||||
tokens['STAT_BL_LAST_UPDATE'] = bl_last_update()
|
tokens['STAT_BL_LAST_UPDATE'] = bl_last_update()
|
||||||
tokens['STAT_UPTIME'] = run('uptime -p') or '-'
|
tokens['STAT_UPTIME'] = factory.run('uptime -p') or '-'
|
||||||
tokens['STAT_NFTABLES_STATUS'] = 'Active' if run('nft list tables 2>/dev/null').strip() else 'Inactive'
|
tokens['STAT_NFTABLES_STATUS'] = 'Active' if factory.run('nft list tables 2>/dev/null').strip() else 'Inactive'
|
||||||
tokens['STAT_PUBLIC_IP'] = ip_str
|
tokens['STAT_PUBLIC_IP'] = ip_str
|
||||||
tokens['STAT_DDNS_HOSTNAME'] = domains_sub
|
tokens['STAT_DDNS_HOSTNAME'] = domains_sub
|
||||||
tokens['DNS_CACHE_SIZE'] = str(dns.get('cache_size', '-'))
|
tokens['DNS_CACHE_SIZE'] = str(dns.get('cache_size', '-'))
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import copy
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash, queued_msg, queue_command
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ def _valid_interface(name):
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/physicalinterfaces/physicalinterface_save', methods=['POST'])
|
@bp.route('/action/physicalinterfaces/physicalinterface_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def physicalinterface_save():
|
def physicalinterface_save():
|
||||||
wan = sanitize.interface_name(request.form.get('wan_interface', ''))
|
wan = sanitize.interface_name(request.form.get('wan_interface', ''))
|
||||||
lan = sanitize.interface_name(request.form.get('lan_interface', ''))
|
lan = sanitize.interface_name(request.form.get('lan_interface', ''))
|
||||||
|
|
@ -48,7 +48,7 @@ def physicalinterface_save():
|
||||||
flash(err, 'error')
|
flash(err, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ def physicalinterface_save():
|
||||||
flash(err, 'error')
|
flash(err, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('network_interfaces', {}))
|
before = copy.deepcopy(cfg.get('network_interfaces', {}))
|
||||||
gen = cfg.setdefault('network_interfaces', {})
|
gen = cfg.setdefault('network_interfaces', {})
|
||||||
gen['wan_interface'] = wan
|
gen['wan_interface'] = wan
|
||||||
|
|
@ -70,15 +70,15 @@ def physicalinterface_save():
|
||||||
for msg in errors:
|
for msg in errors:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
changes = diff_fields(before, cfg['network_interfaces'])
|
changes = config_utils.diff_fields(before, cfg['network_interfaces'])
|
||||||
flash(record_group(cfg, 'network_interfaces', None, None, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'network_interfaces', None, None, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/physicalinterfaces/ifaceconfig_apply', methods=['POST'])
|
@bp.route('/action/physicalinterfaces/ifaceconfig_apply', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def ifaceconfig_apply():
|
def ifaceconfig_apply():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
@ -114,15 +114,15 @@ def ifaceconfig_apply():
|
||||||
|
|
||||||
queued = False
|
queued = False
|
||||||
if mtu_int and str(mtu_int) != original_mtu:
|
if mtu_int and str(mtu_int) != original_mtu:
|
||||||
queue_command(f'mtu {iface} {mtu_int}')
|
config_utils.queue_command(f'mtu {iface} {mtu_int}')
|
||||||
queued = True
|
queued = True
|
||||||
if mac and mac != original_mac:
|
if mac and mac != original_mac:
|
||||||
queue_command(f'mac {iface} {mac}')
|
config_utils.queue_command(f'mac {iface} {mac}')
|
||||||
queued = True
|
queued = True
|
||||||
|
|
||||||
if not queued:
|
if not queued:
|
||||||
flash('No changes detected.', 'info')
|
flash('No changes detected.', 'info')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
flash(queued_msg(action_label='Changes queued'), 'success')
|
flash(config_utils.queued_msg(action_label='Changes queued'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from config_utils import collect_layout_tokens
|
import config_utils
|
||||||
|
|
||||||
_EXCLUDE_PREFIXES = ('lo', 'wg', 'docker', 'br-', 'veth', 'tun', 'tap', 'ppp', 'virbr', 'podman', 'vnet', 'macvtap', 'fc-')
|
_EXCLUDE_PREFIXES = ('lo', 'wg', 'docker', 'br-', 'veth', 'tun', 'tap', 'ppp', 'virbr', 'podman', 'vnet', 'macvtap', 'fc-')
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ def iface_info(iface):
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
net = cfg.get('network_interfaces', {})
|
net = cfg.get('network_interfaces', {})
|
||||||
wan = net.get('wan_interface', '')
|
wan = net.get('wan_interface', '')
|
||||||
lan = net.get('lan_interface', '')
|
lan = net.get('lan_interface', '')
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -75,7 +75,7 @@ def _parse_entry():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portforwarding/addrule_add', methods=['POST'])
|
@bp.route('/action/portforwarding/addrule_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addrule_add():
|
def addrule_add():
|
||||||
entry, err = _parse_entry()
|
entry, err = _parse_entry()
|
||||||
if err:
|
if err:
|
||||||
|
|
@ -83,7 +83,7 @@ def addrule_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
err = validate.check_portfwd_restricted_vlan(entry['nat_ip'], cfg.get('vlans', []))
|
err = validate.check_portfwd_restricted_vlan(entry['nat_ip'], cfg.get('vlans', []))
|
||||||
if err:
|
if err:
|
||||||
flash(err, 'error')
|
flash(err, 'error')
|
||||||
|
|
@ -98,13 +98,13 @@ def addrule_add():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
dest_port = entry.get('dest_port', '')
|
dest_port = entry.get('dest_port', '')
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portforwarding/rules_toggle', methods=['POST'])
|
@bp.route('/action/portforwarding/rules_toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def rules_toggle():
|
def rules_toggle():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -113,7 +113,7 @@ def rules_toggle():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('port_forwarding', [])
|
items = cfg.get('port_forwarding', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -137,13 +137,13 @@ def rules_toggle():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
dest_port = items[idx].get('dest_port', '')
|
dest_port = items[idx].get('dest_port', '')
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portforwarding/rules_edit', methods=['POST'])
|
@bp.route('/action/portforwarding/rules_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def rules_edit():
|
def rules_edit():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -156,7 +156,7 @@ def rules_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('port_forwarding', [])
|
items = cfg.get('port_forwarding', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -179,13 +179,13 @@ def rules_edit():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
dest_port = items[idx].get('dest_port', '')
|
dest_port = items[idx].get('dest_port', '')
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portforwarding/rules_delete', methods=['POST'])
|
@bp.route('/action/portforwarding/rules_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def rules_delete():
|
def rules_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -194,7 +194,7 @@ def rules_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('port_forwarding', [])
|
items = cfg.get('port_forwarding', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -208,6 +208,6 @@ def rules_delete():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
dest_port = removed.get('dest_port', '')
|
dest_port = removed.get('dest_port', '')
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_forwarding', 'dest_port', dest_port, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
tokens['PROTOCOL_OPTIONS'] = json.dumps([
|
tokens['PROTOCOL_OPTIONS'] = json.dumps([
|
||||||
{'value': 'tcp', 'label': 'TCP'},
|
{'value': 'tcp', 'label': 'TCP'},
|
||||||
{'value': 'udp', 'label': 'UDP'},
|
{'value': 'udp', 'label': 'UDP'},
|
||||||
{'value': 'both', 'label': 'TCP/UDP'},
|
{'value': 'both', 'label': 'TCP/UDP'},
|
||||||
])
|
])
|
||||||
content = load_json(f'{PAGES_DIR}/portforwarding/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/portforwarding/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -65,7 +65,7 @@ def _parse_entry():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portwrangling/addrule_add', methods=['POST'])
|
@bp.route('/action/portwrangling/addrule_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addrule_add():
|
def addrule_add():
|
||||||
vlan_name = sanitize.name(request.form.get('vlan_name', ''))
|
vlan_name = sanitize.name(request.form.get('vlan_name', ''))
|
||||||
entry, err = _parse_entry()
|
entry, err = _parse_entry()
|
||||||
|
|
@ -77,7 +77,7 @@ def addrule_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlan = next((v for v in cfg.get('vlans', []) if v.get('name') == vlan_name), None)
|
vlan = next((v for v in cfg.get('vlans', []) if v.get('name') == vlan_name), None)
|
||||||
if vlan is None:
|
if vlan is None:
|
||||||
flash(f'The configuration has not been saved because VLAN "{vlan_name}" was not found.', 'error')
|
flash(f'The configuration has not been saved because VLAN "{vlan_name}" was not found.', 'error')
|
||||||
|
|
@ -91,13 +91,13 @@ def addrule_add():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
flash(record_group(cfg, 'port_wrangling', 'dest_port', entry['dest_port'], changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_wrangling', 'dest_port', entry['dest_port'], changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portwrangling/rules_toggle', methods=['POST'])
|
@bp.route('/action/portwrangling/rules_toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def rules_toggle():
|
def rules_toggle():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -106,7 +106,7 @@ def rules_toggle():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('port_wrangling', [])
|
items = cfg.get('port_wrangling', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -121,13 +121,13 @@ def rules_toggle():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'port_wrangling', 'dest_port', items[idx].get('dest_port', ''), changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_wrangling', 'dest_port', items[idx].get('dest_port', ''), changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portwrangling/rules_edit', methods=['POST'])
|
@bp.route('/action/portwrangling/rules_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def rules_edit():
|
def rules_edit():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -140,7 +140,7 @@ def rules_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('port_wrangling', [])
|
items = cfg.get('port_wrangling', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -156,13 +156,13 @@ def rules_edit():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(before, items[idx])
|
changes = config_utils.diff_fields(before, items[idx])
|
||||||
flash(record_group(cfg, 'port_wrangling', 'dest_port', items[idx].get('dest_port', ''), changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_wrangling', 'dest_port', items[idx].get('dest_port', ''), changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/portwrangling/rules_delete', methods=['POST'])
|
@bp.route('/action/portwrangling/rules_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def rules_delete():
|
def rules_delete():
|
||||||
idx = _row_index()
|
idx = _row_index()
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
|
@ -171,7 +171,7 @@ def rules_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
items = cfg.get('port_wrangling', [])
|
items = cfg.get('port_wrangling', [])
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
flash('Entry not found.', 'error')
|
flash('Entry not found.', 'error')
|
||||||
|
|
@ -184,6 +184,6 @@ def rules_delete():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, 'port_wrangling', 'dest_port', removed.get('dest_port', ''), changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'port_wrangling', 'dest_port', removed.get('dest_port', ''), changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens, load_datasource
|
import config_utils
|
||||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
vlan_names = [v.get('name', '') for v in vlans]
|
vlan_names = [v.get('name', '') for v in vlans]
|
||||||
filter_opts = '<option value="all">All VLANs</option>' + ''.join(
|
filter_opts = '<option value="all">All VLANs</option>' + ''.join(
|
||||||
|
|
@ -21,8 +21,8 @@ def collect_tokens(cfg):
|
||||||
v.get('name', ''): {'subnet': v.get('subnet', ''), 'prefix': v.get('subnet_mask', 0)}
|
v.get('name', ''): {'subnet': v.get('subnet', ''), 'prefix': v.get('subnet_mask', 0)}
|
||||||
for v in vlans if v.get('name') and v.get('subnet')
|
for v in vlans if v.get('name') and v.get('subnet')
|
||||||
})
|
})
|
||||||
content = load_json(f'{PAGES_DIR}/portwrangling/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/portwrangling/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, load_datasource(ds))
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request, session, redirect, flash
|
from flask import Blueprint, request, session, redirect, flash
|
||||||
import json, bcrypt
|
import json, bcrypt
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import ACCOUNTS_FILE
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
@ -13,18 +13,18 @@ bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
def _load_accounts():
|
def _load_accounts():
|
||||||
try:
|
try:
|
||||||
with open(ACCOUNTS_FILE) as f:
|
with open(config_utils.ACCOUNTS_FILE) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {'accounts': []}
|
return {'accounts': []}
|
||||||
|
|
||||||
def _save_accounts(data):
|
def _save_accounts(data):
|
||||||
with open(ACCOUNTS_FILE, 'w') as f:
|
with open(config_utils.ACCOUNTS_FILE, 'w') as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/preferences/accountdetails_save', methods=['POST'])
|
@bp.route('/action/preferences/accountdetails_save', methods=['POST'])
|
||||||
@require_level('viewer')
|
@auth.require_level('viewer')
|
||||||
def accountdetails_save():
|
def accountdetails_save():
|
||||||
tz = sanitize.timezone(request.form.get('timezone', '').strip())
|
tz = sanitize.timezone(request.form.get('timezone', '').strip())
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ def accountdetails_save():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/preferences/changepassword_save', methods=['POST'])
|
@bp.route('/action/preferences/changepassword_save', methods=['POST'])
|
||||||
@require_level('viewer')
|
@auth.require_level('viewer')
|
||||||
def changepassword_save():
|
def changepassword_save():
|
||||||
current_password = request.form.get('current_password', '')
|
current_password = request.form.get('current_password', '')
|
||||||
new_password = request.form.get('new_password', '')
|
new_password = request.form.get('new_password', '')
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import json
|
import json
|
||||||
from flask import session
|
from flask import session
|
||||||
import sanitize
|
import sanitize
|
||||||
from config_utils import collect_layout_tokens
|
import config_utils
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
blank = [{'value': '', 'label': '-- Select timezone --'}]
|
blank = [{'value': '', 'label': '-- Select timezone --'}]
|
||||||
tokens['PREF_EMAIL'] = session.get('email_address', '')
|
tokens['PREF_EMAIL'] = session.get('email_address', '')
|
||||||
tokens['PREF_TIMEZONE'] = session.get('timezone', '')
|
tokens['PREF_TIMEZONE'] = session.get('timezone', '')
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import os
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request, redirect, flash, send_file, abort, jsonify
|
from flask import Blueprint, request, redirect, flash, send_file, abort, jsonify
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import CONFIGS_DIR, load_config, record_group, diff_fields
|
import config_utils
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
import settings as settings
|
import settings
|
||||||
|
|
||||||
_PAGE = Path(__file__).parent.name
|
_PAGE = Path(__file__).parent.name
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ PRO_LICENSE = settings.is_pro()
|
||||||
|
|
||||||
bp = Blueprint(_PAGE, __name__)
|
bp = Blueprint(_PAGE, __name__)
|
||||||
|
|
||||||
RADIUS_SECRET_FILE = Path(CONFIGS_DIR) / '.radius-secret'
|
RADIUS_SECRET_FILE = Path(config_utils.CONFIGS_DIR) / '.radius-secret'
|
||||||
RADIUS_LOG_FILE = '/var/log/freeradius/radius.log'
|
RADIUS_LOG_FILE = '/var/log/freeradius/radius.log'
|
||||||
|
|
||||||
VALID_MAC_FORMATS = {
|
VALID_MAC_FORMATS = {
|
||||||
|
|
@ -26,7 +26,7 @@ VALID_MAC_FORMATS = {
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/radius/regenerate', methods=['POST'])
|
@bp.route('/action/radius/regenerate', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def regenerate():
|
def regenerate():
|
||||||
try:
|
try:
|
||||||
RADIUS_SECRET_FILE.unlink(missing_ok=True)
|
RADIUS_SECRET_FILE.unlink(missing_ok=True)
|
||||||
|
|
@ -38,25 +38,25 @@ def regenerate():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/radius/options_save', methods=['POST'])
|
@bp.route('/action/radius/options_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def options_save():
|
def options_save():
|
||||||
mac_format = request.form.get('mac_format', 'aabbccddeeff')
|
mac_format = request.form.get('mac_format', 'aabbccddeeff')
|
||||||
if mac_format not in VALID_MAC_FORMATS:
|
if mac_format not in VALID_MAC_FORMATS:
|
||||||
flash('Invalid MAC format.', 'error')
|
flash('Invalid MAC format.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('radius', {}).get('options', {}))
|
before = copy.deepcopy(cfg.get('radius', {}).get('options', {}))
|
||||||
after = {**before, 'mac_format': mac_format}
|
after = {**before, 'mac_format': mac_format}
|
||||||
cfg.setdefault('radius', {})['options'] = after
|
cfg.setdefault('radius', {})['options'] = after
|
||||||
|
|
||||||
changes = diff_fields(before, after)
|
changes = config_utils.diff_fields(before, after)
|
||||||
flash(record_group(cfg, 'radius.options', 'setting', 'radius', changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'radius.options', 'setting', 'radius', changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/radius/auth_mode_save', methods=['POST'])
|
@bp.route('/action/radius/auth_mode_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def auth_mode_save():
|
def auth_mode_save():
|
||||||
auth_mode = request.form.get('auth_mode', 'mab')
|
auth_mode = request.form.get('auth_mode', 'mab')
|
||||||
if auth_mode not in ('mab', 'eap_password', 'eap_credential'):
|
if auth_mode not in ('mab', 'eap_password', 'eap_credential'):
|
||||||
|
|
@ -78,7 +78,7 @@ def auth_mode_save():
|
||||||
'eap_ttls': {'md5', 'mschapv2', 'gtc'},
|
'eap_ttls': {'md5', 'mschapv2', 'gtc'},
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('radius', {}).get('options', {}))
|
before = copy.deepcopy(cfg.get('radius', {}).get('options', {}))
|
||||||
after = {**before, 'auth_mode': auth_mode}
|
after = {**before, 'auth_mode': auth_mode}
|
||||||
if auth_mode == 'eap_password':
|
if auth_mode == 'eap_password':
|
||||||
|
|
@ -104,13 +104,13 @@ def auth_mode_save():
|
||||||
after.pop('include_length', None)
|
after.pop('include_length', None)
|
||||||
cfg.setdefault('radius', {})['options'] = after
|
cfg.setdefault('radius', {})['options'] = after
|
||||||
|
|
||||||
changes = diff_fields(before, after)
|
changes = config_utils.diff_fields(before, after)
|
||||||
flash(record_group(cfg, 'radius.options', 'auth_mode', auth_mode, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'radius.options', 'auth_mode', auth_mode, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/radius/default_rule_save', methods=['POST'])
|
@bp.route('/action/radius/default_rule_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def default_rule_save():
|
def default_rule_save():
|
||||||
apply_to = request.form.get('apply_to', 'all')
|
apply_to = request.form.get('apply_to', 'all')
|
||||||
ap_ips = request.form.getlist('ap_ips')
|
ap_ips = request.form.getlist('ap_ips')
|
||||||
|
|
@ -119,7 +119,7 @@ def default_rule_save():
|
||||||
flash('Invalid apply_to value.', 'error')
|
flash('Invalid apply_to value.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
valid_ips = {
|
valid_ips = {
|
||||||
r['ip'] for r in cfg.get('dhcp_reservations', [])
|
r['ip'] for r in cfg.get('dhcp_reservations', [])
|
||||||
if r.get('radius_client') is True
|
if r.get('radius_client') is True
|
||||||
|
|
@ -134,16 +134,16 @@ def default_rule_save():
|
||||||
after = {**before, 'apply_to': apply_to, 'ap_ips': ap_ips}
|
after = {**before, 'apply_to': apply_to, 'ap_ips': ap_ips}
|
||||||
cfg.setdefault('radius', {})['options'] = after
|
cfg.setdefault('radius', {})['options'] = after
|
||||||
|
|
||||||
changes = diff_fields(before, after)
|
changes = config_utils.diff_fields(before, after)
|
||||||
flash(record_group(cfg, 'radius.options', 'default_rule', 'radius', changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'radius.options', 'default_rule', 'radius', changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/radius/default_vlan_save', methods=['POST'])
|
@bp.route('/action/radius/default_vlan_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def default_vlan_save():
|
def default_vlan_save():
|
||||||
chosen = request.form.get('default_vlan', '').strip()
|
chosen = request.form.get('default_vlan', '').strip()
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
|
|
||||||
if chosen and not any(v['name'] == chosen for v in vlans):
|
if chosen and not any(v['name'] == chosen for v in vlans):
|
||||||
|
|
@ -154,14 +154,14 @@ def default_vlan_save():
|
||||||
for v in vlans:
|
for v in vlans:
|
||||||
v['radius_default'] = (v['name'] == chosen) if chosen else False
|
v['radius_default'] = (v['name'] == chosen) if chosen else False
|
||||||
|
|
||||||
changes = diff_fields({'radius_default': old_name}, {'radius_default': chosen})
|
changes = config_utils.diff_fields({'radius_default': old_name}, {'radius_default': chosen})
|
||||||
flash(record_group(cfg, 'radius', 'default_vlan', chosen or 'none', changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'radius', 'default_vlan', chosen or 'none', changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/radius/logging_save', methods=['POST'])
|
@bp.route('/action/radius/logging_save', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_save():
|
def logging_save():
|
||||||
log_max_kb = validate.int_range(request.form.get('log_max_kb', '').strip(), 64, None)
|
log_max_kb = validate.int_range(request.form.get('log_max_kb', '').strip(), 64, None)
|
||||||
if log_max_kb is None:
|
if log_max_kb is None:
|
||||||
|
|
@ -169,18 +169,18 @@ def logging_save():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
logging = 'logging' in request.form
|
logging = 'logging' in request.form
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
before = copy.deepcopy(cfg.get('radius', {}).get('general', {}))
|
before = copy.deepcopy(cfg.get('radius', {}).get('general', {}))
|
||||||
after = {'logging': logging, 'log_max_kb': log_max_kb}
|
after = {'logging': logging, 'log_max_kb': log_max_kb}
|
||||||
cfg.setdefault('radius', {})['general'] = after
|
cfg.setdefault('radius', {})['general'] = after
|
||||||
|
|
||||||
changes = diff_fields(before, after)
|
changes = config_utils.diff_fields(before, after)
|
||||||
flash(record_group(cfg, 'radius.general', 'setting', 'radius', changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, 'radius.general', 'setting', 'radius', changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/radius/logging_download', methods=['GET'])
|
@bp.route('/action/radius/logging_download', methods=['GET'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def logging_download():
|
def logging_download():
|
||||||
log_dir = os.path.dirname(RADIUS_LOG_FILE)
|
log_dir = os.path.dirname(RADIUS_LOG_FILE)
|
||||||
chunks = []
|
chunks = []
|
||||||
|
|
@ -228,10 +228,10 @@ def logging_download():
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/api/radius/log-tail', methods=['GET'])
|
@bp.route('/api/radius/log-tail', methods=['GET'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def api_log_tail():
|
def api_log_tail():
|
||||||
try:
|
try:
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
log_max_kb = cfg.get('radius', {}).get('general', {}).get('log_max_kb', 1024)
|
log_max_kb = cfg.get('radius', {}).get('general', {}).get('log_max_kb', 1024)
|
||||||
|
|
||||||
current = []
|
current = []
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from config_utils import collect_layout_tokens, CONFIGS_DIR
|
import config_utils
|
||||||
import settings as settings
|
import settings
|
||||||
|
|
||||||
PRO_LICENSE = settings.is_pro()
|
PRO_LICENSE = settings.is_pro()
|
||||||
|
|
||||||
|
|
@ -59,9 +59,9 @@ def radius_log_tail(cfg):
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
try:
|
try:
|
||||||
tokens['RADIUS_SECRET'] = open(f'{CONFIGS_DIR}/.radius-secret').read().strip()
|
tokens['RADIUS_SECRET'] = open(f'{config_utils.CONFIGS_DIR}/.radius-secret').read().strip()
|
||||||
except OSError:
|
except OSError:
|
||||||
tokens['RADIUS_SECRET'] = '(Generation is pending - visit Actions to apply generation command)'
|
tokens['RADIUS_SECRET'] = '(Generation is pending - visit Actions to apply generation command)'
|
||||||
fr = cfg.get('radius', {})
|
fr = cfg.get('radius', {})
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import ipaddress
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from flask import Blueprint, make_response, redirect, flash, request
|
from flask import Blueprint, make_response, redirect, flash, request
|
||||||
from auth import require_level
|
import auth
|
||||||
from config_utils import load_config, record_group, diff_fields, verify_config_hash, CONFIGS_DIR, WEB_APP_DISPLAY_NAME
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
import mod_validation as validate
|
import mod_validation as validate
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ def _row_index():
|
||||||
|
|
||||||
|
|
||||||
def _hash_ok():
|
def _hash_ok():
|
||||||
if not verify_config_hash(request.form.get('config_hash', '')):
|
if not config_utils.verify_config_hash(request.form.get('config_hash', '')):
|
||||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -72,7 +72,7 @@ def _generate_wg_keypair():
|
||||||
|
|
||||||
def _server_pubkey(iface):
|
def _server_pubkey(iface):
|
||||||
try:
|
try:
|
||||||
with open(f'{CONFIGS_DIR}/.{iface}.pub') as f:
|
with open(f'{config_utils.CONFIGS_DIR}/.{iface}.pub') as f:
|
||||||
return f.read().strip()
|
return f.read().strip()
|
||||||
except OSError:
|
except OSError:
|
||||||
return None
|
return None
|
||||||
|
|
@ -101,7 +101,7 @@ def _build_client_conf(vlan, peer_name, peer_ip, private_key, server_pubkey):
|
||||||
allowed_ips = f'{subnet}/{prefix}' if split_tunnel else '0.0.0.0/0'
|
allowed_ips = f'{subnet}/{prefix}' if split_tunnel else '0.0.0.0/0'
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
f'# Generated by {WEB_APP_DISPLAY_NAME}', '',
|
f'# Generated by {config_utils.WEB_APP_DISPLAY_NAME}', '',
|
||||||
'[Interface]',
|
'[Interface]',
|
||||||
f'PrivateKey = {private_key}',
|
f'PrivateKey = {private_key}',
|
||||||
f'Address = {peer_ip}/{prefix}',
|
f'Address = {peer_ip}/{prefix}',
|
||||||
|
|
@ -117,7 +117,7 @@ def _build_client_conf(vlan, peer_name, peer_ip, private_key, server_pubkey):
|
||||||
|
|
||||||
|
|
||||||
def _conf_response(vlan, peer_name, peer_ip, private_key):
|
def _conf_response(vlan, peer_name, peer_ip, private_key):
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
iface = _wg_iface(vlan, cfg)
|
iface = _wg_iface(vlan, cfg)
|
||||||
server_pub = _server_pubkey(iface)
|
server_pub = _server_pubkey(iface)
|
||||||
if not server_pub:
|
if not server_pub:
|
||||||
|
|
@ -133,7 +133,7 @@ def _conf_response(vlan, peer_name, peer_ip, private_key):
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/vpn/wireguard_apply', methods=['POST'])
|
@bp.route('/action/vpn/wireguard_apply', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def wireguard_apply():
|
def wireguard_apply():
|
||||||
listen_port_raw = request.form.get('vpn_listen_port', '').strip()
|
listen_port_raw = request.form.get('vpn_listen_port', '').strip()
|
||||||
server_endpoint = validate.domainname(request.form.get('vpn_server_endpoint', ''))
|
server_endpoint = validate.domainname(request.form.get('vpn_server_endpoint', ''))
|
||||||
|
|
@ -166,7 +166,7 @@ def wireguard_apply():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vpn_vlan = _wg_vlan(cfg)
|
vpn_vlan = _wg_vlan(cfg)
|
||||||
if vpn_vlan is None:
|
if vpn_vlan is None:
|
||||||
flash('No WireGuard VLAN found in configuration.', 'error')
|
flash('No WireGuard VLAN found in configuration.', 'error')
|
||||||
|
|
@ -201,13 +201,13 @@ def wireguard_apply():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
vlan_name = vpn_vlan['name']
|
vlan_name = vpn_vlan['name']
|
||||||
changes = diff_fields(before_info, info)
|
changes = config_utils.diff_fields(before_info, info)
|
||||||
flash(record_group(cfg, f'vlans[name={vlan_name}].vpn_information', None, None, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, f'vlans[name={vlan_name}].vpn_information', None, None, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/vpn/addpeer_add', methods=['POST'])
|
@bp.route('/action/vpn/addpeer_add', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def addpeer_add():
|
def addpeer_add():
|
||||||
peer_name = sanitize.name(request.form.get('peer_name', ''))
|
peer_name = sanitize.name(request.form.get('peer_name', ''))
|
||||||
peer_vlan_nm = request.form.get('peer_vlan', '').strip()
|
peer_vlan_nm = request.form.get('peer_vlan', '').strip()
|
||||||
|
|
@ -228,7 +228,7 @@ def addpeer_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vpn_vlan = _wg_vlan_by_name(cfg, peer_vlan_nm)
|
vpn_vlan = _wg_vlan_by_name(cfg, peer_vlan_nm)
|
||||||
if vpn_vlan is None:
|
if vpn_vlan is None:
|
||||||
flash(f'VPN VLAN "{peer_vlan_nm}" not found.', 'error')
|
flash(f'VPN VLAN "{peer_vlan_nm}" not found.', 'error')
|
||||||
|
|
@ -269,13 +269,13 @@ def addpeer_add():
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
changes = diff_fields(None, entry)
|
changes = config_utils.diff_fields(None, entry)
|
||||||
record_group(cfg, f'vlans[name={peer_vlan_nm}].peers', 'name', peer_name, changes, 'core apply')
|
config_utils.record_group(cfg, f'vlans[name={peer_vlan_nm}].peers', 'name', peer_name, changes, 'core apply')
|
||||||
return _conf_response(vpn_vlan, peer_name, peer_ip, private_key)
|
return _conf_response(vpn_vlan, peer_name, peer_ip, private_key)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/vpn/peers_edit', methods=['POST'])
|
@bp.route('/action/vpn/peers_edit', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def peers_edit():
|
def peers_edit():
|
||||||
flat_idx = _row_index()
|
flat_idx = _row_index()
|
||||||
if flat_idx is None:
|
if flat_idx is None:
|
||||||
|
|
@ -292,7 +292,7 @@ def peers_edit():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
||||||
if vlan is None:
|
if vlan is None:
|
||||||
flash('Peer not found.', 'error')
|
flash('Peer not found.', 'error')
|
||||||
|
|
@ -313,13 +313,13 @@ def peers_edit():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
vlan_name = vlan['name']
|
vlan_name = vlan['name']
|
||||||
changes = diff_fields(before, {'name': peer_name, 'split_tunnel': split_tunnel, 'enabled': enabled})
|
changes = config_utils.diff_fields(before, {'name': peer_name, 'split_tunnel': split_tunnel, 'enabled': enabled})
|
||||||
flash(record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', peer_name, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', peer_name, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/vpn/peers_toggle', methods=['POST'])
|
@bp.route('/action/vpn/peers_toggle', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def peers_toggle():
|
def peers_toggle():
|
||||||
flat_idx = _row_index()
|
flat_idx = _row_index()
|
||||||
if flat_idx is None:
|
if flat_idx is None:
|
||||||
|
|
@ -328,7 +328,7 @@ def peers_toggle():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
||||||
if vlan is None:
|
if vlan is None:
|
||||||
flash('Peer not found.', 'error')
|
flash('Peer not found.', 'error')
|
||||||
|
|
@ -346,13 +346,13 @@ def peers_toggle():
|
||||||
|
|
||||||
peer_name = peers[peer_idx]['name']
|
peer_name = peers[peer_idx]['name']
|
||||||
vlan_name = vlan['name']
|
vlan_name = vlan['name']
|
||||||
changes = diff_fields(before, peers[peer_idx])
|
changes = config_utils.diff_fields(before, peers[peer_idx])
|
||||||
flash(record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', peer_name, changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', peer_name, changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/vpn/peers_delete', methods=['POST'])
|
@bp.route('/action/vpn/peers_delete', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def peers_delete():
|
def peers_delete():
|
||||||
flat_idx = _row_index()
|
flat_idx = _row_index()
|
||||||
if flat_idx is None:
|
if flat_idx is None:
|
||||||
|
|
@ -361,7 +361,7 @@ def peers_delete():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
||||||
if vlan is None:
|
if vlan is None:
|
||||||
flash('Peer not found.', 'error')
|
flash('Peer not found.', 'error')
|
||||||
|
|
@ -376,13 +376,13 @@ def peers_delete():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
vlan_name = vlan['name']
|
vlan_name = vlan['name']
|
||||||
changes = diff_fields(removed, None)
|
changes = config_utils.diff_fields(removed, None)
|
||||||
flash(record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', removed['name'], changes, 'core apply'), 'success')
|
flash(config_utils.record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', removed['name'], changes, 'core apply'), 'success')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/action/vpn/peers_regenerate', methods=['POST'])
|
@bp.route('/action/vpn/peers_regenerate', methods=['POST'])
|
||||||
@require_level('administrator')
|
@auth.require_level('administrator')
|
||||||
def peers_regenerate():
|
def peers_regenerate():
|
||||||
flat_idx = _row_index()
|
flat_idx = _row_index()
|
||||||
if flat_idx is None:
|
if flat_idx is None:
|
||||||
|
|
@ -391,7 +391,7 @@ def peers_regenerate():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = config_utils.load_config()
|
||||||
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
|
||||||
if vlan is None:
|
if vlan is None:
|
||||||
flash('Peer not found.', 'error')
|
flash('Peer not found.', 'error')
|
||||||
|
|
@ -408,6 +408,6 @@ def peers_regenerate():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
vlan_name = vlan['name']
|
vlan_name = vlan['name']
|
||||||
changes = diff_fields({'public_key': old_pub_key}, {'public_key': public_key})
|
changes = config_utils.diff_fields({'public_key': old_pub_key}, {'public_key': public_key})
|
||||||
record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', peer['name'], changes, 'core apply')
|
config_utils.record_group(cfg, f'vlans[name={vlan_name}].peers', 'name', peer['name'], changes, 'core apply')
|
||||||
return _conf_response(vlan, peer['name'], peer['ip'], private_key)
|
return _conf_response(vlan, peer['name'], peer['ip'], private_key)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import json
|
import json
|
||||||
from config_utils import collect_layout_tokens, load_datasource, fmt_timestamp, fmt_bytes
|
import config_utils
|
||||||
from factory import run, load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
import factory
|
||||||
|
|
||||||
|
|
||||||
def live_vpn_sessions():
|
def live_vpn_sessions():
|
||||||
rows = []
|
rows = []
|
||||||
out = run('wg show all dump 2>/dev/null')
|
out = factory.run('wg show all dump 2>/dev/null')
|
||||||
for line in out.splitlines():
|
for line in out.splitlines():
|
||||||
parts = line.split('\t')
|
parts = line.split('\t')
|
||||||
if len(parts) == 9:
|
if len(parts) == 9:
|
||||||
|
|
@ -15,15 +15,15 @@ def live_vpn_sessions():
|
||||||
'interface': interface,
|
'interface': interface,
|
||||||
'tunnel_ip': allowed_ips.split(',')[0].split('/')[0] if allowed_ips else '-',
|
'tunnel_ip': allowed_ips.split(',')[0].split('/')[0] if allowed_ips else '-',
|
||||||
'endpoint': endpoint if endpoint != '(none)' else '-',
|
'endpoint': endpoint if endpoint != '(none)' else '-',
|
||||||
'last_handshake': fmt_timestamp(int(last_hs)) if last_hs.isdigit() and last_hs != '0' else 'Never',
|
'last_handshake': config_utils.fmt_timestamp(int(last_hs)) if last_hs.isdigit() and last_hs != '0' else 'Never',
|
||||||
'rx_bytes': fmt_bytes(int(rx)) if rx.isdigit() else '-',
|
'rx_bytes': config_utils.fmt_bytes(int(rx)) if rx.isdigit() else '-',
|
||||||
'tx_bytes': fmt_bytes(int(tx)) if tx.isdigit() else '-',
|
'tx_bytes': config_utils.fmt_bytes(int(tx)) if tx.isdigit() else '-',
|
||||||
})
|
})
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def collect_tokens(cfg):
|
def collect_tokens(cfg):
|
||||||
tokens = collect_layout_tokens(cfg)
|
tokens = config_utils.collect_layout_tokens(cfg)
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
wg_vlans_list = sorted(
|
wg_vlans_list = sorted(
|
||||||
[v for v in vlans if v.get('is_vpn')],
|
[v for v in vlans if v.get('is_vpn')],
|
||||||
|
|
@ -55,9 +55,9 @@ def collect_tokens(cfg):
|
||||||
tokens['VPN_DNS_SERVER'] = str(overrides.get('dns_servers', ''))
|
tokens['VPN_DNS_SERVER'] = str(overrides.get('dns_servers', ''))
|
||||||
tokens['VPN_MTU'] = str(overrides.get('mtu', ''))
|
tokens['VPN_MTU'] = str(overrides.get('mtu', ''))
|
||||||
tokens['VPN_GATEWAY'] = vpn_gateway
|
tokens['VPN_GATEWAY'] = vpn_gateway
|
||||||
content = load_json(f'{PAGES_DIR}/vpn/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/vpn/content.json')
|
||||||
for table_item in iter_table_items(content.get('items', [])):
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
||||||
ds = table_item.get('datasource', '')
|
ds = table_item.get('datasource', '')
|
||||||
rows = live_vpn_sessions() if ds == 'live:vpn_sessions' else load_datasource(ds)
|
rows = live_vpn_sessions() if ds == 'live:vpn_sessions' else config_utils.load_datasource(ds)
|
||||||
tokens[table_token_key(ds)] = build_table(table_item, tokens, rows)
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, rows)
|
||||||
return tokens
|
return tokens
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue