dengdeyan commited on
Commit
ec062c4
·
1 Parent(s): ea17db9
Files changed (3) hide show
  1. Dockerfile +0 -13
  2. client/src/pages/VisitLog.jsx +31 -28
  3. server/main.py +16 -0
Dockerfile CHANGED
@@ -2,25 +2,12 @@
2
  # Use Node.js 20 as required by Vite and its dependencies
3
  FROM node:20-alpine AS client-builder
4
 
5
- # Explicitly declare the build argument for the Google Maps API key.
6
- # This allows Hugging Face to pass the secret during the build process.
7
- ARG VITE_GOOGLE_MAPS_API_KEY
8
-
9
  WORKDIR /app/client
10
  COPY client/package*.json ./
11
  RUN npm install
12
 
13
  COPY client/ .
14
  # Build for production
15
- # The SPACE_ID env var is automatically provided by Hugging Face for correct asset paths.
16
-
17
- # Create a .env file with the API key just before building.
18
- # Vite will automatically load this file. This is a robust way to inject secrets.
19
- RUN echo "VITE_GOOGLE_MAPS_API_KEY=${VITE_GOOGLE_MAPS_API_KEY}" > .env
20
-
21
- # --- DEBUG: Print the contents of the .env file to confirm it was written correctly ---
22
- RUN cat .env
23
-
24
  RUN npm run build
25
 
26
  # Stage 2: Final Production Image
 
2
  # Use Node.js 20 as required by Vite and its dependencies
3
  FROM node:20-alpine AS client-builder
4
 
 
 
 
 
5
  WORKDIR /app/client
6
  COPY client/package*.json ./
7
  RUN npm install
8
 
9
  COPY client/ .
10
  # Build for production
 
 
 
 
 
 
 
 
 
11
  RUN npm run build
12
 
13
  # Stage 2: Final Production Image
client/src/pages/VisitLog.jsx CHANGED
@@ -66,7 +66,6 @@ export default function VisitLog() {
66
  const mapRef = useRef(null);
67
  const markersRef = useRef([]);
68
  const pendingFocusRef = useRef("");
69
- const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY;
70
 
71
  const visitCount = visits.length;
72
  const lastSeen = useMemo(() => {
@@ -160,41 +159,45 @@ export default function VisitLog() {
160
  }, []);
161
 
162
  useEffect(() => {
163
- if (!apiKey) {
164
- setMapError("Missing Google Maps API key.");
165
- return;
166
- }
167
-
168
  let cancelled = false;
169
- loadGoogleMaps(apiKey)
170
- .then(() => {
 
 
 
 
 
 
 
 
 
 
 
 
171
  if (cancelled) return;
 
172
  if (!mapRef.current && mapContainerRef.current) {
173
- mapRef.current = new window.google.maps.Map(
174
- mapContainerRef.current,
175
- {
176
- center: { lat: 20, lng: 0 },
177
- zoom: 2,
178
- mapTypeControl: false,
179
- streetViewControl: false,
180
- fullscreenControl: false,
181
- }
182
- );
183
  }
184
- if (pendingFocusRef.current) {
185
- focusMapOnVisitor(pendingFocusRef.current);
186
- }
187
- })
188
- .catch((err) => {
189
- if (!cancelled) {
190
- setMapError(err.message || "Failed to load Google Maps");
191
- }
192
- });
193
 
194
  return () => {
195
  cancelled = true;
196
  };
197
- }, [apiKey]);
198
 
199
  useEffect(() => {
200
  if (!mapRef.current || !window.google) {
 
66
  const mapRef = useRef(null);
67
  const markersRef = useRef([]);
68
  const pendingFocusRef = useRef("");
 
69
 
70
  const visitCount = visits.length;
71
  const lastSeen = useMemo(() => {
 
159
  }, []);
160
 
161
  useEffect(() => {
 
 
 
 
 
162
  let cancelled = false;
163
+
164
+ const initializeMap = async () => {
165
+ try {
166
+ const configResponse = await fetch(getApiUrl("/api/config"));
167
+ if (!configResponse.ok) throw new Error("Failed to fetch client configuration.");
168
+
169
+ const config = await configResponse.json();
170
+ const apiKey = config.googleMapsApiKey;
171
+
172
+ if (!apiKey) {
173
+ throw new Error("Missing Google Maps API key from server configuration.");
174
+ }
175
+
176
+ await loadGoogleMaps(apiKey);
177
  if (cancelled) return;
178
+
179
  if (!mapRef.current && mapContainerRef.current) {
180
+ mapRef.current = new window.google.maps.Map(mapContainerRef.current, {
181
+ center: { lat: 20, lng: 0 },
182
+ zoom: 2,
183
+ mapTypeControl: false,
184
+ streetViewControl: false,
185
+ fullscreenControl: false,
186
+ });
 
 
 
187
  }
188
+ if (pendingFocusRef.current) focusMapOnVisitor(pendingFocusRef.current);
189
+
190
+ } catch (err) {
191
+ if (!cancelled) setMapError(err.message || "Failed to initialize map.");
192
+ }
193
+ };
194
+
195
+ initializeMap();
 
196
 
197
  return () => {
198
  cancelled = true;
199
  };
200
+ }, []);
201
 
202
  useEffect(() => {
203
  if (!mapRef.current || !window.google) {
server/main.py CHANGED
@@ -5,6 +5,7 @@ from fastapi.middleware.cors import CORSMiddleware
5
  from starlette.background import BackgroundTask
6
  from starlette.responses import StreamingResponse, FileResponse
7
  from fastapi.staticfiles import StaticFiles
 
8
 
9
  app = FastAPI()
10
 
@@ -13,6 +14,7 @@ app = FastAPI()
13
  PRIVATE_SERVER_URL = os.environ.get("PRIVATE_SERVER_URL")
14
  # The broker will read your HF_TOKEN from the Space's secrets.
15
  HF_TOKEN = os.environ.get("HF_TOKEN")
 
16
 
17
  if not PRIVATE_SERVER_URL:
18
  print("WARNING: The `PRIVATE_SERVER_URL` environment variable is not set. The broker will not be able to forward requests.")
@@ -20,6 +22,9 @@ if not PRIVATE_SERVER_URL:
20
  if not HF_TOKEN:
21
  print("WARNING: The `HF_TOKEN` environment variable is not set. Requests to the private server will not be authenticated.")
22
 
 
 
 
23
  # Read timeout from env var, default to 5 minutes (300 seconds) to accommodate slow model conversions.
24
  BROKER_TIMEOUT = float(os.environ.get("BROKER_TIMEOUT", 300.0))
25
  # Use a persistent client for performance (connection pooling).
@@ -62,6 +67,17 @@ async def _reverse_proxy(request: Request):
62
  print(f"Broker: CRITICAL - {error_message}")
63
  return Response(status_code=502, content=f"Bad Gateway: The broker could not connect to the private server. Details: {error_message}")
64
 
 
 
 
 
 
 
 
 
 
 
 
65
  # Add routes to capture all API and data requests and forward them.
66
  app.add_route("/api/{path:path}", _reverse_proxy, ["GET", "POST", "PUT", "DELETE"])
67
  app.add_route("/data/{path:path}", _reverse_proxy, ["GET"])
 
5
  from starlette.background import BackgroundTask
6
  from starlette.responses import StreamingResponse, FileResponse
7
  from fastapi.staticfiles import StaticFiles
8
+ from pydantic import BaseModel
9
 
10
  app = FastAPI()
11
 
 
14
  PRIVATE_SERVER_URL = os.environ.get("PRIVATE_SERVER_URL")
15
  # The broker will read your HF_TOKEN from the Space's secrets.
16
  HF_TOKEN = os.environ.get("HF_TOKEN")
17
+ GOOGLE_MAPS_API_KEY = os.environ.get("VITE_GOOGLE_MAPS_API_KEY")
18
 
19
  if not PRIVATE_SERVER_URL:
20
  print("WARNING: The `PRIVATE_SERVER_URL` environment variable is not set. The broker will not be able to forward requests.")
 
22
  if not HF_TOKEN:
23
  print("WARNING: The `HF_TOKEN` environment variable is not set. Requests to the private server will not be authenticated.")
24
 
25
+ if not GOOGLE_MAPS_API_KEY:
26
+ print("WARNING: The `VITE_GOOGLE_MAPS_API_KEY` environment variable is not set. The map on the visit log page will not load.")
27
+
28
  # Read timeout from env var, default to 5 minutes (300 seconds) to accommodate slow model conversions.
29
  BROKER_TIMEOUT = float(os.environ.get("BROKER_TIMEOUT", 300.0))
30
  # Use a persistent client for performance (connection pooling).
 
67
  print(f"Broker: CRITICAL - {error_message}")
68
  return Response(status_code=502, content=f"Bad Gateway: The broker could not connect to the private server. Details: {error_message}")
69
 
70
+ # --- Client Configuration Endpoint ---
71
+ # This endpoint must be defined *before* the reverse proxy routes.
72
+ class ClientConfig(BaseModel):
73
+ googleMapsApiKey: str | None
74
+
75
+ @app.get("/api/config", response_model=ClientConfig)
76
+ async def get_client_config():
77
+ """Provides public-safe configuration variables to the client at runtime."""
78
+ return ClientConfig(googleMapsApiKey=GOOGLE_MAPS_API_KEY)
79
+
80
+
81
  # Add routes to capture all API and data requests and forward them.
82
  app.add_route("/api/{path:path}", _reverse_proxy, ["GET", "POST", "PUT", "DELETE"])
83
  app.add_route("/data/{path:path}", _reverse_proxy, ["GET"])