Created
May 29, 2025 13:33
-
-
Save kaizu/056dd39b7b49c9855b8e33f64572b3de to your computer and use it in GitHub Desktop.
dashboard.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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