Spaces:
Running
Running
added Admin Reload Feature
Browse files
app.py
CHANGED
|
@@ -6,6 +6,7 @@ from google.oauth2.credentials import Credentials
|
|
| 6 |
from google_auth_oauthlib.flow import InstalledAppFlow
|
| 7 |
import json
|
| 8 |
import gradio as gr
|
|
|
|
| 9 |
import time
|
| 10 |
from datetime import datetime
|
| 11 |
from pytz import timezone
|
|
@@ -152,7 +153,7 @@ def load_reward_points_data():
|
|
| 152 |
print(f"β Error loading Reward Points data: {str(e)}")
|
| 153 |
return None
|
| 154 |
|
| 155 |
-
# MODIFIED FUNCTION: Get activity details from cached data
|
| 156 |
def get_activity_details(roll_no, reward_points_df):
|
| 157 |
"""Get activity details for a specific roll number from cached reward points data in breakdown format"""
|
| 158 |
try:
|
|
@@ -225,6 +226,10 @@ def get_activity_details(roll_no, reward_points_df):
|
|
| 225 |
|
| 226 |
# Format output in breakdown style
|
| 227 |
output = []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
# Define all possible activity categories in order
|
| 229 |
activity_categories = [
|
| 230 |
"INITIAL POINTS / CARRY-OVER",
|
|
@@ -278,12 +283,22 @@ def get_activity_details(roll_no, reward_points_df):
|
|
| 278 |
count = final_count.get(category, 0)
|
| 279 |
points = final_summary.get(category, 0.0)
|
| 280 |
|
|
|
|
|
|
|
| 281 |
|
| 282 |
# Add any categories not in the standard list
|
| 283 |
for category, points in final_summary.items():
|
| 284 |
if category not in activity_categories:
|
| 285 |
count = final_count.get(category, 0)
|
|
|
|
|
|
|
| 286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
|
| 288 |
# Add detailed activity list if needed
|
| 289 |
if len(student_rows) <= 20: # Only show detailed list for reasonable number of activities
|
|
@@ -303,10 +318,9 @@ def get_activity_details(roll_no, reward_points_df):
|
|
| 303 |
points_val = 0
|
| 304 |
|
| 305 |
# Truncate long names for display
|
| 306 |
-
display_name = activity_name[:50] + "..." if len(activity_name) >
|
| 307 |
output.append(f"{idx:2d}. {activity_type}: {display_name} - {points_val:.2f} pts")
|
| 308 |
|
| 309 |
-
|
| 310 |
output.append("=" * 80)
|
| 311 |
|
| 312 |
return "\n".join(output)
|
|
@@ -821,14 +835,6 @@ def get_system_info():
|
|
| 821 |
output.append("-" * 40)
|
| 822 |
output.append(details_info['last_updated'])
|
| 823 |
|
| 824 |
-
# NEW: Add reward points data info
|
| 825 |
-
if reward_points_df is not None:
|
| 826 |
-
output.append(f"\nREWARD POINTS DATA:")
|
| 827 |
-
output.append("-" * 40)
|
| 828 |
-
output.append(f"Total activity records: {len(reward_points_df)}")
|
| 829 |
-
unique_students = reward_points_df.iloc[:, 0].nunique() if not reward_points_df.empty else 0
|
| 830 |
-
output.append(f"Students with activities: {unique_students}")
|
| 831 |
-
|
| 832 |
# Cache info
|
| 833 |
if data_cache["last_update"]:
|
| 834 |
cache_age = datetime.now() - data_cache["last_update"]
|
|
@@ -844,12 +850,87 @@ def get_system_info():
|
|
| 844 |
|
| 845 |
return "\n".join(output)
|
| 846 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 847 |
# Create Gradio interface
|
| 848 |
with gr.Blocks(title="Student Reward Points Check", theme=gr.themes.Soft()) as app:
|
| 849 |
gr.Markdown("# π Student Reward Points Check")
|
| 850 |
gr.Markdown("### Search for student details including reward points and redemption dates")
|
| 851 |
gr.Markdown("π **Auto-Updates**: Data automatically refreshes every 12 hours")
|
| 852 |
|
|
|
|
|
|
|
|
|
|
| 853 |
with gr.Tabs():
|
| 854 |
with gr.TabItem("π Student Search"):
|
| 855 |
with gr.Row():
|
|
@@ -881,15 +962,87 @@ with gr.Blocks(title="Student Reward Points Check", theme=gr.themes.Soft()) as a
|
|
| 881 |
interactive=False,
|
| 882 |
show_label=True
|
| 883 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 884 |
|
| 885 |
# Event handlers
|
| 886 |
search_btn.click(fn=search_student, inputs=roll_input, outputs=result_output)
|
| 887 |
roll_input.submit(fn=search_student, inputs=roll_input, outputs=result_output)
|
| 888 |
system_btn.click(fn=get_system_info, outputs=system_output)
|
| 889 |
|
| 890 |
-
# Load system info on startup
|
| 891 |
-
app.load(fn=get_system_info, outputs=system_output)
|
| 892 |
-
|
| 893 |
# π FOOTER SECTION
|
| 894 |
gr.Markdown("---")
|
| 895 |
with gr.Row():
|
|
@@ -919,6 +1072,23 @@ with gr.Blocks(title="Student Reward Points Check", theme=gr.themes.Soft()) as a
|
|
| 919 |
elem_id="footer"
|
| 920 |
)
|
| 921 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 922 |
|
| 923 |
# Launch the app
|
| 924 |
if __name__ == "__main__":
|
|
|
|
| 6 |
from google_auth_oauthlib.flow import InstalledAppFlow
|
| 7 |
import json
|
| 8 |
import gradio as gr
|
| 9 |
+
import urllib.parse
|
| 10 |
import time
|
| 11 |
from datetime import datetime
|
| 12 |
from pytz import timezone
|
|
|
|
| 153 |
print(f"β Error loading Reward Points data: {str(e)}")
|
| 154 |
return None
|
| 155 |
|
| 156 |
+
# MODIFIED FUNCTION: Get activity details from cached data in breakdown format
|
| 157 |
def get_activity_details(roll_no, reward_points_df):
|
| 158 |
"""Get activity details for a specific roll number from cached reward points data in breakdown format"""
|
| 159 |
try:
|
|
|
|
| 226 |
|
| 227 |
# Format output in breakdown style
|
| 228 |
output = []
|
| 229 |
+
output.append("\n" + "=" * 80)
|
| 230 |
+
output.append("π INDIVIDUAL ACTIVITY BREAKDOWN")
|
| 231 |
+
output.append("=" * 80)
|
| 232 |
+
|
| 233 |
# Define all possible activity categories in order
|
| 234 |
activity_categories = [
|
| 235 |
"INITIAL POINTS / CARRY-OVER",
|
|
|
|
| 283 |
count = final_count.get(category, 0)
|
| 284 |
points = final_summary.get(category, 0.0)
|
| 285 |
|
| 286 |
+
output.append(f"π **{category}**")
|
| 287 |
+
output.append(f" Count: {count} | Points: {points:.2f}")
|
| 288 |
|
| 289 |
# Add any categories not in the standard list
|
| 290 |
for category, points in final_summary.items():
|
| 291 |
if category not in activity_categories:
|
| 292 |
count = final_count.get(category, 0)
|
| 293 |
+
output.append(f"π **{category}**")
|
| 294 |
+
output.append(f" Count: {count} | Points: {points:.2f}")
|
| 295 |
|
| 296 |
+
# Add summary totals
|
| 297 |
+
output.append("")
|
| 298 |
+
output.append("=" * 80)
|
| 299 |
+
output.append(f"π Total Individual Activities: {len(student_rows)}")
|
| 300 |
+
output.append(f"π Total Activity Types: {len(final_summary)}")
|
| 301 |
+
output.append(f"π° Total Activity Points: {total_points:.2f}")
|
| 302 |
|
| 303 |
# Add detailed activity list if needed
|
| 304 |
if len(student_rows) <= 20: # Only show detailed list for reasonable number of activities
|
|
|
|
| 318 |
points_val = 0
|
| 319 |
|
| 320 |
# Truncate long names for display
|
| 321 |
+
display_name = activity_name[:50] + "..." if len(activity_name) > 53 else activity_name
|
| 322 |
output.append(f"{idx:2d}. {activity_type}: {display_name} - {points_val:.2f} pts")
|
| 323 |
|
|
|
|
| 324 |
output.append("=" * 80)
|
| 325 |
|
| 326 |
return "\n".join(output)
|
|
|
|
| 835 |
output.append("-" * 40)
|
| 836 |
output.append(details_info['last_updated'])
|
| 837 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
# Cache info
|
| 839 |
if data_cache["last_update"]:
|
| 840 |
cache_age = datetime.now() - data_cache["last_update"]
|
|
|
|
| 850 |
|
| 851 |
return "\n".join(output)
|
| 852 |
|
| 853 |
+
# Admin UI Controls
|
| 854 |
+
def build_admin_section():
|
| 855 |
+
"""Build admin controls section"""
|
| 856 |
+
with gr.Accordion("π§ Admin Controls", open=False, visible=True) as admin_accordion:
|
| 857 |
+
admin_key = gr.Textbox(
|
| 858 |
+
label="Enter Admin Key",
|
| 859 |
+
type="password",
|
| 860 |
+
placeholder="Admin Only",
|
| 861 |
+
value=""
|
| 862 |
+
)
|
| 863 |
+
load_button = gr.Button("π Reload All Data", visible=False, variant="primary")
|
| 864 |
+
admin_status = gr.Markdown("βΉοΈ Enter admin key to access controls", visible=True)
|
| 865 |
+
|
| 866 |
+
def verify_admin_key(key):
|
| 867 |
+
"""Verify admin key and show/hide controls"""
|
| 868 |
+
if key.strip() == os.getenv("ADMIN_KEY", ""):
|
| 869 |
+
return (
|
| 870 |
+
gr.update(visible=True), # Show reload button
|
| 871 |
+
"β
Access granted. You can reload data now."
|
| 872 |
+
)
|
| 873 |
+
elif key.strip() == "":
|
| 874 |
+
return (
|
| 875 |
+
gr.update(visible=False), # Hide reload button
|
| 876 |
+
"βΉοΈ Enter admin key to access controls"
|
| 877 |
+
)
|
| 878 |
+
else:
|
| 879 |
+
return (
|
| 880 |
+
gr.update(visible=False), # Hide reload button
|
| 881 |
+
"β Invalid admin key."
|
| 882 |
+
)
|
| 883 |
+
|
| 884 |
+
def admin_reload():
|
| 885 |
+
"""Admin function to reload all data"""
|
| 886 |
+
try:
|
| 887 |
+
print("π§ Admin reload triggered...")
|
| 888 |
+
combined_df, studentwise_data, details_info, reward_points_df = load_all_data()
|
| 889 |
+
|
| 890 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 891 |
+
return f"β
Data reloaded successfully at {timestamp}\nπ Total records: {len(combined_df) if not combined_df.empty else 0}"
|
| 892 |
+
except Exception as e:
|
| 893 |
+
return f"β Error reloading data: {str(e)}"
|
| 894 |
+
|
| 895 |
+
# Event handlers
|
| 896 |
+
admin_key.change(
|
| 897 |
+
fn=verify_admin_key,
|
| 898 |
+
inputs=admin_key,
|
| 899 |
+
outputs=[load_button, admin_status]
|
| 900 |
+
)
|
| 901 |
+
|
| 902 |
+
load_button.click(
|
| 903 |
+
fn=admin_reload,
|
| 904 |
+
outputs=admin_status
|
| 905 |
+
)
|
| 906 |
+
|
| 907 |
+
return admin_accordion, admin_key, load_button, admin_status
|
| 908 |
+
|
| 909 |
+
# Function to determine if admin mode is enabled via URL parameter
|
| 910 |
+
def check_admin_mode(request: gr.Request) -> bool:
|
| 911 |
+
"""Check if admin mode is enabled via URL parameter"""
|
| 912 |
+
try:
|
| 913 |
+
query_params = urllib.parse.parse_qs(str(request.url).split('?')[1] if '?' in str(request.url) else "")
|
| 914 |
+
admin_param = query_params.get("admin", [""])[0]
|
| 915 |
+
admin_mode_key = os.getenv("ADMIN_MODE_KEY", "")
|
| 916 |
+
|
| 917 |
+
is_admin = admin_param == admin_mode_key and admin_mode_key != ""
|
| 918 |
+
if is_admin:
|
| 919 |
+
print(f"π§ Admin mode activated via URL parameter")
|
| 920 |
+
return is_admin
|
| 921 |
+
except Exception as e:
|
| 922 |
+
print(f"β οΈ Error checking admin mode: {str(e)}")
|
| 923 |
+
return False
|
| 924 |
+
|
| 925 |
# Create Gradio interface
|
| 926 |
with gr.Blocks(title="Student Reward Points Check", theme=gr.themes.Soft()) as app:
|
| 927 |
gr.Markdown("# π Student Reward Points Check")
|
| 928 |
gr.Markdown("### Search for student details including reward points and redemption dates")
|
| 929 |
gr.Markdown("π **Auto-Updates**: Data automatically refreshes every 12 hours")
|
| 930 |
|
| 931 |
+
# Store admin components for conditional visibility
|
| 932 |
+
admin_components = gr.State(None)
|
| 933 |
+
|
| 934 |
with gr.Tabs():
|
| 935 |
with gr.TabItem("π Student Search"):
|
| 936 |
with gr.Row():
|
|
|
|
| 962 |
interactive=False,
|
| 963 |
show_label=True
|
| 964 |
)
|
| 965 |
+
|
| 966 |
+
# NEW: Admin Controls as a separate tab
|
| 967 |
+
with gr.TabItem("π§ Admin Controls") as admin_tab:
|
| 968 |
+
gr.Markdown("### π Administrative Functions")
|
| 969 |
+
gr.Markdown("β οΈ **Access restricted to authorized personnel only**")
|
| 970 |
+
|
| 971 |
+
with gr.Row():
|
| 972 |
+
with gr.Column(scale=1):
|
| 973 |
+
admin_key = gr.Textbox(
|
| 974 |
+
label="Enter Admin Key",
|
| 975 |
+
type="password",
|
| 976 |
+
placeholder="Enter admin password",
|
| 977 |
+
value=""
|
| 978 |
+
)
|
| 979 |
+
|
| 980 |
+
with gr.Column(scale=1):
|
| 981 |
+
load_button = gr.Button("π Reload All Data", visible=False, variant="primary", size="lg")
|
| 982 |
+
|
| 983 |
+
admin_status = gr.Markdown("βΉοΈ Enter admin key to access controls", visible=True)
|
| 984 |
+
|
| 985 |
+
# Admin functions
|
| 986 |
+
def verify_admin_key(key):
|
| 987 |
+
"""Verify admin key and show/hide controls"""
|
| 988 |
+
if key.strip() == os.getenv("ADMIN_KEY", ""):
|
| 989 |
+
return (
|
| 990 |
+
gr.update(visible=True), # Show reload button
|
| 991 |
+
"β
**Access Granted!** You can now reload data."
|
| 992 |
+
)
|
| 993 |
+
elif key.strip() == "":
|
| 994 |
+
return (
|
| 995 |
+
gr.update(visible=False), # Hide reload button
|
| 996 |
+
"βΉοΈ Enter admin key to access controls"
|
| 997 |
+
)
|
| 998 |
+
else:
|
| 999 |
+
return (
|
| 1000 |
+
gr.update(visible=False), # Hide reload button
|
| 1001 |
+
"β **Access Denied!** Invalid admin key."
|
| 1002 |
+
)
|
| 1003 |
+
|
| 1004 |
+
def admin_reload():
|
| 1005 |
+
"""Admin function to reload all data"""
|
| 1006 |
+
try:
|
| 1007 |
+
print("π§ Admin reload triggered...")
|
| 1008 |
+
combined_df, studentwise_data, details_info, reward_points_df = load_all_data()
|
| 1009 |
+
|
| 1010 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 1011 |
+
total_records = len(combined_df) if not combined_df.empty else 0
|
| 1012 |
+
|
| 1013 |
+
return f"""β
**Data Reload Successful!**
|
| 1014 |
+
|
| 1015 |
+
π
**Timestamp:** {timestamp}
|
| 1016 |
+
π **Total Records:** {total_records:,}
|
| 1017 |
+
π **Status:** All data sources refreshed
|
| 1018 |
+
β° **Next Auto-refresh:** 12 hours from now
|
| 1019 |
+
|
| 1020 |
+
π― **Data Sources Updated:**
|
| 1021 |
+
β’ Main spreadsheet ({len(sheet_configs)} sheets)
|
| 1022 |
+
β’ Studentwise reward points data
|
| 1023 |
+
β’ Activity breakdown data
|
| 1024 |
+
β’ System information"""
|
| 1025 |
+
|
| 1026 |
+
except Exception as e:
|
| 1027 |
+
return f"β **Error reloading data:** {str(e)}"
|
| 1028 |
+
|
| 1029 |
+
# Event handlers for admin tab
|
| 1030 |
+
admin_key.change(
|
| 1031 |
+
fn=verify_admin_key,
|
| 1032 |
+
inputs=admin_key,
|
| 1033 |
+
outputs=[load_button, admin_status]
|
| 1034 |
+
)
|
| 1035 |
+
|
| 1036 |
+
load_button.click(
|
| 1037 |
+
fn=admin_reload,
|
| 1038 |
+
outputs=admin_status
|
| 1039 |
+
)
|
| 1040 |
|
| 1041 |
# Event handlers
|
| 1042 |
search_btn.click(fn=search_student, inputs=roll_input, outputs=result_output)
|
| 1043 |
roll_input.submit(fn=search_student, inputs=roll_input, outputs=result_output)
|
| 1044 |
system_btn.click(fn=get_system_info, outputs=system_output)
|
| 1045 |
|
|
|
|
|
|
|
|
|
|
| 1046 |
# π FOOTER SECTION
|
| 1047 |
gr.Markdown("---")
|
| 1048 |
with gr.Row():
|
|
|
|
| 1072 |
elem_id="footer"
|
| 1073 |
)
|
| 1074 |
|
| 1075 |
+
# Admin mode handler on app load (for tab visibility)
|
| 1076 |
+
def setup_admin_mode(request: gr.Request):
|
| 1077 |
+
"""Setup admin mode based on URL parameters"""
|
| 1078 |
+
is_admin = check_admin_mode(request)
|
| 1079 |
+
if is_admin:
|
| 1080 |
+
return gr.update(visible=True) # Show admin tab
|
| 1081 |
+
else:
|
| 1082 |
+
return gr.update(visible=False) # Hide admin tab
|
| 1083 |
+
|
| 1084 |
+
# Apply admin mode check on load
|
| 1085 |
+
app.load(
|
| 1086 |
+
fn=setup_admin_mode,
|
| 1087 |
+
outputs=admin_tab
|
| 1088 |
+
)
|
| 1089 |
+
|
| 1090 |
+
# Load system info on startup
|
| 1091 |
+
app.load(fn=get_system_info, outputs=system_output)
|
| 1092 |
|
| 1093 |
# Launch the app
|
| 1094 |
if __name__ == "__main__":
|