diff --git a/.vscode/settings.json b/.vscode/settings.json index ed1531ca..86cf3607 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,6 @@ ], "biome.lsp.bin": "node_modules/.bin/biome", "[astro]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "astro-build.astro-vscode" } } diff --git a/Dockerfile b/Dockerfile index bc4f38a8..15535f95 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,10 @@ FROM build-deps AS build COPY . . ARG ZANE_DOMAINS ARG DATABASE_URL +ARG TEMPLATE_API_HOST=https://templates.zaneops.dev +ARG PRIVATE_TEMPLATE_API_HOST=https://templates.zaneops.dev +ENV TEMPLATE_API_HOST=${TEMPLATE_API_HOST} +ENV PRIVATE_TEMPLATE_API_HOST=${PRIVATE_TEMPLATE_API_HOST} ENV DATABASE_URL=${DATABASE_URL} ENV ZANE_DOMAINS=${ZANE_DOMAINS} RUN --mount=type=cache,target=/app/.astro FORCE_COLOR=true pnpm run build diff --git a/astro.config.mjs b/astro.config.mjs index ea3d3b4b..a9cd005a 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -14,9 +14,21 @@ export default defineConfig({ adapter: node({ mode: "standalone" }), - + prefetch: true, env: { schema: { + PRIVATE_TEMPLATE_API_HOST: envField.string({ + context: "server", + access: "secret", + url: true, + default: "https://templates.zaneops.dev" + }), + TEMPLATE_API_HOST: envField.string({ + context: "client", + access: "public", + url: true, + default: "https://templates.zaneops.dev" + }), ASSETS_SERVER_DOMAIN: envField.string({ context: "client", access: "public", @@ -155,7 +167,7 @@ export default defineConfig({ }, { label: "Knowledge base", - autogenerate: { directory: "knowledge-base" } + autogenerate: { directory: "knowledge-base", collapsed: true } }, { label: "Changelog", @@ -167,6 +179,7 @@ export default defineConfig({ }, { label: "API Reference", + collapsed: true, items: [ { label: "Introduction", diff --git a/package.json b/package.json index e6f87556..0e45fcb2 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,19 @@ "@react-email/render": "^2.0.0", "@resvg/resvg-js": "^2.6.2", "@tailwindcss/vite": "^4.1.13", + "@tanstack/react-query": "^5.90.21", "astro": "^5.16.9", "drizzle-orm": "^0.45.1", + "lucide-react": "^0.575.0", "nodemailer": "^7.0.12", + "nuqs": "^2.8.8", "pg": "^8.16.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^10.1.0", "satori": "^0.15.2", "sharp": "^0.32.6", + "tailwind-merge": "^3.5.0", "typescript": "^5.8.3", "zod": "^3.25.76" }, @@ -42,6 +46,7 @@ "@astrojs/node": "^9.5.1", "@biomejs/biome": "2.1.2", "@react-email/preview-server": "5.1.0", + "@tanstack/react-query-devtools": "^5.91.3", "@types/nodemailer": "^7.0.4", "@types/pg": "^8.16.0", "@types/react": "^19.1.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eae943b3..541ee5ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,15 +41,24 @@ importers: '@tailwindcss/vite': specifier: ^4.1.13 version: 4.1.18(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + '@tanstack/react-query': + specifier: ^5.90.21 + version: 5.90.21(react@18.3.1) astro: specifier: ^5.16.9 version: 5.16.9(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2) drizzle-orm: specifier: ^0.45.1 version: 0.45.1(@types/pg@8.16.0)(pg@8.16.3) + lucide-react: + specifier: ^0.575.0 + version: 0.575.0(react@18.3.1) nodemailer: specifier: ^7.0.12 version: 7.0.12 + nuqs: + specifier: ^2.8.8 + version: 2.8.8(next@16.0.10(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) pg: specifier: ^8.16.3 version: 8.16.3 @@ -68,6 +77,9 @@ importers: sharp: specifier: ^0.32.6 version: 0.32.6 + tailwind-merge: + specifier: ^3.5.0 + version: 3.5.0 typescript: specifier: ^5.8.3 version: 5.9.3 @@ -84,6 +96,9 @@ importers: '@react-email/preview-server': specifier: 5.1.0 version: 5.1.0(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-query-devtools': + specifier: ^5.91.3 + version: 5.91.3(@tanstack/react-query@5.90.21(react@18.3.1))(react@18.3.1) '@types/nodemailer': specifier: ^7.0.4 version: 7.0.4 @@ -1641,6 +1656,9 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1734,6 +1752,23 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + + '@tanstack/query-devtools@5.93.0': + resolution: {integrity: sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==} + + '@tanstack/react-query-devtools@5.91.3': + resolution: {integrity: sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==} + peerDependencies: + '@tanstack/react-query': ^5.90.20 + react: ^18 || ^19 + + '@tanstack/react-query@5.90.21': + resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} + peerDependencies: + react: ^18 || ^19 + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2986,6 +3021,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.575.0: + resolution: {integrity: sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3295,6 +3335,27 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nuqs@2.8.8: + resolution: {integrity: sha512-LF5sw9nWpHyPWzMMu9oho3r9C5DvkpmBIg4LQN78sexIzGaeRx8DWr0uy3YiFx5i2QGZN1Qqcb+OAtEVRa2bnA==} + peerDependencies: + '@remix-run/react': '>=2' + '@tanstack/react-router': ^1 + next: '>=14.2.0' + react: '>=18.2.0 || ^19.0.0-0' + react-router: ^5 || ^6 || ^7 + react-router-dom: ^5 || ^6 || ^7 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@tanstack/react-router': + optional: true + next: + optional: true + react-router: + optional: true + react-router-dom: + optional: true + nypm@0.6.0: resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} engines: {node: ^14.16.0 || >=16.10.0} @@ -3869,6 +3930,9 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} @@ -6125,6 +6189,8 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/spec@1.0.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -6197,6 +6263,21 @@ snapshots: tailwindcss: 4.1.18 vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + '@tanstack/query-core@5.90.20': {} + + '@tanstack/query-devtools@5.93.0': {} + + '@tanstack/react-query-devtools@5.91.3(@tanstack/react-query@5.90.21(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/query-devtools': 5.93.0 + '@tanstack/react-query': 5.90.21(react@18.3.1) + react: 18.3.1 + + '@tanstack/react-query@5.90.21(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.90.20 + react: 18.3.1 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 @@ -7565,6 +7646,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.575.0(react@18.3.1): + dependencies: + react: 18.3.1 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -8136,6 +8221,13 @@ snapshots: dependencies: boolbase: 1.0.0 + nuqs@2.8.8(next@16.0.10(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + dependencies: + '@standard-schema/spec': 1.0.0 + react: 18.3.1 + optionalDependencies: + next: 16.0.10(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nypm@0.6.0: dependencies: citty: 0.1.6 @@ -8941,6 +9033,8 @@ snapshots: tagged-tag@1.0.0: {} + tailwind-merge@3.5.0: {} + tailwindcss@4.1.18: {} tapable@2.3.0: {} diff --git a/public/images/build-registries-s3.webp b/public/images/build-registries-s3.webp new file mode 100644 index 00000000..02fb554f Binary files /dev/null and b/public/images/build-registries-s3.webp differ diff --git a/public/images/computed-docker-compose-yml.png b/public/images/computed-docker-compose-yml.png new file mode 100644 index 00000000..6506d4af Binary files /dev/null and b/public/images/computed-docker-compose-yml.png differ diff --git a/public/images/config-versioning-in-compose.png b/public/images/config-versioning-in-compose.png new file mode 100644 index 00000000..8cef7500 Binary files /dev/null and b/public/images/config-versioning-in-compose.png differ diff --git a/public/images/create-from-compose-yml.png b/public/images/create-from-compose-yml.png new file mode 100644 index 00000000..2cae60dd Binary files /dev/null and b/public/images/create-from-compose-yml.png differ diff --git a/public/images/create-from-dokploy.png b/public/images/create-from-dokploy.png new file mode 100644 index 00000000..a0d304f3 Binary files /dev/null and b/public/images/create-from-dokploy.png differ diff --git a/public/images/create-from-template-list.png b/public/images/create-from-template-list.png new file mode 100644 index 00000000..983c95a1 Binary files /dev/null and b/public/images/create-from-template-list.png differ diff --git a/public/images/create-from-zaneops-template.png b/public/images/create-from-zaneops-template.png new file mode 100644 index 00000000..9d9e6c49 Binary files /dev/null and b/public/images/create-from-zaneops-template.png differ diff --git a/public/images/db-compose-stack-with-overrides.png b/public/images/db-compose-stack-with-overrides.png new file mode 100644 index 00000000..fb372c02 Binary files /dev/null and b/public/images/db-compose-stack-with-overrides.png differ diff --git a/public/images/deploy-compose-stack-logs.png b/public/images/deploy-compose-stack-logs.png new file mode 100644 index 00000000..1329a296 Binary files /dev/null and b/public/images/deploy-compose-stack-logs.png differ diff --git a/public/images/deploy-compose-stack.png b/public/images/deploy-compose-stack.png new file mode 100644 index 00000000..c32c363f Binary files /dev/null and b/public/images/deploy-compose-stack.png differ diff --git a/public/images/deploy-n8n-on-zaneops.png b/public/images/deploy-n8n-on-zaneops.png new file mode 100644 index 00000000..cd8e3f31 Binary files /dev/null and b/public/images/deploy-n8n-on-zaneops.png differ diff --git a/public/images/deploy-plausible-template.png b/public/images/deploy-plausible-template.png new file mode 100644 index 00000000..a0b37f89 Binary files /dev/null and b/public/images/deploy-plausible-template.png differ diff --git a/public/images/docker-compose-service-list.png b/public/images/docker-compose-service-list.png new file mode 100644 index 00000000..f84c6614 Binary files /dev/null and b/public/images/docker-compose-service-list.png differ diff --git a/public/images/docker-compose-yml-input.png b/public/images/docker-compose-yml-input.png new file mode 100644 index 00000000..69bd3ce2 Binary files /dev/null and b/public/images/docker-compose-yml-input.png differ diff --git a/public/images/dokploy-base64-template.png b/public/images/dokploy-base64-template.png new file mode 100644 index 00000000..d3ca5e26 Binary files /dev/null and b/public/images/dokploy-base64-template.png differ diff --git a/public/images/dokploy-compose-template.png b/public/images/dokploy-compose-template.png new file mode 100644 index 00000000..0a559746 Binary files /dev/null and b/public/images/dokploy-compose-template.png differ diff --git a/public/images/dokploy-create-from-base64.png b/public/images/dokploy-create-from-base64.png new file mode 100644 index 00000000..9f296411 Binary files /dev/null and b/public/images/dokploy-create-from-base64.png differ diff --git a/public/images/dokploy-create-from-compose.png b/public/images/dokploy-create-from-compose.png new file mode 100644 index 00000000..bc269138 Binary files /dev/null and b/public/images/dokploy-create-from-compose.png differ diff --git a/public/images/env-overrides.png b/public/images/env-overrides.png new file mode 100644 index 00000000..5be6e243 Binary files /dev/null and b/public/images/env-overrides.png differ diff --git a/public/images/git-commit-sha.webp b/public/images/git-commit-sha.webp new file mode 100644 index 00000000..79e6a082 Binary files /dev/null and b/public/images/git-commit-sha.webp differ diff --git a/public/images/networks-in-compose.png b/public/images/networks-in-compose.png new file mode 100644 index 00000000..a73a51c0 Binary files /dev/null and b/public/images/networks-in-compose.png differ diff --git a/public/images/new-compose-stack.png b/public/images/new-compose-stack.png new file mode 100644 index 00000000..3ef48cef Binary files /dev/null and b/public/images/new-compose-stack.png differ diff --git a/public/images/obfuscate-env-in-logs.png b/public/images/obfuscate-env-in-logs.png new file mode 100644 index 00000000..ebd497d7 Binary files /dev/null and b/public/images/obfuscate-env-in-logs.png differ diff --git a/public/images/paste-compose-yml.png b/public/images/paste-compose-yml.png new file mode 100644 index 00000000..2c358706 Binary files /dev/null and b/public/images/paste-compose-yml.png differ diff --git a/public/images/plausible-template-onboarding.png b/public/images/plausible-template-onboarding.png new file mode 100644 index 00000000..7679d283 Binary files /dev/null and b/public/images/plausible-template-onboarding.png differ diff --git a/public/images/plausible-template-stack-details.png b/public/images/plausible-template-stack-details.png new file mode 100644 index 00000000..da63a3e3 Binary files /dev/null and b/public/images/plausible-template-stack-details.png differ diff --git a/public/images/search-plausible-template.png b/public/images/search-plausible-template.png new file mode 100644 index 00000000..54f960f2 Binary files /dev/null and b/public/images/search-plausible-template.png differ diff --git a/public/images/select-dokploy-template.png b/public/images/select-dokploy-template.png new file mode 100644 index 00000000..948dfd12 Binary files /dev/null and b/public/images/select-dokploy-template.png differ diff --git a/public/images/ssh-command-after-creation.webp b/public/images/ssh-command-after-creation.webp new file mode 100644 index 00000000..c36ca5bb Binary files /dev/null and b/public/images/ssh-command-after-creation.webp differ diff --git a/public/images/wordpress-template-onboarding.png b/public/images/wordpress-template-onboarding.png new file mode 100644 index 00000000..950416a7 Binary files /dev/null and b/public/images/wordpress-template-onboarding.png differ diff --git a/public/images/wordpress-template-stack-details.png b/public/images/wordpress-template-stack-details.png new file mode 100644 index 00000000..77b2c78b Binary files /dev/null and b/public/images/wordpress-template-stack-details.png differ diff --git a/src/assets/global.css b/src/assets/global.css index 86f6a8bb..226eb6d9 100644 --- a/src/assets/global.css +++ b/src/assets/global.css @@ -38,6 +38,7 @@ --sl-color-gray-4: #57585c; --sl-color-gray-5: #37383c; --sl-color-gray-6: hsl(226 19% 13%); + --sl-color-grey-6: oklch(0.2905 0.0191 271.65); --sl-color-gray-7: hsl(164 43% 2%); --sl-color-black: hsl(164 43% 2%); --color-border: var(--color-border-dark); diff --git a/src/components/ChatWidget.astro b/src/components/ChatWidget.astro deleted file mode 100644 index feb256c6..00000000 --- a/src/components/ChatWidget.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -import { Chat } from "./Chat"; ---- - - diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 36801fba..bc60d9f8 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -14,7 +14,7 @@ import DefaultFooter from "@astrojs/starlight/components/Footer.astro"; Found a bug? Open an issue - + Sponsor this project diff --git a/src/components/LandingPageCloud.astro b/src/components/LandingPageCloud.astro index 2a659877..a4325f65 100644 --- a/src/components/LandingPageCloud.astro +++ b/src/components/LandingPageCloud.astro @@ -1,152 +1,149 @@ --- import { Icon, LinkButton } from "@astrojs/starlight/components"; import { - CloudUpload, - FileText, - Github, - Heart, - KeyRound, - Network, - Palette, - Plus, - Server, - ShieldCheck, - Users, - Webhook + CloudUpload, + FileText, + Github, + Heart, + KeyRound, + Network, + Palette, + Plus, + Server, + ShieldCheck, + Users, + Webhook, } from "@lucide/astro"; import Logo from "../assets/Logo.svg"; import WaitlistForm from "./WaitlistForm.astro"; type Feature = { - icon: any; - title: string; - description: string; + icon: any; + title: string; + description: string; }; const features: Feature[] = [ - { - icon: Users, - title: "Multi-tenancy", - description: - "Organize projects with custom permissions for teams, groups, and workspaces" - }, - { - icon: KeyRound, - title: "OIDC Authentication", - description: "Enterprise-grade authentication with OpenID Connect support" - }, - { - icon: Github, - title: "Social Authentication", - description: - "Login with Google, Github, and other providers for both dashboard and service access" - }, - { - icon: ShieldCheck, - title: "Advanced Service Auth", - description: - "Protect services using social providers like GitHub and GitLab" - }, - { - icon: Palette, - title: "Custom Branding", - description: "Customize authentication pages with your brand identity" - }, - { - icon: FileText, - title: "Audit Logs", - description: - "Track all activity: logins, deployments, service changes, and more" - }, - { - icon: Server, - title: "Multi-Server Cluster", - description: - "Deploy across multiple servers for high availability and failover" - }, - { - icon: Webhook, - title: "Pre/Post Webhooks", - description: - "Execute custom logic before and after deployments with webhooks" - }, - { - icon: Plus, - title: "And much more...", - description: "More features incoming for the cloud version" - } + { + icon: Users, + title: "Multi-tenancy", + description: + "Organize projects with custom permissions for teams, groups, and workspaces", + }, + { + icon: KeyRound, + title: "OIDC Authentication", + description: + "Enterprise-grade authentication with OpenID Connect support", + }, + { + icon: Github, + title: "Social Authentication", + description: + "Login with Google, Github, and other providers for both dashboard and service access", + }, + { + icon: ShieldCheck, + title: "Advanced Service Auth", + description: + "Protect services using social providers like GitHub and GitLab", + }, + { + icon: Palette, + title: "Custom Branding", + description: "Customize authentication pages with your brand identity", + }, + { + icon: FileText, + title: "Audit Logs", + description: + "Track all activity: logins, deployments, service changes, and more", + }, + { + icon: Server, + title: "Multi-Server Cluster", + description: + "Deploy across multiple servers for high availability and failover", + }, + { + icon: Webhook, + title: "Pre/Post Webhooks", + description: + "Execute custom logic before and after deployments with webhooks", + }, + { + icon: Plus, + title: "And much more...", + description: "More features incoming for the cloud version", + }, ]; type SponsorTier = { - price: string; - features: string[]; - highlight?: boolean; + price: string; + features: string[]; + highlight?: boolean; }; const sponsorTiers: SponsorTier[] = [ - { - price: "$5/month", - features: ["Sponsor badge", "Early alpha access"] - }, - { - price: "$25/month", - features: [ - "Everything in $5", - "Priority bug fixes", - "Early access to features" - ], - highlight: true - }, - { - price: "$100/month", - features: [ - "Everything in $25", - "Company logo on README", - "Private support channel" - ] - } + { + price: "$5/month", + features: ["Sponsor badge", "Early alpha access"], + }, + { + price: "$25/month", + features: [ + "Everything in $5", + "Priority bug fixes", + "Early access to features", + ], + highlight: true, + }, + { + price: "$100/month", + features: [ + "Everything in $25", + "Company logo on README", + "Private support channel", + ], + }, ]; type FAQ = { - question: string; - answer: string; + question: string; + answer: string; }; const faqs: FAQ[] = [ - { - question: "How is ZaneOps Cloud different from other PaaS?", - answer: - "Your servers, your data. We host the control panel with enterprise features, you host your applications on your own infrastructure. This gives you the benefits of a managed platform without the lock-in or loss of control." - }, - { - question: "When will cloud access be available?", - answer: - "We're working toward an alpha release. Join the waitlist to be notified when access becomes available. Sponsors get priority access." - }, - { - question: "What about self-hosted ZaneOps?", - answer: - "Self-hosted ZaneOps remains 100% free and open source. Cloud offers additional enterprise features like multi-tenancy, OIDC auth, audit logs, and more on a managed control panel." - }, - { - question: "How does sponsorship work?", - answer: - "Sponsors get priority access to the cloud platform, priority bug fixes, and early access to new features. See our GitHub Sponsors page for details and to become a sponsor." - } + { + question: "How is ZaneOps Cloud different from other PaaS?", + answer: "Your servers, your data. We host the control panel with enterprise features, you host your applications on your own infrastructure. This gives you the benefits of a managed platform without the lock-in or loss of control.", + }, + { + question: "When will cloud access be available?", + answer: "We're working toward an alpha release. Join the waitlist to be notified when access becomes available. Sponsors get priority access.", + }, + { + question: "What about self-hosted ZaneOps?", + answer: "Self-hosted ZaneOps remains 100% free and open source. Cloud offers additional enterprise features like multi-tenancy, OIDC auth, audit logs, and more on a managed control panel.", + }, + { + question: "How does sponsorship work?", + answer: "Sponsors get priority access to the cloud platform, priority bug fixes, and early access to new features. See our GitHub Sponsors page for details and to become a sponsor.", + }, ]; ---

- ZaneOps Cloud + ZaneOps Cloud

-

+

Deploy with confidence using enterprise-grade features while keeping control of your data and servers.

@@ -155,7 +152,11 @@ const faqs: FAQ[] = [ Join the Waitlist - + Become a Sponsor @@ -219,11 +220,7 @@ const faqs: FAQ[] = [

-
+
{ features.map((feature) => (
@@ -247,9 +244,7 @@ const faqs: FAQ[] = [
-
+
@@ -266,7 +261,9 @@ const faqs: FAQ[] = [

Sponsor benefits

-
    +
    • @@ -335,7 +332,7 @@ const faqs: FAQ[] = [
      Become a Sponsor @@ -372,47 +369,47 @@ const faqs: FAQ[] = [

      -
      +
      -

      Join the waitlist

      -

      - Get notified when ZaneOps Cloud launches and be among the first - to try it -

      - - Sign Up Now - - -
      - - - +

      Join the waitlist

      +

      + Get notified when ZaneOps Cloud launches and be among the + first to try it +

      + + Sign Up Now + + +
      -
      -

      Become a sponsor

      -

      - Get early access, priority support, and help shape the future of - ZaneOps -

      - -
      +
      + + +
      +

      Become a sponsor

      +

      + Get early access, priority support, and help shape the + future of ZaneOps +

      + + Sponsor Now + + +
diff --git a/src/components/templates/button.tsx b/src/components/templates/button.tsx new file mode 100644 index 00000000..e114768a --- /dev/null +++ b/src/components/templates/button.tsx @@ -0,0 +1,22 @@ +import { cn } from "~/lib/utils"; + +export const buttonClassNames = [ + "border border-border", + "bg-(--sl-color-bg-nav) dark:bg-bg hover:dark:bg-(--sl-color-grey-6)", + "focus:dark:bg-bg/80 focus:ring-(--sl-color-accent) focus:outline-none focus:ring-2", + "hover:border-(--sl-color-white) hover:bg-(--sl-color-gray-6)", + "transition-colors rounded-lg shadow-sm", + "inline-flex items-center justify-center", + "px-4 py-2" +]; + +export function Button({ + className, + ...props +}: React.ComponentProps<"button">) { + return + + + + + + + ); +} diff --git a/src/components/templates/template-search.tsx b/src/components/templates/template-search.tsx new file mode 100644 index 00000000..f6aeef27 --- /dev/null +++ b/src/components/templates/template-search.tsx @@ -0,0 +1,377 @@ +import { TEMPLATE_API_HOST } from "astro:env/client"; +import { + keepPreviousData, + QueryClient, + QueryClientProvider, + useQuery +} from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { + ArrowRightIcon, + ArrowUpRightIcon, + CheckIcon, + ChevronRightIcon, + ChevronUpIcon, + LoaderIcon, + SearchIcon +} from "lucide-react"; +import { + parseAsInteger, + parseAsNativeArrayOf, + parseAsString, + useQueryState +} from "nuqs"; +import { NuqsAdapter } from "nuqs/adapters/react"; +import * as React from "react"; +import { Button } from "~/components/templates/button"; +import { Input } from "~/components/templates/input"; +import { Pagination } from "~/components/templates/pagination"; +import type { TemplateSearchAPIResponse } from "~/lib/types"; +import { cn, durationToMs } from "~/lib/utils"; + +export default function TemplateSearchPage() { + const [queryClient] = React.useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + placeholderData: keepPreviousData, + gcTime: durationToMs(3, "days"), + retry(failureCount, error) { + // error responses are valid responses that react router can handle, so we don't want to retry them + return !(error instanceof Response) && failureCount < 3; + } + } + } + }) + ); + + return ( + + + + {import.meta.env.DEV && } + + + ); +} + +const PER_PAGE = 18; + +export function TemplateSearch() { + const [searchTerm, setSearchTerm] = useQueryState("query"); + + const [currentPage, setCurrentPage] = useQueryState( + "page", + parseAsInteger.withDefault(1) + ); + const [tags, setTags] = useQueryState( + "tags", + parseAsNativeArrayOf(parseAsString) + ); + + const templatesQuery = useQuery({ + queryKey: ["TEMPLATES", { searchTerm, currentPage, tags }], + queryFn: async ({ signal }) => { + const url = new URL("/api/search", TEMPLATE_API_HOST); + + if (searchTerm) { + url.searchParams.set("q", searchTerm); + } + + url.searchParams.set("per_page", PER_PAGE.toString()); + url.searchParams.set("page", currentPage.toString()); + for (const tag of tags) { + url.searchParams.append("tags", tag); + } + + const response = await fetch(url, { signal }); + + if (!response.ok) { + throw new Error("Failed to fetch templates"); + } + + return response.json() as Promise; + } + }); + + const hits = templatesQuery.data?.hits ?? []; + + let totalPages = 0; + + if (templatesQuery.data && templatesQuery.data.found > hits.length) { + totalPages = Math.ceil(templatesQuery.data.found / PER_PAGE); + } + + return ( + + ); +} + +type TagsListFormProps = { + selectedTags: string[]; + onTagSelectChange: (newValues: string[]) => void; +}; + +function TagsListForm({ selectedTags, onTagSelectChange }: TagsListFormProps) { + const { data: tags = [] } = useQuery({ + queryKey: ["TAGS"], + queryFn: async ({ signal }) => { + const url = new URL("/api/tags.json", TEMPLATE_API_HOST); + const response = await fetch(url, { signal }); + + if (!response.ok) { + throw new Error("Failed to fetch tags"); + } + + return response.json() as Promise; + } + }); + + const [showAll, setShowAll] = React.useState(false); + + const [tagSearch, setTagSearch] = React.useState(""); + + const tagList = React.useMemo(() => { + let filteredTags = tags.toSorted((tagA, tagB) => { + // put selected tags first & sort alphabetically + if (selectedTags.includes(tagA) && selectedTags.includes(tagB)) { + return tagA > tagB ? 1 : -1; + } + if (selectedTags.includes(tagA)) { + return -1; + } + if (selectedTags.includes(tagB)) { + return 1; + } + return 0; + }); + + if (showAll || tagSearch.trim()) { + filteredTags = filteredTags.filter((tag) => tag.includes(tagSearch)); + } else { + filteredTags = filteredTags.slice(0, 10); + } + + return filteredTags; + }, [showAll, tagSearch, selectedTags, tags]); + + return ( +
+

Tags

+ + { + setTagSearch(ev.currentTarget.value); + }} + /> + +
    + {tagList.map((tag) => ( +
  • + +
  • + ))} +
+ {tags.length > 10 && !tagSearch.trim() && ( + + )} +
+ ); +} + +export function TemplateCard({ + id, + name, + description, + logoUrl: logo +}: { + id: string; + name: string; + description: string; + logoUrl: string; +}) { + const logoUrl = new URL(logo, TEMPLATE_API_HOST); + return ( +
+
+
+ + + +
+ +
+

{description}

+
+
+
+ ); +} diff --git a/src/content/docs/changelog/v1.10.mdx b/src/content/docs/changelog/v1.10.mdx index 8ade4e4c..fdd68c84 100644 --- a/src/content/docs/changelog/v1.10.mdx +++ b/src/content/docs/changelog/v1.10.mdx @@ -4,6 +4,8 @@ description: 'Sh...🤫 ells !' --- import {Aside} from '@astrojs/starlight/components'; +> Release notes: https://github.com/zane-ops/zane-ops/releases/tag/v1.10.0 + 25 May 2025 by [**Fred KISSIE**](https://github.com/Fredkiss3) Today we release ZaneOps v1.10, introducing a web terminal for deployments and a web terminal to the server. diff --git a/src/content/docs/changelog/v1.11.mdx b/src/content/docs/changelog/v1.11.mdx index b81d9b90..d7f86ab6 100644 --- a/src/content/docs/changelog/v1.11.mdx +++ b/src/content/docs/changelog/v1.11.mdx @@ -6,6 +6,9 @@ description: 'Private repos, auto-deploy on Git push' import {Aside} from '@astrojs/starlight/components'; import { ASSETS_SERVER_DOMAIN } from "astro:env/client" + +> Release notes: https://github.com/zane-ops/zane-ops/releases/tag/v1.11.0 + 22 July 2025 by [**Fred KISSIE**](https://github.com/Fredkiss3) diff --git a/src/content/docs/changelog/v1.12.mdx b/src/content/docs/changelog/v1.12.mdx index 27d1413a..bd7c648e 100644 --- a/src/content/docs/changelog/v1.12.mdx +++ b/src/content/docs/changelog/v1.12.mdx @@ -6,6 +6,7 @@ description: Preview environments, new design, account settings and more import {Aside} from '@astrojs/starlight/components'; import { ASSETS_SERVER_DOMAIN } from "astro:env/client" +> Release notes: https://github.com/zane-ops/zane-ops/releases/tag/v1.12 7 October 2025 by [**Fred KISSIE**](https://github.com/Fredkiss3) diff --git a/src/content/docs/changelog/v1.13.mdx b/src/content/docs/changelog/v1.13.mdx new file mode 100644 index 00000000..daa9f375 --- /dev/null +++ b/src/content/docs/changelog/v1.13.mdx @@ -0,0 +1,106 @@ +--- +title: ZaneOps v1.13 +description: Docker compose, Cloud waitlist, templates and more +--- + +import {Aside} from '@astrojs/starlight/components'; +import { ASSETS_SERVER_DOMAIN } from "astro:env/client" + +> Release notes: https://github.com/zane-ops/zane-ops/releases/tag/v1.13.0 + +28 February 2026 by [**Fred KISSIE**](https://github.com/Fredkiss3) + + +ZaneOps v1.13 is here with some big additions: [Docker Compose support](/knowledge-base/docker-compose), [templates](/knowledge-base/docker-compose/01-templates), shared volumes, build registries, and more. + + +**To install:** + +```shell +# assuming you are at /var/www/zaneops +curl https://cdn.zaneops.dev/makefile > Makefile +make setup +make deploy +``` + + + +### ZaneOps Cloud ☁️ waitlist + +The waitlist for [ZaneOps Cloud](/cloud) is now open. ZaneOps Cloud is the next evolution of the platform, designed for teams and enterprise users. It will ship with: + +- **Multi-tenancy**: support for multiple teams with fine-grained permissions +- **OIDC Authentication**: enterprise-grade SSO via OpenID Connect +- **Audit logs**: full activity trail of logins, deployments, service changes, and more +- **Custom authentication pages**: protect any service behind a login page +- **Custom branding**: white-label your authentication pages +- ...and more + +[Join the waitlist](/cloud) to be notified when the cloud version launches. [Sponsor the project](https://github.com/sponsors/zane-ops) to get **early access** when it does. + + + + +### Docker Compose stacks + +The headline feature of v1.13 is native [Docker Compose stack support](/knowledge-base/docker-compose). You can now deploy any `docker-compose.yml` file directly on ZaneOps. + +Under the hood, compose files are deployed as [Docker stacks](https://docs.docker.com/reference/cli/docker/stack/), with: + +- blue/green deployments out of the box +- domain assignment via service labels +- dynamic value generation via template expressions (e.g. `{{ generate_password | 8 }}` generates a secure 8-character password) + +