diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d6c004b --- /dev/null +++ b/.env.example @@ -0,0 +1,30 @@ +# Nike — Football Data Platform +# Copy to .env and fill in your values. Never commit .env. + +# ── TheSportsDB ────────────────────────────────────────── +# Free test key is '3' (limited). Get a premium key at https://www.patreon.com/thesportsdb +NIKE_SPORTSDB_KEY=3 + +# ── Database ────────────────────────────────────────────── +NIKE_DB_HOST=localhost +NIKE_DB_PORT=5432 +NIKE_DB_USER=nike +NIKE_DB_PASSWORD= +NIKE_DB_NAME=nike + +# ── Server ──────────────────────────────────────────────── +NIKE_HOST=0.0.0.0 +NIKE_PORT=8000 +NIKE_LOG_LEVEL=WARNING + +# IPs allowed to set X-Forwarded-* headers (your HAProxy host). +# '*' is safe when Nike's port is firewalled to HAProxy only. +NIKE_TRUSTED_PROXY=* + +# ── Followed teams ──────────────────────────────────────── +# Comma-separated list of "Team Name:League Name" pairs. +NIKE_TEAMS=Toronto FC:MLS, Arsenal:Premier League + +# ── Legacy (not active) ─────────────────────────────────── +# NIKE_RAPIDAPI_KEY= +# NIKE_API_FOOTBALL_KEY= diff --git a/Dockerfile b/Dockerfile index 870418e..51103e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,10 @@ FROM python:3.12-slim WORKDIR /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* + COPY . . RUN pip install --no-cache-dir . diff --git a/dashboard/src/app.css b/dashboard/src/app.css index 3374706..12c6ae4 100644 --- a/dashboard/src/app.css +++ b/dashboard/src/app.css @@ -1,5 +1,8 @@ @import "tailwindcss"; +/* Class-based dark mode: toggled via .dark on . */ +@custom-variant dark (&:where(.dark, .dark *)); + @theme { --color-pitch: #16a34a; --color-pitch-dark: #15803d; diff --git a/dashboard/src/app.html b/dashboard/src/app.html index 046d0e8..3844d39 100644 --- a/dashboard/src/app.html +++ b/dashboard/src/app.html @@ -5,6 +5,16 @@ Nike — Football Data Platform + + %sveltekit.head% diff --git a/dashboard/src/lib/api.ts b/dashboard/src/lib/api.ts index b3412ce..5c27744 100644 --- a/dashboard/src/lib/api.ts +++ b/dashboard/src/lib/api.ts @@ -1,5 +1,25 @@ import type { LogsResponse, RunResult, StatusResponse } from './types'; +interface TelemetryReport { + type: string; + message: string; + url?: string; + line?: number; + col?: number; + stack?: string; +} + +/** Fire-and-forget client-side error report. Never throws. */ +export function sendTelemetry(report: TelemetryReport): void { + fetch('/api/v1/telemetry', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(report), + }).catch(() => { + // telemetry is best-effort — swallow errors silently + }); +} + export async function fetchStatus(): Promise { const r = await fetch('/api/status'); if (!r.ok) throw new Error(`HTTP ${r.status}`); diff --git a/dashboard/src/routes/+layout.svelte b/dashboard/src/routes/+layout.svelte index 9af2b51..6923a69 100644 --- a/dashboard/src/routes/+layout.svelte +++ b/dashboard/src/routes/+layout.svelte @@ -1,21 +1,91 @@ -
-
+
+
- Nike + Nike
+
diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte index 228edc0..50e50d5 100644 --- a/dashboard/src/routes/+page.svelte +++ b/dashboard/src/routes/+page.svelte @@ -83,16 +83,16 @@
-

System Status

+

System Status

{#if invalidateMsg} - {invalidateMsg} + {invalidateMsg} {/if} @@ -100,7 +100,7 @@
{#if loadError} -
+
{loadError}
{/if} @@ -109,65 +109,65 @@
-
+
- Database + Database
Host
-
{status.database.host ?? '—'}
+
{status.database.host ?? '—'}
Latency
-
{fmtMs(status.database.latency_ms)}
+
{fmtMs(status.database.latency_ms)}
{#if status.database.version}
Version
-
+
{status.database.version}
{/if} {#if status.database.error} -
{status.database.error}
+
{status.database.error}
{/if}
-
+
- TheSportsDB + TheSportsDB
Latency
-
{fmtMs(status.api.latency_ms)}
+
{fmtMs(status.api.latency_ms)}
{#if status.api.backend}
Backend
-
{status.api.backend}
+
{status.api.backend}
{/if} {#if status.api.error} -
{status.api.error}
+
{status.api.error}
{/if}
-
+
- MCP Server + MCP Server {#if status.mcp.premium} Premium @@ -176,19 +176,19 @@
Transport
-
{status.mcp.transport}
+
{status.mcp.transport}
Uptime
-
{status.mcp.uptime}
+
{status.mcp.uptime}
Tools
-
{status.mcp.tool_count}
+
{status.mcp.tool_count}
Endpoint
-
+
{status.mcp.endpoint}
@@ -199,7 +199,7 @@
-
+

Followed Teams

@@ -210,39 +210,39 @@ {#each status.data.followed as team}
  • - {team.team} - · - {team.league} + {team.team} + · + {team.league}
  • {/each} {/if} {#if status.data.last_cache} -

    +

    Last cache update: {relTime(status.data.last_cache)}

    {/if}
    -
    +

    MCP Tools

    - + {#each status.tools as tool} - + {/each} @@ -252,7 +252,7 @@ {#if Object.keys(status.data.table_counts).length > 0} -
    +

    Database Contents

    @@ -260,7 +260,7 @@ {#each Object.entries(status.data.table_counts) as [table, count]}
    {table}
    -
    {count.toLocaleString()}
    +
    {count.toLocaleString()}
    {/each}
    @@ -271,12 +271,12 @@ {/if} -
    +
    -

    Request Log

    - {logs.length} entries · auto-refreshes every 5 s +

    Request Log

    + {logs.length} entries · auto-refreshes every 5 s
    {#if logs.length === 0}

    No MCP requests yet.

    @@ -284,23 +284,23 @@
    - {tool.name} + {tool.name} {#if tool.premium} {/if} {tool.description}{tool.description}
    - + - + {#each logs as entry} - + -
    Time Tool Args Duration
    {relTime(entry.timestamp)} - {entry.tool} + {entry.tool} + {fmtArgs(entry.args)} diff --git a/dashboard/src/routes/tools/+page.svelte b/dashboard/src/routes/tools/+page.svelte index bf3634c..4a2fa9a 100644 --- a/dashboard/src/routes/tools/+page.svelte +++ b/dashboard/src/routes/tools/+page.svelte @@ -178,8 +178,8 @@
    -

    Tool Runner

    -

    +

    Tool Runner

    +

    Run MCP tools interactively and inspect raw API responses. Useful for spotting strange API results.

    @@ -189,7 +189,7 @@
    -
    +

    Select Tool

    {#each TOOLS as tool} @@ -198,11 +198,11 @@ class="px-3 py-1.5 rounded text-sm font-medium transition-colors {selectedTool.name === tool.name ? 'bg-green-700 text-white' - : 'bg-gray-800 text-gray-300 hover:bg-gray-700 border border-gray-700'}" + : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 border border-gray-200 dark:border-gray-700'}" > {tool.name} {#if tool.premium} - + {/if} {/each} @@ -210,34 +210,34 @@
    -
    +
    -

    {selectedTool.name}

    +

    {selectedTool.name}

    {#if selectedTool.premium} Premium {/if}
    -

    {selectedTool.description}

    +

    {selectedTool.description}

    {#if selectedTool.params.length > 0}
    {#each selectedTool.params as param}
    -