Ig0tU commited on
Commit
d773970
·
1 Parent(s): 9806491

fix: integrate dashboard into main.py and switch to single-port 7860 for HF

Browse files
Files changed (4) hide show
  1. Dockerfile +3 -5
  2. __pycache__/main.cpython-314.pyc +0 -0
  3. app.py +0 -169
  4. main.py +203 -1
Dockerfile CHANGED
@@ -13,10 +13,8 @@ RUN pip3 install -r requirements.txt
13
 
14
  COPY . .
15
 
16
- # Expose ports for FastAPI (8000) and Streamlit (7860 - default HF Space port)
17
- EXPOSE 8000
18
  EXPOSE 7860
19
 
20
- # Command to run both FastAPI and Streamlit
21
- # We use & to run FastAPI in background and streamlit in foreground
22
- CMD uvicorn main:app --host 0.0.0.0 --port 8000 & streamlit run app.py --server.port 7860 --server.address 0.0.0.0
 
13
 
14
  COPY . .
15
 
16
+ # Expose only the primary port for HF Space
 
17
  EXPOSE 7860
18
 
19
+ # Run FastAPI on the primary port
20
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
__pycache__/main.cpython-314.pyc CHANGED
Binary files a/__pycache__/main.cpython-314.pyc and b/__pycache__/main.cpython-314.pyc differ
 
app.py DELETED
@@ -1,169 +0,0 @@
1
- import streamlit as st
2
- import httpx
3
- import json
4
- import pandas as pd
5
- from datetime import datetime
6
-
7
- st.set_page_config(
8
- page_title="Weatherhack API Dashboard",
9
- page_icon="🌤️",
10
- layout="wide",
11
- )
12
-
13
- # Custom CSS for glassmorphic premium look
14
- st.markdown(
15
- """
16
- <style>
17
- .main {
18
- background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
19
- color: white;
20
- }
21
- .stApp {
22
- background: transparent;
23
- }
24
- [data-testid="stSidebar"] {
25
- background-color: rgba(255, 255, 255, 0.05);
26
- backdrop-filter: blur(10px);
27
- }
28
- .stMetric {
29
- background: rgba(255, 255, 255, 0.1);
30
- padding: 15px;
31
- border-radius: 10px;
32
- border: 1px solid rgba(255, 255, 255, 0.1);
33
- }
34
- .stJson {
35
- background-color: #0e1117;
36
- border-radius: 5px;
37
- padding: 10px;
38
- }
39
- </style>
40
- """,
41
- unsafe_allow_html=True,
42
- )
43
-
44
- st.title("🌤️ Weatherhack API Dashboard")
45
- st.markdown("---")
46
-
47
- # Sidebar - API Configuration
48
- st.sidebar.header("🔑 API Configuration")
49
- api_base_url = st.sidebar.text_input("API Base URL", value="http://localhost:8000")
50
- access_key = st.sidebar.text_input(
51
- "Access Key", value="b55a6ab9aec1ab156c0b46f0a9ef93dd", type="password"
52
- )
53
-
54
- st.sidebar.markdown("---")
55
- st.sidebar.subheader("📍 Quick Test")
56
- query = st.sidebar.text_input("Location Query", value="New York")
57
- units = st.sidebar.selectbox(
58
- "Units",
59
- options=["m", "f", "s"],
60
- format_func=lambda x: {"m": "Metric", "f": "Fahrenheit", "s": "Scientific"}[x],
61
- )
62
-
63
- # Main Area - Tabs
64
- tab1, tab2, tab3 = st.tabs(
65
- ["🌤️ Current Weather", "📅 Forecast / Historical", "🔍 Autocomplete"]
66
- )
67
-
68
- with tab1:
69
- st.header("Real-time Weather Data")
70
- if st.button("Fetch Current Weather"):
71
- with st.spinner("Requesting data..."):
72
- try:
73
- url = f"{api_base_url}/current"
74
- params = {"access_key": access_key, "query": query, "units": units}
75
- response = httpx.get(url, params=params, timeout=10.0)
76
-
77
- if response.status_code == 200:
78
- data = response.json()
79
-
80
- # Dashboard Layout
81
- col1, col2, col3, col4 = st.columns(4)
82
-
83
- with col1:
84
- st.metric("Temperature", f"{data['current']['temperature']}°")
85
- st.write(f"Feels like: {data['current']['feelslike']}°")
86
- with col2:
87
- st.metric("Humidity", f"{data['current']['humidity']}%")
88
- st.write(f"Cloudcover: {data['current']['cloudcover']}%")
89
- with col3:
90
- st.metric("Wind Speed", f"{data['current']['wind_speed']}")
91
- st.write(
92
- f"Direction: {data['current']['wind_dir']} ({data['current']['wind_degree']}°)"
93
- )
94
- with col4:
95
- st.metric("UV Index", f"{data['current']['uv_index']}")
96
- st.write(f"Visibility: {data['current']['visibility']} km")
97
-
98
- st.markdown("### 🍃 Air Quality Indices")
99
- aq = data["current"].get("air_quality")
100
- if aq:
101
- aq_col1, aq_col2, aq_col3 = st.columns(3)
102
- with aq_col1:
103
- st.write(f"**PM2.5**: {aq['pm2_5']}")
104
- st.write(f"**PM10**: {aq['pm10']}")
105
- with aq_col2:
106
- st.write(f"**CO**: {aq['co']}")
107
- st.write(f"**NO2**: {aq['no2']}")
108
- with aq_col3:
109
- st.write(f"**US EPA Index**: {aq.get('us-epa-index')}")
110
- st.write(f"**GB DEFRA Index**: {aq.get('gb-defra-index')}")
111
-
112
- st.markdown("### 📜 Raw JSON Response")
113
- st.json(data)
114
- else:
115
- st.error(f"Error {response.status_code}: {response.text}")
116
- except Exception as e:
117
- st.error(f"Request failed: {str(e)}")
118
-
119
- with tab2:
120
- st.header("Historical & Forecast Data")
121
- sub_tab1, sub_tab2 = st.tabs(["7-Day Forecast", "Historical Query"])
122
-
123
- with sub_tab1:
124
- if st.button("Get Forecast"):
125
- params = {
126
- "access_key": access_key,
127
- "query": query,
128
- "forecast_days": 7,
129
- "units": units,
130
- }
131
- res = httpx.get(f"{api_base_url}/forecast", params=params)
132
- if res.status_code == 200:
133
- st.json(res.json())
134
- else:
135
- st.error(res.text)
136
-
137
- with sub_tab2:
138
- hist_date = st.date_input("Select Date", value=datetime.now())
139
- if st.button("Get Historical"):
140
- params = {
141
- "access_key": access_key,
142
- "query": query,
143
- "historical_date": hist_date.isoformat(),
144
- "units": units,
145
- }
146
- res = httpx.get(f"{api_base_url}/historical", params=params)
147
- if res.status_code == 200:
148
- st.json(res.json())
149
- else:
150
- st.error(res.text)
151
-
152
- with tab3:
153
- st.header("Location Autocomplete")
154
- ac_query = st.text_input("Type a city name...", value="San Fran")
155
- if st.button("Search Suggestions"):
156
- params = {"access_key": access_key, "query": ac_query}
157
- res = httpx.get(f"{api_base_url}/autocomplete", params=params)
158
- if res.status_code == 200:
159
- data = res.json()
160
- if data.get("locations"):
161
- df = pd.DataFrame(data["locations"])
162
- st.table(df[["name", "country", "region", "timezone_id"]])
163
- else:
164
- st.info("No suggestions found.")
165
- else:
166
- st.error(res.text)
167
-
168
- st.markdown("---")
169
- st.caption("Weatherhack API | Zero-Cost Infrastructure | Open-Meteo & Nominatim")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -19,11 +19,20 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
19
  class Settings(BaseSettings):
20
  weatherstack_access_key: str = "test"
21
 
22
- model_config = SettingsConfigDict(env_file=".env", extra="ignore")
 
 
 
 
23
 
24
 
25
  settings = Settings()
26
 
 
 
 
 
 
27
 
28
  def validate_key(access_key: str):
29
  if access_key != settings.weatherstack_access_key:
@@ -214,5 +223,198 @@ async def autocomplete_location(access_key: str, query: str):
214
  )
215
 
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  if __name__ == "__main__":
218
  uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
 
19
  class Settings(BaseSettings):
20
  weatherstack_access_key: str = "test"
21
 
22
+ model_config = SettingsConfigDict(
23
+ env_file=".env",
24
+ extra="ignore",
25
+ env_prefix="", # Ensure it picks up bare env vars
26
+ )
27
 
28
 
29
  settings = Settings()
30
 
31
+ # Debug log for key loading (masked)
32
+ print(
33
+ f"DEBUG: Key loaded: {settings.weatherstack_access_key[:4]}...{settings.weatherstack_access_key[-4:]}"
34
+ )
35
+
36
 
37
  def validate_key(access_key: str):
38
  if access_key != settings.weatherstack_access_key:
 
223
  )
224
 
225
 
226
+ from fastapi.responses import HTMLResponse, RedirectResponse
227
+
228
+
229
+ @app.get("/", include_in_schema=False)
230
+ async def root():
231
+ return RedirectResponse(url="/dashboard")
232
+
233
+
234
+ @app.get("/dashboard", response_class=HTMLResponse, include_in_schema=False)
235
+ async def dashboard():
236
+ return """
237
+ <!DOCTYPE html>
238
+ <html lang="en">
239
+ <head>
240
+ <meta charset="UTF-8">
241
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
242
+ <title>Weatherhack API | Dashboard</title>
243
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
244
+ <style>
245
+ :root {
246
+ --primary: #3b82f6;
247
+ --primary-dark: #1e40af;
248
+ --bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
249
+ --glass: rgba(255, 255, 255, 0.05);
250
+ --glass-border: rgba(255, 255, 255, 0.1);
251
+ }
252
+ body {
253
+ font-family: 'Inter', sans-serif;
254
+ background: var(--bg-gradient);
255
+ color: white;
256
+ min-height: 100vh;
257
+ margin: 0;
258
+ display: flex;
259
+ flex-direction: column;
260
+ align-items: center;
261
+ padding: 2rem;
262
+ }
263
+ .container {
264
+ max-width: 900px;
265
+ width: 100%;
266
+ }
267
+ .header {
268
+ text-align: center;
269
+ margin-bottom: 3rem;
270
+ }
271
+ .header h1 {
272
+ font-size: 3rem;
273
+ margin-bottom: 0.5rem;
274
+ background: linear-gradient(to right, #60a5fa, #a855f7);
275
+ -webkit-background-clip: text;
276
+ -webkit-text-fill-color: transparent;
277
+ }
278
+ .card {
279
+ background: var(--glass);
280
+ backdrop-filter: blur(12px);
281
+ border: 1px solid var(--glass-border);
282
+ border-radius: 1.5rem;
283
+ padding: 2rem;
284
+ margin-bottom: 2rem;
285
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
286
+ }
287
+ .input-group {
288
+ display: flex;
289
+ gap: 1rem;
290
+ margin-bottom: 1.5rem;
291
+ flex-wrap: wrap;
292
+ }
293
+ input, select {
294
+ background: rgba(0,0,0,0.2);
295
+ border: 1px solid var(--glass-border);
296
+ color: white;
297
+ padding: 0.75rem 1rem;
298
+ border-radius: 0.75rem;
299
+ font-size: 1rem;
300
+ flex: 1;
301
+ min-width: 200px;
302
+ }
303
+ button {
304
+ background: var(--primary);
305
+ color: white;
306
+ border: none;
307
+ padding: 0.75rem 2rem;
308
+ border-radius: 0.75rem;
309
+ font-weight: 600;
310
+ cursor: pointer;
311
+ transition: all 0.2s;
312
+ }
313
+ button:hover {
314
+ background: var(--primary-dark);
315
+ transform: translateY(-2px);
316
+ }
317
+ pre {
318
+ background: #000;
319
+ padding: 1rem;
320
+ border-radius: 1rem;
321
+ overflow-x: auto;
322
+ max-height: 400px;
323
+ border: 1px solid var(--glass-border);
324
+ }
325
+ .grid {
326
+ display: grid;
327
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
328
+ gap: 1.5rem;
329
+ margin-bottom: 2rem;
330
+ }
331
+ .stat {
332
+ background: rgba(255,255,255,0.03);
333
+ padding: 1.5rem;
334
+ border-radius: 1rem;
335
+ text-align: center;
336
+ }
337
+ .stat h3 { margin: 0; color: #94a3b8; font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.05em; }
338
+ .stat p { margin: 0.5rem 0 0; font-size: 1.5rem; font-weight: 700; }
339
+ .badge { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 0.5rem; background: var(--primary); font-size: 0.75rem; font-weight: 700; }
340
+ </style>
341
+ </head>
342
+ <body>
343
+ <div class="container">
344
+ <div class="header">
345
+ <h1>🌤️ Weatherhack API</h1>
346
+ <p>Premium, Zero-Cost Weather Infrastructure</p>
347
+ </div>
348
+
349
+ <div class="card">
350
+ <h3>🔑 API Configuration</h3>
351
+ <div class="input-group">
352
+ <input type="password" id="access_key" placeholder="Enter Access Key" value="b55a6ab9aec1ab156c0b46f0a9ef93dd">
353
+ <input type="text" id="query" placeholder="City or Lat,Lon" value="Monticello, Indiana">
354
+ <select id="units">
355
+ <option value="m">Metric</option>
356
+ <option value="f">Fahrenheit</option>
357
+ <option value="s">Scientific</option>
358
+ </select>
359
+ </div>
360
+ <button onclick="fetchWeather()">Fetch Current Weather</button>
361
+ </div>
362
+
363
+ <div id="results" style="display:none;">
364
+ <div class="card">
365
+ <div class="grid" id="stats-grid"></div>
366
+ <h3>📜 API Response</h3>
367
+ <pre id="json-output"></pre>
368
+ </div>
369
+ </div>
370
+ </div>
371
+
372
+ <script>
373
+ async function fetchWeather() {
374
+ const key = document.getElementById('access_key').value;
375
+ const query = document.getElementById('query').value;
376
+ const units = document.getElementById('units').value;
377
+ const btn = document.querySelector('button');
378
+
379
+ btn.innerText = 'Loading...';
380
+ btn.disabled = true;
381
+
382
+ try {
383
+ const url = `/current?access_key=${key}&query=${query}&units=${units}`;
384
+ const res = await fetch(url);
385
+ const data = await res.json();
386
+
387
+ document.getElementById('results').style.display = 'block';
388
+ document.getElementById('json-output').innerText = JSON.stringify(data, null, 2);
389
+
390
+ if (data.current) {
391
+ const stats = [
392
+ { label: 'Temperature', val: `${data.current.temperature}°` },
393
+ { label: 'Condition', val: data.current.weather_descriptions[0] },
394
+ { label: 'Wind Speed', val: `${data.current.wind_speed} ${units === 'f' ? 'mph' : 'km/h'}` },
395
+ { label: 'Air Quality (PM2.5)', val: data.current.air_quality ? data.current.air_quality.pm2_5 : 'N/A' }
396
+ ];
397
+ document.getElementById('stats-grid').innerHTML = stats.map(s => `
398
+ <div class="stat">
399
+ <h3>${s.label}</h3>
400
+ <p>${s.val}</p>
401
+ </div>
402
+ `).join('');
403
+ } else {
404
+ document.getElementById('stats-grid').innerHTML = '<div class="stat"><p style="color:#f87171;">API Error: ' + (data.error ? data.error.info : 'Unknown error') + '</p></div>';
405
+ }
406
+ } catch (e) {
407
+ alert('Connection failure. Make sure the API is accessible.');
408
+ } finally {
409
+ btn.innerText = 'Fetch Current Weather';
410
+ btn.disabled = false;
411
+ }
412
+ }
413
+ </script>
414
+ </body>
415
+ </html>
416
+ """
417
+
418
+
419
  if __name__ == "__main__":
420
  uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)