add admin page to manage private data

This commit is contained in:
Carl Zhang
2025-10-13 23:50:53 -05:00
parent c4066ed15e
commit 421d263b6b
9 changed files with 289 additions and 3 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ files/student_locations.db
__pycache__/
*.pyc
*.cpython-312.pyc
venv/

8
app.py
View File

@@ -2,13 +2,12 @@ import streamlit as st
import webpages as pg
from webpages.navigation import navigation_bar as nv
st.set_page_config(page_title="BuffTeks Student Organization",
page_icon="./images/BuffTeksLogo.png",
layout = "centered"
)
page_label = nv()
# block for main pages
@@ -64,4 +63,7 @@ elif page_label == "CoreTeks":
elif page_label == "Testing":
pg.testing()
elif page_label == "Reference":
pg.reference()
pg.reference()
elif page_label == "Admin":
pg.admin()

View File

@@ -19,3 +19,4 @@ from .outstanding_members import outstanding_members
from .cis_tech_challenge_pages.cis_tech_challenge_homepage import cis_tech_challenge_homepage
from .coreteks_pages.coreteks_homepage import coreteks_homepage
from .SendEmail import send_email
from .admin import admin

34
webpages/admin.py Normal file
View File

@@ -0,0 +1,34 @@
import streamlit as st
import pandas as pd
import streamlit_authenticator as stauth
from webpages.buffteks_authenticator import load_authenticator
from webpages.adminpages.helloween_image import camera_photo
def admin():
try:
authenticator = load_authenticator()
authenticator.login()
except Exception as e:
st.error(e)
if st.session_state.get('authentication_status') is False:
st.error('Username/password is incorrect')
elif st.session_state.get('authentication_status') is None:
st.warning('Please enter your username and password')
if st.session_state.get('authentication_status'):
authenticator.logout()
st.header(f'Welcome *{st.session_state.get("name")}*')
st.divider()
with st.expander("Check Memberships"):
new_members = pd.read_json("new_members.json")
st.dataframe(new_members)
st.divider()
camera_photo()

View File

@@ -0,0 +1,68 @@
import smtplib
import io
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
def send_email_with_attachment(sender_email, password, to_email, subject, image_bytes):
"""
Sends an email with an image attachment.
Args:
sender_email (str): The sender's email address.
password (str): The sender's email password.
to_email (str): The recipient's email address.
subject (str): The subject of the email.
image_bytes (bytes): The image content as a byte string.
"""
message = MIMEMultipart('alternative')
message['Subject'] = subject
message['From'] = sender_email
message['To'] = to_email
html_content = """
<html>
<head>
<title>Happy Halloween!</title>
</head>
<body style="font-family: Arial, sans-serif; background-color: #1a1a1a; color: #f0f0f0; margin: 0; padding: 20px; text-align: center;">
<div style="max-width: 600px; margin: auto; background-color: #2c2c2c; padding: 30px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.5);">
<h2 style="color: #ff6600;">🎃 Happy Halloween! 🎃</h2>
<p style="font-size: 16px; line-height: 1.6;">
Thank you for your interest in the
<a href="https://buffteks.org" target="_blank" rel="noopener" style="color: #ff8533; text-decoration: none;" onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';">BuffTeks</a>
student organization!
</p>
<p style="font-size: 16px; line-height: 1.6;">
Wishing you a spooktacular Halloween!<br>
Here is your special holiday photo generated just for you.<br>
Enjoy and have a frightfully fun day!
</p>
</div>
</body>
</html>
"""
message.attach(MIMEText(html_content, 'html'))
if image_bytes:
# The image object is converted to bytes before attaching.
img_byte_arr = io.BytesIO()
# Assuming image_bytes is a PIL Image object.
# Convert to RGB if necessary for JPEG format.
if image_bytes.mode in ('RGBA', 'P'):
image_bytes = image_bytes.convert('RGB')
image_bytes.save(img_byte_arr, format='JPEG')
img_byte_arr = img_byte_arr.getvalue()
image_part = MIMEImage(img_byte_arr, _subtype="jpeg")
image_part.add_header('Content-Disposition', 'attachment', filename="halloween.jpg")
message.attach(image_part)
try:
with smtplib.SMTP('mail.privateemail.com', 587) as server:
server.starttls()
server.login(sender_email, password)
server.send_message(message)
print("Email sent successfully!")
except Exception as e:
print(f"Failed to send email: {str(e)}")

View File

@@ -0,0 +1,123 @@
import streamlit as st
from PIL import Image
from google import genai
import json
from io import BytesIO
from .email_helloween_image import send_email_with_attachment
def camera_photo():
st.markdown("<h1 style='text-align: center; color: #451002;'>🎃 Halloween Photo Booth 📸</h1>", unsafe_allow_html=True)
st.markdown("<h5 style='text-align: center;'> Capture your Halloween spirit with a festive photo! 👻 </h3>", unsafe_allow_html=True)
if 'image_response' not in st.session_state:
st.session_state.image_response = None
enable_camera = st.checkbox("Enable Camera")
if enable_camera:
st.info("📎Note: No photos are stored. The image is processed in-memory and deleted after refresh app.")
picture = st.camera_input("Take a Halloween-themed photo! 🎃👻🕸️")
if picture is not None:
image = Image.open(picture)
keep_dressing_style = st.checkbox("Keep original dressing style", value=False)
keep_body_pose = st.checkbox("Keep original body pose", value=False)
theme = st.selectbox(
"Select or Input a Halloween theme for your photo:",
[ "🎃 Classic Halloween",
"👻 Haunted House",
"⚗️ Science Experiment Gone Wrong",
"🎥 Horror Film Inspired",
"🕯️ Gothic",
"🧙‍♂️ Harry Potter",
"⚰️ Graveyard",
"🔮 Cult Horror Theme"
],
index=0,
key="theme_selector"
)
if st.button("AI Editing"):
edit_prompt = f"""
Retain original facial features (eyes, nose, mouth etc.) of any persons in the photo. The goal is to achieve a realistic, edited look, not an AI-generated or overly smoothed/perfected appearance. Avoid any artificial alterations to these features.
Keep the original dressing style: {str(keep_dressing_style)}
Keep the original body pose: {str(keep_body_pose)}
Theme: {theme}
If a person is already in costume, enhance the costume and add more Halloween elements and background to the scene.
If multiple persons are present, ensure all are included in the Halloween theme.
- Overall Style: The final image should be photorealistic.
Ensure the final image is vibrant, festive, and captures the Halloween spirit!
"""
with st.spinner("Editing image..."):
text_response, image_response = edit_image_with_ai(image, edit_prompt)
if text_response:
st.info(text_response)
if image_response:
st.session_state.image_response = image_response
# Rerun to display the generated image and send options
st.rerun()
if st.session_state.image_response:
st.image(st.session_state.image_response)
# Convert PIL image to bytes for download
img_byte_arr = BytesIO()
st.session_state.image_response.save(img_byte_arr, format="JPEG")
st.download_button(
label="Download Edited Image",
data=img_byte_arr.getvalue(),
file_name="edited_image.jpeg",
mime="image/jpeg"
)
send_image(st.session_state.image_response)
def send_image(image_response):
st.divider()
to_email = st.text_input("Enter your email to receive the photo:", key="email_input")
if st.button("Send Email"):
with st.spinner("Sending email..."):
if to_email and image_response:
with open('app_config.json') as config_file:
config = json.load(config_file)
sender_email = config["send_email"]["sender_email"]
password = config["send_email"]["password"]
subject = "Your Spooktacular Halloween Photo! 🎃👻"
send_email_with_attachment(sender_email, password, to_email, subject, image_response)
st.success("Email sent successfully!")
else:
st.error("Please enter a valid email address and ensure the image is generated.")
def edit_image_with_ai(image, description):
with open('app_config.json') as config_file:
config = json.load(config_file)
api_key = config["nano-banana"]["api_key"]
client = genai.Client(api_key=api_key)
prompt = description
response = client.models.generate_content(
model="gemini-2.5-flash-image-preview",
contents=[prompt, image],
)
text_response = None
image_response = None
# AttributeError: 'NoneType' object has no attribute 'parts'
if response.candidates and len(response.candidates) <= 0:
st.error("No response from AI model. Please try again.")
return text_response, image_response
for part in response.candidates[0].content.parts:
if part.text is not None:
text_response = part.text
if part.inline_data is not None:
image_response = Image.open(BytesIO(part.inline_data.data))
return text_response, image_response

View File

@@ -0,0 +1,18 @@
# for authentication
import yaml
from yaml.loader import SafeLoader
import streamlit_authenticator as stauth
def load_authenticator():
with open('webpages/users.yaml') as f:
users = yaml.load(f, Loader=SafeLoader)
authenticator = stauth.Authenticate(
users['credentials'],
users['cookie']['name'],
users['cookie']['key'],
users['cookie']['expiry_days']
)
return authenticator

View File

@@ -2,9 +2,13 @@ import streamlit as st
import streamlit_antd_components as sac
from .buffteks_authenticator import load_authenticator
# This doc is used to set up the navigation bar
# basic structure is:
def navigation_bar():
# start page is the homepage
page_label = "Homepage"
@@ -44,6 +48,9 @@ def navigation_bar():
sac.MenuItem(type='divider'),
sac.MenuItem("Reference", icon='paperclip'),
sac.MenuItem(type='divider'),
sac.MenuItem("Admin", icon='lock'),
], open_all=True)

32
webpages/users.yaml Normal file
View File

@@ -0,0 +1,32 @@
cookie:
expiry_days: 30
key: buffteks.org # To be filled with any string
name: buffteks.org # To be filled with any string
credentials:
usernames:
czhang:
email: czhang@wtamu.edu
failed_login_attempts: 0 # Will be managed automatically
first_name: Carl
last_name: Zhang
logged_in: False # Will be managed automatically
password: 1992Carl@* # Will be hashed automatically
roles: # Optional
- admin
- editor
- viewer
# oauth2: # Optional
# google: # Follow instructions: https://developers.google.com/identity/protocols/oauth2
# client_id: # To be filled
# client_secret: # To be filled
# redirect_uri: # URL to redirect to after OAuth2 authentication
# microsoft: # Follow instructions: https://learn.microsoft.com/en-us/graph/auth-register-app-v2
# client_id: # To be filled
# client_secret: # To be filled
# redirect_uri: # URL to redirect to after OAuth2 authentication
# tenant_id: # To be filled
# pre-authorized: # Optional
# emails:
# - melsby@gmail.com
# api_key: # Optional - register to receive a free API key: https://stauthenticator.com