class AdminPanel
Admin configuration interface component
/tf/active/vicechatdev/CDocs/ui/admin_panel.py
69 - 1697
moderate
Purpose
Admin configuration interface component
Source Code
class AdminPanel(param.Parameterized):
"""Admin configuration interface component"""
current_tab = param.String(default='dashboard')
def __init__(self, template, session_manager=None, parent_app=None, embedded=False, **params):
super().__init__(**params)
self.template = template
self.session_manager = session_manager # This may be None now
self.parent_app = parent_app # Store reference to parent app
self.user = self._get_current_user()
self.notification_area = pn.pane.Markdown("")
self.embedded = embedded # Flag for embedded mode
# Add status_area attribute
self.status_area = pn.Column(sizing_mode='stretch_width')
# Set up the user interface
if not embedded:
# Only set up template components in standalone mode
self._setup_header()
self._setup_sidebar()
# Set up the main content area
self._setup_main_area()
# Verify that the user has admin permissions
if self.user and self._verify_admin_access():
# Load initial dashboard
self._load_admin_dashboard()
else:
self.notification_area.object = "**Error:** You do not have permission to access the admin panel."
def _get_current_user(self):
"""Get current user from session"""
# Try to get user from parent app first
if hasattr(self, 'parent_app') and self.parent_app is not None:
if hasattr(self.parent_app, 'current_user'):
return self.parent_app.current_user
# Fall back to session manager if available
if hasattr(self, 'session_manager') and self.session_manager is not None:
return self.session_manager.get_current_user()
# Otherwise return None
return None
def _verify_admin_access(self) -> bool:
"""Verify that the user has admin permissions"""
if not self.user:
return False
# First check direct admin permission
has_admin_permission = permissions.user_has_permission(self.user, "ADMIN")
# Also check if user has ADMIN role directly
has_admin_role = False
if hasattr(self.user, 'roles') and isinstance(self.user.roles, (list, tuple, set)):
has_admin_role = 'ADMIN' in self.user.roles
# Log the results
logger.debug(f"User admin permission check: {has_admin_permission}, role check: {has_admin_role}")
# Expensive operations - only do if necessary
if not (has_admin_permission or has_admin_role):
# Check system settings permissions as a fallback
has_settings_permission = permissions.user_has_permission(self.user, "MANAGE_SYSTEM_SETTINGS")
logger.debug(f"User settings permission check: {has_settings_permission}")
return has_settings_permission
return has_admin_permission or has_admin_role
def _setup_header(self):
"""Set up the header with title and actions"""
# Create back button
back_btn = Button(
name='Back to Dashboard',
button_type='default',
width=150
)
back_btn.on_click(self._navigate_back)
# Create refresh button
refresh_btn = Button(
name='Refresh',
button_type='default',
width=100
)
refresh_btn.on_click(self._refresh_current_view)
# Header with buttons
header = Row(
pn.pane.Markdown("# Admin Panel"),
refresh_btn,
back_btn,
sizing_mode='stretch_width',
align='end'
)
self.template.header.append(header)
def _setup_sidebar(self):
"""Set up the sidebar with navigation options"""
# Create navigation buttons
dashboard_btn = Button(
name='System Dashboard',
button_type='primary',
width=200
)
dashboard_btn.on_click(self._load_admin_dashboard)
users_btn = Button(
name='User Management',
button_type='default',
width=200
)
users_btn.on_click(self._load_user_management)
# Add notifications button
notifications_btn = Button(
name='Notifications',
button_type='default',
width=200
)
notifications_btn.on_click(self._load_notification_management)
departments_btn = Button(
name='Departments',
button_type='default',
width=200
)
departments_btn.on_click(self._load_department_management)
doc_types_btn = Button(
name='Document Types',
button_type='default',
width=200
)
doc_types_btn.on_click(self._load_document_type_management)
settings_btn = Button(
name='System Settings',
button_type='default',
width=200
)
settings_btn.on_click(self._load_system_settings)
backup_btn = Button(
name='Backup & Restore',
button_type='default',
width=200
)
backup_btn.on_click(self._load_backup_restore)
# Add to navigation area
navigation = Column(
Markdown("## Navigation"),
dashboard_btn,
users_btn,
notifications_btn, # Add notifications to navigation
departments_btn,
doc_types_btn,
settings_btn,
backup_btn,
sizing_mode='fixed'
)
self.template.sidebar.append(navigation)
# Add system status summary
self.status_area = Column(
Markdown("## System Status"),
Markdown("*Loading status...*"),
sizing_mode='fixed',
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded']
)
self.template.sidebar.append(self.status_area)
# Update system status
self._update_system_status()
def _setup_main_area(self):
"""Set up the main content area."""
try:
# Create main content container
if not hasattr(self, 'main_content'):
self.main_content = pn.Column(sizing_mode='stretch_width')
# Create tabs for navigation if not in embedded mode
if not self.embedded:
# Create tabs - add Notifications tab
self.tabs = pn.Tabs(
('Dashboard', pn.Column(sizing_mode='stretch_width')),
('Users', pn.Column(sizing_mode='stretch_width')),
('Notifications', pn.Column(sizing_mode='stretch_width')), # Add this tab
('Departments', pn.Column(sizing_mode='stretch_width')),
('Document Types', pn.Column(sizing_mode='stretch_width')),
('Settings', pn.Column(sizing_mode='stretch_width')),
('Backup & Restore', pn.Column(sizing_mode='stretch_width')),
sizing_mode='stretch_width'
)
# Handle tab changes - update indices
def handle_tab_change(event):
tab_index = event.new
if tab_index == 0:
self._load_admin_dashboard()
elif tab_index == 1:
self._load_user_management()
elif tab_index == 2:
self._load_notification_management() # Add notification management
elif tab_index == 3:
self._load_department_management()
elif tab_index == 4:
self._load_document_type_management()
elif tab_index == 5:
self._load_system_settings()
elif tab_index == 6:
self._load_backup_restore()
# Register callback
self.tabs.param.watch(handle_tab_change, 'active')
# Add tabs to main content
self.main_content.append(self.tabs)
except Exception as e:
logger.error(f"Error setting up main area: {e}")
def _refresh_current_view(self, event=None):
"""Refresh the current view"""
if self.current_tab == 'dashboard':
self._load_admin_dashboard()
elif self.current_tab == 'users':
self._load_user_management()
elif self.current_tab == 'notifications': # Add this case
self._load_notification_management()
elif self.current_tab == 'departments':
self._load_department_management()
elif self.current_tab == 'doc_types':
self._load_document_type_management()
elif self.current_tab == 'settings':
self._load_system_settings()
elif self.current_tab == 'backup':
self._load_backup_restore()
def _navigate_back(self, event=None):
"""Navigate back to dashboard"""
# Check if we have a parent app reference
if hasattr(self, 'parent_app') and self.parent_app is not None:
# Use parent app's load_dashboard method
try:
self.parent_app.load_dashboard()
return
except Exception as e:
logger.error(f"Error navigating back to dashboard via parent app: {e}")
# Fallback to direct state manipulation
try:
import panel as pn
pn.state.execute("window.location.href = '/dashboard'")
except Exception as e:
logger.error(f"Error navigating back to dashboard: {e}")
def _update_system_status(self):
"""Update system status information in the sidebar."""
try:
# Create status_area if it doesn't exist
if not hasattr(self, 'status_area'):
self.status_area = pn.Column(sizing_mode='stretch_width')
# Update status area
self.status_area.clear()
# Get system stats
try:
from CDocs.controllers.admin_controller import get_system_stats
stats = get_system_stats()
# Format uptime
uptime_seconds = stats.get('uptime', 0)
days, remainder = divmod(uptime_seconds, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, seconds = divmod(remainder, 60)
uptime_str = f"{days}d {hours}h {minutes}m"
# Create status summary
status_info = f"""
### System Status
**Users:** {stats.get('active_users', 0)}/{stats.get('total_users', 0)} active
**Documents:** {stats.get('active_documents', 0)}/{stats.get('total_documents', 0)} active
**Reviews:** {stats.get('pending_reviews', 0)} pending
**Approvals:** {stats.get('pending_approvals', 0)} pending
**Uptime:** {uptime_str}
"""
self.status_area.append(pn.pane.Markdown(status_info))
except Exception as e:
logger.warning(f"Error getting system stats: {e}")
self.status_area.append(pn.pane.Markdown(
"### System Status\n\n*Status information unavailable*"
))
except Exception as e:
logger.error(f"Error updating system status: {e}")
def _format_duration(self, seconds):
"""Format duration in seconds to readable string"""
if seconds < 60:
return f"{seconds} seconds"
minutes, seconds = divmod(seconds, 60)
if minutes < 60:
return f"{minutes} minutes, {seconds} seconds"
hours, minutes = divmod(minutes, 60)
if hours < 24:
return f"{hours} hours, {minutes} minutes"
days, hours = divmod(hours, 24)
return f"{days} days, {hours} hours"
def _load_admin_dashboard(self, event=None):
"""Load the admin dashboard view"""
try:
self.current_tab = 'dashboard'
self.notification_area.object = "Loading dashboard data..."
self.main_content.clear()
# Update parent sidebar if available
self.update_parent_sidebar()
# Get system statistics with fallback
try:
stats = get_system_stats()
except Exception as e:
logger.warning(f"Error fetching system stats: {e}")
stats = {
'active_users': 0,
'total_users': 0,
'active_documents': 0,
'total_documents': 0,
'pending_reviews': 0,
'total_reviews': 0,
'pending_approvals': 0,
'total_approvals': 0,
'uptime': 0
}
# Get document statistics with fallback
try:
doc_stats = get_document_stats()
except Exception as e:
logger.warning(f"Error fetching document stats: {e}")
doc_stats = {
'status_distribution': {'DRAFT': 0, 'IN_REVIEW': 0, 'APPROVED': 0, 'PUBLISHED': 0},
'type_distribution': {'SOP': 0, 'POLICY': 0, 'WORK_INSTRUCTION': 0}
}
# Get recent user activity with fallback
try:
user_activity = get_user_activity(limit=10)
except Exception as e:
logger.warning(f"Error fetching user activity: {e}")
user_activity = []
# Create dashboard container
dashboard = Column(
Markdown("# System Dashboard"),
sizing_mode='stretch_width'
)
# Create activity summary cards
active_users = stats.get('active_users', 0)
total_users = stats.get('total_users', 0)
user_percent = 0
if total_users > 0:
user_percent = int((active_users / total_users) * 100)
active_docs = stats.get('active_documents', 0)
total_docs = stats.get('total_documents', 0)
doc_percent = 0
if total_docs > 0:
doc_percent = int((active_docs / total_docs) * 100)
# Create metrics row
metrics_row = Row(
self._create_metric_card("Users", active_users, total_users, user_percent),
self._create_metric_card("Documents", active_docs, total_docs, doc_percent),
self._create_metric_card("Reviews", stats.get('pending_reviews', 0), stats.get('total_reviews', 0)),
self._create_metric_card("Approvals", stats.get('pending_approvals', 0), stats.get('total_approvals', 0)),
sizing_mode='stretch_width'
)
dashboard.append(metrics_row)
# Create document status chart
doc_status_chart = self._create_document_status_chart(doc_stats)
# Create document type chart
doc_type_chart = self._create_document_type_chart(doc_stats)
# Create charts row
charts_row = Row(
Column(
Markdown("## Document Status"),
doc_status_chart,
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded']
),
Column(
Markdown("## Document Types"),
doc_type_chart,
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded']
),
sizing_mode='stretch_width'
)
dashboard.append(charts_row)
# Create recent activity table
activity_df = pd.DataFrame(user_activity)
# Format date columns
if 'timestamp' in activity_df.columns:
activity_df['timestamp'] = pd.to_datetime(activity_df['timestamp']).dt.strftime('%Y-%m-%d %H:%M')
# Select and rename columns
display_columns = ['timestamp', 'user_name', 'action_type', 'action_detail']
column_names = {
'timestamp': 'Time',
'user_name': 'User',
'action_type': 'Action',
'action_detail': 'Details'
}
# Filter and rename columns
exist_columns = [col for col in display_columns if col in activity_df.columns]
activity_df = activity_df[exist_columns]
rename_dict = {col: column_names[col] for col in exist_columns if col in column_names}
activity_df = activity_df.rename(columns=rename_dict)
# Create activity table
activity_table = Tabulator(
activity_df,
pagination='local',
page_size=10,
sizing_mode='stretch_width',
height=300
)
# Add to dashboard
activity_section = Column(
Markdown("## Recent Activity"),
activity_table,
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded'],
sizing_mode='stretch_width'
)
dashboard.append(activity_section)
# Add dashboard to main content
self.main_content.append(dashboard)
# Clear notification
self.notification_area.object = ""
except Exception as e:
logger.error(f"Error loading admin dashboard: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
self.main_content.clear()
self.main_content.append(Markdown("# System Dashboard"))
self.main_content.append(Markdown("*Error loading dashboard data*"))
def _create_metric_card(self, title, value, total=None, percent=None):
"""Create a metric card for dashboard"""
if total is not None:
content = f"<h3>{value} / {total}</h3>"
else:
content = f"<h3>{value}</h3>"
if percent is not None:
progress_html = f"""
<div style="width:100%; background-color:#e0e0e0; border-radius:5px; margin:10px 0;">
<div style="width:{percent}%; background-color:#28a745; height:10px; border-radius:5px;"></div>
</div>
<div style="text-align:center;">{percent}%</div>
"""
content += progress_html
card = Column(
HTML(f"<h2>{title}</h2>"),
HTML(content),
width=200,
height=150,
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded', 'text-center']
)
return card
def _create_document_status_chart(self, doc_stats):
"""Create a chart showing document status distribution"""
try:
# Extract status data
status_data = doc_stats.get('status_distribution', {})
# Convert to DataFrame for visualization
status_df = pd.DataFrame({
'Status': list(status_data.keys()),
'Count': list(status_data.values())
})
# Sort by count
status_df = status_df.sort_values('Count', ascending=False)
# Create matplotlib figure
fig = plt.figure(figsize=(8, 4))
ax = fig.add_subplot(111)
bars = ax.bar(status_df['Status'], status_df['Count'], color='steelblue')
# Add value labels on top of bars
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 0.1,
str(int(height)),
ha='center', va='bottom', rotation=0)
ax.set_xlabel('Status')
ax.set_ylabel('Count')
ax.grid(axis='y', linestyle='--', alpha=0.7)
# Rotate x-axis labels if many categories
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
fig.tight_layout()
# Create the pane with the correct syntax
status_chart = pn.pane.Matplotlib(fig, dpi=72)
return status_chart
except Exception as e:
logger.error(f"Error creating status chart: {e}")
# Return a placeholder if chart creation fails
return pn.pane.Markdown("*Error creating chart*")
def _create_document_type_chart(self, doc_stats):
"""Create a chart showing document type distribution"""
try:
# Extract type data
type_data = doc_stats.get('type_distribution', {})
# Convert to DataFrame for visualization
type_df = pd.DataFrame({
'Type': list(type_data.keys()),
'Count': list(type_data.values())
})
# Sort by count
type_df = type_df.sort_values('Count', ascending=False)
# Create matplotlib figure
fig = plt.figure(figsize=(8, 4))
ax = fig.add_subplot(111)
bars = ax.bar(type_df['Type'], type_df['Count'], color='forestgreen')
# Add value labels on top of bars
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 0.1,
str(int(height)),
ha='center', va='bottom', rotation=0)
ax.set_xlabel('Document Type')
ax.set_ylabel('Count')
ax.grid(axis='y', linestyle='--', alpha=0.7)
# Rotate x-axis labels if many categories
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
fig.tight_layout()
# Create the pane with the correct syntax
type_chart = pn.pane.Matplotlib(fig, dpi=72)
return type_chart
except Exception as e:
logger.error(f"Error creating type chart: {e}")
# Return a placeholder if chart creation fails
return pn.pane.Markdown("*Error creating chart*")
def _load_user_management(self, event=None):
"""Load the user management interface with improved error handling"""
try:
self.current_tab = 'users'
self.notification_area.object = "Loading user data..."
self.main_content.clear()
# Update parent sidebar if available
self.update_parent_sidebar()
# Get user data
users = get_users()
# Ensure all users have required fields to prevent KeyError
for user in users:
# Ensure required fields exist (even if empty)
required_fields = ['id', 'UID', 'Name', 'Mail', 'username', 'department', 'active']
for field in required_fields:
if field not in user:
user[field] = ""
# Fetch roles for each user directly from graph relationships
for user in users:
try:
# Create a DocUser instance to access roles
from CDocs.models.user_extensions import DocUser
from CDocs.db.db_operations import run_query
# Get the user's UID to query relationships - safely handle missing fields
user_uid = user.get('id') or user.get('UID')
if user_uid:
# Query Neo4j for role relationships
role_query = """
MATCH (u:User {UID: $uid})-[:HAS_ROLE]->(r:CDocs_Role)
RETURN r.name as role_name
"""
role_results = run_query(role_query, {"uid": user_uid})
# Extract role names from results
roles = [r.get('role_name', '') for r in role_results if r.get('role_name')]
# Store roles in user data
user['roles'] = roles
user['roles_display'] = ", ".join(roles) if roles else "—"
else:
user['roles'] = []
user['roles_display'] = "—"
except Exception as e:
logger.error(f"Error getting roles for user {user.get('id', 'unknown')}: {e}")
user['roles'] = []
user['roles_display'] = "—"
# Create user management container
user_mgmt = Column(
Markdown("# User Management"),
sizing_mode='stretch_width'
)
# Create user creation form
create_form = self._create_user_form()
# Create user table - wrap in try/except to catch DataFrame creation errors
try:
user_df = pd.DataFrame(users)
# Standardize column names and ensure single source of truth for each attribute
# This mapping preserves Neo4j naming conventions for field names
field_mapping = {
'id': 'id',
'UID': 'id',
'username': 'username',
'Name': 'Name', # Keep Neo4j naming convention
'Mail': 'Mail', # Keep Neo4j naming convention
'email': 'Mail', # Map email to Mail
'first_name': 'first_name',
'last_name': 'last_name',
#'Department': 'Department',
'department': 'department', # Map department to Department
'role': 'roles_display', # Consolidate all role columns
'roles': 'roles_display', # Consolidate all role columns
'Roles': 'roles_display', # Consolidate all role columns
'active': 'active'
}
# Standardize column names - handle safely in case columns don't exist
for old_col, new_col in field_mapping.items():
if old_col in user_df.columns:
if new_col not in user_df.columns:
user_df[new_col] = user_df[old_col]
elif old_col != new_col:
# If the target column already exists, update it only for empty values
mask = user_df[new_col].isnull() | (user_df[new_col] == '')
user_df.loc[mask, new_col] = user_df.loc[mask, old_col]
# Fill username from Mail or Name if missing - safely handle missing columns
if 'username' in user_df.columns:
if 'Mail' in user_df.columns:
# For rows where username is empty but Mail exists
mail_mask = user_df['username'].isnull() & user_df['Mail'].notnull()
user_df.loc[mail_mask, 'username'] = user_df.loc[mail_mask, 'Mail']
if 'Name' in user_df.columns:
# For rows where username is still empty but Name exists
name_mask = user_df['username'].isnull() & user_df['Name'].notnull()
user_df.loc[name_mask, 'username'] = user_df.loc[name_mask, 'Name']
# Ensure we have a roles_display column and rename to 'Roles' for display
if 'roles_display' in user_df.columns:
user_df = user_df.rename(columns={'roles_display': 'Roles'})
# Define columns to display - only use columns that actually exist
desired_columns = ['username', 'Name', 'Mail', 'department', 'Roles', 'active', 'id']
available_columns = [col for col in desired_columns if col in user_df.columns]
# Use only available columns
if available_columns:
user_df = user_df[available_columns]
# Create user table
if not user_df.empty:
# Create user table WITHOUT the actions column first
user_table = Tabulator(
user_df,
pagination='local',
page_size=10,
sizing_mode='stretch_width',
height=400,
selectable=1, # Allow single row selection
header_align='center',
hidden_columns=['id'], # Hide id column but keep it for reference
show_index=False
)
# Add edit and delete buttons below the table
edit_btn = Button(
name="Edit Selected",
button_type="primary",
width=120,
disabled=True # Start disabled until a row is selected
)
delete_btn = Button(
name="Delete Selected",
button_type="danger",
width=120,
disabled=True # Start disabled until a row is selected
)
# Enable buttons when a row is selected
def enable_buttons(event):
selected_rows = event.obj.selected_dataframe
edit_btn.disabled = len(selected_rows) == 0
delete_btn.disabled = len(selected_rows) == 0
user_table.param.watch(enable_buttons, 'selection')
# Set up button actions
def edit_selected(event):
selected_rows = user_table.selected_dataframe
if len(selected_rows) > 0 and 'id' in selected_rows.columns:
user_id = selected_rows.iloc[0]['id']
self._edit_user(user_id)
def delete_selected(event):
selected_rows = user_table.selected_dataframe
if len(selected_rows) > 0 and 'id' in selected_rows.columns:
user_id = selected_rows.iloc[0]['id']
self._delete_user(user_id)
edit_btn.on_click(edit_selected)
delete_btn.on_click(delete_selected)
# Add instruction text and buttons
user_actions = Column(
Markdown("*Select a row to edit or delete a user*"),
Row(
edit_btn,
delete_btn,
align='end'
),
align='end'
)
else:
user_table = pn.pane.Markdown("No users found")
user_actions = pn.pane.Markdown("")
# Add to user management
user_section = Column(
Markdown("## User List"),
user_table,
user_actions, # Add the actions below the table
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded'],
sizing_mode='stretch_width'
)
# Add form and table to main content
user_mgmt.append(Row(
Column(
Markdown("## Create New User"),
create_form,
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded'],
width=400
),
Column(width=20), # spacing
user_section,
sizing_mode='stretch_width'
))
except Exception as e:
logger.error(f"Error creating user table: {e}")
user_mgmt.append(Markdown(f"**Error creating user table:** {str(e)}"))
user_mgmt.append(Markdown("User data appears to be missing required fields."))
# Add user management to main content
self.main_content.append(user_mgmt)
# Clear notification
self.notification_area.object = ""
except Exception as e:
logger.error(f"Error loading user management: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
self.main_content.clear()
self.main_content.append(Markdown("# User Management"))
self.main_content.append(Markdown(f"*Error loading user data: {str(e)}*"))
def _load_department_management(self, event=None):
"""Load the department management interface"""
try:
self.current_tab = 'departments'
self.notification_area.object = "Loading department data..."
self.main_content.clear()
# Update parent sidebar if available
self.update_parent_sidebar()
# Get department data
departments = get_departments()
# Create department management container
dept_mgmt = Column(
Markdown("# Department Management"),
Markdown("Departments are configured in settings and cannot be modified through the UI."),
sizing_mode='stretch_width'
)
# Create department table
dept_df = pd.DataFrame(departments)
if not dept_df.empty:
# Create department table
dept_table = Tabulator(
dept_df,
pagination='local',
page_size=10,
sizing_mode='stretch_width',
height=400,
selectable=False, # Read-only table
header_align='center'
)
dept_section = Column(
Markdown("## Departments"),
dept_table,
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded'],
sizing_mode='stretch_width'
)
else:
dept_section = Column(
Markdown("## Departments"),
Markdown("No departments configured in settings."),
styles={'background':'#f8f9fa'},
css_classes=['p-3', 'border', 'rounded'],
sizing_mode='stretch_width'
)
# Add to main content
dept_mgmt.append(dept_section)
self.main_content.append(dept_mgmt)
# Clear notification
self.notification_area.object = ""
except Exception as e:
logger.error(f"Error loading department management: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
self.main_content.clear()
self.main_content.append(Markdown("# Department Management"))
self.main_content.append(Markdown(f"*Error loading department data: {str(e)}*"))
def _load_document_type_management(self, event=None):
"""Load the document type management interface"""
try:
self.current_tab = 'doc_types'
self.notification_area.object = "Loading document type data..."
self.main_content.clear()
# Update parent sidebar if available
self.update_parent_sidebar()
# Get document type data
#doc_types = get_document_types()
# Create document type management container
doc_type_mgmt = Column(
Markdown("# Document Type Management"),
Markdown("Document types are configured in settings and cannot be modified through the UI."),
sizing_mode='stretch_width'
)
# Create document type table
# doc_type_df = pd.DataFrame(doc_types)
# if not doc_type_df.empty:
# # Create document type table
# doc_type_table = Tabulator(
# doc_type_df,
# pagination='local',
# page_size=10,
# sizing_mode='stretch_width',
# height=400,
# selectable=False, # Read-only table
# header_align='center'
# )
# doc_type_section = Column(
# Markdown("## Document Types"),
# doc_type_table,
# styles={'background':'#f8f9fa'},
# css_classes=['p-3', 'border', 'rounded'],
# sizing_mode='stretch_width'
# )
# else:
# doc_type_section = Column(
# Markdown("## Document Types"),
# Markdown("No document types configured in settings."),
# styles={'background':'#f8f9fa'},
# css_classes=['p-3', 'border', 'rounded'],
# sizing_mode='stretch_width'
# )
# # Add to main content
# doc_type_mgmt.append(doc_type_section)
self.main_content.append(doc_type_mgmt)
# Clear notification
self.notification_area.object = ""
except Exception as e:
logger.error(f"Error loading document type management: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
self.main_content.clear()
self.main_content.append(Markdown("# Document Type Management"))
self.main_content.append(Markdown(f"*Error loading document type data: {str(e)}*"))
def _load_system_settings(self, event=None):
"""Load the system settings interface"""
self.current_tab = 'settings'
self.notification_area.object = "System settings interface not yet implemented"
self.main_content.clear()
# Update parent sidebar if available
self.update_parent_sidebar()
self.main_content.append(Markdown("# System Settings"))
self.main_content.append(Markdown("*This feature is coming soon*"))
def _load_backup_restore(self, event=None):
"""Load the backup and restore interface"""
self.current_tab = 'backup'
self.notification_area.object = "Backup and restore interface not yet implemented"
self.main_content.clear()
# Update parent sidebar if available
self.update_parent_sidebar()
self.main_content.append(Markdown("# Backup & Restore"))
self.main_content.append(Markdown("*This feature is coming soon*"))
def set_user(self, user):
"""Set the current user - needed for compatibility with main app"""
try:
self.user = user
# Update parent sidebar if available (do this first to ensure admin button appears)
self.update_parent_sidebar()
# Verify admin access with the new user
if not self._verify_admin_access():
self.notification_area.object = "**Error:** You do not have permission to access the admin panel."
return False
# Update the UI with the new user
try:
# Create status_area if it doesn't exist
if not hasattr(self, 'status_area'):
self.status_area = pn.Column(sizing_mode='stretch_width')
# Update system status in sidebar
self._update_system_status()
# Refresh the current view based on the active tab
self._refresh_current_view()
return True
except Exception as e:
logger.error(f"Error in set_user: {str(e)}")
return False
except Exception as e:
logger.error(f"Error in set_user: {str(e)}")
return False
def get_admin_view(self):
"""Return the admin panel view for embedding in other panels"""
container = pn.Column(
self.notification_area,
self.main_content,
sizing_mode='stretch_width'
)
return container
# Add this method to the AdminPanel class to update the parent app's sidebar
def update_parent_sidebar(self):
"""Update the parent app's sidebar if available"""
try:
if hasattr(self, 'parent_app') and self.parent_app is not None:
# Instead of calling parent_app._update_sidebar_buttons()
# Use the specific admin sidebar setup method
if hasattr(self.parent_app, '_setup_admin_sidebar'):
self.parent_app._setup_admin_sidebar()
return True
elif hasattr(self.parent_app, '_update_sidebar_buttons'):
# Fallback to the general update method
self.parent_app._update_sidebar_buttons()
return True
return False
except Exception as e:
logger.error(f"Error updating parent sidebar: {e}")
return False
def _edit_user(self, user_id):
"""Load user data for editing and show edit form"""
try:
# Get user data
users = get_users(user_id=user_id)
if not users:
self.notification_area.object = f"**Error:** User not found"
return
user = users[0]
logger.debug(f"Loaded user data for editing: {user}")
# Make sure ID is available for the save function
user['id'] = user.get('id', user.get('UID', user_id))
# Update notification
self.notification_area.object = f"Editing user: {user.get('username', user.get('Name', 'Unknown'))}"
# Get user roles directly from Neo4j relationships
try:
from CDocs.db.db_operations import run_query
# Query for user roles
role_query = """
MATCH (u:User {UID: $uid})-[:HAS_ROLE]->(r:CDocs_Role)
RETURN r.name as role_name
"""
role_results = run_query(role_query, {"uid": user_id})
# Extract role names from results
roles = [r.get('role_name', '') for r in role_results if r.get('role_name')]
user['roles'] = roles
logger.debug(f"Retrieved roles for user: {roles}")
except Exception as e:
logger.error(f"Error getting user roles from Neo4j: {e}")
# If direct query fails, try DocUser model as fallback
try:
from CDocs.models.user_extensions import DocUser
doc_user = DocUser(uid=user_id)
user['roles'] = doc_user.roles
logger.debug(f"Retrieved roles from DocUser: {user['roles']}")
except Exception as nested_e:
logger.error(f"Error getting user roles via DocUser: {nested_e}")
user['roles'] = []
# Debug what data we have before passing to form
logger.debug(f"User data before form creation: {user}")
# Ensure we have the Neo4j field mappings for the form
if 'Mail' in user and not user.get('email'):
user['email'] = user['Mail']
# Extract first and last names from Name if not already available
if 'Name' in user and (not user.get('first_name') or not user.get('last_name')):
name_parts = user['Name'].split(' ', 1)
if len(name_parts) > 0:
user['first_name'] = name_parts[0]
if len(name_parts) > 1:
user['last_name'] = name_parts[1]
# Create edit form with user data
edit_form = self._create_user_form(user_data=user)
# Create edit container
edit_container = Column(
Markdown(f"## Edit User: {user.get('username', user.get('Name', 'Unknown'))}"),
edit_form,
styles={'background':'#f9f9f9'},
css_classes=['p-3', 'border', 'rounded'],
width=500
)
# Replace main content with edit form
self.main_content.clear()
self.main_content.append(edit_container)
except Exception as e:
logger.error(f"Error editing user: {e}")
logger.error(f"Stack trace: {traceback.format_exc()}")
self.notification_area.object = f"**Error:** {str(e)}"
def _create_user_form(self, user_data=None):
"""Create a form for user creation/editing with improved data mapping"""
# Log what data we received
if user_data:
logger.debug(f"Creating form with user data: {user_data}")
# Get data for dropdowns
departments = settings.DEPARTMENTS
# Create department options dict - format properly for Panel 1.6.1
# Convert from list of tuples to dictionary {label: value}
department_options = {name: code for name, code in departments.items()}
# Extract name and email from Neo4j attributes if needed
if user_data:
# Map Mail to email if email is not present
if not user_data.get('email') and user_data.get('Mail'):
user_data['email'] = user_data['Mail']
# Handle first/last name from the Name field if needed
if not user_data.get('first_name') and not user_data.get('last_name') and user_data.get('Name'):
name_parts = user_data['Name'].split(' ', 1)
if len(name_parts) > 0:
user_data['first_name'] = name_parts[0]
if len(name_parts) > 1:
user_data['last_name'] = name_parts[1]
# Get all available roles from settings, or use default if not defined
try:
all_roles = getattr(settings, 'USER_ROLES', [
'ADMIN', 'MANAGER', 'EDITOR', 'REVIEWER', 'APPROVER',
'QA', 'EXECUTIVE', 'USER', 'GUEST'
])
except Exception as e:
logger.warning(f"Could not get USER_ROLES from settings: {e}")
all_roles = [
'ADMIN', 'MANAGER', 'EDITOR', 'REVIEWER', 'APPROVER',
'QA', 'EXECUTIVE', 'USER', 'GUEST'
]
# Format the roles for the checkbox group - use the roles as both label and value
# In Panel 1.6.1, CheckBoxGroup expects list of values (not tuples or dicts)
role_options = all_roles
# Create roles checkbox group - for multiple role selection
user_roles = user_data.get('roles', []) if user_data else []
# Ensure roles are strings for comparison
user_roles = [str(role) for role in user_roles if role]
# Create CheckBoxGroup with proper value selection
roles_group = CheckBoxGroup(
name="Roles",
options=role_options, # Use simple list for CheckBoxGroup
value=user_roles,
inline=False
)
# Create form inputs with existing values if editing
username_input = TextInput(
name="Username",
placeholder="Enter username",
value=user_data.get('username', '') if user_data else ''
)
# Use PasswordInput instead of TextInput with password=True
password_input = pn.widgets.PasswordInput(
name="Password",
placeholder="Leave blank to keep current password" if user_data else "Enter password",
)
# Handle both Mail and email fields
email_input = TextInput(
name="Email",
placeholder="Enter email",
value=user_data.get('email', user_data.get('Mail', '')) if user_data else ''
)
first_name = TextInput(
name="First Name",
placeholder="Enter first name",
value=user_data.get('first_name', '') if user_data else ''
)
last_name = TextInput(
name="Last Name",
placeholder="Enter last name",
value=user_data.get('last_name', '') if user_data else ''
)
# Create department select with current value if editing
# Handle both Department and department fields - use dict format for options
# For editing an existing user, we need to set the value to the department code
# that's already stored in the database (Department field in Neo4j)
department_code = user_data.get('department', '') if user_data else ''
department_select = Select(
name="Department",
options=department_options, # Dictionary format {label: value}
value=department_code # This should be the code (e.g., 'HR'), not the name
)
# Create active switch with current value if editing
active_switch = Switch(
name="Active",
value=user_data.get('active', True) if user_data else True
)
# Create buttons
save_btn = Button(
name="Update User" if user_data else "Create User",
button_type="success"
)
cancel_btn = Button(
name="Cancel",
button_type="default"
)
# Create form layout
user_form = Column(
username_input,
password_input,
email_input,
first_name,
last_name,
Column(
Markdown("### User Roles"),
Markdown("Select one or more roles for this user:"),
roles_group,
styles={'background':'#f5f5f5'},
css_classes=['p-2', 'border', 'rounded']
),
department_select,
Row(Markdown("**Active:**"), active_switch),
Row(cancel_btn, save_btn, align='end')
)
# Set up event handlers for buttons
user_id = user_data.get('id', None) if user_data else None
save_btn.on_click(lambda event: self._save_user(
user_id, # may be None for new user
username_input.value,
password_input.value,
email_input.value,
first_name.value,
last_name.value,
roles_group.value, # Now passing selected roles as a list
department_select.value,
active_switch.value
))
cancel_btn.on_click(lambda event: self._load_user_management())
return user_form
def _save_user(self, user_id, username, password, email, first_name, last_name, roles, department, active):
"""Save user to database with proper role relationships"""
try:
# Create full name from first and last name
full_name = f"{first_name} {last_name}".strip()
# Prepare user data with correct Neo4j attribute schema
user_data = {
'username': username,
'Name': full_name, # Primary name field in Neo4j
'Mail': email, # Primary email field in Neo4j
'first_name': first_name,
'last_name': last_name,
'department': department,
'active': active,
'roles': roles
}
# Add password only if provided (for updates)
if password:
user_data['password'] = password
# Create or update user
if user_id:
# Update existing user
update_user(user_id, user_data)
self.notification_area.object = f"User {username} updated successfully"
else:
# For new users, use a different approach to ensure proper relationship
from CDocs.db.db_operations import run_query
import uuid
# First find the People node
people_result = run_query("MATCH (p:People) RETURN p.UID as uid LIMIT 1")
if not people_result or 'uid' not in people_result[0]:
# Create People node if it doesn't exist
people_uid = str(uuid.uuid4())
run_query(
"""
CREATE (p:People {UID: $uid, Name: 'People'})
RETURN p.UID as uid
""",
{"uid": people_uid}
)
else:
people_uid = people_result[0]['uid']
# Create user with proper properties and relationship to People node
new_user_uid = str(uuid.uuid4())
user_props = {
'UID': new_user_uid,
'Name': full_name,
'Mail': email,
'username': username,
'department': department,
'active': active,
'createdDate': datetime.now().isoformat()
}
# Add password if provided (hash it for security)
if password:
import hashlib
user_props['password'] = hashlib.sha256(password.encode()).hexdigest()
# Create user node and relationship to People node
run_query(
"""
MATCH (p:People {UID: $people_uid})
CREATE (p)-[:DIRECTORY]->(u:User)
SET u = $props
RETURN u.UID as uid
""",
{"people_uid": people_uid, "props": user_props}
)
# Set up CDocs user extension (adds CDocs_User label and default VIEWER role)
from CDocs.models.user_extensions import DocUser
DocUser._setup_user_extension(new_user_uid)
# Add roles if specified
if roles:
doc_user = DocUser(uid=new_user_uid)
# Get available roles from settings, or use a default set if not defined
try:
from CDocs.config import settings
available_roles = getattr(settings, 'USER_ROLES', [
'ADMIN', 'MANAGER', 'EDITOR', 'REVIEWER', 'APPROVER',
'QA', 'EXECUTIVE', 'USER', 'GUEST'
])
except Exception as e:
logger.warning(f"Could not get USER_ROLES from settings: {e}")
available_roles = [
'ADMIN', 'MANAGER', 'EDITOR', 'REVIEWER', 'APPROVER',
'QA', 'EXECUTIVE', 'USER', 'GUEST'
]
for role in roles:
if role in available_roles:
try:
doc_user.add_role(role)
except Exception as role_err:
logger.error(f"Error adding role {role} to user: {role_err}")
self.notification_area.object = f"User {username} created successfully"
# Refresh the view
self._load_user_management()
except Exception as e:
logger.error(f"Error saving user: {e}")
logger.error(f"Stack trace: {traceback.format_exc()}")
self.notification_area.object = f"**Error:** {str(e)}"
def _load_notification_management(self, event=None):
"""Load notification management interface."""
try:
self.current_tab = 'notifications'
self.notification_area.object = "Loading notification data..."
self.main_content.clear()
# Update parent sidebar if available
self.update_parent_sidebar()
# Get notification statistics
notification_stats = self._get_notification_stats()
# Create notification management interface
stats_cards = pn.Row(
self._create_metric_card("Total Notifications", notification_stats.get('total', 0)),
self._create_metric_card("Unread", notification_stats.get('unread', 0)),
self._create_metric_card("High Priority", notification_stats.get('high_priority', 0)),
self._create_metric_card("Last 24 Hours", notification_stats.get('recent', 0)),
sizing_mode='stretch_width'
)
# Create bulk action controls
mark_all_read_btn = pn.widgets.Button(name="Mark All Read", button_type="primary", width=120)
delete_old_btn = pn.widgets.Button(name="Delete Old (30+ days)", button_type="default", width=150)
send_test_btn = pn.widgets.Button(name="Send Test Notification", button_type="default", width=150)
# Set up button handlers
mark_all_read_btn.on_click(self._mark_all_notifications_read)
delete_old_btn.on_click(self._delete_old_notifications)
send_test_btn.on_click(self._send_test_notification)
bulk_controls = pn.Row(
mark_all_read_btn,
delete_old_btn,
send_test_btn,
align='start'
)
# Get recent notifications for display
recent_notifications = self._get_recent_notifications()
# Create notifications table
if recent_notifications:
notifications_df = pd.DataFrame(recent_notifications)
# Format columns for display
display_columns = ['created_date', 'user_name', 'notification_type', 'message', 'priority', 'read']
available_columns = [col for col in display_columns if col in notifications_df.columns]
if available_columns:
notifications_df = notifications_df[available_columns]
# Rename columns for better display
column_names = {
'created_date': 'Date',
'user_name': 'User',
'notification_type': 'Type',
'message': 'Message',
'priority': 'Priority',
'read': 'Read'
}
notifications_df = notifications_df.rename(columns=column_names)
# Format date column
if 'Date' in notifications_df.columns:
notifications_df['Date'] = pd.to_datetime(notifications_df['Date']).dt.strftime('%Y-%m-%d %H:%M')
notifications_table = Tabulator(
notifications_df,
pagination='local',
page_size=15,
sizing_mode='stretch_width',
height=400,
show_index=False
)
else:
notifications_table = pn.pane.Markdown("*No notification data available*")
else:
notifications_table = pn.pane.Markdown("*No recent notifications found*")
# Create the complete notification management layout
notification_management = pn.Column(
pn.pane.Markdown("# Notification Management"),
pn.pane.Markdown("Monitor and manage system notifications."),
pn.Spacer(height=10),
stats_cards,
pn.Spacer(height=20),
pn.pane.Markdown("## Actions"),
bulk_controls,
pn.Spacer(height=20),
pn.pane.Markdown("## Recent Notifications"),
notifications_table,
sizing_mode='stretch_width'
)
self.main_content.append(notification_management)
# Clear notification
self.notification_area.object = ""
except Exception as e:
logger.error(f"Error loading notification management: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
def _get_notification_stats(self) -> Dict[str, int]:
"""Get notification statistics."""
try:
notification_manager = NotificationManager()
# Get total notifications
from CDocs.db.db_operations import run_query
# Count total notifications
total_result = run_query("MATCH (n:CDocs_Notification) RETURN count(n) as total")
total = total_result[0]['total'] if total_result else 0
# Count unread notifications
unread_result = run_query("MATCH (n:CDocs_Notification {read: false}) RETURN count(n) as unread")
unread = unread_result[0]['unread'] if unread_result else 0
# Count high priority notifications
high_priority_result = run_query("MATCH (n:CDocs_Notification {priority: 'HIGH'}) RETURN count(n) as high_priority")
high_priority = high_priority_result[0]['high_priority'] if high_priority_result else 0
# Count recent notifications (last 24 hours)
from datetime import datetime, timedelta
yesterday = (datetime.now() - timedelta(days=1)).isoformat()
recent_result = run_query(
"MATCH (n:CDocs_Notification) WHERE n.created_date > $since RETURN count(n) as recent",
{"since": yesterday}
)
recent = recent_result[0]['recent'] if recent_result else 0
return {
'total': total,
'unread': unread,
'high_priority': high_priority,
'recent': recent
}
except Exception as e:
logger.error(f"Error getting notification stats: {e}")
return {'total': 0, 'unread': 0, 'high_priority': 0, 'recent': 0}
def _get_recent_notifications(self, limit=50) -> List[Dict[str, Any]]:
"""Get recent notifications for display."""
try:
from CDocs.db.db_operations import run_query
query = """
MATCH (n:CDocs_Notification)
OPTIONAL MATCH (n)-[:NOTIFIES]->(u:User)
RETURN n.uid as uid, n.notification_type as notification_type,
n.message as message, n.priority as priority, n.read as read,
n.created_date as created_date, u.username as user_name
ORDER BY n.created_date DESC
LIMIT $limit
"""
results = run_query(query, {"limit": limit})
return results
except Exception as e:
logger.error(f"Error getting recent notifications: {e}")
return []
def _mark_all_notifications_read(self, event=None):
"""Mark all notifications as read."""
try:
from CDocs.db.db_operations import run_query
result = run_query("MATCH (n:CDocs_Notification {read: false}) SET n.read = true RETURN count(n) as updated")
updated_count = result[0]['updated'] if result else 0
self.notification_area.object = f"Marked {updated_count} notifications as read"
# Refresh the view
self._load_notification_management()
except Exception as e:
logger.error(f"Error marking notifications as read: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
def _delete_old_notifications(self, event=None):
"""Delete notifications older than 30 days."""
try:
from CDocs.db.db_operations import run_query
from datetime import datetime, timedelta
thirty_days_ago = (datetime.now() - timedelta(days=30)).isoformat()
result = run_query(
"MATCH (n:CDocs_Notification) WHERE n.created_date < $cutoff DELETE n RETURN count(n) as deleted",
{"cutoff": thirty_days_ago}
)
deleted_count = result[0]['deleted'] if result else 0
self.notification_area.object = f"Deleted {deleted_count} old notifications"
# Refresh the view
self._load_notification_management()
except Exception as e:
logger.error(f"Error deleting old notifications: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
def _send_test_notification(self, event=None):
"""Send a test notification to the current user."""
try:
if not self.user or not hasattr(self.user, 'uid'):
self.notification_area.object = "**Error:** No user available for test notification"
return
notification_manager = NotificationManager()
# Fix: Remove priority parameter and pass it in details instead
notification_manager.create_notification(
notification_type='SYSTEM_NOTIFICATION',
user_uid=self.user.uid,
message=f"Test notification sent at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
details={
'test': True,
'sent_by': 'admin_panel',
'priority': 'INFO' # Move priority to details
}
)
self.notification_area.object = "Test notification sent successfully"
# Refresh the view
self._load_notification_management()
except Exception as e:
logger.error(f"Error sending test notification: {e}")
self.notification_area.object = f"**Error:** {str(e)}"
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
param.Parameterized | - |
Parameter Details
bases: Parameter of type param.Parameterized
Return Value
Returns unspecified type
Class Interface
Methods
__init__(self, template, session_manager, parent_app, embedded)
Purpose: Internal method: init
Parameters:
template: Parametersession_manager: Parameterparent_app: Parameterembedded: Parameter
Returns: None
_get_current_user(self)
Purpose: Get current user from session
Returns: None
_verify_admin_access(self) -> bool
Purpose: Verify that the user has admin permissions
Returns: Returns bool
_setup_header(self)
Purpose: Set up the header with title and actions
Returns: None
_setup_sidebar(self)
Purpose: Set up the sidebar with navigation options
Returns: None
_setup_main_area(self)
Purpose: Set up the main content area.
Returns: None
_refresh_current_view(self, event)
Purpose: Refresh the current view
Parameters:
event: Parameter
Returns: None
_navigate_back(self, event)
Purpose: Navigate back to dashboard
Parameters:
event: Parameter
Returns: None
_update_system_status(self)
Purpose: Update system status information in the sidebar.
Returns: None
_format_duration(self, seconds)
Purpose: Format duration in seconds to readable string
Parameters:
seconds: Parameter
Returns: None
_load_admin_dashboard(self, event)
Purpose: Load the admin dashboard view
Parameters:
event: Parameter
Returns: None
_create_metric_card(self, title, value, total, percent)
Purpose: Create a metric card for dashboard
Parameters:
title: Parametervalue: Parametertotal: Parameterpercent: Parameter
Returns: None
_create_document_status_chart(self, doc_stats)
Purpose: Create a chart showing document status distribution
Parameters:
doc_stats: Parameter
Returns: None
_create_document_type_chart(self, doc_stats)
Purpose: Create a chart showing document type distribution
Parameters:
doc_stats: Parameter
Returns: None
_load_user_management(self, event)
Purpose: Load the user management interface with improved error handling
Parameters:
event: Parameter
Returns: None
_load_department_management(self, event)
Purpose: Load the department management interface
Parameters:
event: Parameter
Returns: None
_load_document_type_management(self, event)
Purpose: Load the document type management interface
Parameters:
event: Parameter
Returns: None
_load_system_settings(self, event)
Purpose: Load the system settings interface
Parameters:
event: Parameter
Returns: None
_load_backup_restore(self, event)
Purpose: Load the backup and restore interface
Parameters:
event: Parameter
Returns: None
set_user(self, user)
Purpose: Set the current user - needed for compatibility with main app
Parameters:
user: Parameter
Returns: None
get_admin_view(self)
Purpose: Return the admin panel view for embedding in other panels
Returns: See docstring for return details
update_parent_sidebar(self)
Purpose: Update the parent app's sidebar if available
Returns: None
_edit_user(self, user_id)
Purpose: Load user data for editing and show edit form
Parameters:
user_id: Parameter
Returns: None
_create_user_form(self, user_data)
Purpose: Create a form for user creation/editing with improved data mapping
Parameters:
user_data: Parameter
Returns: None
_save_user(self, user_id, username, password, email, first_name, last_name, roles, department, active)
Purpose: Save user to database with proper role relationships
Parameters:
user_id: Parameterusername: Parameterpassword: Parameteremail: Parameterfirst_name: Parameterlast_name: Parameterroles: Parameterdepartment: Parameteractive: Parameter
Returns: None
_load_notification_management(self, event)
Purpose: Load notification management interface.
Parameters:
event: Parameter
Returns: None
_get_notification_stats(self) -> Dict[str, int]
Purpose: Get notification statistics.
Returns: Returns Dict[str, int]
_get_recent_notifications(self, limit) -> List[Dict[str, Any]]
Purpose: Get recent notifications for display.
Parameters:
limit: Parameter
Returns: Returns List[Dict[str, Any]]
_mark_all_notifications_read(self, event)
Purpose: Mark all notifications as read.
Parameters:
event: Parameter
Returns: None
_delete_old_notifications(self, event)
Purpose: Delete notifications older than 30 days.
Parameters:
event: Parameter
Returns: None
_send_test_notification(self, event)
Purpose: Send a test notification to the current user.
Parameters:
event: Parameter
Returns: None
Required Imports
import logging
import traceback
from typing import Dict
from typing import List
from typing import Any
Usage Example
# Example usage:
# result = AdminPanel(bases)
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ApprovalPanel 70.3% similar
-
class ApprovalPanel_v1 68.5% similar
-
class ReviewPanel 63.3% similar
-
function create_admin_panel 55.7% similar
-
function create_embedded_admin_panel 52.3% similar