OpenAthlete
AGPLv3 endurance training platform: EU-oriented hosting, transparent CTL/ATL/TSB logic in code, self-hosting, and full export. Built in Grenoble.
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Your Strava data, beautifully unpacked. Ascent turns your rides and runs into a fast, private analytics dashboard — stats, trends, heatmaps, segments, milestones — plus a studio for sharing gorgeous 9:16 activity cards.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A personal Strava activity dashboard with an Instagram/TikTok-style story card generator. Built as a static single-page app — no framework, no backend.
Live demo: https://ascent-analytics.doniwirawan.xyz/
All screenshots use the built-in demo mode (sample data — no real account).
| Trends | Heatmap |
|---|---|
| Calendar | Eddington number |
|---|---|
| Activity detail | Story card generator |
|---|---|
Story card layouts:
| Strava | Pace | Map |
|---|---|---|
🇬🇧 English · 🇮🇩 Bahasa Indonesia
This app does not collect, store, or send your data to any server we control. There is no analytics, no tracking pixels, and no third-party advertising. The whole app runs client-side in your browser.
When you connect Strava, you authorise the app with these OAuth scopes:
| Scope | Why it's requested |
|---|---|
read | Basic public profile info |
activity:read_all | Read all your activities (incl. private) to build the dashboard |
profile:read_all | Read your full profile (name, avatar, stats) |
activity:write | Optional — lets you push a generated story image / update an activity |
Using those scopes, your browser fetches from the Strava API:
All of this is requested directly by your browser from Strava — it never passes through a server of ours.
code.code to api/strava-token.js, a tiny stateless
serverless function. It adds the confidential Strava client secret (kept
server-side, never shipped to the browser), exchanges the code with Strava,
and returns the tokens. It stores nothing — it just relays the request.localStorage and are auto-refreshed when they expire.localStorage, keyed per athlete, with a
6-hour TTL, so the app doesn't re-fetch on every visit.localStorage.sw.js) caches HTML/CSS/JS so the
PWA works offline. It does not cache your Strava data._haveRemote = false in js/config.js). As shipped, no copy of your
activities is kept on any server — "everything runs in your browser" is
literally true. If you re-enable it (see the Supabase step below), your
activities would be stored in your own Supabase project, not ours.localStorage, andIndependent project — not affiliated with or endorsed by Strava, Inc.
localhost for local dev (change to your domain for production)Note: the remote Supabase cache is off in this codebase (
_haveRemote = falseinjs/config.js). The app works fully without it using the browser-local cache. Only follow this step if you want cross-device syncing — then set_haveRemoteback to the env check to enable it.
-- One row per athlete; `id` is the Strava athlete id (multi-user)
CREATE TABLE IF NOT EXISTS strava_cache (
id BIGINT PRIMARY KEY,
activities JSONB NOT NULL,
synced_at TIMESTAMPTZ DEFAULT NOW()
);
Privacy note: the browser uses the public anon key, so by default any visitor could read any row. Each athlete's data is keyed by their Strava id, but to truly isolate users you should enable Row Level Security on
strava_cache. Without a backend, full per-user isolation isn't possible from the anon key alone — the localStorage cache keeps each browser's data local regardless.
git clone https://github.com/doniwirawan/Ascent-Strava-Dashboard.git
cd Ascent-Strava-Dashboard
# Install dependencies
npm install
# Copy env file and fill in your values
cp .env.example .env.local
# Build (injects credentials into dist/)
node build.js
# Serve dist/ with any static server, e.g.:
npx serve dist
Open http://localhost:3000 and click Connect with Strava.
| Variable | Description |
|---|---|
STRAVA_CLIENT_ID | From https://www.strava.com/settings/api |
STRAVA_CLIENT_SECRET | From https://www.strava.com/settings/api |
SUPABASE_URL | Your Supabase project URL (optional) |
SUPABASE_ANON_KEY | Your Supabase anon/public key (optional) |
ORS_API_KEY | OpenRouteService key for the Route Builder (optional, free tier) |
OWNER_ATHLETE_ID | Your Strava athlete id — gates the AI/Route proxies to you (optional, recommended) |
yourdomain.vercel.app)npm install && node build.js on each deployandroid/ holds a small Kotlin app (no UI libraries) with native screens over the
personal API, the full dashboard one tap away, and home-screen widgets.
Grab the APK from Releases,
or build it yourself:
cd android
./gradlew assembleRelease
blob: downloads
silently).Strava exposes no HRV, resting heart rate or sleep, so these are not measured
physiological signals — they are derived from training load (see js/training.js,
mirrored in android/.../Repo.kt):
Ascent-Strava-Dashboard/
├── index.html # Main app — all JS is inline
├── callback.html # OAuth callback page
├── build.js # Injects env vars into dist/ at build time
├── manifest.json # PWA manifest
├── sw.js # Service worker (offline cache)
├── android/ # Kotlin app: WebView shell + home-screen widgets
├── icon.png # App icon
├── vercel.json # Vercel config
├── package.json
├── .env.example # Copy to .env.local and fill in values
└── dist/ # Build output (generated, not committed)
Automated dependency/image bots (e.g. ImgBot) also contribute commits.
Personal, non-commercial use only. You're welcome to clone this repo and run your own copy for your own personal use, and to learn from or modify the code.
You may not:
See LICENSE for the full terms. The Android app (android/) is the exception:
it includes JoyoFit's Huawei watch link and is AGPL-3.0-or-later (android/LICENSE).
Not affiliated with Strava, Inc.