Skip to content

Instantly share code, notes, and snippets.

@kaizu
Created May 29, 2025 13:33
Show Gist options
  • Save kaizu/056dd39b7b49c9855b8e33f64572b3de to your computer and use it in GitHub Desktop.
Save kaizu/056dd39b7b49c9855b8e33f64572b3de to your computer and use it in GitHub Desktop.
dashboard.py
import streamlit as st
import requests
import json
import uuid
import os
from typing import Dict, Any, Optional
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Configuration
API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000")
# Set page config
st.set_page_config(
page_title="SimpleDeckServer Dashboard",
page_icon="🎴",
layout="wide"
)
# Helper functions
def make_api_request(method: str, endpoint: str, data: Optional[Dict] = None) -> tuple[bool, Any]:
"""Make API request and return success status and response"""
try:
url = f"{API_BASE_URL}{endpoint}"
if method == "GET":
response = requests.get(url)
elif method == "POST":
response = requests.post(url, json=data)
elif method == "PUT":
response = requests.put(url, json=data)
elif method == "PATCH":
response = requests.patch(url, json=data)
elif method == "DELETE":
response = requests.delete(url)
if response.status_code == 200:
return True, response.json()
else:
return False, f"Error {response.status_code}: {response.text}"
except requests.exceptions.ConnectionError:
return False, "Connection error: Make sure the FastAPI server is running on localhost:8000"
except Exception as e:
return False, f"Error: {str(e)}"
def get_deck_state() -> tuple[bool, Dict]:
"""Get current deck state"""
return make_api_request("GET", "/state")
def get_consumables_state() -> tuple[bool, Dict]:
"""Get current consumables state"""
return make_api_request("GET", "/consumables_state")
# Main app
def main():
st.title("🎴 SimpleDeckServer Dashboard")
# Sidebar for server status
with st.sidebar:
st.header("Server Status")
if st.button("🔄 Refresh"):
st.rerun()
# Test connection
success, _ = make_api_request("GET", "/state")
if success:
st.success("✅ Server Connected")
else:
st.error("❌ Server Disconnected")
st.warning("Make sure FastAPI server is running:\n`uvicorn main:app --reload`")
# Main content tabs
tab1, tab2, tab3 = st.tabs(["🎴 Deck Management", "📦 Consumables", "🔧 Operations"])
with tab1:
st.header("Deck State")
# Get and display deck state
success, deck_state = get_deck_state()
if success:
# Display deck state in a grid
cols = st.columns(4)
spots = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
for i, spot in enumerate(spots):
with cols[i % 4]:
spot_data = deck_state.get(spot, {})
if spot_data:
st.info(f"**Spot {spot}**\n\nType: {spot_data.get('item_type', 'Unknown')}\n\nUUID: {spot_data.get('uuid', 'N/A')[:8]}...")
else:
st.success(f"**Spot {spot}**\n\n*Empty*")
else:
st.error(f"Failed to get deck state: {deck_state}")
with tab2:
st.header("Consumables Management")
# Get and display consumables state
success, consumables_state = get_consumables_state()
if success:
if consumables_state:
st.subheader("Current Consumables")
for item_type, amount in consumables_state.items():
with st.container():
col1, col2, col3, col4, col5 = st.columns([2, 1, 1, 1, 1])
with col1:
st.write(f"**{item_type}**: {amount}")
with col2:
refill_amount = st.number_input(
"Amount",
min_value=1,
value=1,
key=f"refill_amount_{item_type}",
label_visibility="collapsed"
)
with col3:
if st.button(f"Refill", key=f"refill_{item_type}"):
success, result = make_api_request("PATCH", "/refill/", {
"item_type": item_type,
"amount": refill_amount
})
if success:
st.success(f"Refilled {refill_amount} {item_type}")
st.rerun()
else:
st.error(f"Failed to refill: {result}")
with col4:
use_amount = st.number_input(
"Amount",
min_value=1,
value=1,
key=f"use_amount_{item_type}",
label_visibility="collapsed"
)
with col5:
if st.button(f"Use", key=f"use_{item_type}"):
success, result = make_api_request("PATCH", "/use/", {
"item_type": item_type,
"amount": use_amount
})
if success:
st.success(f"Used {use_amount} {item_type}")
st.rerun()
else:
st.error(f"Failed to use: {result}")
st.divider()
else:
st.info("No consumables registered")
else:
st.error(f"Failed to get consumables state: {consumables_state}")
# Add new consumable
st.subheader("Add New Consumable")
with st.form("new_consumable_form"):
new_item_type = st.text_input("Item Type", placeholder="e.g., tips, reagent")
new_amount = st.number_input("Initial Amount", min_value=0, value=10)
if st.form_submit_button("Add Consumable"):
if new_item_type:
success, result = make_api_request("POST", "/new_consumable", {
"item_type": new_item_type,
"amount": new_amount
})
if success:
st.success(f"Added new consumable: {new_item_type}")
st.rerun()
else:
st.error(f"Failed to add consumable: {result}")
else:
st.error("Please enter an item type")
with tab3:
st.header("Deck Operations")
# Move item
st.subheader("Move Item")
with st.form("move_form"):
col1, col2 = st.columns(2)
with col1:
from_spot = st.selectbox("From Spot", ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
with col2:
to_spot = st.selectbox("To Spot", ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
if st.form_submit_button("Move Item"):
if from_spot != to_spot:
success, result = make_api_request("POST", "/move", {
"from_spot": from_spot,
"to_spot": to_spot
})
if success:
st.success(f"Moved item from {from_spot} to {to_spot}")
st.rerun()
else:
st.error(f"Failed to move item: {result}")
else:
st.error("From and To spots must be different")
# Put item
st.subheader("Put Item")
with st.form("put_item_form"):
put_spot = st.selectbox("Spot", ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'], key="put_spot")
item_type = st.text_input("Item Type", placeholder="e.g., sample, plate")
if st.form_submit_button("Put Item"):
if item_type:
item_uuid = str(uuid.uuid4())
success, result = make_api_request("PUT", f"/put_item/{put_spot}/", {
"uuid": item_uuid,
"item_type": item_type
})
if success:
st.success(f"Put item '{item_type}' in spot {put_spot}")
st.rerun()
else:
st.error(f"Failed to put item: {result}")
else:
st.error("Please enter an item type")
# Delete item
st.subheader("Delete Item")
with st.form("delete_form"):
delete_spot = st.selectbox("Spot", ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'], key="delete_spot")
if st.form_submit_button("Delete Item", type="primary"):
success, result = make_api_request("DELETE", f"/delete/{delete_spot}")
if success:
st.success(f"Deleted item from spot {delete_spot}")
st.rerun()
else:
st.error(f"Failed to delete item: {result}")
# Consumables operations
st.subheader("Consumables Operations")
# Refill consumables
with st.expander("Refill Consumables"):
with st.form("refill_form"):
refill_item_type = st.text_input("Item Type", key="refill_item_type")
refill_amount = st.number_input("Amount to Refill", min_value=1, value=1, key="refill_amount")
if st.form_submit_button("Refill"):
if refill_item_type:
success, result = make_api_request("PATCH", "/refill/", {
"item_type": refill_item_type,
"amount": refill_amount
})
if success:
st.success(f"Refilled {refill_amount} of {refill_item_type}")
st.rerun()
else:
st.error(f"Failed to refill: {result}")
else:
st.error("Please enter an item type")
# Use consumables
with st.expander("Use Consumables"):
with st.form("use_form"):
use_item_type = st.text_input("Item Type", key="use_item_type")
use_amount = st.number_input("Amount to Use", min_value=1, value=1, key="use_amount")
if st.form_submit_button("Use"):
if use_item_type:
success, result = make_api_request("PATCH", "/use/", {
"item_type": use_item_type,
"amount": use_amount
})
if success:
st.success(f"Used {use_amount} of {use_item_type}")
st.rerun()
else:
st.error(f"Failed to use: {result}")
else:
st.error("Please enter an item type")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment