Skip to content

Commit e671957

Browse files
TamTunnelTamTunnel
authored andcommitted
Implement GUI Demo Mode and Create Wiki
GUI Demo Mode: - Added backend/core-orbits/app/services/demo.py: Reusable demo seed service - Added backend/core-orbits/app/demo_routes.py: API endpoint for seeding - Updated backend/common/config.py: Added DEMO_MODE - Updated frontend/web/src/pages/Login.tsx: New Login page with Demo controls - Updated frontend/web/src/api/client.ts: Added auth interceptor and demo methods - Updated frontend/web/src/App.tsx: Added /login route - Updated frontend/web/src/components/Layout.tsx: Added User/Login access Wiki Documentation: - Created wiki/ directory with comprehensive docs: - Home.md, Getting-Started.md, Architecture.md - Security.md, User-Guide.md, Limitations.md, Development.md
1 parent 8df9054 commit e671957

File tree

16 files changed

+1100
-341
lines changed

16 files changed

+1100
-341
lines changed

backend/common/config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ class Settings(BaseSettings):
2626
auth_disabled: bool = False # Set True for local dev without auth
2727
jwt_secret_key: str = "change-me-in-production-use-a-real-secret-key"
2828
jwt_algorithm: str = "HS256"
29-
jwt_expire_minutes: int = 60
29+
jwt_expire_minutes: int = 60 * 24 # 24 hours
30+
31+
# Demo Mode
32+
demo_mode: bool = True
3033
admin_api_key: str = "" # Optional API key for service-to-service calls
3134

3235
# AI/LLM
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Demo Routes.
3+
4+
Provides endpoints for demo setup and management.
5+
"""
6+
from fastapi import APIRouter, Depends, HTTPException
7+
from sqlalchemy.ext.asyncio import AsyncSession
8+
9+
from .db import get_db
10+
from common.config import get_settings
11+
from .services.demo import seed_demo_data
12+
13+
router = APIRouter(prefix="/demo", tags=["Demo"])
14+
15+
@router.post("/seed", status_code=201)
16+
async def seed_demo(
17+
db: AsyncSession = Depends(get_db),
18+
settings = Depends(get_settings)
19+
):
20+
"""
21+
Seed the database with demo data.
22+
23+
Only available if DEMO_MODE is enabled.
24+
"""
25+
if not settings.demo_mode:
26+
raise HTTPException(status_code=403, detail="Demo mode is disabled")
27+
28+
try:
29+
result = await seed_demo_data(db)
30+
return result
31+
except Exception as e:
32+
raise HTTPException(status_code=500, detail=str(e))

backend/core-orbits/app/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
sys.path.insert(0, str(__file__).replace('/core-orbits/app/main.py', ''))
1313

1414
from .routes import router
15+
from .demo_routes import router as demo_router
1516
from .db import init_db, get_db
1617

1718
# Import from common module
@@ -82,6 +83,7 @@ async def request_id_middleware(request: Request, call_next):
8283

8384
# Include main routes
8485
app.include_router(router)
86+
app.include_router(demo_router)
8587

8688
# Include TLE routes
8789
try:
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""
2+
Demo Data Service.
3+
4+
Provides functionality to seed the database with demo data.
5+
"""
6+
from sqlalchemy import text
7+
from sqlalchemy.ext.asyncio import AsyncSession
8+
from common.auth import hash_password
9+
from common.logger import get_logger
10+
11+
logger = get_logger("demo-service")
12+
13+
# Sample TLE data (ISS and Starlink samples)
14+
DEMO_TLES = [
15+
{
16+
"name": "ISS (ZARYA)",
17+
"norad_id": "25544",
18+
"tle1": "1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9021",
19+
"tle2": "2 25544 51.6400 208.9163 0006703 280.7808 79.2154 15.49815776 29",
20+
},
21+
{
22+
"name": "STARLINK-1007",
23+
"norad_id": "44713",
24+
"tle1": "1 44713U 19074A 24001.50000000 .00001234 00000-0 98765-4 0 9012",
25+
"tle2": "2 44713 53.0000 120.0000 0001500 100.0000 260.0000 15.06000000123456",
26+
},
27+
{
28+
"name": "STARLINK-1008",
29+
"norad_id": "44714",
30+
"tle1": "1 44714U 19074B 24001.50000000 .00001234 00000-0 98765-4 0 9013",
31+
"tle2": "2 44714 53.0000 121.0000 0001500 101.0000 259.0000 15.06000000123457",
32+
},
33+
{
34+
"name": "STARLINK-1009",
35+
"norad_id": "44715",
36+
"tle1": "1 44715U 19074C 24001.50000000 .00001234 00000-0 98765-4 0 9014",
37+
"tle2": "2 44715 53.0000 122.0000 0001500 102.0000 258.0000 15.06000000123458",
38+
},
39+
{
40+
"name": "STARLINK-1010",
41+
"norad_id": "44716",
42+
"tle1": "1 44716U 19074D 24001.50000000 .00001234 00000-0 98765-4 0 9015",
43+
"tle2": "2 44716 53.0000 123.0000 0001500 103.0000 257.0000 15.06000000123459",
44+
},
45+
{
46+
"name": "STARLINK-1011",
47+
"norad_id": "44717",
48+
"tle1": "1 44717U 19074E 24001.50000000 .00001234 00000-0 98765-4 0 9016",
49+
"tle2": "2 44717 53.0000 124.0000 0001500 104.0000 256.0000 15.06000000123460",
50+
},
51+
]
52+
53+
GROUND_STATIONS = [
54+
{
55+
"name": "AWS Ground Station - US West",
56+
"latitude": 37.7749,
57+
"longitude": -122.4194,
58+
"elevation_m": 50,
59+
"min_elevation_deg": 10.0,
60+
},
61+
{
62+
"name": "ESA Ground Station - Europe",
63+
"latitude": 52.2985,
64+
"longitude": 5.1719,
65+
"elevation_m": 100,
66+
"min_elevation_deg": 10.0,
67+
},
68+
{
69+
"name": "JAXA Ground Station - Asia",
70+
"latitude": 35.6762,
71+
"longitude": 139.6503,
72+
"elevation_m": 20,
73+
"min_elevation_deg": 10.0,
74+
},
75+
]
76+
77+
DEMO_USERS = [
78+
{
79+
"username": "demo_viewer",
80+
"email": "viewer@demo.constellation-hub.local",
81+
"password": "viewer123",
82+
"full_name": "Demo Viewer",
83+
"role": "viewer",
84+
},
85+
{
86+
"username": "demo_ops",
87+
"email": "ops@demo.constellation-hub.local",
88+
"password": "operator123",
89+
"full_name": "Demo Operator",
90+
"role": "operator",
91+
},
92+
{
93+
"username": "demo_admin",
94+
"email": "admin@demo.constellation-hub.local",
95+
"password": "admin123",
96+
"full_name": "Demo Administrator",
97+
"role": "admin",
98+
},
99+
]
100+
101+
async def seed_demo_data(session: AsyncSession):
102+
"""Seed the database with demo data."""
103+
logger.info("Initializing demo data seed...")
104+
105+
# Create constellation
106+
logger.info("📡 Creating demo constellation...")
107+
result = await session.execute(text("""
108+
INSERT INTO constellations (name, description, orbit_regime, altitude_km, inclination_deg, num_planes, sats_per_plane)
109+
VALUES (:name, :description, :orbit_regime, :altitude_km, :inclination_deg, :num_planes, :sats_per_plane)
110+
RETURNING id
111+
"""), {
112+
"name": "Demo LEO Constellation",
113+
"description": "Demonstration constellation for Constellation Hub showcase",
114+
"orbit_regime": "LEO",
115+
"altitude_km": 550.0,
116+
"inclination_deg": 53.0,
117+
"num_planes": 1,
118+
"sats_per_plane": len(DEMO_TLES)
119+
})
120+
constellation_id = result.scalar_one()
121+
122+
# Create satellites
123+
for tle_data in DEMO_TLES:
124+
await session.execute(text("""
125+
INSERT INTO satellites (constellation_id, name, norad_id, tle_line1, tle_line2, mass_kg, power_watts, orbit_type, status)
126+
VALUES (:constellation_id, :name, :norad_id, :tle1, :tle2, :mass_kg, :power_watts, :orbit_type, :status)
127+
"""), {
128+
"constellation_id": constellation_id,
129+
"name": tle_data["name"],
130+
"norad_id": tle_data["norad_id"],
131+
"tle1": tle_data["tle1"],
132+
"tle2": tle_data["tle2"],
133+
"mass_kg": 260.0 if "STARLINK" in tle_data["name"] else 420000.0,
134+
"power_watts": 1500.0 if "STARLINK" in tle_data["name"] else 84000.0,
135+
"orbit_type": "LEO",
136+
"status": "operational"
137+
})
138+
139+
# Create ground stations
140+
logger.info("🌍 Creating ground stations...")
141+
for gs_data in GROUND_STATIONS:
142+
await session.execute(text("""
143+
INSERT INTO ground_stations (name, latitude, longitude, elevation_m, min_elevation_deg, status)
144+
VALUES (:name, :latitude, :longitude, :elevation_m, :min_elevation_deg, :status)
145+
"""), {
146+
**gs_data,
147+
"status": "operational"
148+
})
149+
150+
# Create demo users
151+
logger.info("👥 Creating demo users...")
152+
for user_data in DEMO_USERS:
153+
# Check if user exists
154+
result = await session.execute(
155+
text("SELECT id FROM users WHERE username = :username"),
156+
{"username": user_data["username"]}
157+
)
158+
if result.scalar_one_or_none():
159+
logger.info(f" ⚠️ User '{user_data['username']}' already exists, skipping")
160+
continue
161+
162+
await session.execute(text("""
163+
INSERT INTO users (username, email, hashed_password, full_name, role, is_active)
164+
VALUES (:username, :email, :hashed_password, :full_name, :role, :is_active)
165+
"""), {
166+
"username": user_data["username"],
167+
"email": user_data["email"],
168+
"hashed_password": hash_password(user_data["password"]),
169+
"full_name": user_data["full_name"],
170+
"role": user_data["role"],
171+
"is_active": True
172+
})
173+
174+
logger.info("✅ Demo data seed completed")
175+
return {"status": "success", "message": "Demo data loaded successfully"}

0 commit comments

Comments
 (0)