Development

This commit is contained in:
Matthew Grotke 2026-06-07 00:21:08 -04:00
parent 563d82daf3
commit 70ccfe2c29
48 changed files with 549 additions and 578 deletions

View file

@ -1,16 +1,14 @@
from pathlib import Path
from flask import Blueprint, request, redirect, flash, session
from auth import require_level
from config_utils import (flush_pending_to_queue, get_dashboard_pending,
revert_group, revert_group_chain, queued_msg,
DASHBOARD_PENDING, _db)
import auth
import config_utils
_PAGE = Path(__file__).parent.name
bp = Blueprint(_PAGE, __name__)
@bp.route('/action/actions/pending_save', methods=['POST'])
@require_level('administrator')
@auth.require_level('administrator')
def pending_save():
session['apply_changes_immediately'] = 'apply_changes_immediately' in request.form
flash('Preference saved.', 'success')
@ -18,20 +16,20 @@ def pending_save():
@bp.route('/action/actions/pending_apply', methods=['POST'])
@require_level('administrator')
@auth.require_level('administrator')
def pending_apply():
pending = get_dashboard_pending()
pending = config_utils.get_dashboard_pending()
if not pending:
flash('No pending changes to apply.', 'info')
return redirect(f'/{_PAGE}')
flush_pending_to_queue()
config_utils.flush_pending_to_queue()
if any(cmd != 'fix problems' for _, _, cmd, _ in pending):
flash('Changes queued.', 'success')
return redirect(f'/{_PAGE}')
@bp.route('/action/actions/history_revert', methods=['POST'])
@require_level('administrator')
@auth.require_level('administrator')
def history_revert():
selected_uuids = request.form.getlist('selected_uuids')
if not selected_uuids:
@ -42,7 +40,7 @@ def history_revert():
return redirect(f'/{_PAGE}')
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:
flash(msg, 'error')
@ -56,14 +54,14 @@ def history_revert():
@bp.route('/action/actions/history_clear', methods=['POST'])
@require_level('manager')
@auth.require_level('manager')
def history_clear():
selected_uuids = request.form.getlist('selected_uuids')
if not selected_uuids:
flash('No items selected.', 'info')
return redirect(f'/{_PAGE}')
count = 0
conn = _db()
conn = config_utils._db()
try:
for uid in selected_uuids:
conn.execute('DELETE FROM changes WHERE group_id=?', (uid,))
@ -78,15 +76,15 @@ def history_clear():
@bp.route('/action/actions/pending_dismiss', methods=['POST'])
@require_level('manager')
@auth.require_level('manager')
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']
if not dismissible:
flash('No pending changes to dismiss.', 'info')
return redirect(f'/{_PAGE}')
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:
f.write(f'{u} {t} [{c}] ({usr})\n')
flash('Pending changes dismissed.', 'success')

View file

@ -2,20 +2,17 @@ import json
from collections import defaultdict
from datetime import datetime
from flask import session
from config_utils import (
collect_layout_tokens, get_dashboard_pending, load_all_groups, get_done_timestamps,
_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
import config_utils
import factory
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'
all_groups = load_all_groups()
all_groups = config_utils.load_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:
pgroups = defaultdict(list)
@ -39,8 +36,8 @@ def collect_tokens(cfg):
req_cell = '<td class="table-cell">-</td>'
rows += (
'<tr>'
f'<td class="table-cell">{e(cmd)}</td>'
f'<td class="table-cell">{e(users)}</td>'
f'<td class="table-cell">{factory.e(cmd)}</td>'
f'<td class="table-cell">{factory.e(users)}</td>'
f'{req_cell}'
'</tr>'
)
@ -59,13 +56,13 @@ def collect_tokens(cfg):
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['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 ''
)
done_ts_map = get_done_timestamps()
done_ts_map = config_utils.get_done_timestamps()
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']}
hist_rows = ''
hist_onclick = (
@ -89,28 +86,28 @@ def collect_tokens(cfg):
item = g.get('item_value') or ''
summary_text = f'{verb} {g["parent_path"]}: {item}' if item else f'{verb} {g["parent_path"]}'
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:
summary = e(summary_text)
summary = factory.e(summary_text)
snap_tag = (
f'<div class="tag-list"><span class="tag" data-tooltip="{e(uuid)}" data-uuid="{e(uuid)}">'
f'<span class="tl-full">{e(uuid[:8])}</span>'
f'<span class="tl-short">{e(uuid[:8])}</span>'
f'<span class="tl-min">{e(uuid[:8])}</span>'
f'<div class="tag-list"><span class="tag" data-tooltip="{factory.e(uuid)}" data-uuid="{factory.e(uuid)}">'
f'<span class="tl-full">{factory.e(uuid[:8])}</span>'
f'<span class="tl-short">{factory.e(uuid[:8])}</span>'
f'<span class="tl-min">{factory.e(uuid[:8])}</span>'
'</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 '')
hist_rows += (
f'<tr class="row-expandable" data-uuid="{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">{e(dt_str)}</td>'
f'<tr class="row-expandable" data-uuid="{factory.e(uuid)}" {hist_onclick}>'
f'<td class="table-cell"><input type="checkbox" name="selected_uuids" value="{factory.e(uuid)}" {cb_attrs}/></td>'
f'<td class="table-cell">{factory.e(dt_str)}</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_user}</td>'
'</tr>'
f'{snap_expand_row(changes, 6)}'
f'{factory.snap_expand_row(changes, 6)}'
)
select_all = (
'<input type="checkbox" '