powerdns-admin/powerdnsadmin/routes/dashboard.py
Jan Koppe 0d0339a316
fix #1485: allow more than 100 rows default in dashboard
The dashboard.domains_custom route was hardcoded to either return all
the domains, or at most 100, regardless of default_domain_table_size
setting.

Make this limit be dependent on default_domain_table_size instead.

The API will now limit to 100 or default_domain_table_size, whichever
one is higher. This is done to not break any seconday use-cases that
might depend on the hardcoded setting.
2023-03-29 14:52:00 +02:00

206 lines
7.1 KiB
Python

import datetime
from collections import namedtuple
from flask import Blueprint, render_template, url_for, current_app, request, jsonify, redirect, g, session, abort
from flask_login import login_required, current_user, login_manager
from sqlalchemy import not_
from ..decorators import operator_role_required
from ..models.user import User, Anonymous
from ..models.account import Account
from ..models.account_user import AccountUser
from ..models.domain import Domain
from ..models.domain_user import DomainUser
from ..models.setting import Setting
from ..models.history import History
from ..models.server import Server
from ..models.base import db
dashboard_bp = Blueprint('dashboard',
__name__,
template_folder='templates',
url_prefix='/dashboard')
class ZoneTabs:
"""Config data for the zone tabs on the dashboard."""
TabInfo = namedtuple('TabInfo', ['display_name', 'filter_pattern'])
"""Info about a single tab.
`display_name` is the name on the tab.
`filter_pattern` is a SQL LIKE pattern , which is case-insensitively matched against the zone
name (without the final root-dot).
If a filter is present, the tab will show zones that match the filter.
If no filter is present, the tab will show zones that are not matched by any other tab filter.
"""
tabs = {
'forward': TabInfo("", None),
'reverse_ipv4': TabInfo("in-addr.arpa", '%.in-addr.arpa'),
'reverse_ipv6': TabInfo("ip6.arpa", '%.ip6.arpa'),
}
"""Dict of unique tab id to a TabInfo."""
order = ['forward', 'reverse_ipv4', 'reverse_ipv6']
"""List of tab ids in the order they will appear."""
@dashboard_bp.before_request
def before_request():
# Check if user is anonymous
g.user = current_user
login_manager.anonymous_user = Anonymous
# Check site is in maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name not in [
'Administrator', 'Operator'
]:
return render_template('maintenance.html')
# Manage session timeout
session.permanent = True
current_app.permanent_session_lifetime = datetime.timedelta(
minutes=int(Setting().get('session_timeout')))
session.modified = True
@dashboard_bp.route('/domains-custom/<path:tab_id>', methods=['GET'])
@login_required
def domains_custom(tab_id):
if tab_id not in ZoneTabs.tabs:
abort(404)
if current_user.role.name in ['Administrator', 'Operator']:
domains = Domain.query
else:
# Get query for domain to which the user has access permission.
# This includes direct domain permission AND permission through
# account membership
domains = db.session.query(Domain) \
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
.outerjoin(Account, Domain.account_id == Account.id) \
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
.filter(
db.or_(
DomainUser.user_id == current_user.id,
AccountUser.user_id == current_user.id
))
template = current_app.jinja_env.get_template("dashboard_domain.html")
render = template.make_module(
vars={"current_user": current_user, "allow_user_view_history": Setting().get('allow_user_view_history')})
columns = [
Domain.name, Domain.dnssec, Domain.type, Domain.serial, Domain.master,
Domain.account_id
]
order_by = []
for i in range(len(columns)):
column_index = request.args.get("order[{0}][column]".format(i))
sort_direction = request.args.get("order[{0}][dir]".format(i))
if column_index is None:
break
if sort_direction != "asc" and sort_direction != "desc":
sort_direction = "asc"
column = columns[int(column_index)]
order_by.append(getattr(column, sort_direction)())
if order_by:
domains = domains.order_by(*order_by)
if ZoneTabs.tabs[tab_id].filter_pattern:
# If the tab has a filter, use only that
domains = domains.filter(Domain.name.ilike(ZoneTabs.tabs[tab_id].filter_pattern))
else:
# If the tab has no filter, use all the other filters in negated form
for tab_info in ZoneTabs.tabs.values():
if not tab_info.filter_pattern:
continue
domains = domains.filter(not_(Domain.name.ilike(tab_info.filter_pattern)))
total_count = domains.count()
search = request.args.get("search[value]")
if search:
start = "" if search.startswith("^") else "%"
end = "" if search.endswith("$") else "%"
if current_user.role.name in ['Administrator', 'Operator']:
domains = domains.outerjoin(Account).filter(
Domain.name.ilike(start + search.strip("^$") + end)
| Account.name.ilike(start + search.strip("^$") + end)
| Account.description.ilike(start + search.strip("^$") + end))
else:
domains = domains.filter(
Domain.name.ilike(start + search.strip("^$") + end))
filtered_count = domains.count()
start = int(request.args.get("start", 0))
length = min(int(request.args.get("length", 0)), max(100, int(Setting().get('default_domain_table_size'))))
if length != -1:
domains = domains[start:start + length]
data = []
for domain in domains:
data.append([
render.name(domain),
render.dnssec(domain),
render.type(domain),
render.serial(domain),
render.master(domain),
render.account(domain),
render.actions(domain),
])
response_data = {
"draw": int(request.args.get("draw", 0)),
"recordsTotal": total_count,
"recordsFiltered": filtered_count,
"data": data,
}
return jsonify(response_data)
@dashboard_bp.route('/', methods=['GET', 'POST'])
@login_required
def dashboard():
if not Setting().get('pdns_api_url') or not Setting().get(
'pdns_api_key') or not Setting().get('pdns_version'):
return redirect(url_for('admin.setting_pdns'))
BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates')
if not BG_DOMAIN_UPDATE:
current_app.logger.info('Updating zones in foreground...')
Domain().update()
else:
current_app.logger.info('Updating zones in background...')
show_bg_domain_button = BG_DOMAIN_UPDATE
if BG_DOMAIN_UPDATE and current_user.role.name not in ['Administrator', 'Operator']:
show_bg_domain_button = False
# Add custom boxes to render_template
return render_template('dashboard.html',
zone_tabs=ZoneTabs,
show_bg_domain_button=show_bg_domain_button,
pdns_version=Setting().get('pdns_version'))
@dashboard_bp.route('/domains-updater', methods=['GET', 'POST'])
@login_required
@operator_role_required
def domains_updater():
current_app.logger.debug('Update zones in background')
d = Domain().update()
response_data = {
"result": d,
}
return jsonify(response_data)