diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7ea5739..cf4a1559 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,3 +48,22 @@ jobs: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u "${{ github.actor }}" --password-stdin helm package helm/altinity-sql-browser --version "${VERSION}" --app-version "${VERSION}" helm push "altinity-sql-browser-${VERSION}.tgz" oci://ghcr.io/altinity/altinity-sql-browser/helm + + # Mirror the just-built sql.html onto docs.altinity.com/altinity-sql-browser/ + # (GitHub Pages is configured to serve this repo's /docs on main). dist/ is + # gitignored, so it survives the branch switch below. Run last so nothing + # else in this job depends on which ref ends up checked out. + - name: Publish sql.html to GitHub Pages docs/ + run: | + git fetch --depth=1 origin main + git checkout -B main origin/main + cp dist/sql.html docs/sql.html + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add docs/sql.html + if git diff --cached --quiet; then + echo "docs/sql.html unchanged, skipping commit" + else + git commit -m "docs: publish sql.html ${GITHUB_REF_NAME} to GitHub Pages" + git push origin main + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index df3269cd..8ab5a3fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ auto-generated per-PR notes; this file is the curated, human-readable history. ## [Unreleased] ### Added +- **Every tagged release now mirrors `sql.html` to + `docs.altinity.com/altinity-sql-browser/sql.html`** (`docs/sql.html`, + published by a new `release.yml` step, alongside a demo `docs/config.json` + offering the Antalya/github.demo clusters) — a try-it-now link independent + of any ClickHouse-served deployment. Fixed `loadConfigDoc` (`src/net/oauth-config.ts`) + to resolve `config.json` from the containing directory when `basePath` is a + static filename (e.g. `sql.html`) rather than a route (e.g. `/sql`), which + this static hosting case needs and the ClickHouse route did not exercise. - **File ▾ → Import example dashboard…** (#506): a modal dialog lists the three flagship example dashboards (ClickHouse Operations, Shop Charts, OnTime Charts) by their catalogue name; Import is disabled until one is selected, diff --git a/docs/ASSET-DISTRIBUTION.md b/docs/ASSET-DISTRIBUTION.md index 8a4dd344..5a8bc8f6 100644 --- a/docs/ASSET-DISTRIBUTION.md +++ b/docs/ASSET-DISTRIBUTION.md @@ -6,6 +6,11 @@ real design question on a multi-node cluster, because **ClickHouse does not replicate the `user_files/` directory**: it is a node-local folder. This doc explains the trade-offs and what `deploy/install.sh` ships today. +(A `docs/sql.html` mirror is also published to GitHub Pages on every tagged +release, for an unauthenticated try-it-now demo — see `release.yml` — but +that's a separate distribution channel from the cluster-serving problem +below, not a replacement for it: a real deployment still needs the bytes on +every ClickHouse node.) ## What has to reach every node diff --git a/docs/config.json b/docs/config.json new file mode 100644 index 00000000..9bca1c41 --- /dev/null +++ b/docs/config.json @@ -0,0 +1,26 @@ +{ + "basic_login": true, + "idps": [ + { + "id": "google-antalya", + "label": "Google (Antalya)", + "issuer": "https://accounts.google.com", + "client_id": "925653064731-tpj128lp5qm5vkqn4llgbl96udvt3lsf.apps.googleusercontent.com", + "client_secret": "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" + }, + { + "id": "google-github", + "label": "Google (github.demo)", + "issuer": "https://accounts.google.com", + "client_id": "925653064731-vsep3e0vjq74q16svj5jshgvdlm6uptm.apps.googleusercontent.com", + "client_secret": "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", + "ch_auth": "basic" + } + ], + "hosts": [ + { "label": "Antalya (demo:demo)", "url": "https://antalya.demo.altinity.cloud", "auth": "basic", "user": "demo", "password": "demo" }, + { "label": "Antalya (Google SSO)", "url": "https://antalya.demo.altinity.cloud", "auth": "oauth", "idp": "google-antalya" }, + { "label": "github.demo (demo:demo)", "url": "https://github.demo.altinity.cloud", "auth": "basic", "user": "demo", "password": "demo" }, + { "label": "github.demo (Google SSO)", "url": "https://github.demo.altinity.cloud", "auth": "oauth", "idp": "google-github" } + ] +} diff --git a/docs/sql.html b/docs/sql.html new file mode 100644 index 00000000..37e9c242 --- /dev/null +++ b/docs/sql.html @@ -0,0 +1,422 @@ + + + + + + +Altinity® SQL Browser + + + + + +
+ + + diff --git a/src/net/oauth-config.ts b/src/net/oauth-config.ts index 56573576..7b9b4fdf 100644 --- a/src/net/oauth-config.ts +++ b/src/net/oauth-config.ts @@ -181,10 +181,14 @@ function normalizeHost(h: RawHostEntry | null | undefined): HostDescriptor { * describes a credentials-only deployment, so `idps` comes back empty rather * than throwing. `basicLogin` (top-level `basic_login`, default true) lets an * SSO-only deployment hide the username/password path. - * @param basePath e.g. location.pathname ('/sql') + * @param basePath e.g. location.pathname ('/sql', or '/sql/sql.html' for a + * static-file deployment — the last segment is dropped when it looks like a + * file (has a dot), so config.json is fetched from its containing directory + * either way) */ export async function loadConfigDoc(fetchFn: typeof fetch, basePath = ''): Promise { - const cfgUrl = basePath.replace(/\/$/, '') + '/config.json'; + const dir = basePath.replace(/\/[^/]*\.[^/]*$/, '').replace(/\/$/, ''); + const cfgUrl = dir + '/config.json'; const cfgResp = await fetchFn(cfgUrl, { cache: 'no-store' }); if (!cfgResp.ok) throw new Error('GET ' + cfgUrl + ': ' + cfgResp.status); const cfg = await cfgResp.json() as RawConfigDoc; diff --git a/tests/unit/oauth-config.test.ts b/tests/unit/oauth-config.test.ts index aa3c3eb4..b7ed5507 100644 --- a/tests/unit/oauth-config.test.ts +++ b/tests/unit/oauth-config.test.ts @@ -47,6 +47,11 @@ describe('loadConfigDoc', () => { }]); expect(f.mock.calls[0][0]).toBe('/sql/config.json'); }); + it('fetches config.json from the containing directory when basePath is a static file (e.g. sql.html)', async () => { + const f = fetcher([[/config\.json$/, resp(true, { idps: [] })]]); + await loadConfigDoc(asFetch(f), '/altinity-sql-browser/sql.html'); + expect(f.mock.calls[0][0]).toBe('/altinity-sql-browser/config.json'); + }); it('parses a list and honours explicit id/label', async () => { const idps = await docOf({ idps: [ { id: 'g', label: 'Google', issuer: 'https://accounts.google.com', client_id: 'c1' },