180 lines
6.8 KiB
Python
180 lines
6.8 KiB
Python
import os, json, sys, importlib.util as _importlib_util
|
|
from flask import Flask, Blueprint, session, redirect, get_flashed_messages, send_from_directory
|
|
from markupsafe import Markup
|
|
from config_utils import (
|
|
ACCOUNTS_FILE, APP_DIR, CONFIGS_DIR, HEALTH_FILE, WWW_DIR,
|
|
load_config, queue_command, _find_cmd_in_queues,
|
|
)
|
|
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.bannedips.action import bp as bannedips_bp
|
|
from pages.ddns.action import bp as ddns_bp
|
|
from pages.dhcpreservations.action import bp as dhcpreservations_bp
|
|
from pages.dnsblocking.action import bp as dnsblocking_bp
|
|
from pages.dnsserver.action import bp as dnsserver_bp
|
|
from pages.hostoverrides.action import bp as hostoverrides_bp
|
|
from pages.intervlan.action import bp as intervlan_bp
|
|
from pages.accountlogin.action import bp as accountlogin_bp
|
|
from pages.networklayout.action import bp as networklayout_bp
|
|
from pages.physicalinterfaces.action import bp as physicalinterfaces_bp
|
|
from pages.portforwarding.action import bp as portforwarding_bp
|
|
from pages.portwrangling.action import bp as portwrangling_bp
|
|
from pages.preferences.action import bp as preferences_bp
|
|
from pages.accountverifyemail.action import bp as accountverifyemail_bp
|
|
from pages.vpn.action import bp as vpn_bp
|
|
from pages.accountcreate.action import bp as accountcreate_bp
|
|
from pages.accountmanage.action import bp as accountmanage_bp
|
|
from pages.mdns.action import bp as mdns_bp
|
|
from pages.radius.action import bp as radius_bp
|
|
from action_accountlogout import bp as accountlogout_bp
|
|
from api_apply_health import bp as api_apply_health_bp
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = os.environ.get('SECRET_KEY', os.urandom(24))
|
|
|
|
# Static www/ serving =================================================
|
|
|
|
@app.route('/www/<path:filename>')
|
|
def serve_www(filename):
|
|
response = send_from_directory(WWW_DIR, filename)
|
|
if settings.is_production():
|
|
response.cache_control.max_age = 86400
|
|
response.cache_control.public = True
|
|
return response
|
|
|
|
# View blueprint ======================================================
|
|
|
|
bp = Blueprint('view_page', __name__)
|
|
|
|
page_view_cache = {}
|
|
|
|
def load_page_view(page_name):
|
|
if page_name not in page_view_cache:
|
|
path = os.path.join(PAGES_DIR, page_name, 'view.py')
|
|
if not os.path.exists(path):
|
|
page_view_cache[page_name] = None
|
|
else:
|
|
spec = _importlib_util.spec_from_file_location(f'page_view_{page_name}', path)
|
|
mod = _importlib_util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
page_view_cache[page_name] = mod
|
|
return page_view_cache[page_name]
|
|
|
|
|
|
@bp.route('/')
|
|
def index():
|
|
return serve_view('overview')
|
|
|
|
@bp.route('/<page_name>')
|
|
def view(page_name):
|
|
return serve_view(page_name)
|
|
|
|
def serve_view(page_name):
|
|
view_def = load_json(os.path.join(PAGES_DIR, page_name, 'content.json'))
|
|
if not view_def:
|
|
from flask import abort
|
|
abort(404)
|
|
|
|
view_req = view_def.get('client_requirement')
|
|
level = client_level()
|
|
if not passes(view_req, level):
|
|
return redirect('/overview' if level > 0 else '/accountlogin')
|
|
|
|
cfg = load_config()
|
|
|
|
if level >= LEVEL_RANK['administrator']:
|
|
try:
|
|
st = json.load(open(HEALTH_FILE))
|
|
has_problems = any(
|
|
item.get('status') == 'problem'
|
|
for section in ('configurations', 'logs', 'services')
|
|
for item in st.get(section, [])
|
|
)
|
|
if has_problems:
|
|
fix_uuid, _ = _find_cmd_in_queues('fix problems')
|
|
if fix_uuid is None:
|
|
queue_command('fix problems', user=session.get('email_address', ''))
|
|
except Exception:
|
|
pass
|
|
|
|
page_view = load_page_view(page_name)
|
|
tokens = {}
|
|
if page_view and hasattr(page_view, 'collect_tokens'):
|
|
tokens.update(page_view.collect_tokens(cfg))
|
|
|
|
if page_name == 'radius' and not os.path.exists(f'{CONFIGS_DIR}/.radius-secret'):
|
|
queue_command('gen radius')
|
|
|
|
flash_html = ''
|
|
for category, message in get_flashed_messages(with_categories=True):
|
|
variant = {'error': 'danger', 'warning': 'warning', 'success': 'success'}.get(category, 'info')
|
|
msg_html = message if isinstance(message, Markup) else e(message)
|
|
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)
|
|
return render_layout(page_name, content_html, tokens, page_name=page_name)
|
|
|
|
# Register blueprints =================================================
|
|
|
|
app.register_blueprint(bp)
|
|
app.register_blueprint(actions_bp)
|
|
app.register_blueprint(bannedips_bp)
|
|
app.register_blueprint(ddns_bp)
|
|
app.register_blueprint(dhcpreservations_bp)
|
|
app.register_blueprint(dnsblocking_bp)
|
|
app.register_blueprint(dnsserver_bp)
|
|
app.register_blueprint(hostoverrides_bp)
|
|
app.register_blueprint(intervlan_bp)
|
|
app.register_blueprint(accountlogin_bp)
|
|
app.register_blueprint(networklayout_bp)
|
|
app.register_blueprint(physicalinterfaces_bp)
|
|
app.register_blueprint(portforwarding_bp)
|
|
app.register_blueprint(portwrangling_bp)
|
|
app.register_blueprint(preferences_bp)
|
|
app.register_blueprint(accountverifyemail_bp)
|
|
app.register_blueprint(vpn_bp)
|
|
app.register_blueprint(accountcreate_bp)
|
|
app.register_blueprint(accountmanage_bp)
|
|
app.register_blueprint(accountlogout_bp)
|
|
app.register_blueprint(mdns_bp)
|
|
app.register_blueprint(radius_bp)
|
|
app.register_blueprint(api_apply_health_bp)
|
|
|
|
|
|
def _seed_initial_account():
|
|
email = os.environ.get('INITIAL_MANAGER_EMAIL', '').strip().lower()
|
|
if not email:
|
|
try:
|
|
with open(ACCOUNTS_FILE) as f:
|
|
data = json.load(f)
|
|
except Exception:
|
|
data = {'accounts': []}
|
|
if not data.get('accounts'):
|
|
print('[main] WARNING: No accounts exist and INITIAL_MANAGER_EMAIL is not set. '
|
|
'Set it in docker-compose.yml to seed the initial manager account.', file=sys.stderr)
|
|
return
|
|
try:
|
|
with open(ACCOUNTS_FILE) as f:
|
|
data = json.load(f)
|
|
except Exception:
|
|
data = {'accounts': []}
|
|
if data.get('accounts'):
|
|
return
|
|
data['accounts'] = [{
|
|
'email_address': email,
|
|
'access_level': 'manager',
|
|
'hashed_password': '',
|
|
'timezone': '',
|
|
}]
|
|
with open(ACCOUNTS_FILE, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
print(f'[main] Seeded initial manager account: {email}', file=sys.stderr)
|
|
|
|
_seed_initial_account()
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=25327)
|