Skip to content

Projects: simple load balancers with targets, services, domains (+ live UI) - #2

Merged
2shiraz merged 8 commits into
mainfrom
build/phase-0-1
Oct 14, 2026
Merged

2shiraz merged 8 commits into
mainfrom
build/phase-0-1

Conversation

@2shiraz

@2shiraz 2shiraz commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

This PR makes Loxy simpler: a project is one load balancer. Each project has Overview, Graphs, Targets, Services, Domains, Logs and Settings tabs. The UI is redesigned to be modern, responsive and always live.

How it works

  • Ports: a new project gets its own free port automatically (20000+). You can add more services: http, https or tcp, each with its own port.
  • Domains: attach a domain to a project and traffic for that hostname on the shared ports 80/443 is routed to the project. HTTPS is automatic: once the domain resolves to the server, Loxy issues a certificate and redirects :80 to :443.
  • Targets: an IP (optionally ip:port), a host from the inventory, or a label selector. Health is checked per service. Changes are applied at runtime with drain-before-remove.
  • Under the hood: each service compiles into the existing, already verified HAProxy engine (fe_<id>/be_<id>). Domains use shared sys_http/sys_https frontends.

UI redesign

The design brief is in web/DESIGN.md. It was produced with the frontend-design, emil-design-eng, ui-ux-pro-max, impeccable and web-design-guidelines skills.

  • Look: a calm console style with light and dark themes. Fonts are Hanken Grotesk and JetBrains Mono, self-hosted (OFL licence), with no new npm dependencies. Each project's overview shows a route strip: "People connect to → Loxy → Traffic goes to".
  • Responsive:
    • The sidebar can collapse to an icon rail, which tablets use by default.
    • Phones get a bottom tab bar.
    • Tables become cards and dialogs become sheets on small screens.
  • Always live: an SSE live.tick event every 2 s drives lists, the dashboard, project headers and target health.
    • The target list refetches when a project's health fingerprint changes; periodic polling is removed.
    • Sparklines and a real-time 15-minute graphs view.
    • Indicators for Live/Reconnecting and "Applying…".
  • Simpler create flow: name it, where traffic goes, how people reach it.

Upgrade

Migration 0002_projects.sql upgrades databases from the frontend/backend version:

  • Hosts, certificates, users and settings are kept.
  • Domains are kept but detached from any project.
  • Frontend/backend definitions are dropped.
  • Stored health-alert kinds are renamed.

Decisions

  • Reserved ports: 80/443 (domains), the panel port, 8405, 5514 and the API port. Automatic ports come from 20000–29999.
  • Default service for domains: the project's first http/https service. Removing the last one while the project has domains returns 409.
  • Route order: exact hosts are matched before wildcards.
  • Maintenance: a 503 rule in the backend, covering both the service port and domain traffic (verified against HAProxy 3.4.6).
  • Deleting a project deletes its domains and their managed DNS records.
  • Wizard labels: "Public port" and "Server port".

Verification

  • Lint passes (0 issues). Unit tests pass, including the upgrade-migration and live-snapshot tests.
  • Integration tests against haproxy:3.4.6-alpine pass:
    • domain routing and 404 for unknown hosts;
    • maintenance on the service port and the domain;
    • ACME HTTP-01 through the shared :80 (Pebble);
    • 0 failed requests out of about 320k during 20 reloads plus runtime target add/remove.
  • npm run check: 0 errors and 0 warnings. The initial bundle is 64 KB gzip (budget 150 KB).
  • Playwright e2e on the redesigned UI: 2/2 pass.
  • One review pass; all 10 findings were fixed.

Screenshots are in docs/screenshots/, including mobile.

🤖 Generated with Claude Code

https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc

…ervices, domains

Rewrite the spec and README around projects (Overview, Graphs, Targets,
Services, Domains). Each project gets its own port; attached domains are
routed on the shared :80/:443 with automatic HTTPS. ADR 0006 supersedes
ADR 0005. Record the newly verified HAProxy behaviour (domain routing on
sys_http, maintenance inside a backend).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc
Replace user-facing frontends/backends with projects (table balancers):
- services listen on their own port (auto 20000-29999; 80/443 reserved),
  each compiled to fe_<id>/be_<id> sharing the project targets
- domains route by Host on shared sys_http/sys_https, exact hosts before
  wildcards; certificates are issued automatically once a domain resolves
- project metrics (pr scope) include domain traffic; target health is
  reported per service; events carry project_id
- migration 0002 upgrades frontend/backend databases (domains kept)
- CLI: loxy projects ls

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc
…ains

Projects list and create wizard (own port by default, optional domain),
per-project tabs, domain cards with DNS records and certificate status.
Target health refreshes while the page is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc
- Sticky cookie secure only when no plain-HTTP domain reaches the backend.
- Maintenance 503 rendered once, in every http backend (covers ports and
  domains); tcp keeps the frontend reject.
- Migration 0002 renames stored server.health.changed alert kinds.
- Deleting a project deletes its domains and their managed DNS records.
- Automatic certificate issuance re-checks the domain in the transaction
  and is serialized (no duplicate certificates).
- Detached domains stay editable; long FQDNs get valid certificate names.
- Panel on :80/:443 blocks domains instead of refusing to start.
- Domains indexed per project in the snapshot; UI timeout up to 86400 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc
Ephemeral event with per-project status, health, traffic and a health
fingerprint (changes when any target check changes), plus node totals.
Ephemeral events are no longer kept in the reconnect replay buffer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc
Design brief in web/DESIGN.md (design skills: frontend-design,
emil-design-eng, ui-ux-pro-max, impeccable, web-design-guidelines).
- Calm console look, light/dark, self-hosted Hanken Grotesk + JetBrains
  Mono (OFL, no new npm deps); route strip on project overview.
- Responsive: collapsible sidebar, tablet rail, phone bottom bar, tables
  become cards, dialogs become sheets.
- Live: live.tick drives lists, dashboard, headers and target health
  (refetch on health_sig change, polling removed); live sparklines and a
  real-time 15-minute graphs view; Live/Reconnecting indicator;
  "Applying..." while HAProxy catches up; plain-language check results.
- Simpler create flow: name, where traffic goes, how people reach it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159fEExV6P6AdgP7HbsJNBc
@2shiraz
2shiraz marked this pull request as ready for review October 14, 2026 16:26
@2shiraz
2shiraz merged commit 53b23d2 into main Oct 14, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants