2026-06-02 00:47:03 -04:00
|
|
|
import json
|
2026-06-10 13:16:28 -04:00
|
|
|
import sqlite3
|
2026-06-10 10:06:13 -04:00
|
|
|
import time
|
|
|
|
|
from datetime import datetime, timezone
|
2026-06-07 00:21:08 -04:00
|
|
|
import config_utils
|
|
|
|
|
import factory
|
2026-06-02 00:47:03 -04:00
|
|
|
|
|
|
|
|
|
2026-06-10 10:06:13 -04:00
|
|
|
def _fmt_ts(ts):
|
|
|
|
|
try:
|
|
|
|
|
dt = datetime.fromtimestamp(int(ts), tz=timezone.utc)
|
|
|
|
|
return dt.strftime('%Y-%m-%d %H:%M UTC')
|
|
|
|
|
except Exception:
|
|
|
|
|
return '-'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _active_sessions_table():
|
2026-06-10 13:16:28 -04:00
|
|
|
try:
|
|
|
|
|
con = sqlite3.connect(config_utils.SESSIONS_DB, timeout=5)
|
|
|
|
|
rows = con.execute(
|
|
|
|
|
'SELECT session_id, email, access_level, created_at, last_seen'
|
|
|
|
|
' FROM sessions ORDER BY last_seen DESC'
|
|
|
|
|
).fetchall()
|
|
|
|
|
con.close()
|
|
|
|
|
except Exception:
|
|
|
|
|
rows = []
|
|
|
|
|
|
2026-06-10 10:06:13 -04:00
|
|
|
if not rows:
|
2026-06-10 13:16:28 -04:00
|
|
|
return '<p class="text-muted" style="margin:0">No active sessions.</p>'
|
|
|
|
|
|
2026-06-10 10:06:13 -04:00
|
|
|
now = int(time.time())
|
|
|
|
|
trs = ''
|
2026-06-10 13:16:28 -04:00
|
|
|
for sid, email, access_level, created_at, last_seen in rows:
|
|
|
|
|
online = (now - int(last_seen)) < 300
|
|
|
|
|
badge = (
|
|
|
|
|
'<span class="badge badge-enabled">Online</span>'
|
|
|
|
|
if online else
|
|
|
|
|
'<span class="badge badge-disabled">Offline</span>'
|
|
|
|
|
)
|
|
|
|
|
btn = (
|
|
|
|
|
f'<form method="post" action="/action/accountmanage/session_invalidate"'
|
|
|
|
|
f' style="display:inline;margin:0">'
|
|
|
|
|
f'<input type="hidden" name="session_id" value="{factory.e(sid)}">'
|
|
|
|
|
f'<button type="submit" class="btn btn-danger btn-sm">Invalidate</button>'
|
|
|
|
|
f'</form>'
|
|
|
|
|
)
|
2026-06-10 10:06:13 -04:00
|
|
|
trs += (
|
|
|
|
|
f'<tr>'
|
|
|
|
|
f'<td class="table-cell">{factory.e(email)}</td>'
|
|
|
|
|
f'<td class="table-cell">{factory.e(access_level)}</td>'
|
2026-06-10 13:16:28 -04:00
|
|
|
f'<td class="table-cell">{badge}</td>'
|
|
|
|
|
f'<td class="table-cell">{_fmt_ts(created_at)}</td>'
|
|
|
|
|
f'<td class="table-cell">{_fmt_ts(last_seen)}</td>'
|
|
|
|
|
f'<td class="table-cell">{btn}</td>'
|
2026-06-10 10:06:13 -04:00
|
|
|
f'</tr>'
|
|
|
|
|
)
|
|
|
|
|
return (
|
|
|
|
|
'<table class="data-table"><thead><tr>'
|
|
|
|
|
'<th class="table-header">Email</th>'
|
|
|
|
|
'<th class="table-header">Access Level</th>'
|
2026-06-10 13:16:28 -04:00
|
|
|
'<th class="table-header">Status</th>'
|
2026-06-10 10:06:13 -04:00
|
|
|
'<th class="table-header">Logged In</th>'
|
|
|
|
|
'<th class="table-header">Last Seen</th>'
|
2026-06-10 13:16:28 -04:00
|
|
|
'<th class="table-header"></th>'
|
2026-06-10 10:06:13 -04:00
|
|
|
'</tr></thead><tbody>' + trs + '</tbody></table>'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-06-02 00:47:03 -04:00
|
|
|
def collect_tokens(cfg):
|
2026-06-07 00:21:08 -04:00
|
|
|
tokens = config_utils.collect_layout_tokens(cfg)
|
2026-06-02 12:49:39 -04:00
|
|
|
tokens['ACCOUNT_LEVEL_OPTIONS'] = json.dumps([
|
|
|
|
|
{'value': 'viewer', 'label': 'Viewer (read-only access to live data)'},
|
|
|
|
|
{'value': 'administrator', 'label': 'Administrator (can modify configuration)'},
|
|
|
|
|
{'value': 'manager', 'label': 'Manager (full access including account management)'},
|
|
|
|
|
])
|
2026-06-10 10:06:13 -04:00
|
|
|
tokens['ACTIVE_SESSIONS_TABLE'] = _active_sessions_table()
|
2026-06-07 00:21:08 -04:00
|
|
|
content = factory.load_json(f'{factory.PAGES_DIR}/accountmanage/content.json')
|
|
|
|
|
for table_item in factory.iter_table_items(content.get('items', [])):
|
2026-06-02 12:49:39 -04:00
|
|
|
ds = table_item.get('datasource', '')
|
2026-06-07 00:21:08 -04:00
|
|
|
tokens[factory.table_token_key(ds)] = factory.build_table(table_item, tokens, config_utils.load_datasource(ds))
|
2026-06-02 12:49:39 -04:00
|
|
|
return tokens
|