feat(dashboard): GitHub sync UX — honest webhook origin + project-page sync switcher - #47
Merged
Merged
Conversation
…l optional) The Webhook Integrations panel showed a "No live tunnel" warning whenever no managed tunnel was running — even when CIX_PUBLIC_URL was set and the server was already publicly reachable via infrastructure (reverse proxy, ingress, static IP). A tunnel is optional in that case; webhooks deliver fine. - New GET /api/v1/github/webhooks/origin returns the effective webhook delivery origin and its source (tunnel | public_url | none), mirroring the server's publicBaseURL() precedence. - WebhooksTab now renders three states: active tunnel URL, infra-provided origin via CIX_PUBLIC_URL (neutral — tunnel optional), or — only when neither is configured — a "no public origin" warning. Backend tests cover the public_url and none sources. Frontend uses the ad-hoc api client (no generated-client dependency); src/api/generated.ts can be regenerated via `npm run gen:api` when node is available (non-blocking). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a "Sync settings" card to the project page that lets an operator
reconfigure how an external GitHub project is reindexed — webhook,
polling, or manual — without leaving the page.
Server:
- Generalize PATCH /api/v1/projects/{hash}/git-repo into a sync-method
switcher (operationId updateProjectGitRepoSync). Request now takes
{ sync_method: webhook|polling|manual, poll_interval_seconds? } and
returns { git_repo, note }.
- webhook → webhook_mode=auto + auto-register the hook; on failure
(no admin / no public URL) fall back to polling with a note.
- polling → webhook_mode=disabled + polling on (+ interval).
- manual → webhook_mode=disabled, polling off.
- Switching away from webhook best-effort de-registers the hook
(githubapi.DeleteWebhook) and clears webhook_id.
- gitrepos: SetSync (atomic webhook_mode + polling + interval, XOR
enforced) and ClearWebhookID; remove the now-unused SetPolling.
- Webhook receiver now ignores deliveries when webhook_mode='disabled',
so a lingering hook can't double-sync alongside the poll scheduler.
Dashboard:
- SyncSettingsCard (method radio + interval input, shows webhook_mode,
next poll, last error). useProjectGitRepo + useUpdateProjectSync hooks.
Read-only for non-admins.
Backend tests cover all three methods, the webhook→polling fallback,
invalid method (422), SetSync transitions, ClearWebhookID, and the
receiver ignoring disabled repos. Frontend not built locally (no node);
needs `npm run build` + visual check before merge.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The sync switcher's "Webhook" option only ever attempted auto-registration and silently fell back to polling on failure, and the card never surfaced the webhook URL/secret — so a user without an admin token (or public URL) could not actually set up a webhook. It looked broken. - Server: choosing webhook no longer falls back to polling. If auto-register can't install the hook (no admin token / no public URL), the repo is left in webhook_mode='manual' — a valid webhook the operator finishes by hand — with a note explaining how. (Add-repo's auto→polling fallback is unchanged.) - Dashboard: when Webhook is selected, the card shows the payload URL (copy) and the HMAC secret behind a reveal toggle (admin-only), GitHub setup instructions, a hint when no public origin is configured, and whether the hook was auto-registered. New useProjectWebhookInfo hook. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Require admin on PATCH /projects/{hash}/git-repo: it decrypts the PAT and
registers/deletes GitHub webhooks, matching ReconcileWebhooks' privilege.
Closes the direct-API hole where a viewer could drive those operations
(the dashboard already gated the card behind isAdmin).
- Re-saving "webhook" on a repo that's already a manually-configured hook
(webhook_mode=manual, no stored hook id) preserves manual instead of
flipping to auto and registering a SECOND hook beside the operator's.
Test covers the manual-preservation path (second webhook save keeps manual
with the "left as-is" note).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related improvements to the GitHub project sync UX in the dashboard.
1. Honest webhook delivery origin (tunnel optional)
The Webhook Integrations panel flagged
⚠ No live tunnelwhenever no managed tunnel was running — even whenCIX_PUBLIC_URLwas set and the server is publicly reachable via infrastructure. A tunnel is optional in that case.GET /api/v1/github/webhooks/origin→{ origin, source }(tunnel | public_url | none), mirroring the server'spublicBaseURL()precedence.WebhooksTabrenders three honest states; the warning only shows fornone.2. Manage the sync method from the project page
A new Sync settings card lets an operator reconfigure how an external GitHub project is reindexed — webhook / polling / manual — without leaving the project page.
PATCH /api/v1/projects/{hash}/git-repogeneralized into a sync-method switcher (updateProjectGitRepoSync): body{ sync_method, poll_interval_seconds? }, returns{ git_repo, note }.webhook→ auto-register the hook; on failure (no admin / no public URL) fall back to polling with anote.polling→webhook_mode=disabled+ polling on (+ interval).manual→ disabled, polling off.webhook_id.gitrepos.SetSync(atomic, XOR-enforced) +ClearWebhookID; removed the now-unusedSetPolling.webhook_mode='disabled'— a lingering hook can't double-sync alongside the poll scheduler.SyncSettingsCard(method radio + interval input, shows webhook_mode / next poll / last error), read-only for non-admins.Reviewer notes
developafter the polling PR (feat(server): git polling sync as webhook alternative for non-admin repos #46) merged.openapi.gen.go's large diff is oapi-codegen's deterministic reordering — verified routes/symbols, nothing dropped; all Go tests pass.apiclient (no generated-client dependency);src/api/generated.tscan be regenerated vianpm run gen:apilater (non-blocking).Test plan
go build ./...,go vet ./...cleango test ./...green — new coverage: webhook-origin (public_url/none); sync switcher (webhook fallback, polling, manual, invalid→422);SetSync/ClearWebhookID; receiver ignores disabled reposnpm run build+ a visual pass of the Webhook panel states and the project-page Sync settings card before merge🤖 Generated with Claude Code