vibecarbon 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -663
- package/README.md +4 -4
- package/carbon/.env.example +11 -0
- package/carbon/backup/compose-backup.sh +8 -0
- package/carbon/docker-compose.yml +11 -0
- package/carbon/ha/primary-init.sql +10 -2
- package/carbon/k8s/base/backup/cronjob.yaml +9 -0
- package/carbon/k8s/base/repl-gateway/kustomization.yaml +11 -0
- package/carbon/k8s/base/repl-gateway/repl-gateway.yaml +92 -0
- package/carbon/k8s/values/supabase.values.yaml +6 -1
- package/carbon/scripts/dev-init.js +6 -2
- package/carbon/src/client/assets/vibecarbon-icon.svg +6 -6
- package/carbon/src/client/assets/vibecarbon-wordmark-text-dark.svg +12 -0
- package/carbon/src/client/assets/vibecarbon-wordmark-text-light.svg +12 -0
- package/carbon/src/client/components/AIIntegrationSection.tsx +1 -3
- package/carbon/src/client/components/AppSidebar.tsx +35 -28
- package/carbon/src/client/components/CTAFooter.tsx +1 -1
- package/carbon/src/client/components/ComparisonSection.tsx +1 -3
- package/carbon/src/client/components/ContentPanel.tsx +1 -1
- package/carbon/src/client/components/DeploymentScenariosSection.tsx +1 -3
- package/carbon/src/client/components/FAQSection.tsx +1 -1
- package/carbon/src/client/components/Hero.tsx +13 -1
- package/carbon/src/client/components/Logo.tsx +66 -49
- package/carbon/src/client/components/Nav.tsx +1 -1
- package/carbon/src/client/components/NewsletterSignup.tsx +1 -1
- package/carbon/src/client/components/NotificationDrawer.tsx +3 -3
- package/carbon/src/client/components/PageHeader.tsx +1 -1
- package/carbon/src/client/components/PricingSection.tsx +2 -6
- package/carbon/src/client/components/TechStackSection.tsx +1 -3
- package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +5 -0
- package/carbon/src/client/components/ui/button.tsx +1 -1
- package/carbon/src/client/components/ui/card.tsx +1 -1
- package/carbon/src/client/components/ui/dialog.tsx +1 -1
- package/carbon/src/client/components/ui/dropdown-menu.tsx +2 -2
- package/carbon/src/client/components/ui/popover.tsx +1 -1
- package/carbon/src/client/components/ui/select.tsx +1 -1
- package/carbon/src/client/components/ui/sidebar.tsx +1 -1
- package/carbon/src/client/index.css +186 -104
- package/carbon/src/client/lib/supabase.ts +41 -0
- package/carbon/src/client/lib/theme.ts +14 -14
- package/carbon/src/client/locales/de.json +27 -12
- package/carbon/src/client/locales/en.json +33 -18
- package/carbon/src/client/locales/es.json +27 -12
- package/carbon/src/client/locales/fr.json +27 -12
- package/carbon/src/client/locales/pt.json +27 -12
- package/carbon/src/client/pages/Blog.tsx +1 -1
- package/carbon/src/client/pages/Changelog.tsx +1 -1
- package/carbon/src/client/pages/Checkout.tsx +1 -1
- package/carbon/src/client/pages/Dashboard.tsx +33 -40
- package/carbon/src/client/pages/Docs.tsx +1 -1
- package/carbon/src/client/pages/Home.tsx +1 -1
- package/carbon/src/client/pages/Legal.tsx +1 -1
- package/carbon/src/server/billing/providers/paddle.ts +21 -2
- package/carbon/src/server/billing/providers/polar.ts +14 -0
- package/carbon/src/server/index.ts +12 -9
- package/carbon/src/server/lib/client-ip.ts +68 -0
- package/carbon/src/server/lib/env.ts +20 -6
- package/carbon/src/server/lib/rate-limiter.ts +11 -65
- package/carbon/src/server/lib/supabase.ts +10 -4
- package/carbon/src/server/middleware/requireOrgRole.ts +76 -0
- package/carbon/src/server/middleware/requirePlan.ts +64 -34
- package/carbon/src/server/middleware/requireSuperAdmin.ts +26 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +5 -20
- package/carbon/src/server/routes/v1/admin/jobs.ts +6 -20
- package/carbon/src/server/routes/v1/admin/newsletter.ts +26 -23
- package/carbon/src/server/routes/v1/auth.ts +5 -2
- package/carbon/src/server/routes/v1/billing.ts +24 -64
- package/carbon/src/server/routes/v1/index.ts +8 -1
- package/carbon/src/server/routes/v1/newsletter.ts +18 -12
- package/carbon/src/server/routes/v1/theme.ts +36 -15
- package/carbon/supabase/migrations/00003_pg_cron.sql +2 -2
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +24 -14
- package/carbon/tests/component/supabase-env-guard.test.ts +54 -0
- package/carbon/tests/integration/server/middleware/rate-limit-ip-spoof.test.ts +48 -0
- package/carbon/tests/integration/server/middleware/require-plan.test.ts +101 -0
- package/carbon/tests/integration/server/routes/admin-newsletter.test.ts +93 -0
- package/carbon/tests/integration/server/routes/billing-subscription-idor.test.ts +92 -0
- package/carbon/tests/integration/server/routes/newsletter-unsubscribe.test.ts +65 -0
- package/carbon/tests/integration/server/routes/theme-validation.test.ts +66 -0
- package/carbon/tests/unit/server/client-ip.test.ts +76 -0
- package/carbon/tests/unit/server/env-bool.test.ts +63 -0
- package/carbon/tests/unit/server/paddle-webhook.test.ts +57 -0
- package/carbon/tests/unit/server/polar-webhook.test.ts +63 -0
- package/carbon/tests/unit/server/supabase-client.test.ts +30 -0
- package/carbon/volumes/db/wal-archive.sh +36 -0
- package/package.json +2 -2
- package/src/backup.js +0 -2
- package/src/cli.js +16 -0
- package/src/deploy.js +16 -5
- package/src/destroy.js +78 -0
- package/src/failover.js +118 -237
- package/src/lib/command.js +19 -1
- package/src/lib/deploy/compose/ha.js +180 -102
- package/src/lib/deploy/compose/index.js +63 -20
- package/src/lib/deploy/image.js +19 -23
- package/src/lib/deploy/k8s/ha/index.js +397 -150
- package/src/lib/deploy/k8s/k3s.js +65 -14
- package/src/lib/deploy/orchestrator.js +176 -6
- package/src/lib/deploy/remote-build.js +8 -1
- package/src/lib/deploy/replication.js +540 -0
- package/src/lib/deploy/state.js +62 -5
- package/src/lib/deploy/utils.js +30 -2
- package/src/lib/deploy/wireguard.js +146 -0
- package/src/lib/host-keys.js +120 -5
- package/src/lib/iac/index.js +57 -23
- package/src/lib/licensing/gate.js +59 -0
- package/src/lib/licensing/index.js +1 -1
- package/src/lib/licensing/tiers.js +6 -5
- package/src/lib/prod-confirm.js +65 -0
- package/src/lib/providers/hetzner-s3.js +85 -0
- package/src/lib/ssh.js +35 -35
- package/src/restore.js +102 -14
- package/src/scale.js +4 -2
- package/src/status.js +122 -0
- package/src/upgrade.js +0 -4
- package/carbon/ha/activate-standby.sh +0 -52
- package/carbon/ha/standby-init.sh +0 -74
- package/carbon/src/client/assets/vibecarbon-logo-dark.svg +0 -29
- package/carbon/src/client/assets/vibecarbon-logo-light.svg +0 -29
- package/carbon/src/server/lib/request.ts +0 -34
- package/src/lib/pod-backups.js +0 -53
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Vibecarbon is a full-stack SaaS starter that combines React 19, Vite, Tailwind v
|
|
|
18
18
|
|
|
19
19
|
Skip the choice between vendor lock-in and DIY infrastructure. Create web applications with authentication, billing, teams, email, blog, docs, i18n, and production deployment — without a dedicated DevOps engineer.
|
|
20
20
|
|
|
21
|
-
Includes Docker Compose, Kubernetes, High-Availability with replication and failover,
|
|
21
|
+
Includes Docker Compose, Kubernetes, High-Availability with replication and one-command failover, scaling, and automated backups.
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -39,9 +39,9 @@ npx vibecarbon deploy
|
|
|
39
39
|
npx vibecarbon destroy
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
> **Create for free, always. Pay to deploy.** `create`, `up`, `down`, and all local development are free forever
|
|
42
|
+
> **Create for free, always. Pay to deploy.** `create`, `up`, `down`, and all local development are free forever — and so is `destroy`, because teardown is never held hostage. `deploy`, `backup`, `restore`, `failover`, `scale`, and `upgrade` require a Diamond or Fullerene license — a one-time payment, no subscriptions, your servers.
|
|
43
43
|
|
|
44
|
-
>
|
|
44
|
+
> Fair Source (FSL-1.1-MIT) — every release becomes MIT open source after two years. Generated project code is MIT today — you own your app.
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -171,7 +171,7 @@ _End-to-end wall-clock from the last fully-green e2e matrix run against real Het
|
|
|
171
171
|
|
|
172
172
|
## License & Terms
|
|
173
173
|
|
|
174
|
-
The CLI is
|
|
174
|
+
The CLI is Fair Source under the [Functional Source License 1.1 with MIT future license](./LICENSE). **Building from source is free for any non-competing use — and every release automatically becomes MIT open source two years after publication.**
|
|
175
175
|
|
|
176
176
|
Using the distributed `npx vibecarbon` package requires a license for deployment commands:
|
|
177
177
|
|
package/carbon/.env.example
CHANGED
|
@@ -34,6 +34,17 @@ VITE_PUBLIC_URL="http://localhost:5173"
|
|
|
34
34
|
ADMIN_EMAIL="admin@example.com"
|
|
35
35
|
ADMIN_PASSWORD="your-password-here"
|
|
36
36
|
|
|
37
|
+
# =============================================================================
|
|
38
|
+
# REVERSE PROXY (optional)
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Number of trusted reverse-proxy hops in front of the API server. The server
|
|
41
|
+
# uses this to pick the real client IP out of X-Forwarded-For for rate limiting
|
|
42
|
+
# and login lockout — only the entry this many positions from the RIGHT is
|
|
43
|
+
# trusted; everything to its left is treated as attacker-controlled. The default
|
|
44
|
+
# of 1 matches the standard single-Traefik deployment. Increase it only if you
|
|
45
|
+
# add another trusted proxy in front (e.g. Cloudflare -> Traefik -> app = 2).
|
|
46
|
+
TRUSTED_PROXY_HOPS="1"
|
|
47
|
+
|
|
37
48
|
# =============================================================================
|
|
38
49
|
# OAUTH PROVIDERS (optional)
|
|
39
50
|
# =============================================================================
|
|
@@ -54,6 +54,14 @@ docker compose exec -T db bash -c '
|
|
|
54
54
|
echo "scheduled backup skipped: no S3 credentials configured (empty AWS_ACCESS_KEY_ID/SECRET)."
|
|
55
55
|
exit 0
|
|
56
56
|
fi
|
|
57
|
+
# WRITE-GUARD (finding #3): a standby must never base-backup into the single
|
|
58
|
+
# canonical prefix. WALG_ROLE=standby is set on the standby db env. This closes
|
|
59
|
+
# the bring-up window the pg_is_in_recovery() check below cannot (an
|
|
60
|
+
# independent primary not yet in recovery).
|
|
61
|
+
if [ "${WALG_ROLE:-primary}" = "standby" ]; then
|
|
62
|
+
echo "WALG_ROLE=standby — skipping base backup (only the primary writes to the canonical prefix)."
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
57
65
|
if [ "$(psql -U supabase_admin -d postgres -tAc "SELECT pg_is_in_recovery()")" != "f" ]; then
|
|
58
66
|
echo "supabase-db is in recovery (standby) — skipping base backup."
|
|
59
67
|
exit 0
|
|
@@ -200,7 +200,18 @@ services:
|
|
|
200
200
|
# `WARN The "S3_*" variable is not set` for every compose invocation.
|
|
201
201
|
# WAL-G is dormant locally without these anyway; the warns were pure
|
|
202
202
|
# noise.
|
|
203
|
+
#
|
|
204
|
+
# WALG_S3_PREFIX is a SINGLE canonical path (NO role segment). Reads
|
|
205
|
+
# (backup-fetch/restore/reseed) and writes (backup-push/wal-push) must all
|
|
206
|
+
# agree on ONE prefix — otherwise a standby restoring/reseeding would read
|
|
207
|
+
# an empty `…/walg/standby` and fail with "No backups found". Anti-collision
|
|
208
|
+
# (finding #3: a standby / bring-up-phase independent primary must never
|
|
209
|
+
# WRITE into this prefix) is enforced by the WALG_ROLE WRITE-GUARD:
|
|
210
|
+
# wal-archive.sh and the backup path both no-op when WALG_ROLE=standby.
|
|
211
|
+
# WALG_ROLE is exposed to the container so those write guards can read it;
|
|
212
|
+
# deployComposeHA writes WALG_ROLE=standby into the standby's .env.
|
|
203
213
|
WALG_S3_PREFIX: s3://${S3_BACKUP_BUCKET:-${S3_BUCKET:-}}/backups/${PROJECT_NAME}/walg
|
|
214
|
+
WALG_ROLE: ${WALG_ROLE:-primary}
|
|
204
215
|
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY:-}
|
|
205
216
|
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY:-}
|
|
206
217
|
AWS_ENDPOINT: ${S3_ENDPOINT:-}
|
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
-- Run on the primary database before initializing the standby.
|
|
3
3
|
--
|
|
4
4
|
-- This script is idempotent: safe to run multiple times.
|
|
5
|
+
--
|
|
6
|
+
-- SECURITY: `{{REPL_PASSWORD}}` below is a PLACEHOLDER, not a real password.
|
|
7
|
+
-- This file MUST be rendered with a real per-deploy password before running;
|
|
8
|
+
-- never run it raw. The vibecarbon deploy tooling renders it automatically
|
|
9
|
+
-- (src/lib/deploy/k8s/ha/index.js substitutes '{{REPL_PASSWORD}}' with the
|
|
10
|
+
-- random REPL_PASSWORD generated at create time). Running this file unrendered
|
|
11
|
+
-- would either fail (invalid SQL) or, if hand-edited, risk a weak-cred
|
|
12
|
+
-- `replicator` role — do not do it.
|
|
5
13
|
|
|
6
14
|
-- Create dedicated replication role (if not exists), or update its password on re-runs.
|
|
7
15
|
DO $$
|
|
8
16
|
BEGIN
|
|
9
17
|
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'replicator') THEN
|
|
10
|
-
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '
|
|
18
|
+
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '{{REPL_PASSWORD}}';
|
|
11
19
|
ELSE
|
|
12
|
-
ALTER ROLE replicator WITH PASSWORD '
|
|
20
|
+
ALTER ROLE replicator WITH PASSWORD '{{REPL_PASSWORD}}';
|
|
13
21
|
END IF;
|
|
14
22
|
END $$;
|
|
15
23
|
|
|
@@ -74,6 +74,15 @@ spec:
|
|
|
74
74
|
# (wal-push/archiving and backup-fetch/restore do not connect to
|
|
75
75
|
# PG, which is why this only bit backup-push. RCA 2026-05-30.)
|
|
76
76
|
export PGUSER=supabase_admin PGHOST=localhost PGPORT=5432 PGDATABASE=postgres
|
|
77
|
+
# WRITE-GUARD (finding #3): never base-backup on a standby into
|
|
78
|
+
# the single canonical WALG_S3_PREFIX. WALG_ROLE is rendered
|
|
79
|
+
# into the db container env (supabase.values.yaml) and inherited
|
|
80
|
+
# by this kubectl-exec. Closes the bring-up window the recovery
|
|
81
|
+
# check below cannot (independent primary, not yet in recovery).
|
|
82
|
+
if [ "${WALG_ROLE:-primary}" = "standby" ]; then
|
|
83
|
+
echo "WALG_ROLE=standby — skipping base backup (only the primary writes the canonical prefix)."
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
77
86
|
if [ "$(psql -U supabase_admin -d postgres -tAc "SELECT pg_is_in_recovery()")" != "f" ]; then
|
|
78
87
|
echo "supabase-db is in recovery (standby) — skipping base backup."
|
|
79
88
|
exit 0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# repl-gateway — HA-only cross-cluster replication transport (WireGuard + socat).
|
|
2
|
+
#
|
|
3
|
+
# NOTE: this kustomization is intentionally NOT referenced by the parent
|
|
4
|
+
# carbon/k8s/base/kustomization.yaml. The gateway is applied only on HA k8s
|
|
5
|
+
# deploys (by setupReplication), with per-cluster placeholders rendered at apply
|
|
6
|
+
# time. Keeping it as a standalone base lets `kustomize build` validate the
|
|
7
|
+
# manifest in isolation.
|
|
8
|
+
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
9
|
+
kind: Kustomization
|
|
10
|
+
resources:
|
|
11
|
+
- repl-gateway.yaml
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# VIBECARBON REPLICATION GATEWAY (HA k8s only)
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Cross-cluster PostgreSQL streaming replication runs over a point-to-point
|
|
5
|
+
# WireGuard tunnel between the two clusters' supabase nodes (wg0, UDP 51821).
|
|
6
|
+
# This gateway is a hostNetwork socat relay pinned to the supabase node; it
|
|
7
|
+
# bridges the intra-cluster db pod to the tunnel.
|
|
8
|
+
#
|
|
9
|
+
# standby db pod ──tcp <node-priv-ip>:15433──► standby repl-gateway (this pod)
|
|
10
|
+
# └─ socat into wg0 → 10.99.0.1:15433 ──► primary repl-gateway (this pod)
|
|
11
|
+
# └─ socat → 127.0.0.1:5433 (primary postgres hostPort)
|
|
12
|
+
#
|
|
13
|
+
# The WireGuard device (wg0) is owned by the HOST kernel (brought up over SSH by
|
|
14
|
+
# setupReplication / exchangeAndBringUpTunnel). Because this pod is
|
|
15
|
+
# `hostNetwork: true`, its socat shares the host netns and reaches wg0 + the
|
|
16
|
+
# node's private IP directly — so the pod itself needs NO extra capabilities.
|
|
17
|
+
#
|
|
18
|
+
# This manifest is NOT part of the base kustomization: it is HA-only and needs
|
|
19
|
+
# per-cluster values (relay direction + node private IP). setupReplication
|
|
20
|
+
# renders the placeholders below and applies it to each cluster via stdin.
|
|
21
|
+
#
|
|
22
|
+
# Placeholders (patched at apply time by setupReplication — NEVER at runtime):
|
|
23
|
+
# __RELAY_LISTEN__ socat listen address spec (differs per primary/standby)
|
|
24
|
+
# __RELAY_TARGET__ socat downstream target (differs per primary/standby)
|
|
25
|
+
# __SUPABASE_PRIVATE_IP__ this cluster's supabase node private IP (egress netpol)
|
|
26
|
+
# =============================================================================
|
|
27
|
+
apiVersion: v1
|
|
28
|
+
kind: Pod
|
|
29
|
+
metadata:
|
|
30
|
+
name: repl-gateway
|
|
31
|
+
namespace: vibecarbon
|
|
32
|
+
labels:
|
|
33
|
+
app: repl-gateway
|
|
34
|
+
spec:
|
|
35
|
+
# Share the host netns so socat can bind the node's private IP / the wg0
|
|
36
|
+
# tunnel IP and reach the host WireGuard device.
|
|
37
|
+
hostNetwork: true
|
|
38
|
+
dnsPolicy: ClusterFirstWithHostNet
|
|
39
|
+
# The supabase node carries a dedicated taint; without this toleration the
|
|
40
|
+
# pod stays Pending ("untolerated taint {dedicated: supabase}").
|
|
41
|
+
tolerations:
|
|
42
|
+
- key: dedicated
|
|
43
|
+
operator: Equal
|
|
44
|
+
value: supabase
|
|
45
|
+
effect: NoSchedule
|
|
46
|
+
nodeSelector:
|
|
47
|
+
dedicated: supabase
|
|
48
|
+
restartPolicy: Always
|
|
49
|
+
containers:
|
|
50
|
+
- name: relay
|
|
51
|
+
# Floating tag (no digest pin — users always get the latest socat). The
|
|
52
|
+
# image ENTRYPOINT is `socat`; args are its two address specs.
|
|
53
|
+
image: alpine/socat
|
|
54
|
+
imagePullPolicy: IfNotPresent
|
|
55
|
+
args:
|
|
56
|
+
- "__RELAY_LISTEN__"
|
|
57
|
+
- "__RELAY_TARGET__"
|
|
58
|
+
resources:
|
|
59
|
+
requests:
|
|
60
|
+
cpu: 10m
|
|
61
|
+
memory: 16Mi
|
|
62
|
+
limits:
|
|
63
|
+
cpu: 250m
|
|
64
|
+
memory: 64Mi
|
|
65
|
+
---
|
|
66
|
+
# The ONE additive egress NetworkPolicy that admits the db pod → local gateway
|
|
67
|
+
# hop. The gateway presents as the node's private IP (inside the 10.0.0.0/8
|
|
68
|
+
# range excluded by supabase-db-s3-egress) on tcp 15433, so the hop is blocked
|
|
69
|
+
# by default. This policy is additive to default-deny-all and scoped to exactly
|
|
70
|
+
# one destination IP + one port. NetworkPolicy is connection-stateful, so
|
|
71
|
+
# allowing the egress SYN clears the return path too — no ingress rule needed
|
|
72
|
+
# (and none applies to the hostNetwork gateway). Shipped on BOTH clusters (each
|
|
73
|
+
# targeting its own node) for failover symmetry.
|
|
74
|
+
apiVersion: networking.k8s.io/v1
|
|
75
|
+
kind: NetworkPolicy
|
|
76
|
+
metadata:
|
|
77
|
+
name: allow-db-repl-gateway-egress
|
|
78
|
+
namespace: vibecarbon
|
|
79
|
+
spec:
|
|
80
|
+
podSelector:
|
|
81
|
+
matchLabels:
|
|
82
|
+
app.kubernetes.io/instance: supabase
|
|
83
|
+
app.kubernetes.io/name: supabase-db
|
|
84
|
+
policyTypes:
|
|
85
|
+
- Egress
|
|
86
|
+
egress:
|
|
87
|
+
- to:
|
|
88
|
+
- ipBlock:
|
|
89
|
+
cidr: __SUPABASE_PRIVATE_IP__/32
|
|
90
|
+
ports:
|
|
91
|
+
- protocol: TCP
|
|
92
|
+
port: 15433
|
|
@@ -270,9 +270,14 @@ environment:
|
|
|
270
270
|
# SECRET keys (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) are injected
|
|
271
271
|
# separately via a StatefulSet env patch in applyK3sManifests.
|
|
272
272
|
# {{WALG_S3_PREFIX}} resolves to s3://<backup-bucket|storage-bucket>/
|
|
273
|
-
# backups/<project>/walg (fallback handled in
|
|
273
|
+
# backups/<project>/walg (a SINGLE canonical prefix; fallback handled in
|
|
274
|
+
# installSupabase). {{WALG_ROLE}} is 'primary' or 'standby' — it is NOT part
|
|
275
|
+
# of the prefix; it's the WRITE-GUARD signal that wal-archive.sh + the backup
|
|
276
|
+
# CronJob read to no-op on the standby (so a standby never writes into the
|
|
277
|
+
# canonical prefix), while restore/reseed still READ the one canonical prefix.
|
|
274
278
|
db:
|
|
275
279
|
WALG_S3_PREFIX: "{{WALG_S3_PREFIX}}"
|
|
280
|
+
WALG_ROLE: "{{WALG_ROLE}}"
|
|
276
281
|
WALG_COMPRESSION_METHOD: lz4
|
|
277
282
|
WALG_UPLOAD_CONCURRENCY: "2"
|
|
278
283
|
AWS_ENDPOINT: "{{S3_ENDPOINT}}"
|
|
@@ -174,12 +174,16 @@ if (existsSync(sqlTemplatePath)) {
|
|
|
174
174
|
writeFileSync(sqlGeneratedPath, sql);
|
|
175
175
|
console.log(` CREATED: ${sqlGeneratedPath} (dev admin credentials)`);
|
|
176
176
|
|
|
177
|
-
// Write a compose override that mounts the generated SQL instead of the template
|
|
177
|
+
// Write a compose override that mounts the generated SQL instead of the template.
|
|
178
|
+
// Must target /tmp/super-admin.sql — that's the path `db:migrate` executes, and
|
|
179
|
+
// docker-compose.override.yml mounts the raw template there. This file is loaded
|
|
180
|
+
// after the override (getComposeFiles), so the same target wins and the seeded
|
|
181
|
+
// admin gets real credentials instead of literal {{ADMIN_EMAIL}} placeholders.
|
|
178
182
|
const composeOverride = `# Generated by dev-init.js — DO NOT COMMIT
|
|
179
183
|
services:
|
|
180
184
|
db:
|
|
181
185
|
volumes:
|
|
182
|
-
- ./volumes/db/super-admin.generated.sql:/
|
|
186
|
+
- ./volumes/db/super-admin.generated.sql:/tmp/super-admin.sql:Z
|
|
183
187
|
`;
|
|
184
188
|
writeFileSync('docker-compose.dev-init.yml', composeOverride);
|
|
185
189
|
console.log(' CREATED: docker-compose.dev-init.yml (mounts generated admin SQL)');
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<svg width="877" height="1012" viewBox="0 0 877 1012" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<g clip-path="url(#
|
|
3
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M434.346 2.3064C436.82 0.879051 439.867 0.879058 442.342 2.30641L872.685 250.582C875.162 252.01 876.688 254.652 876.688 257.511V754.051C876.688 756.91 875.162 759.552 872.685 760.981L442.342 1009.26C439.867 1010.68 436.82 1010.68 434.346 1009.26L4.00221 760.981C1.52575 759.552 0 756.91 0 754.051V257.511C0 254.652 1.52576 252.01 4.00222 250.582L434.346 2.3064ZM708.094 505.781C708.094 577.323 679.674 645.935 629.086 696.523C578.498 747.111 509.886 775.531 438.344 775.531C366.802 775.531 298.19 747.111 247.602 696.523C197.014 645.935 168.594 577.323 168.594 505.781C168.594 434.239 197.014 365.627 247.602 315.039C298.19 264.451 366.802 236.031 438.344 236.031C509.886 236.031 578.498 264.451 629.086 315.039C679.674 365.627 708.094 434.239 708.094 505.781Z" fill="url(#
|
|
4
|
-
<path d="M876.688 252.891V472.062L438.344 606.938L0 472.062V252.891L438.344 0L876.688 252.891Z" fill="url(#
|
|
2
|
+
<g clip-path="url(#clip0_114_6)">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M434.346 2.3064C436.82 0.879051 439.867 0.879058 442.342 2.30641L872.685 250.582C875.162 252.01 876.688 254.652 876.688 257.511V754.051C876.688 756.91 875.162 759.552 872.685 760.981L442.342 1009.26C439.867 1010.68 436.82 1010.68 434.346 1009.26L4.00221 760.981C1.52575 759.552 0 756.91 0 754.051V257.511C0 254.652 1.52576 252.01 4.00222 250.582L434.346 2.3064ZM708.094 505.781C708.094 577.323 679.674 645.935 629.086 696.523C578.498 747.111 509.886 775.531 438.344 775.531C366.802 775.531 298.19 747.111 247.602 696.523C197.014 645.935 168.594 577.323 168.594 505.781C168.594 434.239 197.014 365.627 247.602 315.039C298.19 264.451 366.802 236.031 438.344 236.031C509.886 236.031 578.498 264.451 629.086 315.039C679.674 365.627 708.094 434.239 708.094 505.781Z" fill="url(#paint0_linear_114_6)"/>
|
|
4
|
+
<path d="M876.688 252.891V472.062L438.344 606.938L0 472.062V252.891L438.344 0L876.688 252.891Z" fill="url(#paint1_linear_114_6)"/>
|
|
5
5
|
</g>
|
|
6
6
|
<defs>
|
|
7
|
-
<linearGradient id="
|
|
7
|
+
<linearGradient id="paint0_linear_114_6" x1="0" y1="0" x2="0" y2="1011.56" gradientUnits="userSpaceOnUse">
|
|
8
8
|
<stop stop-color="#5EEAD4"/>
|
|
9
9
|
<stop offset="1" stop-color="#0D9488"/>
|
|
10
10
|
</linearGradient>
|
|
11
|
-
<linearGradient id="
|
|
11
|
+
<linearGradient id="paint1_linear_114_6" x1="175.338" y1="-5.42644e-06" x2="516.194" y2="492.348" gradientUnits="userSpaceOnUse">
|
|
12
12
|
<stop stop-color="white" stop-opacity="0.5"/>
|
|
13
13
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
|
14
14
|
</linearGradient>
|
|
15
|
-
<clipPath id="
|
|
15
|
+
<clipPath id="clip0_114_6">
|
|
16
16
|
<rect width="876.688" height="1011.56" fill="white"/>
|
|
17
17
|
</clipPath>
|
|
18
18
|
</defs>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg width="5175" height="1061" viewBox="0 0 5175 1061" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M189.981 758.92C186.619 758.92 183.616 756.818 182.465 753.66L3.91491 263.842C2.01266 258.623 5.87675 253.102 11.4311 253.102H128.387C131.803 253.102 134.842 255.271 135.952 258.501L255.668 606.681C258.146 613.888 268.343 613.876 270.804 606.663L389.594 258.519C390.699 255.279 393.743 253.102 397.165 253.102H511.149C516.703 253.102 520.567 258.623 518.665 263.842L340.115 753.66C338.964 756.818 335.961 758.92 332.599 758.92H189.981Z" fill="white"/>
|
|
3
|
+
<path d="M586.913 758.92C582.495 758.92 578.913 755.338 578.913 750.92V261.102C578.913 256.684 582.495 253.102 586.913 253.102H696.135C700.553 253.102 704.135 256.684 704.135 261.102V750.92C704.135 755.338 700.553 758.92 696.135 758.92H586.913ZM641.031 187.04C600.605 187.04 571.025 157.46 571.025 118.02C571.025 78.58 600.605 49 641.031 49C680.471 49 711.037 78.58 711.037 118.02C711.037 157.46 680.471 187.04 641.031 187.04Z" fill="white"/>
|
|
4
|
+
<path d="M1092 764.836C1024.91 764.836 976.303 739.429 944.129 702.222C939.464 696.826 930.128 698.971 928.878 705.993L920.628 752.323C919.948 756.14 916.629 758.92 912.751 758.92H818.007C813.588 758.92 810.007 755.338 810.007 750.92V76.72C810.007 72.3017 813.588 68.72 818.007 68.72H927.229C931.647 68.72 935.229 72.3017 935.229 76.72V298.558C935.229 306.201 945.285 309.773 950.605 304.285C982.946 270.932 1028.97 247.186 1092.99 247.186C1229.06 247.186 1328.64 343.814 1328.64 505.518C1328.64 661.306 1229.06 764.836 1092 764.836ZM1067.35 661.306C1148.2 661.306 1201.45 599.188 1201.45 506.504C1201.45 412.834 1148.2 350.716 1067.35 350.716C986.501 350.716 933.257 412.834 933.257 505.518C933.257 599.188 986.501 661.306 1067.35 661.306Z" fill="white"/>
|
|
5
|
+
<path d="M1655.33 764.836C1490.67 764.836 1390.1 662.292 1390.1 507.49C1390.1 350.716 1492.64 247.186 1645.47 247.186C1794.36 247.186 1895.92 341.842 1895.92 492.7C1895.92 503.334 1895.92 514.647 1895.35 526.639C1895.16 530.846 1891.66 534.112 1887.45 534.112H1527.15C1523.88 534.112 1521.24 536.761 1521.24 540.028C1521.24 617.922 1573.49 667.222 1649.42 667.222C1708.04 667.222 1750.35 641.829 1765.02 596.261C1766.14 592.793 1769.29 590.314 1772.94 590.314H1881.48C1886.44 590.314 1890.22 594.795 1889.14 599.64C1868.5 691.905 1782.22 764.836 1655.33 764.836ZM1524.87 439.185C1523.84 443.964 1527.6 448.33 1532.49 448.33H1760.6C1765.43 448.33 1769.17 444.061 1768.29 439.314C1756.7 376.993 1712.41 342.828 1647.44 342.828C1587.31 342.828 1537.92 378.645 1524.87 439.185Z" fill="white"/>
|
|
6
|
+
<path d="M2212.61 764.836C2053.87 764.836 1957.24 665.25 1957.24 506.504C1957.24 349.73 2058.8 247.186 2216.56 247.186C2347.1 247.186 2430.93 316.338 2453.47 429.168C2454.44 434.016 2450.67 438.47 2445.73 438.47H2330.48C2326.71 438.47 2323.48 435.836 2322.52 432.199C2309.16 381.668 2271.43 350.716 2214.59 350.716C2135.71 350.716 2085.42 414.806 2085.42 506.504C2085.42 598.202 2134.72 661.306 2214.59 661.306C2272.41 661.306 2310.19 629.409 2323.53 577.881C2324.48 574.229 2327.71 571.58 2331.49 571.58H2445.82C2450.73 571.58 2454.49 575.983 2453.56 580.809C2431.73 694.688 2343.22 764.836 2212.61 764.836Z" fill="white"/>
|
|
7
|
+
<path d="M2750.45 764.836C2616.36 764.836 2512.83 666.236 2512.83 506.504C2512.83 350.716 2616.36 247.186 2750.45 247.186C2817.76 247.186 2865.7 271.986 2898.38 309.905C2903.03 315.295 2912.31 313.151 2913.56 306.146L2921.83 259.699C2922.51 255.882 2925.83 253.102 2929.71 253.102H3024.45C3028.87 253.102 3032.45 256.684 3032.45 261.102V750.92C3032.45 755.338 3028.87 758.92 3024.45 758.92H2929.72C2925.84 758.92 2922.51 756.13 2921.84 752.304L2913.41 704.325C2912.2 697.4 2903.13 695.195 2898.49 700.478C2865.28 738.288 2818.02 764.836 2750.45 764.836ZM2775.1 661.306C2855.96 661.306 2908.21 599.188 2908.21 507.49C2908.21 412.834 2855.96 350.716 2775.1 350.716C2693.27 350.716 2641.01 412.834 2641.01 506.504C2641.01 599.188 2693.27 661.306 2775.1 661.306Z" fill="white"/>
|
|
8
|
+
<path d="M3146.28 758.92C3141.86 758.92 3138.28 755.338 3138.28 750.92V261.102C3138.28 256.684 3141.86 253.102 3146.28 253.102H3240.87C3244.81 253.102 3248.17 255.975 3248.78 259.87L3256.21 307.536C3257.35 314.889 3267.31 316.992 3271.87 311.112C3298.35 276.986 3337.34 253.102 3407.45 253.102H3426.08C3430.49 253.102 3434.08 256.684 3434.08 261.102V359.478C3434.08 363.896 3430.49 367.478 3426.08 367.478H3381.82C3294.06 367.478 3263.5 430.582 3263.5 508.476V750.92C3263.5 755.338 3259.92 758.92 3255.5 758.92H3146.28Z" fill="white"/>
|
|
9
|
+
<path d="M3783.28 764.836C3716.19 764.836 3667.58 739.429 3635.41 702.221C3630.74 696.826 3621.41 698.971 3620.16 705.993L3611.91 752.323C3611.23 756.14 3607.91 758.92 3604.03 758.92H3509.29C3504.87 758.92 3501.29 755.338 3501.29 750.92V76.72C3501.29 72.3017 3504.87 68.72 3509.29 68.72H3618.51C3622.93 68.72 3626.51 72.3017 3626.51 76.72V298.558C3626.51 306.201 3636.56 309.773 3641.88 304.285C3674.23 270.932 3720.25 247.186 3784.27 247.186C3920.34 247.186 4019.92 343.814 4019.92 505.518C4019.92 661.306 3920.34 764.836 3783.28 764.836ZM3758.63 661.306C3839.48 661.306 3892.73 599.188 3892.73 506.504C3892.73 412.834 3839.48 350.716 3758.63 350.716C3677.78 350.716 3624.54 412.834 3624.54 505.518C3624.54 599.188 3677.78 661.306 3758.63 661.306Z" fill="white"/>
|
|
10
|
+
<path d="M4344.64 764.836C4186.88 764.836 4081.38 661.306 4081.38 506.504C4081.38 350.716 4186.88 247.186 4344.64 247.186C4502.4 247.186 4607.9 350.716 4607.9 506.504C4607.9 661.306 4502.4 764.836 4344.64 764.836ZM4344.64 661.306C4427.46 661.306 4479.72 597.216 4479.72 506.504C4479.72 414.806 4427.46 350.716 4344.64 350.716C4261.82 350.716 4209.56 414.806 4209.56 506.504C4209.56 597.216 4261.82 661.306 4344.64 661.306Z" fill="white"/>
|
|
11
|
+
<path d="M4699.42 758.92C4695 758.92 4691.42 755.338 4691.42 750.92V261.102C4691.42 256.684 4695 253.102 4699.42 253.102H4794.19C4798.05 253.102 4801.37 255.867 4802.06 259.671L4809.55 300.893C4810.78 307.645 4819.5 309.909 4824.22 304.93C4855.56 271.903 4900.75 247.186 4971.44 247.186C5081.88 247.186 5174.56 306.346 5174.56 477.91V750.92C5174.56 755.338 5170.98 758.92 5166.56 758.92H5057.34C5052.92 758.92 5049.34 755.338 5049.34 750.92V487.77C5049.34 398.044 5011.87 351.702 4938.9 351.702C4859.04 351.702 4816.64 401.988 4816.64 492.7V750.92C4816.64 755.338 4813.06 758.92 4808.64 758.92H4699.42Z" fill="white"/>
|
|
12
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg width="5175" height="1061" viewBox="0 0 5175 1061" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M189.981 758.92C186.619 758.92 183.616 756.818 182.465 753.66L3.91491 263.842C2.01266 258.623 5.87675 253.102 11.4311 253.102H128.387C131.803 253.102 134.842 255.271 135.952 258.501L255.668 606.681C258.146 613.888 268.343 613.876 270.804 606.663L389.594 258.519C390.699 255.279 393.743 253.102 397.165 253.102H511.149C516.703 253.102 520.567 258.623 518.665 263.842L340.115 753.66C338.964 756.818 335.961 758.92 332.599 758.92H189.981Z" fill="black"/>
|
|
3
|
+
<path d="M586.913 758.92C582.495 758.92 578.913 755.338 578.913 750.92V261.102C578.913 256.684 582.495 253.102 586.913 253.102H696.135C700.553 253.102 704.135 256.684 704.135 261.102V750.92C704.135 755.338 700.553 758.92 696.135 758.92H586.913ZM641.031 187.04C600.605 187.04 571.025 157.46 571.025 118.02C571.025 78.58 600.605 49 641.031 49C680.471 49 711.037 78.58 711.037 118.02C711.037 157.46 680.471 187.04 641.031 187.04Z" fill="black"/>
|
|
4
|
+
<path d="M1092 764.836C1024.91 764.836 976.303 739.429 944.129 702.222C939.464 696.826 930.128 698.971 928.878 705.993L920.628 752.323C919.948 756.14 916.629 758.92 912.751 758.92H818.007C813.588 758.92 810.007 755.338 810.007 750.92V76.72C810.007 72.3017 813.588 68.72 818.007 68.72H927.229C931.647 68.72 935.229 72.3017 935.229 76.72V298.558C935.229 306.201 945.285 309.773 950.605 304.285C982.946 270.932 1028.97 247.186 1092.99 247.186C1229.06 247.186 1328.64 343.814 1328.64 505.518C1328.64 661.306 1229.06 764.836 1092 764.836ZM1067.35 661.306C1148.2 661.306 1201.45 599.188 1201.45 506.504C1201.45 412.834 1148.2 350.716 1067.35 350.716C986.501 350.716 933.257 412.834 933.257 505.518C933.257 599.188 986.501 661.306 1067.35 661.306Z" fill="black"/>
|
|
5
|
+
<path d="M1655.33 764.836C1490.67 764.836 1390.1 662.292 1390.1 507.49C1390.1 350.716 1492.64 247.186 1645.47 247.186C1794.36 247.186 1895.92 341.842 1895.92 492.7C1895.92 503.334 1895.92 514.647 1895.35 526.639C1895.16 530.846 1891.66 534.112 1887.45 534.112H1527.15C1523.88 534.112 1521.24 536.761 1521.24 540.028C1521.24 617.922 1573.49 667.222 1649.42 667.222C1708.04 667.222 1750.35 641.829 1765.02 596.261C1766.14 592.793 1769.29 590.314 1772.94 590.314H1881.48C1886.44 590.314 1890.22 594.795 1889.14 599.64C1868.5 691.905 1782.22 764.836 1655.33 764.836ZM1524.87 439.185C1523.84 443.964 1527.6 448.33 1532.49 448.33H1760.6C1765.43 448.33 1769.17 444.061 1768.29 439.314C1756.7 376.993 1712.41 342.828 1647.44 342.828C1587.31 342.828 1537.92 378.645 1524.87 439.185Z" fill="black"/>
|
|
6
|
+
<path d="M2212.61 764.836C2053.87 764.836 1957.24 665.25 1957.24 506.504C1957.24 349.73 2058.8 247.186 2216.56 247.186C2347.1 247.186 2430.93 316.338 2453.47 429.168C2454.44 434.016 2450.67 438.47 2445.73 438.47H2330.48C2326.71 438.47 2323.48 435.836 2322.52 432.199C2309.16 381.668 2271.43 350.716 2214.59 350.716C2135.71 350.716 2085.42 414.806 2085.42 506.504C2085.42 598.202 2134.72 661.306 2214.59 661.306C2272.41 661.306 2310.19 629.409 2323.53 577.881C2324.48 574.229 2327.71 571.58 2331.49 571.58H2445.82C2450.73 571.58 2454.49 575.983 2453.56 580.809C2431.73 694.688 2343.22 764.836 2212.61 764.836Z" fill="black"/>
|
|
7
|
+
<path d="M2750.45 764.836C2616.36 764.836 2512.83 666.236 2512.83 506.504C2512.83 350.716 2616.36 247.186 2750.45 247.186C2817.76 247.186 2865.7 271.986 2898.38 309.905C2903.03 315.295 2912.31 313.151 2913.56 306.146L2921.83 259.699C2922.51 255.882 2925.83 253.102 2929.71 253.102H3024.45C3028.87 253.102 3032.45 256.684 3032.45 261.102V750.92C3032.45 755.338 3028.87 758.92 3024.45 758.92H2929.72C2925.84 758.92 2922.51 756.13 2921.84 752.304L2913.41 704.325C2912.2 697.4 2903.13 695.195 2898.49 700.478C2865.28 738.288 2818.02 764.836 2750.45 764.836ZM2775.1 661.306C2855.96 661.306 2908.21 599.188 2908.21 507.49C2908.21 412.834 2855.96 350.716 2775.1 350.716C2693.27 350.716 2641.01 412.834 2641.01 506.504C2641.01 599.188 2693.27 661.306 2775.1 661.306Z" fill="black"/>
|
|
8
|
+
<path d="M3146.28 758.92C3141.86 758.92 3138.28 755.338 3138.28 750.92V261.102C3138.28 256.684 3141.86 253.102 3146.28 253.102H3240.87C3244.81 253.102 3248.17 255.975 3248.78 259.87L3256.21 307.536C3257.35 314.889 3267.31 316.992 3271.87 311.112C3298.35 276.986 3337.34 253.102 3407.45 253.102H3426.08C3430.49 253.102 3434.08 256.684 3434.08 261.102V359.478C3434.08 363.896 3430.49 367.478 3426.08 367.478H3381.82C3294.06 367.478 3263.5 430.582 3263.5 508.476V750.92C3263.5 755.338 3259.92 758.92 3255.5 758.92H3146.28Z" fill="black"/>
|
|
9
|
+
<path d="M3783.28 764.836C3716.19 764.836 3667.58 739.429 3635.41 702.221C3630.74 696.826 3621.41 698.971 3620.16 705.993L3611.91 752.323C3611.23 756.14 3607.91 758.92 3604.03 758.92H3509.29C3504.87 758.92 3501.29 755.338 3501.29 750.92V76.72C3501.29 72.3017 3504.87 68.72 3509.29 68.72H3618.51C3622.93 68.72 3626.51 72.3017 3626.51 76.72V298.558C3626.51 306.201 3636.56 309.773 3641.88 304.285C3674.23 270.932 3720.25 247.186 3784.27 247.186C3920.34 247.186 4019.92 343.814 4019.92 505.518C4019.92 661.306 3920.34 764.836 3783.28 764.836ZM3758.63 661.306C3839.48 661.306 3892.73 599.188 3892.73 506.504C3892.73 412.834 3839.48 350.716 3758.63 350.716C3677.78 350.716 3624.54 412.834 3624.54 505.518C3624.54 599.188 3677.78 661.306 3758.63 661.306Z" fill="black"/>
|
|
10
|
+
<path d="M4344.64 764.836C4186.88 764.836 4081.38 661.306 4081.38 506.504C4081.38 350.716 4186.88 247.186 4344.64 247.186C4502.4 247.186 4607.9 350.716 4607.9 506.504C4607.9 661.306 4502.4 764.836 4344.64 764.836ZM4344.64 661.306C4427.46 661.306 4479.72 597.216 4479.72 506.504C4479.72 414.806 4427.46 350.716 4344.64 350.716C4261.82 350.716 4209.56 414.806 4209.56 506.504C4209.56 597.216 4261.82 661.306 4344.64 661.306Z" fill="black"/>
|
|
11
|
+
<path d="M4699.42 758.92C4695 758.92 4691.42 755.338 4691.42 750.92V261.102C4691.42 256.684 4695 253.102 4699.42 253.102H4794.19C4798.05 253.102 4801.37 255.867 4802.06 259.671L4809.55 300.893C4810.78 307.645 4819.5 309.909 4824.22 304.93C4855.56 271.903 4900.75 247.186 4971.44 247.186C5081.88 247.186 5174.56 306.346 5174.56 477.91V750.92C5174.56 755.338 5170.98 758.92 5166.56 758.92H5057.34C5052.92 758.92 5049.34 755.338 5049.34 750.92V487.77C5049.34 398.044 5011.87 351.702 4938.9 351.702C4859.04 351.702 4816.64 401.988 4816.64 492.7V750.92C4816.64 755.338 4813.06 758.92 4808.64 758.92H4699.42Z" fill="black"/>
|
|
12
|
+
</svg>
|
|
@@ -58,9 +58,7 @@ export function AIIntegrationSection() {
|
|
|
58
58
|
</motion.div>
|
|
59
59
|
<h2 className="text-3xl md:text-5xl font-black tracking-tight mb-4">
|
|
60
60
|
{t('landing.aiIntegration.headline1')}{' '}
|
|
61
|
-
<span className="
|
|
62
|
-
{t('landing.aiIntegration.headlineHighlight')}
|
|
63
|
-
</span>{' '}
|
|
61
|
+
<span className="text-primary">{t('landing.aiIntegration.headlineHighlight')}</span>{' '}
|
|
64
62
|
{t('landing.aiIntegration.headline2')}
|
|
65
63
|
</h2>
|
|
66
64
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
|
|
@@ -63,7 +63,7 @@ import { useEnabledServices } from '@/hooks/useRunningServices';
|
|
|
63
63
|
import { getServiceUrl } from '@/lib/admin-services';
|
|
64
64
|
import { cn, getUserInitials } from '@/lib/utils';
|
|
65
65
|
import { useAuth } from './auth/AuthProvider';
|
|
66
|
-
import {
|
|
66
|
+
import { Logo, WordmarkText } from './Logo';
|
|
67
67
|
|
|
68
68
|
const prefetchMap: Record<string, () => Promise<unknown>> = {
|
|
69
69
|
'/dashboard': () => import('@/pages/Dashboard'),
|
|
@@ -188,7 +188,14 @@ export function AppSidebar() {
|
|
|
188
188
|
onClick={toggleSidebar}
|
|
189
189
|
className="flex h-full w-full cursor-pointer items-center"
|
|
190
190
|
>
|
|
191
|
-
<
|
|
191
|
+
<IconSlot>
|
|
192
|
+
<Logo size="sm" />
|
|
193
|
+
</IconSlot>
|
|
194
|
+
<span className="grid shrink-0 grid-cols-[1fr] opacity-100 transition-[grid-template-columns,opacity] duration-300 ease-in-out group-data-[collapsible=icon]:grid-cols-[0fr] group-data-[collapsible=icon]:opacity-0">
|
|
195
|
+
<span className="overflow-hidden">
|
|
196
|
+
<WordmarkText size="sm" className="font-display text-2xl font-bold" />
|
|
197
|
+
</span>
|
|
198
|
+
</span>
|
|
192
199
|
</button>
|
|
193
200
|
</SidebarHeader>
|
|
194
201
|
|
|
@@ -200,7 +207,7 @@ export function AppSidebar() {
|
|
|
200
207
|
<SidebarMenu>
|
|
201
208
|
<SidebarMenuItem>
|
|
202
209
|
<DropdownMenu>
|
|
203
|
-
<DropdownMenuTrigger className="flex w-full items-center
|
|
210
|
+
<DropdownMenuTrigger className="flex w-full items-center rounded-md border border-border text-left text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground h-10 transition-colors cursor-pointer group-data-[collapsible=icon]:size-10 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0">
|
|
204
211
|
<IconSlot>
|
|
205
212
|
<Building2 className="size-4" />
|
|
206
213
|
</IconSlot>
|
|
@@ -262,7 +269,7 @@ export function AppSidebar() {
|
|
|
262
269
|
onMouseEnter={() => prefetch(item.path)}
|
|
263
270
|
onFocus={() => prefetch(item.path)}
|
|
264
271
|
tooltip={t(item.titleKey)}
|
|
265
|
-
className="p-0"
|
|
272
|
+
className="gap-0 p-0"
|
|
266
273
|
>
|
|
267
274
|
<IconSlot>
|
|
268
275
|
<item.icon className="size-4" />
|
|
@@ -288,7 +295,7 @@ export function AppSidebar() {
|
|
|
288
295
|
<SidebarMenuButton
|
|
289
296
|
onClick={() => navigate('/dashboard')}
|
|
290
297
|
tooltip={t('common.back')}
|
|
291
|
-
className="p-0"
|
|
298
|
+
className="gap-0 p-0"
|
|
292
299
|
>
|
|
293
300
|
<IconSlot>
|
|
294
301
|
<ArrowLeft className="size-4" />
|
|
@@ -313,7 +320,7 @@ export function AppSidebar() {
|
|
|
313
320
|
onMouseEnter={() => prefetch('/settings/profile')}
|
|
314
321
|
onFocus={() => prefetch('/settings/profile')}
|
|
315
322
|
tooltip={t('sidebar.profile')}
|
|
316
|
-
className="p-0"
|
|
323
|
+
className="gap-0 p-0"
|
|
317
324
|
>
|
|
318
325
|
<IconSlot>
|
|
319
326
|
<User className="size-4" />
|
|
@@ -330,7 +337,7 @@ export function AppSidebar() {
|
|
|
330
337
|
onMouseEnter={() => prefetch('/settings/billing')}
|
|
331
338
|
onFocus={() => prefetch('/settings/billing')}
|
|
332
339
|
tooltip={t('sidebar.billing')}
|
|
333
|
-
className="p-0"
|
|
340
|
+
className="gap-0 p-0"
|
|
334
341
|
>
|
|
335
342
|
<IconSlot>
|
|
336
343
|
<CreditCard className="size-4" />
|
|
@@ -347,7 +354,7 @@ export function AppSidebar() {
|
|
|
347
354
|
onMouseEnter={() => prefetch('/settings/security')}
|
|
348
355
|
onFocus={() => prefetch('/settings/security')}
|
|
349
356
|
tooltip={t('sidebar.security')}
|
|
350
|
-
className="p-0"
|
|
357
|
+
className="gap-0 p-0"
|
|
351
358
|
>
|
|
352
359
|
<IconSlot>
|
|
353
360
|
<Shield className="size-4" />
|
|
@@ -372,7 +379,7 @@ export function AppSidebar() {
|
|
|
372
379
|
<SidebarMenuButton
|
|
373
380
|
onClick={() => navigate('/dashboard')}
|
|
374
381
|
tooltip={t('common.back')}
|
|
375
|
-
className="p-0"
|
|
382
|
+
className="gap-0 p-0"
|
|
376
383
|
>
|
|
377
384
|
<IconSlot>
|
|
378
385
|
<ArrowLeft className="size-4" />
|
|
@@ -395,7 +402,7 @@ export function AppSidebar() {
|
|
|
395
402
|
isActive={location.pathname === `/organizations/${currentOrgId}/details`}
|
|
396
403
|
onClick={() => navigate(`/organizations/${currentOrgId}/details`)}
|
|
397
404
|
tooltip={t('sidebar.orgDetails')}
|
|
398
|
-
className="p-0"
|
|
405
|
+
className="gap-0 p-0"
|
|
399
406
|
>
|
|
400
407
|
<IconSlot>
|
|
401
408
|
<Building2 className="size-4" />
|
|
@@ -410,7 +417,7 @@ export function AppSidebar() {
|
|
|
410
417
|
isActive={location.pathname === `/organizations/${currentOrgId}/members`}
|
|
411
418
|
onClick={() => navigate(`/organizations/${currentOrgId}/members`)}
|
|
412
419
|
tooltip={t('sidebar.members')}
|
|
413
|
-
className="p-0"
|
|
420
|
+
className="gap-0 p-0"
|
|
414
421
|
>
|
|
415
422
|
<IconSlot>
|
|
416
423
|
<Users className="size-4" />
|
|
@@ -435,7 +442,7 @@ export function AppSidebar() {
|
|
|
435
442
|
<SidebarMenuButton
|
|
436
443
|
onClick={() => navigate('/dashboard')}
|
|
437
444
|
tooltip={t('common.back')}
|
|
438
|
-
className="p-0"
|
|
445
|
+
className="gap-0 p-0"
|
|
439
446
|
>
|
|
440
447
|
<IconSlot>
|
|
441
448
|
<ArrowLeft className="size-4" />
|
|
@@ -460,7 +467,7 @@ export function AppSidebar() {
|
|
|
460
467
|
onMouseEnter={() => prefetch('/admin/dashboard')}
|
|
461
468
|
onFocus={() => prefetch('/admin/dashboard')}
|
|
462
469
|
tooltip={t('sidebar.overview')}
|
|
463
|
-
className="p-0"
|
|
470
|
+
className="gap-0 p-0"
|
|
464
471
|
>
|
|
465
472
|
<IconSlot>
|
|
466
473
|
<LayoutDashboard className="size-4" />
|
|
@@ -477,7 +484,7 @@ export function AppSidebar() {
|
|
|
477
484
|
onMouseEnter={() => prefetch('/admin/organizations')}
|
|
478
485
|
onFocus={() => prefetch('/admin/organizations')}
|
|
479
486
|
tooltip={t('sidebar.organizations')}
|
|
480
|
-
className="p-0"
|
|
487
|
+
className="gap-0 p-0"
|
|
481
488
|
>
|
|
482
489
|
<IconSlot>
|
|
483
490
|
<Building2 className="size-4" />
|
|
@@ -494,7 +501,7 @@ export function AppSidebar() {
|
|
|
494
501
|
onMouseEnter={() => prefetch('/admin/users')}
|
|
495
502
|
onFocus={() => prefetch('/admin/users')}
|
|
496
503
|
tooltip={t('sidebar.users')}
|
|
497
|
-
className="p-0"
|
|
504
|
+
className="gap-0 p-0"
|
|
498
505
|
>
|
|
499
506
|
<IconSlot>
|
|
500
507
|
<Users className="size-4" />
|
|
@@ -511,7 +518,7 @@ export function AppSidebar() {
|
|
|
511
518
|
onMouseEnter={() => prefetch('/admin/notifications')}
|
|
512
519
|
onFocus={() => prefetch('/admin/notifications')}
|
|
513
520
|
tooltip={t('sidebar.notifications')}
|
|
514
|
-
className="p-0"
|
|
521
|
+
className="gap-0 p-0"
|
|
515
522
|
>
|
|
516
523
|
<IconSlot>
|
|
517
524
|
<Bell className="size-4" />
|
|
@@ -528,7 +535,7 @@ export function AppSidebar() {
|
|
|
528
535
|
onMouseEnter={() => prefetch('/admin/logs')}
|
|
529
536
|
onFocus={() => prefetch('/admin/logs')}
|
|
530
537
|
tooltip={t('sidebar.logs')}
|
|
531
|
-
className="p-0"
|
|
538
|
+
className="gap-0 p-0"
|
|
532
539
|
>
|
|
533
540
|
<IconSlot>
|
|
534
541
|
<FileText className="size-4" />
|
|
@@ -545,7 +552,7 @@ export function AppSidebar() {
|
|
|
545
552
|
onMouseEnter={() => prefetch('/admin/theme')}
|
|
546
553
|
onFocus={() => prefetch('/admin/theme')}
|
|
547
554
|
tooltip={t('sidebar.theme')}
|
|
548
|
-
className="p-0"
|
|
555
|
+
className="gap-0 p-0"
|
|
549
556
|
>
|
|
550
557
|
<IconSlot>
|
|
551
558
|
<Palette className="size-4" />
|
|
@@ -562,7 +569,7 @@ export function AppSidebar() {
|
|
|
562
569
|
onMouseEnter={() => prefetch('/admin/jobs')}
|
|
563
570
|
onFocus={() => prefetch('/admin/jobs')}
|
|
564
571
|
tooltip="Background Jobs"
|
|
565
|
-
className="p-0"
|
|
572
|
+
className="gap-0 p-0"
|
|
566
573
|
>
|
|
567
574
|
<IconSlot>
|
|
568
575
|
<Clock className="size-4" />
|
|
@@ -577,7 +584,7 @@ export function AppSidebar() {
|
|
|
577
584
|
onMouseEnter={() => prefetch('/admin/contact')}
|
|
578
585
|
onFocus={() => prefetch('/admin/contact')}
|
|
579
586
|
tooltip="Contact Submissions"
|
|
580
|
-
className="p-0"
|
|
587
|
+
className="gap-0 p-0"
|
|
581
588
|
>
|
|
582
589
|
<IconSlot>
|
|
583
590
|
<MessageSquare className="size-4" />
|
|
@@ -592,7 +599,7 @@ export function AppSidebar() {
|
|
|
592
599
|
onMouseEnter={() => prefetch('/admin/newsletter')}
|
|
593
600
|
onFocus={() => prefetch('/admin/newsletter')}
|
|
594
601
|
tooltip="Newsletter"
|
|
595
|
-
className="p-0"
|
|
602
|
+
className="gap-0 p-0"
|
|
596
603
|
>
|
|
597
604
|
<IconSlot>
|
|
598
605
|
<Mail className="size-4" />
|
|
@@ -607,7 +614,7 @@ export function AppSidebar() {
|
|
|
607
614
|
onMouseEnter={() => prefetch('/admin/settings')}
|
|
608
615
|
onFocus={() => prefetch('/admin/settings')}
|
|
609
616
|
tooltip={t('sidebar.settings')}
|
|
610
|
-
className="p-0"
|
|
617
|
+
className="gap-0 p-0"
|
|
611
618
|
>
|
|
612
619
|
<IconSlot>
|
|
613
620
|
<Settings className="size-4" />
|
|
@@ -628,7 +635,7 @@ export function AppSidebar() {
|
|
|
628
635
|
? service.description
|
|
629
636
|
: 'Internal only — kubectl port-forward svc/traefik-dashboard 8080:8080 -n vibecarbon'
|
|
630
637
|
}
|
|
631
|
-
className={cn('p-0', !url && 'cursor-default opacity-50')}
|
|
638
|
+
className={cn('gap-0 p-0', !url && 'cursor-default opacity-50')}
|
|
632
639
|
>
|
|
633
640
|
<IconSlot>
|
|
634
641
|
<service.icon className="size-4" />
|
|
@@ -653,7 +660,7 @@ export function AppSidebar() {
|
|
|
653
660
|
<SidebarFooter>
|
|
654
661
|
{/* User Menu */}
|
|
655
662
|
<DropdownMenu>
|
|
656
|
-
<DropdownMenuTrigger className="group/user flex w-full items-center
|
|
663
|
+
<DropdownMenuTrigger className="group/user flex w-full items-center rounded-md text-left text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground h-12 transition-colors group-data-[collapsible=icon]:size-10 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0">
|
|
657
664
|
<IconSlot>
|
|
658
665
|
<Avatar size="sm">
|
|
659
666
|
<AvatarFallback>{getUserInitials(user)}</AvatarFallback>
|
|
@@ -672,7 +679,7 @@ export function AppSidebar() {
|
|
|
672
679
|
<DropdownMenuContent
|
|
673
680
|
align="start"
|
|
674
681
|
side={isCollapsed ? 'right' : 'top'}
|
|
675
|
-
className="w-56"
|
|
682
|
+
className="min-w-56"
|
|
676
683
|
>
|
|
677
684
|
<div className="flex items-start justify-between px-3 py-2">
|
|
678
685
|
<div>
|
|
@@ -691,18 +698,18 @@ export function AppSidebar() {
|
|
|
691
698
|
</div>
|
|
692
699
|
<DropdownMenuSeparator />
|
|
693
700
|
<DropdownMenuItem onClick={() => navigate('/settings/profile')}>
|
|
694
|
-
<Settings className="size-4
|
|
701
|
+
<Settings className="size-4" />
|
|
695
702
|
{t('common.settings')}
|
|
696
703
|
</DropdownMenuItem>
|
|
697
704
|
{isSuperAdmin && (
|
|
698
705
|
<DropdownMenuItem onClick={() => navigate('/admin/dashboard')}>
|
|
699
|
-
<Shield className="size-4
|
|
706
|
+
<Shield className="size-4" />
|
|
700
707
|
{t('sidebar.superAdmin')}
|
|
701
708
|
</DropdownMenuItem>
|
|
702
709
|
)}
|
|
703
710
|
<DropdownMenuSeparator />
|
|
704
711
|
<DropdownMenuItem onClick={handleSignOut}>
|
|
705
|
-
<LogOut className="size-4
|
|
712
|
+
<LogOut className="size-4" />
|
|
706
713
|
{t('common.signOut')}
|
|
707
714
|
</DropdownMenuItem>
|
|
708
715
|
</DropdownMenuContent>
|
|
@@ -51,7 +51,7 @@ export function CTAFooter({
|
|
|
51
51
|
|
|
52
52
|
{/* Footer */}
|
|
53
53
|
<div className="relative mx-auto flex max-w-7xl flex-col items-center justify-between gap-4 px-6 py-8 sm:flex-row">
|
|
54
|
-
<Wordmark size="sm" className="text-white" />
|
|
54
|
+
<Wordmark size="sm" className="text-white" forceTheme="dark" />
|
|
55
55
|
{footerLinks}
|
|
56
56
|
</div>
|
|
57
57
|
</footer>
|
|
@@ -72,9 +72,7 @@ export function ComparisonSection() {
|
|
|
72
72
|
</motion.div>
|
|
73
73
|
<h2 className="text-3xl md:text-5xl font-black tracking-tight mb-4">
|
|
74
74
|
{t('landing.comparison.headline')}{' '}
|
|
75
|
-
<span className="
|
|
76
|
-
{t('landing.comparison.headlineHighlight')}
|
|
77
|
-
</span>
|
|
75
|
+
<span className="text-primary">{t('landing.comparison.headlineHighlight')}</span>
|
|
78
76
|
</h2>
|
|
79
77
|
<p className="text-lg text-muted-foreground max-w-xl mx-auto">
|
|
80
78
|
{t('landing.comparison.subheading')}
|