Development
This commit is contained in:
parent
3c7b13e98a
commit
67d951e853
2 changed files with 88 additions and 67 deletions
|
|
@ -217,10 +217,10 @@ def flush_pending_to_queue():
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _queue_pending_command(cmd):
|
def _queue_pending_command(cmd, user=None):
|
||||||
"""Append cmd to .dashboard-pending if not already present for this cmd+user."""
|
"""Append cmd to .dashboard-pending if not already present for this cmd+user."""
|
||||||
existing = _read_dashboard_pending()
|
existing = _read_dashboard_pending()
|
||||||
current_user = session.get('email_address', 'unknown')
|
current_user = user or session.get('email_address', 'unknown')
|
||||||
for entry_uuid, entry_ts, entry_cmd, entry_user in existing:
|
for entry_uuid, entry_ts, entry_cmd, entry_user in existing:
|
||||||
if entry_cmd == cmd and entry_user == current_user:
|
if entry_cmd == cmd and entry_user == current_user:
|
||||||
return entry_uuid, entry_ts
|
return entry_uuid, entry_ts
|
||||||
|
|
@ -238,12 +238,12 @@ def _queue_pending_presigned(cmd, entry_uuid, entry_ts):
|
||||||
f.write(f'{entry_uuid} {entry_ts} [{cmd}] ({current_user})\n')
|
f.write(f'{entry_uuid} {entry_ts} [{cmd}] ({current_user})\n')
|
||||||
|
|
||||||
|
|
||||||
def _queue_command(cmd):
|
def _queue_command(cmd, user=None):
|
||||||
if not _apply_changes_immediately():
|
if not _apply_changes_immediately():
|
||||||
return _queue_pending_command(cmd)
|
return _queue_pending_command(cmd, user=user)
|
||||||
done_set = _load_done_set()
|
done_set = _load_done_set()
|
||||||
pending = _read_pending(done_set)
|
pending = _read_pending(done_set)
|
||||||
current_user = session.get('email_address', 'unknown')
|
current_user = user or session.get('email_address', 'unknown')
|
||||||
for entry_uuid, entry_ts, entry_cmd, entry_user in pending:
|
for entry_uuid, entry_ts, entry_cmd, entry_user in pending:
|
||||||
if entry_cmd == cmd and entry_user == current_user:
|
if entry_cmd == cmd and entry_user == current_user:
|
||||||
return entry_uuid, entry_ts
|
return entry_uuid, entry_ts
|
||||||
|
|
@ -255,6 +255,18 @@ def _queue_command(cmd):
|
||||||
return entry_uuid, entry_ts
|
return entry_uuid, entry_ts
|
||||||
|
|
||||||
|
|
||||||
|
def _find_cmd_in_queues(cmd):
|
||||||
|
"""Return (uuid, ts) of first matching entry in .dashboard-pending or .dashboard-queue, or (None, None)."""
|
||||||
|
for entry_uuid, entry_ts, entry_cmd, entry_user in _read_dashboard_pending():
|
||||||
|
if entry_cmd == cmd:
|
||||||
|
return entry_uuid, entry_ts
|
||||||
|
done_set = _load_done_set()
|
||||||
|
for entry_uuid, entry_ts, entry_cmd, entry_user in _read_pending(done_set):
|
||||||
|
if entry_cmd == cmd:
|
||||||
|
return entry_uuid, entry_ts
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def _entry_ts_from_queue(entry_uuid):
|
def _entry_ts_from_queue(entry_uuid):
|
||||||
try:
|
try:
|
||||||
for line in open(DASHBOARD_QUEUE).read().splitlines():
|
for line in open(DASHBOARD_QUEUE).read().splitlines():
|
||||||
|
|
@ -309,9 +321,9 @@ def _build_timing_msg(entry_ts, action_label='Configuration saved'):
|
||||||
return _timing_status_msg(entry_ts, action_label)
|
return _timing_status_msg(entry_ts, action_label)
|
||||||
|
|
||||||
|
|
||||||
def queue_command(cmd, description=''):
|
def queue_command(cmd, description='', user=None):
|
||||||
"""Queue a command without generating a flash message. description is ignored (kept for compat)."""
|
"""Queue a command without generating a flash message. description is ignored (kept for compat)."""
|
||||||
return _queue_command(cmd)
|
return _queue_command(cmd, user=user)
|
||||||
|
|
||||||
|
|
||||||
def queued_msg(cmd=None, description='', action_label='Configuration saved'):
|
def queued_msg(cmd=None, description='', action_label='Configuration saved'):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import json, re, subprocess, os, sys, html as html_mod
|
||||||
import sanitize
|
import sanitize
|
||||||
import validation as validate
|
import validation as validate
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from config_utils import config_hash, get_pending_entries, get_dashboard_pending, get_dashboard_done, load_snapshot_for_uuid, load_all_snapshots, get_done_timestamps, queue_command, _apply_changes_immediately, _seconds_until_next_run, _format_timing, _is_locked, _lock_mtime, WEB_APP_DISPLAY_NAME, CONFIGS_DIR, DATA_DIR
|
from config_utils import config_hash, get_pending_entries, get_dashboard_pending, get_dashboard_done, load_snapshot_for_uuid, load_all_snapshots, get_done_timestamps, queue_command, _find_cmd_in_queues, _apply_changes_immediately, _seconds_until_next_run, _format_timing, _is_locked, _lock_mtime, WEB_APP_DISPLAY_NAME, CONFIGS_DIR, DATA_DIR
|
||||||
|
|
||||||
bp = Blueprint('view_page', __name__)
|
bp = Blueprint('view_page', __name__)
|
||||||
|
|
||||||
|
|
@ -1660,72 +1660,81 @@ def render_layout(view_id, content_html, tokens):
|
||||||
if o_user in seen_other_users:
|
if o_user in seen_other_users:
|
||||||
continue
|
continue
|
||||||
seen_other_users.add(o_user)
|
seen_other_users.add(o_user)
|
||||||
|
_display_user = 'Another user' if o_user in ('unknown', '') else e(o_user)
|
||||||
if locked and lock_mtime and o_ts < lock_mtime:
|
if locked and lock_mtime and o_ts < lock_mtime:
|
||||||
text = f'{e(o_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 = _format_timing(secs)
|
||||||
text = f'{e(o_user)} has pending changes which will be applied {timing}.' if timing else f'{e(o_user)} has pending changes. The processing service is not running.'
|
text = f'{_display_user} has pending changes which will be applied {timing}.' if timing else f'{_display_user} has pending changes. The processing service is not running.'
|
||||||
cls = 'info-bar-warning'
|
cls = 'info-bar-warning'
|
||||||
other_bars += f'<div class="info-bar {cls}" data-apply-uuid="{e(o_uuid)}" data-apply-user="{e(o_user)}"><span>{text}</span></div>\n'
|
other_bars += f'<div class="info-bar {cls}" data-apply-uuid="{e(o_uuid)}" data-apply-user="{e(o_user)}"><span>{text}</span></div>\n'
|
||||||
|
|
||||||
problem_bars = ''
|
problem_bars = ''
|
||||||
try:
|
if level >= LEVEL_RANK['viewer']:
|
||||||
import json as _j
|
try:
|
||||||
st = _j.load(open(f'{CONFIGS_DIR}/.health'))
|
import json as _j
|
||||||
grouped = {'error': [], 'warning': []}
|
st = _j.load(open(f'{CONFIGS_DIR}/.health'))
|
||||||
for section in ('configurations', 'logs'):
|
grouped = {'error': [], 'warning': []}
|
||||||
for item in st.get(section, []):
|
for section in ('configurations', 'logs'):
|
||||||
|
for item in st.get(section, []):
|
||||||
|
if item.get('status') == 'problem':
|
||||||
|
sev = item.get('severity', 'error')
|
||||||
|
text = e(item.get('detail', item.get('name', '')))
|
||||||
|
grouped.setdefault(sev, []).append(text)
|
||||||
|
for item in st.get('services', []):
|
||||||
if item.get('status') == 'problem':
|
if item.get('status') == 'problem':
|
||||||
sev = item.get('severity', 'error')
|
name = item.get('name', '')
|
||||||
text = e(item.get('detail', item.get('name', '')))
|
utype = 'timer' if name.endswith('.timer') else 'service' if name.endswith('.service') else 'unit'
|
||||||
grouped.setdefault(sev, []).append(text)
|
exp_parts, act_parts = [], []
|
||||||
for item in st.get('services', []):
|
if not item.get('active_ok'):
|
||||||
if item.get('status') == 'problem':
|
exp_parts.append(item.get('expected_active', 'active'))
|
||||||
name = item.get('name', '')
|
act_parts.append(item.get('active', 'unknown'))
|
||||||
utype = 'timer' if name.endswith('.timer') else 'service' if name.endswith('.service') else 'unit'
|
if not item.get('enabled_ok'):
|
||||||
exp_parts, act_parts = [], []
|
exp_parts.append(item.get('expected_enabled', 'enabled'))
|
||||||
if not item.get('active_ok'):
|
act_parts.append(item.get('enabled', 'unknown'))
|
||||||
exp_parts.append(item.get('expected_active', 'active'))
|
detail = (f"The {utype} `{name}` is expected to be "
|
||||||
act_parts.append(item.get('active', 'unknown'))
|
f"{' and '.join(exp_parts)} but is {' and '.join(act_parts)}.")
|
||||||
if not item.get('enabled_ok'):
|
grouped.setdefault(item.get('severity', 'error'), []).append(e(detail))
|
||||||
exp_parts.append(item.get('expected_enabled', 'enabled'))
|
has_problems = any(items for items in grouped.values())
|
||||||
act_parts.append(item.get('enabled', 'unknown'))
|
fix_suffix = ''
|
||||||
detail = (f"The {utype} `{name}` is expected to be "
|
fix_uuid = None
|
||||||
f"{' and '.join(exp_parts)} but is {' and '.join(act_parts)}.")
|
if has_problems:
|
||||||
grouped.setdefault(item.get('severity', 'error'), []).append(e(detail))
|
if level < LEVEL_RANK['administrator']:
|
||||||
has_problems = any(items for items in grouped.values())
|
fix_suffix = 'Please contact an administrator.'
|
||||||
fix_suffix = ''
|
|
||||||
if has_problems:
|
|
||||||
fix_uuid, fix_ts = queue_command('core apply')
|
|
||||||
if _apply_changes_immediately():
|
|
||||||
if _is_locked():
|
|
||||||
mtime = _lock_mtime()
|
|
||||||
fix_suffix = ('Fix is being applied now...' if fix_ts and mtime and fix_ts < mtime
|
|
||||||
else 'Fix will be applied on the next run.')
|
|
||||||
else:
|
else:
|
||||||
timing = _format_timing(_seconds_until_next_run())
|
fix_uuid, fix_ts = _find_cmd_in_queues('fix problems')
|
||||||
fix_suffix = (f'Fix will be applied {timing}.' if timing
|
if fix_uuid is None:
|
||||||
else 'Fix pending. The processing service is not running.')
|
fix_uuid, fix_ts = queue_command('fix problems', user=current_user)
|
||||||
else:
|
if _apply_changes_immediately():
|
||||||
fix_suffix = 'Fix pending. Visit the <strong>Actions</strong> page ASAP to apply fix.'
|
if _is_locked():
|
||||||
for sev, items in grouped.items():
|
mtime = _lock_mtime()
|
||||||
if not items:
|
fix_suffix = ('Fix is being applied now...' if fix_ts and mtime and fix_ts < mtime
|
||||||
continue
|
else 'Fix will be applied on the next run.')
|
||||||
cls = 'info-bar-danger' if sev == 'error' else 'info-bar-warning'
|
else:
|
||||||
problems_list = ('<ul style="margin:0.25em 0;padding-left:1.25em">'
|
timing = _format_timing(_seconds_until_next_run())
|
||||||
+ ''.join(f'<li>{d}</li>' for d in items)
|
fix_suffix = (f'Fix will be applied {timing}.' if timing
|
||||||
+ '</ul>')
|
else 'Fix pending. The processing service is not running.')
|
||||||
uuid_attr = f' data-health-uuid="{e(fix_uuid)}"' if _apply_changes_immediately() else ''
|
else:
|
||||||
fix_html = (f'<div style="margin-top:0.5em"{uuid_attr}>{fix_suffix}</div>'
|
fix_suffix = 'Fix pending. Visit the <strong>Actions</strong> page ASAP to apply fix.'
|
||||||
if fix_suffix else '')
|
for sev, items in grouped.items():
|
||||||
content = ('<div style="width:100%">'
|
if not items:
|
||||||
'<div style="font-weight:600;margin-bottom:0.25em">Health check - problems found:</div>'
|
continue
|
||||||
+ problems_list + fix_html
|
cls = 'info-bar-danger' if sev == 'error' else 'info-bar-warning'
|
||||||
+ '</div>')
|
problems_list = ('<ul style="margin:0.25em 0;padding-left:1.25em">'
|
||||||
problem_bars += f'<div class="info-bar {cls}">{content}</div>\n'
|
+ ''.join(f'<li>{d}</li>' for d in items)
|
||||||
except Exception:
|
+ '</ul>')
|
||||||
pass
|
uuid_attr = (f' data-health-uuid="{e(fix_uuid)}"'
|
||||||
|
if fix_uuid and _apply_changes_immediately() else '')
|
||||||
|
fix_html = (f'<div style="margin-top:0.5em"{uuid_attr}>{fix_suffix}</div>'
|
||||||
|
if fix_suffix else '')
|
||||||
|
content = ('<div style="width:100%">'
|
||||||
|
'<div style="font-weight:600;margin-bottom:0.25em">Health check - problems found:</div>'
|
||||||
|
+ problems_list + fix_html
|
||||||
|
+ '</div>')
|
||||||
|
problem_bars += f'<div class="info-bar {cls}">{content}</div>\n'
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
pending_bar = ''
|
pending_bar = ''
|
||||||
if has_pending_alert and not problem_bars and view_id != 'view_actions':
|
if has_pending_alert and not problem_bars and view_id != 'view_actions':
|
||||||
|
|
@ -2610,7 +2619,7 @@ function startPoller(uuid, handlers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function startApplyPoller(uuid, bar, mine) {
|
function startApplyPoller(uuid, bar, mine) {
|
||||||
function user() { return bar.getAttribute('data-apply-user') || ''; }
|
function user() { var u = bar.getAttribute('data-apply-user') || ''; return (u === 'unknown' || u === '') ? 'Another user' : u; }
|
||||||
function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||||
startPoller(uuid, {
|
startPoller(uuid, {
|
||||||
onPending: function(nextIn) {
|
onPending: function(nextIn) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue