diff --git a/docker/routlin-dash/app/pages/clientcredentials/action.py b/docker/routlin-dash/app/pages/clientcredentials/action.py
index 684201a..fd3dbd8 100644
--- a/docker/routlin-dash/app/pages/clientcredentials/action.py
+++ b/docker/routlin-dash/app/pages/clientcredentials/action.py
@@ -238,13 +238,15 @@ def addedit():
flash(f"Username '{username}' is already taken.", 'error')
return redirect(f'/{_PAGE}')
conn.close()
- flash(f"Credential '{username}' updated.", 'success')
+ if password:
+ flash(f'User account "{username}" updated with password "{password}".', 'success')
+ else:
+ flash(f'User account "{username}" updated.', 'success')
else:
if not password:
import secrets, string
password = ''.join(secrets.choice(string.ascii_lowercase + string.digits) for _ in range(8))
- flash(f"Auto-generated password for '{username}': {password}", 'success')
try:
hashed = _hash_password(password, digest_type)
@@ -267,7 +269,7 @@ def addedit():
flash(f"Username '{username}' already exists.", 'error')
return redirect(f'/{_PAGE}')
conn.close()
- flash(f"Credential '{username}' added.", 'success')
+ flash(f'User account "{username}" created with password "{password}".', 'success')
return redirect(f'/{_PAGE}')
diff --git a/docker/routlin-dash/app/pages/clientcredentials/content.json b/docker/routlin-dash/app/pages/clientcredentials/content.json
index 63d5fa3..5aafa9f 100644
--- a/docker/routlin-dash/app/pages/clientcredentials/content.json
+++ b/docker/routlin-dash/app/pages/clientcredentials/content.json
@@ -22,6 +22,24 @@
"type": "table",
"datasource": "sqlite:client_credentials",
"empty_message": "No credentials configured.",
+ "toolbar": {
+ "items": [
+ {
+ "type": "select",
+ "name": "type_filter",
+ "value": "all",
+ "filter_col": "user_type_label",
+ "options": "%TYPE_FILTER_OPTIONS%"
+ },
+ {
+ "type": "select",
+ "name": "vlan_filter",
+ "value": "all",
+ "filter_col": "vlan",
+ "options": "%CRED_VLAN_FILTER_OPTIONS%"
+ }
+ ]
+ },
"columns": [
{
"label": "Username",
@@ -42,6 +60,11 @@
"field": "vlan",
"class": "col-narrow col-mono"
},
+ {
+ "label": "Expiration",
+ "field": "expiration_label",
+ "class": "col-narrow"
+ },
{
"label": "Enabled",
"field": "enabled",
diff --git a/docker/routlin-dash/app/pages/clientcredentials/view.py b/docker/routlin-dash/app/pages/clientcredentials/view.py
index 4fb7b09..641d608 100644
--- a/docker/routlin-dash/app/pages/clientcredentials/view.py
+++ b/docker/routlin-dash/app/pages/clientcredentials/view.py
@@ -1,3 +1,4 @@
+import datetime
import json
import sqlite3
import time
@@ -48,6 +49,16 @@ def _load_credentials():
return []
+def _format_expiration(date_set, expires_seconds):
+ if not expires_seconds:
+ return 'Never'
+ expires_ts = (date_set or 0) + expires_seconds
+ now = int(time.time())
+ if expires_ts <= now:
+ return 'Expired'
+ return datetime.datetime.fromtimestamp(expires_ts).strftime('%Y-%m-%d')
+
+
def _format_session(session_seconds):
if not session_seconds:
return 'No limit'
@@ -75,7 +86,17 @@ def collect_tokens(cfg):
cfg.get('radius', {}).get('options', {}).get('default_expiration_seconds', 0) or 0
)
+ tokens['TYPE_FILTER_OPTIONS'] = (
+ ''
+ ''
+ ''
+ )
+
vlans = [v for v in cfg.get('vlans', []) if not v.get('is_vpn')]
+ tokens['CRED_VLAN_FILTER_OPTIONS'] = (
+ '' +
+ ''.join(f'' for v in vlans)
+ )
tokens['VLAN_OPTIONS'] = json.dumps(
[{'value': '', 'label': '-- Select VLAN --'}] +
[{'value': v['name'], 'label': f"{v['name']} (VLAN {v['vlan_id']})"} for v in vlans]
@@ -103,8 +124,9 @@ def collect_tokens(cfg):
for row in raw_rows:
r = dict(row)
r.pop('password', None)
- r['user_type_label'] = USER_TYPE_LABELS.get(r.get('user_type'), str(r.get('user_type', '')))
- r['expires_label'] = _format_session(r.get('session_seconds', 0))
+ r['user_type_label'] = USER_TYPE_LABELS.get(r.get('user_type'), str(r.get('user_type', '')))
+ r['expires_label'] = _format_session(r.get('session_seconds', 0))
+ r['expiration_label'] = _format_expiration(r.get('date_set', 0), r.get('expires_seconds', 0))
display_rows.append(r)
content = factory.load_json(f'{factory.PAGES_DIR}/clientcredentials/content.json')