mirror of
https://github.com/Dokploy/website.git
synced 2026-06-30 11:35:24 +02:00
Merge branch 'main' into docs-add-cloudflare-origin-certs
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
|
||||
import { Github, GlobeIcon, HeartIcon, Rss } from "lucide-react";
|
||||
import {
|
||||
Github,
|
||||
GlobeIcon,
|
||||
HeartIcon,
|
||||
Rss,
|
||||
LogIn,
|
||||
UserPlus,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
/**
|
||||
* Shared layout configurations
|
||||
@@ -45,6 +52,18 @@ export const baseOptions: BaseLayoutProps = {
|
||||
),
|
||||
},
|
||||
links: [
|
||||
{
|
||||
text: "Login",
|
||||
url: "https://app.dokploy.com/",
|
||||
active: "nested-url",
|
||||
icon: <LogIn />,
|
||||
},
|
||||
{
|
||||
text: "Sign Up",
|
||||
url: "https://app.dokploy.com/register",
|
||||
active: "nested-url",
|
||||
icon: <UserPlus />,
|
||||
},
|
||||
{
|
||||
text: "Website",
|
||||
url: "https://dokploy.com",
|
||||
|
||||
@@ -38,24 +38,20 @@ If you choose the second option, we will proceed to configure the different serv
|
||||
|
||||
To start, we need to configure a Docker registry, as when deploying an application, you need a registry to deploy and download the application image on the other servers.
|
||||
|
||||
We offer two ways to configure a registry:
|
||||
|
||||
1. **External Registry**: Use any registry you want.
|
||||
2. **Self-Hosted Registry**: We create and configure a self-hosted registry for you.
|
||||
|
||||
### External Registry
|
||||
|
||||
You can use any registry, such as Docker Hub, DigitalOcean Spaces, ECR, or your choice. Make sure to enter the correct credentials and test the connection before adding the registry.
|
||||
You can use any external registry of your choice. Here are some popular options:
|
||||
|
||||
### Self-Hosted Registry
|
||||
1. **Docker Hub** - Free tier available, easy to set up
|
||||
2. **GitHub Container Registry (ghcr.io)** - Free for public repositories
|
||||
3. **DigitalOcean Container Registry** - Simple setup with good integration
|
||||
4. **Amazon ECR** - AWS's managed container registry
|
||||
5. **Google Container Registry** - Google Cloud's managed registry
|
||||
6. **Azure Container Registry** - Microsoft's managed registry
|
||||
|
||||
We will ask you for three things:
|
||||
Make sure to enter the correct credentials and test the connection before adding the registry to your cluster configuration.
|
||||
|
||||
1. A user.
|
||||
2. A password.
|
||||
3. A domain. Ensure this domain is pointing to the dokploy VPS.
|
||||
|
||||
Once set up, the Cluster section will be unlocked.
|
||||
Once configured, the Cluster section will be unlocked.
|
||||
|
||||
## Understanding Docker Swarm
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ volumes:
|
||||
- "../files/my-configs:/etc/my-app/config" ✅
|
||||
```
|
||||
|
||||
**Important:** If you need to use files from your repository (configuration files, scripts, etc.), you must move them to Dokploy's File Mounts (via Advanced → Mounts) instead of mounting them directly from the repository. When using AutoDeploy, Dokploy performs a `git clone` on each deployment, which clears the repository directory. Mounting files directly from your repository using relative paths (e.g., `./` or `./config/file.conf`) will cause them to be lost or empty in subsequent deployments. See the [Troubleshooting guide](/docs/core/troubleshooting#using-files-from-your-repository) for more details.
|
||||
|
||||
</Callout>
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
@@ -143,7 +143,7 @@ install_dokploy() {
|
||||
--mount type=volume,source=redis-data-volume,target=/data \
|
||||
redis:7
|
||||
|
||||
docker pull traefik:v3.5.0
|
||||
docker pull traefik:v3.6.1
|
||||
docker pull dokploy/dokploy:latest
|
||||
|
||||
# Installation
|
||||
@@ -167,11 +167,11 @@ install_dokploy() {
|
||||
--restart always \
|
||||
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
|
||||
-v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-p 80:80/tcp \
|
||||
-p 443:443/tcp \
|
||||
-p 443:443/udp \
|
||||
traefik:v3.5.0
|
||||
traefik:v3.6.1
|
||||
|
||||
docker network connect dokploy-network dokploy-traefik
|
||||
|
||||
@@ -183,11 +183,11 @@ install_dokploy() {
|
||||
# --network dokploy-network \
|
||||
# --mount type=bind,source=/etc/dokploy/traefik/traefik.yml,target=/etc/traefik/traefik.yml \
|
||||
# --mount type=bind,source=/etc/dokploy/traefik/dynamic,target=/etc/dokploy/traefik/dynamic \
|
||||
# --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
# --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock,readonly \
|
||||
# --publish mode=host,published=443,target=443 \
|
||||
# --publish mode=host,published=80,target=80 \
|
||||
# --publish mode=host,published=443,target=443,protocol=udp \
|
||||
# traefik:v3.5.0
|
||||
# traefik:v3.6.1
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
|
||||
@@ -16,6 +16,10 @@ The server setup process prepares the necessary environment for securely and eff
|
||||
Root access to the server is required. We currently do not support non-root deployments.
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning">
|
||||
If your remote server is configured with a different shell (other than bash), you must configure bash as the default shell, as Dokploy has been developed and tested with bash.
|
||||
</Callout>
|
||||
|
||||
<ImageZoom
|
||||
src="/assets/images/server-deploy.png"
|
||||
alt="Multi-Server Setup"
|
||||
|
||||
@@ -11,6 +11,10 @@ Multi server allows you to deploy your apps remotely to different servers withou
|
||||
|
||||
1. To install Dokploy UI, follow the [installation guide](en/docs/core/get-started/installation).
|
||||
|
||||
<Callout type="warning">
|
||||
If your remote server is configured with a different shell (other than bash), you must configure bash as the default shell, as Dokploy has been developed and tested with bash.
|
||||
</Callout>
|
||||
|
||||
2. Create an SSH key by going to `/dashboard/settings/ssh-keys` and add a new key. Be sure to copy the public key.
|
||||
|
||||
<ImageZoom
|
||||
|
||||
@@ -54,6 +54,46 @@ volumes:
|
||||
- "../files/my-configs:/etc/my-app/config" ✅
|
||||
```
|
||||
|
||||
### Using Files from Your Repository
|
||||
|
||||
<Callout type="warning">
|
||||
If you need to use files from your repository (e.g., configuration files, scripts, or directories), you **must** move them to Dokploy's file mounts and reference them manually using the Dokploy interface. This is because when using AutoDeploy, Dokploy performs a `git clone` operation on each deployment, which clears the repository directory. If you mount files directly from your repository using relative paths like `./` or `./docker/config/odoo.conf`, these files will be lost or empty in subsequent deployments, even though the first deployment may work correctly.
|
||||
</Callout>
|
||||
|
||||
**Why this happens:**
|
||||
- On the first deployment, the files exist and are mounted correctly
|
||||
- On subsequent deployments, Dokploy cleans the directory and performs a fresh `git clone`
|
||||
- Docker loses the reference to the files that were in the filesystem, and the new files have a new reference
|
||||
- This causes mounted directories and files to be empty or missing inside the container
|
||||
|
||||
**Solution:**
|
||||
1. Go to **Advanced** → **Mounts** in your Docker Compose application
|
||||
2. Create a new **File Mount** for each file or directory you need from your repository
|
||||
3. Copy the content from your repository files into the File Mount content field
|
||||
4. Specify the file path for your configuration
|
||||
5. Reference the file mount in your `docker-compose.yml` using the `../files/` path:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- "../files/my-config.json:/etc/my-app/config" ✅
|
||||
- "../files/my-directory:/path/in/container" ✅
|
||||
```
|
||||
|
||||
**Example:**
|
||||
Instead of mounting directly from your repository:
|
||||
```yaml
|
||||
volumes:
|
||||
- ./:/mnt/extra-addons/va_subscription_18 ❌
|
||||
- ./docker/config/odoo.conf:/etc/odoo/odoo.conf ❌
|
||||
```
|
||||
|
||||
Use Dokploy's file mounts:
|
||||
```yaml
|
||||
volumes:
|
||||
- ../files/va_subscription_18:/mnt/extra-addons/va_subscription_18 ✅
|
||||
- ../files/odoo.conf:/etc/odoo/odoo.conf ✅
|
||||
```
|
||||
|
||||
## Logs Not Loading When Deploying to a Remote Server?
|
||||
|
||||
There are a few potential reasons for this:
|
||||
@@ -204,6 +244,10 @@ volumes:
|
||||
- ../files/my-config.json:/etc/my-app/config
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
**Important for AutoDeploy users:** If you have configuration files or directories in your repository that you need to mount into your containers, you must copy their content to Dokploy's File Mounts (via Advanced → Mounts) instead of mounting them directly from the repository. This ensures the files persist across deployments, as the repository directory is cleaned and re-cloned on each AutoDeploy.
|
||||
</Callout>
|
||||
|
||||
|
||||
## Failed to initialize Docker Swarm
|
||||
|
||||
@@ -428,11 +472,11 @@ docker run -d \
|
||||
--restart always \
|
||||
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
|
||||
-v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-p 80:80/tcp \
|
||||
-p 443:443/tcp \
|
||||
-p 443:443/udp \
|
||||
traefik:v3.5.0
|
||||
traefik:v3.6.1
|
||||
|
||||
docker network connect dokploy-network dokploy-traefik
|
||||
|
||||
@@ -445,11 +489,11 @@ docker run -d \
|
||||
--restart always \
|
||||
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
|
||||
-v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-p 80:80/tcp \
|
||||
-p 443:443/tcp \
|
||||
-p 443:443/udp \
|
||||
traefik:v3.5.0
|
||||
traefik:v3.6.1
|
||||
```
|
||||
|
||||
Remove the dokploy service:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Main Landing Page of Dokploy
|
||||
|
||||
## Development
|
||||
|
||||
Run development server:
|
||||
|
||||
```bash
|
||||
@@ -14,9 +16,20 @@ yarn dev
|
||||
|
||||
Open http://localhost:3000 with your browser to see the result.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
For Blog Page, you can use the following command to generate the static pages:
|
||||
### Required for Contact Form
|
||||
```
|
||||
RESEND_API_KEY=your_resend_api_key_here
|
||||
```
|
||||
|
||||
### Required for HubSpot Integration (Sales Forms)
|
||||
```
|
||||
HUBSPOT_PORTAL_ID=147033433
|
||||
HUBSPOT_FORM_GUID=0d788925-ef54-4fda-9b76-741fb5877056
|
||||
```
|
||||
|
||||
### Required for Blog Page
|
||||
```
|
||||
GHOST_URL=""
|
||||
GHOST_KEY=""
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default function CatchAll() {
|
||||
notFound();
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { getPost } from "@/lib/ghost";
|
||||
import { generateOGImage } from "@/lib/og-image";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { locale: string } },
|
||||
) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const slug = searchParams.get("slug");
|
||||
|
||||
console.log(
|
||||
"Generating OG image for slug:",
|
||||
slug,
|
||||
"locale:",
|
||||
params.locale,
|
||||
);
|
||||
|
||||
if (!slug) {
|
||||
console.error("Missing slug parameter");
|
||||
return new Response("Missing slug parameter", { status: 400 });
|
||||
}
|
||||
|
||||
const post = await getPost(slug);
|
||||
|
||||
if (!post) {
|
||||
console.error("Post not found for slug:", slug);
|
||||
return new Response("Post not found", { status: 404 });
|
||||
}
|
||||
|
||||
console.log("Found post:", post.title);
|
||||
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(
|
||||
params.locale,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
},
|
||||
);
|
||||
|
||||
const ogImage = await generateOGImage({
|
||||
title: post.title,
|
||||
author: post.primary_author
|
||||
? {
|
||||
name: post.primary_author.name,
|
||||
image: post.primary_author.profile_image || undefined,
|
||||
}
|
||||
: undefined,
|
||||
date: formattedDate,
|
||||
readingTime: post.reading_time,
|
||||
});
|
||||
|
||||
console.log("Successfully generated OG image");
|
||||
|
||||
return new Response(ogImage, {
|
||||
headers: {
|
||||
"Content-Type": "image/png",
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error generating OG image:", error);
|
||||
return new Response(`Error generating image: ${error}`, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { Inter, Lexend } from "next/font/google";
|
||||
import "@/styles/tailwind.css";
|
||||
import "react-photo-view/dist/react-photo-view.css";
|
||||
import { Footer } from "@/components/Footer";
|
||||
import { Header } from "@/components/Header";
|
||||
import type { Metadata } from "next";
|
||||
import { setRequestLocale } from "next-intl/server";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://dokploy.com"),
|
||||
title: {
|
||||
default: "Dokploy - Effortless Deployment Solutions",
|
||||
template: "%s | Simplify Your DevOps",
|
||||
},
|
||||
icons: {
|
||||
icon: "icon.svg",
|
||||
apple: "apple-touch-icon.png",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://dokploy.com",
|
||||
languages: {
|
||||
en: "https://dokploy.com",
|
||||
},
|
||||
},
|
||||
description:
|
||||
"Streamline your deployment process with Dokploy. Effortlessly manage applications and databases on any VPS using Docker and Traefik for improved performance and security.",
|
||||
applicationName: "Dokploy",
|
||||
keywords: [
|
||||
"Dokploy",
|
||||
"Docker",
|
||||
"Traefik",
|
||||
"deployment",
|
||||
"VPS",
|
||||
"application management",
|
||||
"database management",
|
||||
"DevOps",
|
||||
"cloud infrastructure",
|
||||
"UI Self hosted",
|
||||
],
|
||||
referrer: "origin",
|
||||
robots: "index, follow",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
url: "https://dokploy.com",
|
||||
title: "Dokploy - Effortless Deployment Solutions",
|
||||
description:
|
||||
"Simplify your DevOps with Dokploy. Deploy applications and manage databases efficiently on any VPS.",
|
||||
siteName: "Dokploy",
|
||||
images: [
|
||||
{
|
||||
url: "https://dokploy.com/og.png",
|
||||
},
|
||||
{
|
||||
url: "https://dokploy.com/icon.svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
alt: "Dokploy Logo",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
site: "@Dokploy",
|
||||
creator: "@Dokploy",
|
||||
title: "Dokploy - Simplify Your DevOps",
|
||||
description:
|
||||
"Deploy applications and manage databases with ease using Dokploy. Learn how our platform can elevate your infrastructure management.",
|
||||
images: "https://dokploy.com/og.png",
|
||||
},
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: { locale: string };
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
145
apps/website/app/api/contact/route.ts
Normal file
145
apps/website/app/api/contact/route.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { Resend } from "resend";
|
||||
import { submitToHubSpot, getHubSpotUTK } from "@/lib/hubspot";
|
||||
|
||||
interface ContactFormData {
|
||||
inquiryType: "support" | "sales" | "other";
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
company: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Initialize Resend with API key check
|
||||
const apiKey = process.env.RESEND_API_KEY;
|
||||
if (!apiKey) {
|
||||
console.error("RESEND_API_KEY is not configured");
|
||||
return NextResponse.json(
|
||||
{ error: "Email service not configured" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
const resend = new Resend(apiKey);
|
||||
const body: ContactFormData = await request.json();
|
||||
|
||||
// Validate required fields
|
||||
if (
|
||||
!body.inquiryType ||
|
||||
!body.firstName ||
|
||||
!body.lastName ||
|
||||
!body.email ||
|
||||
!body.company ||
|
||||
!body.message
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{ error: "All fields are required" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(body.email)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid email format" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Submit to HubSpot if it's a sales inquiry
|
||||
if (body.inquiryType === "sales") {
|
||||
try {
|
||||
const hutk = getHubSpotUTK(request.headers.get("cookie") || undefined);
|
||||
const hubspotSuccess = await submitToHubSpot(body, hutk);
|
||||
|
||||
if (hubspotSuccess) {
|
||||
console.log("Successfully submitted sales inquiry to HubSpot");
|
||||
} else {
|
||||
console.warn(
|
||||
"Failed to submit sales inquiry to HubSpot, but continuing with email",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting to HubSpot:", error);
|
||||
// Continue with email even if HubSpot fails
|
||||
}
|
||||
}
|
||||
|
||||
// Format email content
|
||||
const emailSubject = `[${body.inquiryType.toUpperCase()}] New contact form submission from ${body.firstName} ${body.lastName}`;
|
||||
const emailBody = `
|
||||
New contact form submission:
|
||||
|
||||
Type: ${body.inquiryType}
|
||||
First Name: ${body.firstName}
|
||||
Last Name: ${body.lastName}
|
||||
Email: ${body.email}
|
||||
Company: ${body.company}
|
||||
|
||||
Message:
|
||||
${body.message}
|
||||
|
||||
---
|
||||
Sent from Dokploy website contact form
|
||||
`.trim();
|
||||
|
||||
// Send email to Dokploy team
|
||||
await resend.emails.send({
|
||||
from: "Dokploy Contact Form <noreply@emails.dokploy.com>",
|
||||
to:
|
||||
body.inquiryType === "sales"
|
||||
? ["sales@dokploy.com", "contact@dokploy.com"]
|
||||
: ["contact@dokploy.com"],
|
||||
subject: emailSubject,
|
||||
text: emailBody,
|
||||
replyTo: body.email,
|
||||
});
|
||||
|
||||
// Send confirmation email to the user
|
||||
const confirmationSubject =
|
||||
"Thank you for contacting Dokploy - We received your message";
|
||||
const confirmationBody = `
|
||||
Hello ${body.firstName} ${body.lastName},
|
||||
|
||||
Thank you for reaching out to us! We have successfully received your message and our team will get back to you as soon as possible.
|
||||
|
||||
Here's a summary of what you sent us:
|
||||
|
||||
Subject: ${body.inquiryType.charAt(0).toUpperCase() + body.inquiryType.slice(1)} inquiry
|
||||
Company: ${body.company}
|
||||
Message: ${body.message}
|
||||
|
||||
We typically respond within 24-48 hours during business days. If your inquiry is urgent, please don't hesitate to reach out to us directly.
|
||||
|
||||
Best regards,
|
||||
The Dokploy Team
|
||||
|
||||
---
|
||||
This is an automated confirmation email. Please do not reply to this email.
|
||||
If you need immediate assistance, contact us at contact@dokploy.com
|
||||
`.trim();
|
||||
|
||||
await resend.emails.send({
|
||||
from: "Dokploy Team <noreply@emails.dokploy.com>",
|
||||
to: [body.email],
|
||||
subject: confirmationSubject,
|
||||
text: confirmationBody,
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: "Contact form submitted successfully" },
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error processing contact form:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
77
apps/website/app/api/github-stars/route.ts
Normal file
77
apps/website/app/api/github-stars/route.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// Cache the result for 5 minutes to avoid rate limiting
|
||||
let cachedStars: { count: number; timestamp: number } | null = null;
|
||||
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const owner = searchParams.get("owner");
|
||||
const repo = searchParams.get("repo");
|
||||
|
||||
if (!owner || !repo) {
|
||||
return NextResponse.json(
|
||||
{ error: "Owner and repo parameters are required" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Check if we have a valid cached result
|
||||
if (
|
||||
cachedStars &&
|
||||
Date.now() - cachedStars.timestamp < CACHE_DURATION
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{ stargazers_count: cachedStars.count },
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=600",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.github.com/repos/${owner}/${repo}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "Dokploy-Website",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch repository data" },
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const starCount = data.stargazers_count;
|
||||
|
||||
// Cache the result
|
||||
cachedStars = {
|
||||
count: starCount,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
return NextResponse.json(
|
||||
{ stargazers_count: starCount },
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=600",
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching GitHub stars:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export async function GET(request: NextRequest) {
|
||||
return new Response("Post not found", { status: 404 });
|
||||
}
|
||||
|
||||
console.log("Found post:", post.title);
|
||||
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(
|
||||
"en-US",
|
||||
@@ -44,8 +43,6 @@ export async function GET(request: NextRequest) {
|
||||
readingTime: post.reading_time,
|
||||
});
|
||||
|
||||
console.log("Successfully generated OG image");
|
||||
|
||||
return new Response(ogImage, {
|
||||
headers: {
|
||||
"Content-Type": "image/png",
|
||||
|
||||
@@ -18,9 +18,7 @@ interface CodeBlockProps {
|
||||
async function formatCode(code: string, lang: string) {
|
||||
try {
|
||||
let parser: string;
|
||||
let plugins = [];
|
||||
|
||||
// Select parser and plugins based on language
|
||||
let plugins = [] as any[];
|
||||
switch (lang.toLowerCase()) {
|
||||
case "yaml":
|
||||
case "yml":
|
||||
@@ -35,12 +33,8 @@ async function formatCode(code: string, lang: string) {
|
||||
plugins = [babel, estree];
|
||||
break;
|
||||
default:
|
||||
// For unsupported languages, return the original code
|
||||
return code;
|
||||
}
|
||||
|
||||
console.log(`Formatting ${lang} with parser:`, parser);
|
||||
|
||||
const formatted = await prettier.format(code, {
|
||||
parser,
|
||||
plugins,
|
||||
@@ -50,12 +44,10 @@ async function formatCode(code: string, lang: string) {
|
||||
useTabs: false,
|
||||
printWidth: 120,
|
||||
});
|
||||
|
||||
console.log("Formatted code:", formatted);
|
||||
return formatted;
|
||||
} catch (error) {
|
||||
console.error("Error formatting code:", error);
|
||||
return code; // Return original code if there's an error
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,22 +58,15 @@ export function CodeBlock({ code, lang, initial }: CodeBlockProps) {
|
||||
useLayoutEffect(() => {
|
||||
async function formatAndHighlight() {
|
||||
try {
|
||||
console.log("Original code:", code);
|
||||
console.log("Language:", lang);
|
||||
const formatted = await formatCode(code, lang);
|
||||
setFormattedCode(formatted);
|
||||
|
||||
// Then highlight the formatted code
|
||||
const highlighted = await highlight(formatted, lang);
|
||||
setNodes(highlighted);
|
||||
} catch (error) {
|
||||
console.error("Error in formatAndHighlight:", error);
|
||||
// If formatting fails, try to highlight the original code
|
||||
const highlighted = await highlight(code, lang);
|
||||
setNodes(highlighted);
|
||||
}
|
||||
}
|
||||
|
||||
void formatAndHighlight();
|
||||
}, [code, lang]);
|
||||
|
||||
@@ -29,15 +29,10 @@ function LinkIcon() {
|
||||
|
||||
export function H1({ children, ...props }: HeadingProps) {
|
||||
const router = useRouter();
|
||||
const id = slugify(children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
const id = slugify(children?.toString() || "", { lower: true, strict: true });
|
||||
const handleClick = () => {
|
||||
router.push(`#${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<h1
|
||||
id={id}
|
||||
@@ -53,15 +48,10 @@ export function H1({ children, ...props }: HeadingProps) {
|
||||
|
||||
export function H2({ children, ...props }: HeadingProps) {
|
||||
const router = useRouter();
|
||||
const id = slugify(children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
const id = slugify(children?.toString() || "", { lower: true, strict: true });
|
||||
const handleClick = () => {
|
||||
router.push(`#${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<h2
|
||||
id={id}
|
||||
@@ -77,15 +67,10 @@ export function H2({ children, ...props }: HeadingProps) {
|
||||
|
||||
export function H3({ children, ...props }: HeadingProps) {
|
||||
const router = useRouter();
|
||||
const id = slugify(children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
const id = slugify(children?.toString() || "", { lower: true, strict: true });
|
||||
const handleClick = () => {
|
||||
router.push(`#${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<h3
|
||||
id={id}
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface Heading {
|
||||
id: string;
|
||||
text: string;
|
||||
@@ -19,7 +20,6 @@ export function TableOfContents() {
|
||||
text: element.textContent || "",
|
||||
level: Number(element.tagName.charAt(1)),
|
||||
}));
|
||||
|
||||
setHeadings(elements);
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
@@ -35,9 +35,7 @@ export function TableOfContents() {
|
||||
|
||||
for (const { id } of elements) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
observer.observe(element);
|
||||
}
|
||||
if (element) observer.observe(element);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
@@ -48,31 +46,25 @@ export function TableOfContents() {
|
||||
<p className="font-medium mb-4">Table of Contents</p>
|
||||
<ul className="space-y-2">
|
||||
{headings.length > 0 ? (
|
||||
<>
|
||||
{headings.map((heading) => (
|
||||
<li
|
||||
key={heading.id}
|
||||
style={{ paddingLeft: `${(heading.level - 1) * 1}rem` }}
|
||||
headings.map((heading) => (
|
||||
<li
|
||||
key={heading.id}
|
||||
style={{ paddingLeft: `${(heading.level - 1) * 1}rem` }}
|
||||
>
|
||||
<a
|
||||
href={`#${heading.id}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
document
|
||||
.getElementById(heading.id)
|
||||
?.scrollIntoView({ behavior: "smooth" });
|
||||
}}
|
||||
className={`hover:text-primary transition-colors block ${activeId === heading.id ? "text-primary font-medium" : "text-muted-foreground"}`}
|
||||
>
|
||||
<a
|
||||
href={`#${heading.id}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById(heading.id)?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
className={`hover:text-primary transition-colors block ${
|
||||
activeId === heading.id
|
||||
? "text-primary font-medium"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{heading.text}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
{heading.text}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li>
|
||||
<p className="text-muted-foreground">No headings found</p>
|
||||
@@ -3,6 +3,7 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PhotoProvider, PhotoView } from "react-photo-view";
|
||||
import "react-photo-view/dist/react-photo-view.css";
|
||||
|
||||
interface ZoomableImageProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
@@ -10,10 +10,5 @@ export async function highlight(code: string, lang: BundledLanguage) {
|
||||
lang,
|
||||
theme: "houston",
|
||||
});
|
||||
|
||||
return toJsxRuntime(out, {
|
||||
Fragment,
|
||||
jsx,
|
||||
jsxs,
|
||||
}) as JSX.Element;
|
||||
return toJsxRuntime(out, { Fragment, jsx, jsxs }) as JSX.Element;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getPost, getPosts } from "@/lib/ghost";
|
||||
import type { Metadata, ResolvingMetadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
@@ -18,15 +17,16 @@ import { CodeBlock } from "./components/CodeBlock";
|
||||
import { H1, H2, H3 } from "./components/Headings";
|
||||
import { TableOfContents } from "./components/TableOfContents";
|
||||
import { ZoomableImage } from "./components/ZoomableImage";
|
||||
|
||||
type Props = {
|
||||
params: { locale: string; slug: string };
|
||||
params: { slug: string };
|
||||
};
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: Props,
|
||||
parent: ResolvingMetadata,
|
||||
): Promise<Metadata> {
|
||||
const { locale, slug } = await params;
|
||||
const { slug } = await params;
|
||||
const post = await getPost(slug);
|
||||
|
||||
if (!post) {
|
||||
@@ -36,7 +36,7 @@ export async function generateMetadata(
|
||||
}
|
||||
|
||||
const ogUrl = new URL(
|
||||
`/${locale}/api/og`,
|
||||
`/api/og`,
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://dokploy.com"
|
||||
: "http://localhost:3000",
|
||||
@@ -69,51 +69,29 @@ export async function generateMetadata(
|
||||
};
|
||||
}
|
||||
|
||||
// export async function generateStaticParams() {
|
||||
// const posts = await getPosts();
|
||||
// const locales = ["en", "fr", "es", "zh-Hans"];
|
||||
|
||||
// return posts.flatMap((post) =>
|
||||
// locales.map((locale) => ({
|
||||
// locale,
|
||||
// slug: post.slug,
|
||||
// })),
|
||||
// );
|
||||
// }
|
||||
|
||||
export default async function BlogPostPage({ params }: Props) {
|
||||
const { slug } = await params;
|
||||
const t = await getTranslations("blog");
|
||||
const post = await getPost(slug);
|
||||
const allPosts = await getPosts();
|
||||
|
||||
// Get related posts (excluding current post)
|
||||
const relatedPosts = allPosts.filter((p) => p.id !== post?.id).slice(0, 3); // Show only 3 related posts
|
||||
const relatedPosts = allPosts.filter((p) => p.id !== post?.id).slice(0, 3);
|
||||
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Limpiar HTML antes de convertir a Markdown
|
||||
const cleanHtml = (html: string) => {
|
||||
// Crear un DOM temporal para limpiar el HTML
|
||||
if (typeof window !== "undefined") {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
|
||||
// Remover scripts JSON-LD y otros scripts
|
||||
const scripts = doc.querySelectorAll(
|
||||
'script[type="application/ld+json"], script',
|
||||
);
|
||||
scripts.forEach((script) => script.remove());
|
||||
|
||||
// Remover otros elementos no deseados
|
||||
const unwantedElements = doc.querySelectorAll("style, meta, link");
|
||||
unwantedElements.forEach((el) => el.remove());
|
||||
|
||||
return doc.body.innerHTML;
|
||||
} else {
|
||||
// Fallback para servidor - usar regex para limpiar
|
||||
return html
|
||||
.replace(
|
||||
/<script[^>]*type="application\/ld\+json"[^>]*>[\s\S]*?<\/script>/gi,
|
||||
@@ -126,7 +104,6 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
// Convertir HTML a Markdown
|
||||
const turndownService = new TurndownService({
|
||||
headingStyle: "atx",
|
||||
codeBlockStyle: "fenced",
|
||||
@@ -219,8 +196,6 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
children,
|
||||
inline,
|
||||
}: { className: string; children: React.ReactNode; inline: boolean }) => {
|
||||
console.log(className, children, inline);
|
||||
// Si es código inline (no tiene className con language-*), renderizar como span
|
||||
if (inline || !className || !/language-(\w+)/.test(className)) {
|
||||
return (
|
||||
<code className="px-1.5 py-0.5 bg-muted text-sm rounded font-mono text-foreground">
|
||||
@@ -228,8 +203,6 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
// Si es un bloque de código, usar CodeBlock
|
||||
const match = /language-(\w+)/.exec(className);
|
||||
return (
|
||||
<CodeBlock
|
||||
@@ -258,7 +231,7 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{t("backToBlog")}
|
||||
Back to Blog
|
||||
</Link>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_250px] gap-8">
|
||||
@@ -338,7 +311,7 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div className="mt-12 pt-6 border-t border-border">
|
||||
<h2 className="text-xl font-semibold mb-4">{t("tags")}</h2>
|
||||
<h2 className="text-xl font-semibold mb-4">Tags</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.tags.map((tag) => (
|
||||
<Link
|
||||
@@ -363,7 +336,7 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
|
||||
{relatedPosts.length > 0 && (
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold mb-6">{t("relatedPosts")}</h2>
|
||||
<h2 className="text-2xl font-bold mb-6">Related Posts</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{relatedPosts.map((relatedPost) => {
|
||||
const relatedPostDate = new Date(
|
||||
@@ -3,14 +3,14 @@
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface BlogPostCardProps {
|
||||
post: Post;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
export function BlogPostCard({ post, locale }: BlogPostCardProps) {
|
||||
export function BlogPostCard({ post }: BlogPostCardProps) {
|
||||
const router = useRouter();
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(locale, {
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString("en", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
@@ -2,10 +2,10 @@ import { getPosts, getTags } from "@/lib/ghost";
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import { RssIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Link from "next/link";
|
||||
import { BlogPostCard } from "./components/BlogPostCard";
|
||||
import { SearchAndFilter } from "./components/SearchAndFilter";
|
||||
|
||||
interface Tag {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -13,20 +13,16 @@ interface Tag {
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Blog | Dokploy",
|
||||
title: "Blog",
|
||||
description: "Latest news, updates, and articles from Dokploy",
|
||||
};
|
||||
|
||||
export default async function BlogPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: { locale: string };
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
const searchParams2 = await searchParams;
|
||||
const t = await getTranslations("blog");
|
||||
const posts = await getPosts();
|
||||
const tags = (await getTags()) as Tag[];
|
||||
const search =
|
||||
@@ -63,24 +59,24 @@ export default async function BlogPage({
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<SearchAndFilter
|
||||
tags={tags}
|
||||
initialSearch={search}
|
||||
initialTag={selectedTag}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
allTagsText={t("allTags")}
|
||||
/>
|
||||
<SearchAndFilter
|
||||
tags={tags}
|
||||
initialSearch={search}
|
||||
initialTag={selectedTag}
|
||||
searchPlaceholder="Search posts..."
|
||||
allTagsText="All Tags"
|
||||
/>
|
||||
|
||||
{filteredPosts.length === 0 ? (
|
||||
<div className="text-center py-12 min-h-[20vh] flex items-center justify-center">
|
||||
<p className="text-xl text-muted-foreground">
|
||||
{search || selectedTag ? t("noResults") : t("noPosts")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
{filteredPosts.length === 0 ? (
|
||||
<div className="text-center py-12 min-h-[20vh] flex items-center justify-center">
|
||||
<p className="text-xl text-muted-foreground">
|
||||
{search || selectedTag ? "No posts found matching your criteria" : "No posts available"}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{filteredPosts.map((post: Post) => (
|
||||
<BlogPostCard key={post.id} post={post} locale={locale} />
|
||||
<BlogPostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -1,43 +1,47 @@
|
||||
import { getPostsByTag, getTags } from "@/lib/ghost";
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
type Props = {
|
||||
params: { locale: string; tag: string };
|
||||
params: { tag: string };
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { tag } = await params;
|
||||
const t = await getTranslations("blog");
|
||||
const posts = await getPostsByTag(tag);
|
||||
|
||||
if (!posts || posts.length === 0) {
|
||||
return {
|
||||
title: "Tag Not Found",
|
||||
description: "The requested tag could not be found",
|
||||
};
|
||||
}
|
||||
|
||||
const tagName =
|
||||
posts[0].tags?.find((t: { slug: string }) => t.slug === tag)?.name || tag;
|
||||
|
||||
return {
|
||||
title: `${t("tagTitle", { tag })}`,
|
||||
description: t("tagDescription", { tag }),
|
||||
title: `${tagName} Posts`,
|
||||
description: `Browse all posts tagged with ${tagName}`,
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const tags = await getTags();
|
||||
|
||||
return tags.map((tag: { slug: string }) => ({
|
||||
tag: tag.slug,
|
||||
}));
|
||||
return tags.map((tag: { slug: string }) => ({ tag: tag.slug }));
|
||||
}
|
||||
|
||||
export default async function TagPage({ params }: Props) {
|
||||
const { tag } = await params;
|
||||
const t = await getTranslations("blog");
|
||||
const posts = await getPostsByTag(tag);
|
||||
|
||||
if (!posts || posts.length === 0) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Get the tag name from the first post
|
||||
const tagName =
|
||||
posts[0].tags?.find((t: { slug: string }) => t.slug === tag)?.name || tag;
|
||||
|
||||
@@ -59,30 +63,30 @@ export default async function TagPage({ params }: Props) {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{t("backToBlog")}
|
||||
Back to Blog
|
||||
</Link>
|
||||
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold mb-2">
|
||||
{t("postsTaggedWith")}{" "}
|
||||
Posts tagged with{" "}
|
||||
<span className="text-primary-600">"{tagName}"</span>
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{t("foundPosts", { count: posts.length })}
|
||||
{posts.length} {posts.length === 1 ? 'post' : 'posts'} found
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{posts.map((post: Post) => (
|
||||
<BlogPostCard key={post.id} post={post} locale={locale} />
|
||||
<BlogPostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BlogPostCard({ post, locale }: { post: Post; locale: string }) {
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(locale, {
|
||||
function BlogPostCard({ post }: { post: Post }) {
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString("en", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
File diff suppressed because one or more lines are too long
13
apps/website/app/contact/layout.tsx
Normal file
13
apps/website/app/contact/layout.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Metadata } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Contact Us",
|
||||
description:
|
||||
"Get in touch with our team. We're here to help with any questions about Dokploy.",
|
||||
};
|
||||
|
||||
export default function ContactLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
322
apps/website/app/contact/page.tsx
Normal file
322
apps/website/app/contact/page.tsx
Normal file
@@ -0,0 +1,322 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Container } from "@/components/Container";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { trackGAEvent } from "@/components/analitycs";
|
||||
import AnimatedGridPattern from "@/components/ui/animated-grid-pattern";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ContactFormData {
|
||||
inquiryType: "" | "support" | "sales" | "other";
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
company: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default function ContactPage() {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
const [formData, setFormData] = useState<ContactFormData>({
|
||||
inquiryType: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
company: "",
|
||||
message: "",
|
||||
});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
if (!formData.inquiryType) {
|
||||
newErrors.inquiryType = "Please select what we can help you with";
|
||||
}
|
||||
if (!formData.firstName.trim()) {
|
||||
newErrors.firstName = "First name is required";
|
||||
}
|
||||
if (!formData.lastName.trim()) {
|
||||
newErrors.lastName = "Last name is required";
|
||||
}
|
||||
if (!formData.email.trim()) {
|
||||
newErrors.email = "Email is required";
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
newErrors.email = "Please enter a valid email address";
|
||||
}
|
||||
if (!formData.company.trim()) {
|
||||
newErrors.company = "Company name is required";
|
||||
}
|
||||
if (!formData.message.trim()) {
|
||||
newErrors.message = "Message is required";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
trackGAEvent({
|
||||
action: "Contact Form Submitted",
|
||||
category: "Contact",
|
||||
label: formData.inquiryType,
|
||||
});
|
||||
|
||||
setFormData({
|
||||
inquiryType: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
company: "",
|
||||
message: "",
|
||||
});
|
||||
setErrors({});
|
||||
setIsSubmitted(true);
|
||||
} else {
|
||||
throw new Error("Failed to submit form");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting form:", error);
|
||||
alert("There was an error sending your message. Please try again.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (field: keyof ContactFormData, value: any) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
if (errors[field]) {
|
||||
setErrors((prev) => {
|
||||
const newErrors = { ...prev };
|
||||
delete newErrors[field];
|
||||
return newErrors;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isSubmitted) {
|
||||
return (
|
||||
<div className="bg-background py-24 sm:py-32">
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||
Thank you for contacting us!
|
||||
</h1>
|
||||
<p className="mt-6 text-lg leading-8 text-muted-foreground">
|
||||
We've received your message and will get back to you as soon as
|
||||
possible.
|
||||
</p>
|
||||
<div className="mt-10">
|
||||
<Button onClick={() => setIsSubmitted(false)} variant="outline">
|
||||
Send Another Message
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-background py-24 sm:py-32 relative">
|
||||
<AnimatedGridPattern
|
||||
numSquares={30}
|
||||
maxOpacity={0.1}
|
||||
height={40}
|
||||
width={40}
|
||||
duration={3}
|
||||
repeatDelay={1}
|
||||
className={cn(
|
||||
"[mask-image:radial-gradient(800px_circle_at_center,white,transparent)]",
|
||||
"absolute inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",
|
||||
)}
|
||||
/>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-3xl border border-border rounded-lg p-8 bg-black z-10 relative">
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||
Contact Us
|
||||
</h1>
|
||||
<p className="mt-6 text-lg leading-8 text-muted-foreground">
|
||||
Get in touch with our team. We're here to help with any questions
|
||||
about Dokploy.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="mt-16 space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="inquiryType"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
What can we help you with today?{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Select
|
||||
value={formData.inquiryType}
|
||||
onValueChange={(value) =>
|
||||
handleInputChange(
|
||||
"inquiryType",
|
||||
value as "support" | "sales" | "other",
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-input">
|
||||
<SelectValue placeholder="Select an option" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="support">Support</SelectItem>
|
||||
<SelectItem value="sales">Sales</SelectItem>
|
||||
<SelectItem value="other">Other</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.inquiryType && (
|
||||
<p className="text-sm text-red-600">{errors.inquiryType}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="firstName"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
First Name <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="firstName"
|
||||
type="text"
|
||||
value={formData.firstName}
|
||||
onChange={(e) =>
|
||||
handleInputChange("firstName", e.target.value)
|
||||
}
|
||||
placeholder="Your first name"
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<p className="text-sm text-red-600">{errors.firstName}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="lastName"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
Last Name <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="lastName"
|
||||
type="text"
|
||||
value={formData.lastName}
|
||||
onChange={(e) =>
|
||||
handleInputChange("lastName", e.target.value)
|
||||
}
|
||||
placeholder="Your last name"
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<p className="text-sm text-red-600">{errors.lastName}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
Email <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange("email", e.target.value)}
|
||||
placeholder="your.email@company.com"
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="text-sm text-red-600">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="company"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
Company Name <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="company"
|
||||
type="text"
|
||||
value={formData.company}
|
||||
onChange={(e) => handleInputChange("company", e.target.value)}
|
||||
placeholder="Your company name"
|
||||
/>
|
||||
{errors.company && (
|
||||
<p className="text-sm text-red-600">{errors.company}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="message"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
How can we help? <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
value={formData.message}
|
||||
onChange={(e) => handleInputChange("message", e.target.value)}
|
||||
placeholder="Tell us more about your inquiry..."
|
||||
rows={6}
|
||||
className="flex w-full rounded-md bg-input border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none"
|
||||
/>
|
||||
{errors.message && (
|
||||
<p className="text-sm text-red-600">{errors.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="min-w-[120px]"
|
||||
>
|
||||
{isSubmitting ? "Sending..." : "Send Message"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,36 +1,41 @@
|
||||
import clsx from "clsx";
|
||||
import type { Metadata } from "next";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages } from "next-intl/server";
|
||||
import { Inter, Lexend } from "next/font/google";
|
||||
import type { ReactNode } from "react";
|
||||
import { GoogleAnalytics } from "@next/third-parties/google";
|
||||
import "@/styles/tailwind.css";
|
||||
import "react-photo-view/dist/react-photo-view.css";
|
||||
import { Header } from "@/components/Header";
|
||||
import { Footer } from "@/components/Footer";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
// export const metadata: Metadata = {
|
||||
// metadataBase: new URL("https://dokploy.com"),
|
||||
// title: "Dokploy - Deploy your applications with ease",
|
||||
// description: "Deploy your applications with ease using Dokploy",
|
||||
// icons: {
|
||||
// icon: "icon.svg",
|
||||
// apple: "apple-touch-icon.png",
|
||||
// },
|
||||
// openGraph: {
|
||||
// title: "Dokploy - Deploy your applications with ease",
|
||||
// description: "Deploy your applications with ease using Dokploy",
|
||||
// images: "favicon.ico",
|
||||
// type: "website",
|
||||
// },
|
||||
// twitter: {
|
||||
// card: "summary_large_image",
|
||||
// title: "Dokploy - Deploy your applications with ease",
|
||||
// description: "Deploy your applications with ease using Dokploy",
|
||||
// images: ["/og.png"],
|
||||
// },
|
||||
// };
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://dokploy.com"),
|
||||
title: {
|
||||
default: "Dokploy - Deploy your applications with ease",
|
||||
template: "%s | Dokploy",
|
||||
},
|
||||
description: "Deploy your applications with ease using Dokploy",
|
||||
icons: {
|
||||
icon: "icon.svg",
|
||||
apple: "apple-touch-icon.png",
|
||||
},
|
||||
openGraph: {
|
||||
title: "Dokploy - Deploy your applications with ease",
|
||||
description: "Deploy your applications with ease using Dokploy",
|
||||
images: "/og.png",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Dokploy - Deploy your applications with ease",
|
||||
description: "Deploy your applications with ease using Dokploy",
|
||||
images: ["/og.png"],
|
||||
},
|
||||
};
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
@@ -44,29 +49,32 @@ const lexend = Lexend({
|
||||
});
|
||||
// Since we have a `not-found.tsx` page on the root, a layout file
|
||||
// is required, even if it's just passing children through.
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
params: { locale: string };
|
||||
}) {
|
||||
const { locale } = params;
|
||||
const messages = await getMessages();
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
lang="en"
|
||||
className={clsx(
|
||||
"h-full scroll-smooth antialiased",
|
||||
inter.variable,
|
||||
lexend.variable,
|
||||
)}
|
||||
>
|
||||
<head>
|
||||
<script
|
||||
type="text/javascript"
|
||||
id="hs-script-loader"
|
||||
async
|
||||
defer
|
||||
src="//js-eu1.hs-scripts.com/147033433.js"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<GoogleAnalytics gaId="G-0RTZ5EPB26" />
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<div className="flex h-full flex-col">
|
||||
<Header />
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
<Footer />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -7,11 +7,16 @@ import { Pricing } from "@/components/pricing";
|
||||
import { SecondaryFeaturesSections } from "@/components/secondary-features";
|
||||
import { Sponsors } from "@/components/sponsors";
|
||||
import { StatsSection } from "@/components/stats";
|
||||
import { setRequestLocale } from "next-intl/server";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export default async function Home({ params }: { params: { locale: string } }) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: "Dokploy - Deploy your applications with ease",
|
||||
},
|
||||
description: "Open-source self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases",
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<main>
|
||||
@@ -1,6 +1,14 @@
|
||||
export default function Home() {
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Privacy Policy",
|
||||
description:
|
||||
"Learn about how Dokploy collects, uses, and safeguards your personal information when you use our website and services.",
|
||||
};
|
||||
|
||||
export default function PrivacyPage() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto">
|
||||
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto py-12 px-4">
|
||||
<h1 className="text-3xl font-bold text-center mb-6">Privacy</h1>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
@@ -97,7 +105,7 @@ export default function Home() {
|
||||
please contact us at:
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
Email:
|
||||
Email:{" "}
|
||||
<a
|
||||
href="mailto:support@dokploy.com"
|
||||
className="text-blue-500 hover:underline"
|
||||
@@ -109,3 +117,4 @@ export default function Home() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
export default function Home() {
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Terms and Conditions",
|
||||
description:
|
||||
"Read the terms and conditions for using Dokploy's website and services.",
|
||||
};
|
||||
|
||||
export default function TermsPage() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto">
|
||||
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto py-12 px-4">
|
||||
<h1 className="text-3xl font-bold text-center mb-6">
|
||||
Terms and Conditions
|
||||
</h1>
|
||||
@@ -8,7 +16,7 @@ export default function Home() {
|
||||
<section className="flex flex-col gap-2">
|
||||
<p>
|
||||
Welcome to Dokploy! These Terms and Conditions outline the rules and
|
||||
regulations for the use of Dokploy’s website and services.
|
||||
regulations for the use of Dokploy's website and services.
|
||||
</p>
|
||||
<p>
|
||||
By accessing or using our services, you agree to be bound by the
|
||||
@@ -180,7 +188,7 @@ export default function Home() {
|
||||
These Terms & Conditions are governed by applicable laws based on the
|
||||
user's location. Any disputes arising under these terms will be
|
||||
resolved in accordance with the legal jurisdiction relevant to the
|
||||
user’s location, unless otherwise required by applicable law.
|
||||
user's location, unless otherwise required by applicable law.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -191,7 +199,7 @@ export default function Home() {
|
||||
reach us at:
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
Email:
|
||||
Email:{" "}
|
||||
<a
|
||||
href="mailto:support@dokploy.com"
|
||||
className="text-blue-500 hover:underline"
|
||||
@@ -203,3 +211,4 @@ export default function Home() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { Container } from "@/components/Container";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
export function CallToAction() {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<section
|
||||
id="get-started-today"
|
||||
@@ -32,22 +30,22 @@ export function CallToAction() {
|
||||
<Container className="relative z-30">
|
||||
<div className="mx-auto max-w-lg text-center">
|
||||
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl">
|
||||
{t("callToAction.title")}
|
||||
Unlock Your Deployment Potential with Dokploy Cloud
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
{t("callToAction.des")}
|
||||
Say goodbye to infrastructure hassles—Dokploy Cloud handles it all. Effortlessly deploy, manage Docker containers, and secure your traffic with Traefik. Focus on building, we'll handle the rest.
|
||||
</p>
|
||||
|
||||
<Button className="mt-10 rounded-full" asChild>
|
||||
<Link
|
||||
href={"https://app.dokploy.com/register"}
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row items-center gap-2"
|
||||
>
|
||||
{t("callToAction.button")}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button className="mt-10 rounded-full" asChild>
|
||||
<Link
|
||||
href={"https://app.dokploy.com/register"}
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row items-center gap-2"
|
||||
>
|
||||
Create an account
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
@@ -4,82 +4,80 @@ import {
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Container } from "./Container";
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: "faq.q1",
|
||||
answer: "faq.a1",
|
||||
question: "What is Dokploy?",
|
||||
answer: "Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as a free alternative self-hostable solution to platforms like Heroku, Vercel, and Netlify.",
|
||||
},
|
||||
{
|
||||
question: "faq.q11",
|
||||
answer: "faq.a11",
|
||||
question: "How does Dokploy's Open Source plan work?",
|
||||
answer: "You can host Dokploy UI on your own infrastructure and you will be responsible for the maintenance and updates.",
|
||||
},
|
||||
{
|
||||
question: "faq.q12",
|
||||
answer: "faq.a12",
|
||||
question: "Do I need to provide my own server for the managed plan?",
|
||||
answer: "Yes, in the managed plan, you provide your own server (e.g., Hetzner, Hostinger, AWS, etc.) VPS, and we manage the Dokploy UI infrastructure for you.",
|
||||
},
|
||||
{
|
||||
question: "faq.q13",
|
||||
answer: "faq.a13",
|
||||
question: "What happens if I need more than one server?",
|
||||
answer: "The first server costs $4.50/month, if you buy more than one it will be $3.50/month per server.",
|
||||
},
|
||||
{
|
||||
question: "faq.q14",
|
||||
answer: "faq.a14",
|
||||
question: "Is there a limit on the number of deployments?",
|
||||
answer: "No, there is no limit on the number of deployments in any of the plans.",
|
||||
},
|
||||
{
|
||||
question: "faq.q15",
|
||||
answer: "faq.a15",
|
||||
question: "What happens if I exceed my purchased server limit?",
|
||||
answer: "The most recently added servers will be deactivated. You won't be able to create services on inactive servers until they are reactivated.",
|
||||
},
|
||||
{
|
||||
question: "faq.q17",
|
||||
answer: "faq.a17",
|
||||
question: "What kind of support do you offer?",
|
||||
answer: "We offer community support for the open source version and priority support for paid plans (via Discord or Email at support@dokploy.com).",
|
||||
},
|
||||
{
|
||||
question: "faq.q18",
|
||||
answer: "faq.a18",
|
||||
question: "What's the catch on the Paid Plan?",
|
||||
answer: "Nothing, once you link your server (VPS) to your account, you can deploy unlimited applications, databases, and users, and you get unlimited updates, deployments, backups, and more.",
|
||||
},
|
||||
{
|
||||
question: "faq.q2",
|
||||
answer: "faq.a2",
|
||||
question: "Why Choose Dokploy?",
|
||||
answer: "Dokploy offers simplicity, flexibility, and speed in application deployment and management.",
|
||||
},
|
||||
{
|
||||
question: "faq.q4",
|
||||
answer: "faq.a4",
|
||||
question: "Is it open source?",
|
||||
answer: "Yes, Dokploy is open source and free to use.",
|
||||
},
|
||||
{
|
||||
question: "faq.q5",
|
||||
answer: "faq.a5",
|
||||
question: "What types of languages can I deploy with Dokploy?",
|
||||
answer: "Dokploy does not restrict programming languages. You are free to choose your preferred language and framework.",
|
||||
},
|
||||
{
|
||||
question: "faq.q6",
|
||||
answer: "faq.a6",
|
||||
question: "How do I request a feature or report a bug?",
|
||||
answer: "To request a feature or report a bug, please create an issue on our GitHub repository or ask in our Discord channel.",
|
||||
},
|
||||
{
|
||||
question: "faq.q7",
|
||||
answer: "faq.a7",
|
||||
question: "Do you track the usage of Dokploy?",
|
||||
answer: "No, we don't track any usage data.",
|
||||
},
|
||||
{
|
||||
question: "faq.q8",
|
||||
answer: "faq.a8",
|
||||
question: "Are there any user forums or communities where I can interact with other users?",
|
||||
answer: "Yes, we have active GitHub discussions and Discord where you can share ideas, ask for help, and connect with other users.",
|
||||
},
|
||||
{
|
||||
question: "faq.q16",
|
||||
answer: "faq.a16",
|
||||
question: "Do you offer a refunds?",
|
||||
answer: "We do not offer refunds. However, you can cancel your subscription at any time. Feel free to try our open-source version for free before making a purchase.",
|
||||
},
|
||||
{
|
||||
question: "faq.q9",
|
||||
answer: "faq.a9",
|
||||
question: "What types of applications can I deploy with Dokploy?",
|
||||
answer: "You can deploy any application that can be Dockerized, with no limits. Dokploy supports builds from Git repositories, Dockerfiles, Nixpacks, and Buildpacks like Heroku and Paketo.",
|
||||
},
|
||||
{
|
||||
question: "faq.q10",
|
||||
answer: "faq.a10",
|
||||
question: "How does Dokploy handle database management?",
|
||||
answer: "Dokploy supports multiple database systems including Postgres, MySQL, MariaDB, MongoDB, and Redis, providing tools for easy deployment and management and backups directly from the dashboard.",
|
||||
},
|
||||
];
|
||||
|
||||
export function Faqs() {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<section
|
||||
id="faqs"
|
||||
@@ -92,10 +90,10 @@ export function Faqs() {
|
||||
id="faq-title"
|
||||
className="font-display text-3xl tracking-tight text-primary sm:text-4xl text-center"
|
||||
>
|
||||
{t("faq.title")}
|
||||
Frequently asked questions
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground text-center">
|
||||
{t("faq.des")}
|
||||
If you can't find what you're looking for, please submit an issue through our GitHub repository or ask questions on our Discord.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -104,12 +102,12 @@ export function Faqs() {
|
||||
collapsible
|
||||
className="w-full max-w-3xl mx-auto"
|
||||
>
|
||||
{faqs.map((column, columnIndex) => (
|
||||
{faqs.map((faq, columnIndex) => (
|
||||
<AccordionItem value={`${columnIndex}`} key={columnIndex}>
|
||||
<AccordionTrigger className="text-left">
|
||||
{t(column.question)}
|
||||
{faq.question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>{t(column.answer)}</AccordionContent>
|
||||
<AccordionContent>{faq.answer}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "@/components/ui/select";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import type { SVGProps } from "react";
|
||||
import { Container } from "./Container";
|
||||
import { NavLink } from "./NavLink";
|
||||
@@ -33,11 +26,6 @@ const I18nIcon = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
);
|
||||
|
||||
export function Footer() {
|
||||
const router = useRouter();
|
||||
const locale = useLocale();
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
|
||||
return (
|
||||
<footer className="bg-black">
|
||||
<Container>
|
||||
@@ -51,10 +39,13 @@ export function Footer() {
|
||||
|
||||
<nav className="mt-10 text-sm" aria-label="quick links">
|
||||
<div className="-my-1 flex flex-wrap justify-center gap-6">
|
||||
<NavLink href="/#features">{t("navigation.features")}</NavLink>
|
||||
<NavLink href="/#faqs">{t("navigation.faqs")}</NavLink>
|
||||
<NavLink href={linkT("docs.intro")} target="_blank">
|
||||
{t("navigation.docs")}
|
||||
<NavLink href="/#features">Features</NavLink>
|
||||
<NavLink href="/#faqs">FAQ</NavLink>
|
||||
<NavLink
|
||||
href="https://docs.dokploy.com/docs/core"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -89,37 +80,9 @@ export function Footer() {
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
|
||||
</svg>
|
||||
</Link>
|
||||
<Select
|
||||
onValueChange={(locale) => {
|
||||
router.replace("/", {
|
||||
locale: locale as "en" | "zh-Hans" | "fr" | "es",
|
||||
});
|
||||
}}
|
||||
value={locale}
|
||||
>
|
||||
<SelectTrigger
|
||||
className={buttonVariants({
|
||||
variant: "outline",
|
||||
className:
|
||||
" flex items-center gap-2 !rounded-full visited:outline-none focus-within:outline-none focus:outline-none",
|
||||
})}
|
||||
>
|
||||
<I18nIcon width={20} height={20} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="en">{t("navigation.i18nEn")}</SelectItem>
|
||||
<SelectItem value="fr">{t("navigation.i18nFr")}</SelectItem>
|
||||
<SelectItem value="es">{t("navigation.i18nEs")}</SelectItem>
|
||||
<SelectItem value="zh-Hans">
|
||||
{t("navigation.i18nZh-Hans")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<p className="mt-6 text-sm text-muted-foreground sm:mt-0">
|
||||
{t("footer.copyright", {
|
||||
year: new Date().getFullYear(),
|
||||
})}
|
||||
{`Copyright © ${new Date().getFullYear()} Dokploy. All rights reserved.`}
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
186
apps/website/components/GithubStars.tsx
Normal file
186
apps/website/components/GithubStars.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type GithubStarsProps = {
|
||||
className?: string;
|
||||
repoUrl?: string;
|
||||
label?: string;
|
||||
count?: string;
|
||||
};
|
||||
|
||||
// Function to format star count (e.g., 26400 -> "26.4k")
|
||||
function formatStarCount(count: number): string {
|
||||
if (count >= 1000000) {
|
||||
return `${(count / 1000000).toFixed(1)}M`;
|
||||
}
|
||||
if (count >= 1000) {
|
||||
return `${(count / 1000).toFixed(1)}k`;
|
||||
}
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
// Extract owner and repo from GitHub URL
|
||||
function extractRepoInfo(url: string): { owner: string; repo: string } | null {
|
||||
try {
|
||||
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/);
|
||||
if (match) {
|
||||
return { owner: match[1], repo: match[2].replace(/\.git$/, "") };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error extracting repo info:", error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function GithubStars({
|
||||
className,
|
||||
repoUrl = "https://github.com/dokploy/dokploy",
|
||||
label = "GitHub Stars",
|
||||
count: defaultCount = "26.4k",
|
||||
}: GithubStarsProps) {
|
||||
const [starCount, setStarCount] = useState<string>(defaultCount);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStarCount = async () => {
|
||||
const repoInfo = extractRepoInfo(repoUrl);
|
||||
if (!repoInfo) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/github-stars?owner=${encodeURIComponent(repoInfo.owner)}&repo=${encodeURIComponent(repoInfo.repo)}`,
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const formattedCount = formatStarCount(data.stargazers_count);
|
||||
setStarCount(formattedCount);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching GitHub stars:", error);
|
||||
// Keep default count on error
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchStarCount();
|
||||
}, [repoUrl]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={repoUrl}
|
||||
target="_blank"
|
||||
aria-label={`${label}: ${starCount}`}
|
||||
className={cn(
|
||||
"group relative inline-flex items-center gap-2 rounded-full px-3 py-1",
|
||||
"shadow-[0_0_0_2px_#000_inset,0_2px_8px_rgba(0,0,0,0.35)]",
|
||||
"bg-gradient-to-b from-yellow-300 via-yellow-400 to-yellow-500",
|
||||
"text-black",
|
||||
"transition-transform hover:scale-[1.02] active:scale-[0.99]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{/* sparkling stars */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 overflow-visible"
|
||||
>
|
||||
{/* top-left star */}
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
className={cn(
|
||||
"absolute -top-1 -left-1 h-3 w-3 text-yellow-100",
|
||||
"drop-shadow-[0_0_6px_rgba(255,255,200,0.9)]",
|
||||
"animate-pulse [animation-duration:1.6s] [animation-delay:.2s]",
|
||||
)}
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 2l2.4 5.6L20 10l-5.6 2.4L12 18l-2.4-5.6L4 10l5.6-2.4L12 2z" />
|
||||
</svg>
|
||||
{/* top-right star */}
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
className={cn(
|
||||
"absolute -top-2 right-1 h-2.5 w-2.5 text-yellow-50",
|
||||
"drop-shadow-[0_0_6px_rgba(255,255,220,0.95)]",
|
||||
"animate-pulse [animation-duration:1.9s] [animation-delay:.7s]",
|
||||
)}
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 2l2.4 5.6L20 10l-5.6 2.4L12 18l-2.4-5.6L4 10l5.6-2.4L12 2z" />
|
||||
</svg>
|
||||
{/* bottom-right star */}
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
className={cn(
|
||||
"absolute -bottom-1 -right-1 h-3.5 w-3.5 text-yellow-200",
|
||||
"drop-shadow-[0_0_8px_rgba(255,255,180,0.85)]",
|
||||
"animate-pulse [animation-duration:2.2s] [animation-delay:1.1s]",
|
||||
)}
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 2l2.4 5.6L20 10l-5.6 2.4L12 18l-2.4-5.6L4 10l5.6-2.4L12 2z" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
{/* subtle shine */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 overflow-hidden rounded-full"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute -inset-x-10 -top-6 h-10 rotate-12",
|
||||
"bg-white/40 blur-md",
|
||||
"opacity-0 transition-opacity duration-500",
|
||||
"group-hover:opacity-40",
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
|
||||
{/* GitHub mark */}
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-6 w-6 items-center justify-center rounded-full",
|
||||
"bg-black text-white",
|
||||
"shadow-[inset_0_0_0_1px_rgba(255,255,255,0.15)]",
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className="h-3.5 w-3.5"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12a12 12 0 0 0 8.21 11.43c.6.11.82-.26.82-.58 0-.29-.01-1.05-.02-2.07-3.34.73-4.04-1.61-4.04-1.61-.55-1.4-1.35-1.77-1.35-1.77-1.1-.75.08-.74.08-.74 1.22.09 1.87 1.25 1.87 1.25 1.08 1.85 2.83 1.32 3.52 1.01.11-.78.42-1.32.76-1.62-2.66-.3-5.46-1.33-5.46-5.9 0-1.3.47-2.36 1.24-3.2-.13-.31-.54-1.56.12-3.24 0 0 1.01-.32 3.3 1.22.96-.27 1.98-.4 3-.4s2.04.13 3 .4c2.29-1.54 3.3-1.22 3.3-1.22.66 1.68.25 2.93.12 3.24.77.84 1.24 1.9 1.24 3.2 0 4.58-2.8 5.6-5.47 5.9.43.37.81 1.1.81 2.22 0 1.6-.02 2.89-.02 3.29 0 .32.22.69.83.57A12 12 0 0 0 24 12C24 5.37 18.63 0 12 0Z" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
{/* copy */}
|
||||
<span className="flex items-baseline gap-1 pr-0.5">
|
||||
<span className="text-xs font-semibold">Stars</span>
|
||||
<span className="text-sm font-extrabold tracking-tight">
|
||||
{isLoading ? "..." : starCount}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{/* subtle ring on hover */}
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 rounded-full",
|
||||
"ring-1 ring-black/10 group-hover:ring-black/20",
|
||||
)}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default GithubStars;
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Link } from "@/i18n/routing";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { ChevronRight, HeartIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Fragment, type JSX, type SVGProps } from "react";
|
||||
import { Container } from "./Container";
|
||||
import { NavLink } from "./NavLink";
|
||||
@@ -12,6 +11,7 @@ import { trackGAEvent } from "./analitycs";
|
||||
import { Logo } from "./shared/Logo";
|
||||
import AnimatedGradientText from "./ui/animated-gradient-text";
|
||||
import { Button, buttonVariants } from "./ui/button";
|
||||
import GithubStars from "./GithubStars";
|
||||
|
||||
function MobileNavLink({
|
||||
href,
|
||||
@@ -84,8 +84,6 @@ const I18nIcon = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
);
|
||||
|
||||
function MobileNavigation() {
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
return (
|
||||
<Popover>
|
||||
<Popover.Button
|
||||
@@ -120,15 +118,20 @@ function MobileNavigation() {
|
||||
as="div"
|
||||
className="absolute inset-x-0 top-full mt-4 flex origin-top flex-col rounded-2xl border border-border bg-background p-4 text-lg tracking-tight text-primary shadow-xl ring-1 ring-border/5"
|
||||
>
|
||||
<MobileNavLink href="/#pricing">
|
||||
{t("navigation.pricing")}
|
||||
<MobileNavLink href="/#pricing">Pricing</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">FAQ</MobileNavLink>
|
||||
<MobileNavLink
|
||||
href="https://docs.dokploy.com/docs/core"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">{t("navigation.faqs")}</MobileNavLink>
|
||||
<MobileNavLink href={linkT("docs.intro")} target="_blank">
|
||||
{t("navigation.docs")}
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/blog">{t("navigation.blog")}</MobileNavLink>
|
||||
<MobileNavLink href={linkT("docs.intro")} target="_blank">
|
||||
<MobileNavLink href="/blog">Blog</MobileNavLink>
|
||||
<MobileNavLink href="/contact">Contact</MobileNavLink>
|
||||
<MobileNavLink
|
||||
href="https://docs.dokploy.com/docs/core"
|
||||
target="_blank"
|
||||
>
|
||||
<Button className=" w-full" asChild>
|
||||
<Link
|
||||
href="https://app.dokploy.com/register"
|
||||
@@ -136,7 +139,7 @@ function MobileNavigation() {
|
||||
target="_blank"
|
||||
>
|
||||
<div className="group flex-row relative mx-auto flex max-w-fit items-center justify-center rounded-2xl text-sm font-medium w-full">
|
||||
<span>{t("navigation.dashboard")}</span>
|
||||
<span>Sign In</span>
|
||||
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||
</div>
|
||||
</Link>
|
||||
@@ -150,11 +153,8 @@ function MobileNavigation() {
|
||||
}
|
||||
|
||||
export function Header() {
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
|
||||
return (
|
||||
<header className="bg-background py-10">
|
||||
<header className="sticky top-0 z-50 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b border-border/40 py-5">
|
||||
<Container>
|
||||
<nav className="relative z-50 flex justify-between">
|
||||
<div className="flex items-center md:gap-x-12">
|
||||
@@ -162,15 +162,20 @@ export function Header() {
|
||||
<Logo className="h-10 w-auto" />
|
||||
</Link>
|
||||
<div className="hidden md:flex md:gap-x-6">
|
||||
<NavLink href="/#pricing">{t("navigation.pricing")}</NavLink>
|
||||
<NavLink href="/#faqs">{t("navigation.faqs")}</NavLink>
|
||||
<NavLink href={linkT("docs.intro")} target="_blank">
|
||||
{t("navigation.docs")}
|
||||
<NavLink href="/#pricing">Pricing</NavLink>
|
||||
<NavLink href="/#faqs">FAQ</NavLink>
|
||||
<NavLink
|
||||
href="https://docs.dokploy.com/docs/core"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
</NavLink>
|
||||
<NavLink href="/blog">{t("navigation.blog")}</NavLink>
|
||||
<NavLink href="/blog">Blog</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 md:gap-x-5">
|
||||
<GithubStars className="max-md:hidden" />
|
||||
|
||||
<Link href="https://x.com/getdokploy" target="_blank">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
@@ -184,6 +189,25 @@ export function Header() {
|
||||
</svg>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="rounded-full max-md:hidden"
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
href="/contact"
|
||||
onClick={() => {
|
||||
trackGAEvent({
|
||||
action: "Contact Button Clicked",
|
||||
category: "Contact",
|
||||
label: "Header",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Contact
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
{/* <Link
|
||||
className={buttonVariants({
|
||||
variant: "outline",
|
||||
@@ -205,7 +229,7 @@ export function Header() {
|
||||
target="_blank"
|
||||
>
|
||||
<div className="group flex-row relative mx-auto flex max-w-fit items-center justify-center rounded-2xl text-sm font-medium w-full">
|
||||
<span>{t("navigation.dashboard")}</span>
|
||||
<span>Sign In</span>
|
||||
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { Check, ChevronRight, Copy } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import AnimatedGradientText from "./ui/animated-gradient-text";
|
||||
@@ -43,7 +42,6 @@ import HeroVideoDialog from "./ui/hero-video-dialog";
|
||||
// };
|
||||
|
||||
export function Hero() {
|
||||
const t = useTranslations("HomePage");
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -57,7 +55,7 @@ export function Hero() {
|
||||
<div className=" bottom-0 flex w-full items-center justify-center overflow-hidden rounded-lg bg-background md:shadow-xl">
|
||||
<div className="relative px-4">
|
||||
<div className="text-center">
|
||||
<motion.a
|
||||
{/* <motion.a
|
||||
href="#pricing"
|
||||
className="relative z-10 mb-4 inline-block"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -72,41 +70,41 @@ export function Hero() {
|
||||
"inline animate-gradient bg-gradient-to-r from-[#ffaa40] via-[#9c40ff] to-[#ffaa40] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent",
|
||||
)}
|
||||
>
|
||||
{t("hero.cloud")}
|
||||
Introducing Dokploy Cloud
|
||||
</span>
|
||||
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||
</AnimatedGradientText>
|
||||
</div>
|
||||
</motion.a>
|
||||
</motion.a> */}
|
||||
|
||||
<motion.h1
|
||||
className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
<motion.h1
|
||||
className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Simplify{" "}
|
||||
<span className="relative whitespace-nowrap text-primary">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 418 42"
|
||||
className="absolute left-0 top-2/3 h-[0.58em] w-full fill-primary"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
{t("hero.deploy")}{" "}
|
||||
<span className="relative whitespace-nowrap text-primary">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 418 42"
|
||||
className="absolute left-0 top-2/3 h-[0.58em] w-full fill-primary"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
|
||||
</svg>
|
||||
<span className="relative"> {t("hero.anywhere")}</span>
|
||||
</span>{" "}
|
||||
{t("hero.with")}
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-muted-foreground"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, delay: 0.2 }}
|
||||
>
|
||||
{t("hero.des")}
|
||||
</motion.p>
|
||||
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
|
||||
</svg>
|
||||
<span className="relative">Application and Database</span>
|
||||
</span>{" "}
|
||||
Deployments
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-muted-foreground"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, delay: 0.2 }}
|
||||
>
|
||||
Manage containerized deployments across multiple servers with ease thanks to our all-in-one platform for developers.
|
||||
</motion.p>
|
||||
<motion.div
|
||||
className="flex flex-col items-center justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -168,7 +166,7 @@ export function Hero() {
|
||||
>
|
||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||
</svg>
|
||||
{t("navigation.discord")}
|
||||
Discord
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Link } from "@/i18n/routing";
|
||||
import Link from "next/link";
|
||||
import { trackGAEvent } from "./analitycs";
|
||||
|
||||
export function NavLink({
|
||||
|
||||
@@ -4,10 +4,9 @@ import { cn } from "@/lib/utils";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Layers, Terminal, Users } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Container } from "./Container";
|
||||
interface Feature {
|
||||
name: React.ReactNode;
|
||||
name: string;
|
||||
summary: string;
|
||||
description: string;
|
||||
image: string;
|
||||
@@ -16,9 +15,9 @@ interface Feature {
|
||||
|
||||
const features: Array<Feature> = [
|
||||
{
|
||||
name: "secondaryFeatures.templates",
|
||||
summary: "secondaryFeatures.templatesSummary",
|
||||
description: "secondaryFeatures.templatesDes",
|
||||
name: "Open Source Templates",
|
||||
summary: "One click to deploy open source templates.",
|
||||
description: "Deploy open source templates with one click, powered by Docker Compose, (Plausible, Calcom, Pocketbase, etc.)",
|
||||
image: "/secondary/templates.png",
|
||||
icon: function ReportingIcon() {
|
||||
return (
|
||||
@@ -29,9 +28,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secondaryFeatures.traefik",
|
||||
summary: "secondaryFeatures.traefikSummary",
|
||||
description: "secondaryFeatures.traefikDes",
|
||||
name: "Real-Time Traefik Configuration",
|
||||
summary: "Modify Traefik settings on-the-fly via a graphical interface or API.",
|
||||
description: "Users can adjust Traefik's configuration, including middleware, forwarding rules, and SSL certificates through an intuitive interface or API. This feature enables seamless traffic routing and security adjustments without the need to restart services",
|
||||
image: "/secondary/traefik.png",
|
||||
icon: function ReportingIcon() {
|
||||
return (
|
||||
@@ -215,9 +214,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secondaryFeatures.users",
|
||||
summary: "secondaryFeatures.usersSummary",
|
||||
description: "secondaryFeatures.usersDes",
|
||||
name: "User Permission Management",
|
||||
summary: "Detailed control over user permissions for accessing and managing projects and services.",
|
||||
description: "Allows administrators to define specific roles and permissions for each user, including the ability to create, modify, or delete applications and databases. This feature ensures secure and efficient management of large and diverse teams.",
|
||||
image: "/secondary/users.png",
|
||||
icon: function InventoryIcon() {
|
||||
return (
|
||||
@@ -228,9 +227,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secondaryFeatures.terminal",
|
||||
summary: "secondaryFeatures.terminalSummary",
|
||||
description: "secondaryFeatures.terminalDes",
|
||||
name: "Terminal Access",
|
||||
summary: "Direct access to each container's and server terminal for advanced management.",
|
||||
description: "Provides an interface to access the command line of any active container, allowing developers to execute commands, manage services, and troubleshoot directly from the dashboard",
|
||||
image: "/secondary/terminal.png",
|
||||
icon: function ContactsIcon() {
|
||||
return (
|
||||
@@ -251,7 +250,6 @@ function Feature({
|
||||
feature: Feature;
|
||||
isActive: boolean;
|
||||
}) {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -289,10 +287,10 @@ function Feature({
|
||||
{feature.name}
|
||||
</h3>
|
||||
<p className="mt-2 font-display text-xl text-foreground">
|
||||
{t(feature.summary)}
|
||||
{feature.summary}
|
||||
</p>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
{t(feature.description)}
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -322,7 +320,6 @@ function FeaturesMobile() {
|
||||
}
|
||||
|
||||
function FeaturesDesktop() {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<Tab.Group as="div" className="hidden lg:mt-20 lg:block">
|
||||
{({ selectedIndex }) => (
|
||||
@@ -336,7 +333,7 @@ function FeaturesDesktop() {
|
||||
name: (
|
||||
<Tab className="ui-not-focus-visible:outline-none">
|
||||
<span className="absolute inset-0" />
|
||||
{t(feature.name)}
|
||||
{feature.name}
|
||||
</Tab>
|
||||
),
|
||||
}}
|
||||
@@ -380,7 +377,6 @@ function FeaturesDesktop() {
|
||||
}
|
||||
|
||||
export function SecondaryFeatures() {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<section
|
||||
id="secondary-features"
|
||||
@@ -390,10 +386,10 @@ export function SecondaryFeatures() {
|
||||
<Container className="max-w-[95rem]">
|
||||
<div className="mx-auto max-w-2xl md:text-center">
|
||||
<h2 className="font-display text-3xl tracking-tight text-primary sm:text-4xl">
|
||||
{t("secondaryFeatures.title")}
|
||||
Advanced Management Tools
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
{t("secondaryFeatures.des")}
|
||||
Elevate your infrastructure with tools that offer precise control, detailed monitoring, and enhanced security, ensuring seamless management and robust performance.
|
||||
</p>
|
||||
</div>
|
||||
<FeaturesMobile />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Link } from "@/i18n/routing";
|
||||
import Link from "next/link";
|
||||
|
||||
export function SlimLayout() {
|
||||
return (
|
||||
|
||||
@@ -4,11 +4,10 @@ import Link from "next/link";
|
||||
|
||||
interface BlogCardProps {
|
||||
post: Post;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
export function BlogCard({ post, locale }: BlogCardProps) {
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(locale, {
|
||||
export function BlogCard({ post }: BlogCardProps) {
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString("en", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
@@ -37,7 +36,7 @@ export function BlogCard({ post, locale }: BlogCardProps) {
|
||||
{post.primary_tag.name}
|
||||
</p>
|
||||
)}
|
||||
<Link href={`/${locale}/blog/${post.slug}`} className="mt-2 block">
|
||||
<Link href={`/blog/${post.slug}`} className="mt-2 block">
|
||||
<h3 className="text-xl font-semibold text-gray-900">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
@@ -60,7 +60,7 @@ export function FirstFeaturesSection() {
|
||||
{
|
||||
title: "Open Source Templates",
|
||||
description:
|
||||
"Get started quickly with pre-configured templates for popular tools like Supabase, Cal.com, and Pocketbase.",
|
||||
"Get started quickly with pre-configured templates for popular tools like Supabase, Cal.com, and PocketBase.",
|
||||
icon: <IconTemplate />,
|
||||
},
|
||||
{
|
||||
@@ -76,7 +76,7 @@ export function FirstFeaturesSection() {
|
||||
icon: <IconActivity />,
|
||||
},
|
||||
{
|
||||
title: "Built for developers",
|
||||
title: "Built for Developers",
|
||||
description:
|
||||
"Designed specifically for engineers and developers seeking control and flexibility.",
|
||||
icon: <IconTerminal2 />,
|
||||
@@ -91,7 +91,7 @@ export function FirstFeaturesSection() {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center mt-20 px-4">
|
||||
<h2 className="font-display text-3xl tracking-tight text-primary sm:text-4xl text-center">
|
||||
Powerful Deployment, Tailored for You
|
||||
Powerful Deployment Tailored to You
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground text-center">
|
||||
Unlock seamless multi-server deployments, advanced user control, and
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
X,
|
||||
XCircleIcon,
|
||||
} from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
@@ -85,7 +84,6 @@ export const calculatePrice = (count: number, isAnnual = false) => {
|
||||
|
||||
export function Pricing() {
|
||||
const router = useRouter();
|
||||
const t = useTranslations("Pricing");
|
||||
const [isAnnual, setIsAnnual] = useState(false);
|
||||
const [serverQuantity, setServerQuantity] = useState(1);
|
||||
const featured = true;
|
||||
@@ -119,12 +117,12 @@ export function Pricing() {
|
||||
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl">
|
||||
<span className="relative whitespace-nowrap">
|
||||
<SwirlyDoodle className="absolute left-0 top-1/2 h-[1em] w-full fill-muted-foreground" />
|
||||
<span className="relative">{t("swirlyDoodleTitle")}</span>
|
||||
<span className="relative">Simple Affordable</span>
|
||||
</span>{" "}
|
||||
{t("restTitle")}
|
||||
Pricing.
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-muted-foreground">
|
||||
{t("description")}
|
||||
Deploy Smarter and Scale Faster, Without Breaking the Bank
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -137,10 +135,10 @@ export function Pricing() {
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="monthly">
|
||||
{t("billingCycle.monthly")}
|
||||
Monthly
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="annual">
|
||||
{t("billingCycle.annual")}
|
||||
Annual
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
@@ -153,69 +151,70 @@ export function Pricing() {
|
||||
: "lg:py-8",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<p className=" text-2xl font-semibold tracking-tight text-primary ">
|
||||
{t("plan.free.title")}
|
||||
</p>
|
||||
|
|
||||
<p className=" text-base font-semibold tracking-tight text-muted-foreground">
|
||||
{t("plan.free.subTitle")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 className="mt-5 text-lg font-medium text-white">
|
||||
{t("plan.free.section.title")}
|
||||
</h3>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-sm",
|
||||
featured ? "text-white" : "text-slate-400",
|
||||
)}
|
||||
>
|
||||
{t("plan.free.section.description")}
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<p className=" text-2xl font-semibold tracking-tight text-primary ">
|
||||
Free
|
||||
</p>
|
||||
|
|
||||
<p className=" text-base font-semibold tracking-tight text-muted-foreground">
|
||||
Open Source
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
role="list"
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
)}
|
||||
>
|
||||
{[
|
||||
t("plan.free.features.f1"),
|
||||
t("plan.free.features.f2"),
|
||||
t("plan.free.features.f3"),
|
||||
t("plan.free.features.f4"),
|
||||
t("plan.free.features.f5"),
|
||||
].map((feature) => (
|
||||
<li key={feature} className="flex text-muted-foreground">
|
||||
<CheckIcon />
|
||||
<span className="ml-2">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
<li className="flex text-muted-foreground">
|
||||
<XCircleIcon className="size-5 self-center text-destructive" />
|
||||
<span className="ml-3 text-destructive">
|
||||
Remote Servers Monitoring
|
||||
</span>
|
||||
<h3 className="mt-5 text-lg font-medium text-white">
|
||||
Dokploy Open Source
|
||||
</h3>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-sm",
|
||||
featured ? "text-white" : "text-slate-400",
|
||||
)}
|
||||
>
|
||||
Install and manage Dokploy UI on your own server.
|
||||
</p>
|
||||
|
||||
<ul
|
||||
role="list"
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
)}
|
||||
>
|
||||
{[
|
||||
"Complete Flexibility: Install Dokploy UI on your own infrastructure",
|
||||
"Self-hosted Infrastructure",
|
||||
"Community Support",
|
||||
"Access to Core Features",
|
||||
"Access to All Updates",
|
||||
"Unlimited Servers",
|
||||
].map((feature) => (
|
||||
<li key={feature} className="flex text-muted-foreground">
|
||||
<CheckIcon />
|
||||
<span className="ml-2">{feature}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("plan.free.features.f9")}
|
||||
</span>
|
||||
<Link
|
||||
href="https://docs.dokploy.com/docs/core/installation#docker"
|
||||
target="_blank"
|
||||
className="flex items-start text-sm text-primary"
|
||||
>
|
||||
{t("plan.free.go")}{" "}
|
||||
<ArrowRight className="ml-2 size-4 self-center" />
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
<li className="flex text-muted-foreground">
|
||||
<XCircleIcon className="size-5 self-center text-destructive" />
|
||||
<span className="ml-3 text-destructive">
|
||||
Remote Servers Monitoring
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Unlimited Servers
|
||||
</span>
|
||||
<Link
|
||||
href="https://docs.dokploy.com/docs/core/installation#docker"
|
||||
target="_blank"
|
||||
className="flex items-start text-sm text-primary"
|
||||
>
|
||||
Start deploying{" "}
|
||||
<ArrowRight className="ml-2 size-4 self-center" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
className={clsx(
|
||||
@@ -224,12 +223,12 @@ export function Pricing() {
|
||||
? "order-first border bg-black py-8 lg:order-none"
|
||||
: "lg:py-8",
|
||||
)}
|
||||
>
|
||||
{isAnnual && (
|
||||
<div className="mb-4 flex flex-row items-center gap-2">
|
||||
<Badge>{t("plan.cloud.title")} 🚀</Badge>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{isAnnual && (
|
||||
<div className="mb-4 flex flex-row items-center gap-2">
|
||||
<Badge>Recommended 🚀</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAnnual ? (
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
@@ -251,50 +250,48 @@ export function Pricing() {
|
||||
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
|
||||
</p>
|
||||
)}
|
||||
<h3 className="mt-5 text-lg font-medium text-white">
|
||||
{t("plan.cloud.section.title")}
|
||||
</h3>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-sm",
|
||||
featured ? "text-white" : "text-slate-400",
|
||||
)}
|
||||
>
|
||||
{t("plan.cloud.section.description")}
|
||||
</p>
|
||||
<h3 className="mt-5 text-lg font-medium text-white">
|
||||
Dokploy Plan
|
||||
</h3>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-sm",
|
||||
featured ? "text-white" : "text-slate-400",
|
||||
)}
|
||||
>
|
||||
We manage the Dokploy UI infrastructure, we take care of it for you.
|
||||
</p>
|
||||
|
||||
<ul
|
||||
role="list"
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
)}
|
||||
>
|
||||
{[
|
||||
t("plan.cloud.features.f1"),
|
||||
t("plan.cloud.features.f2"),
|
||||
t("plan.cloud.features.f3"),
|
||||
t("plan.cloud.features.f4"),
|
||||
t("plan.cloud.features.f5"),
|
||||
t("plan.cloud.features.f6"),
|
||||
t("plan.cloud.features.f7"),
|
||||
].map((feature, index) => (
|
||||
<li
|
||||
key={`${feature}-${index}`}
|
||||
className="flex text-muted-foreground"
|
||||
>
|
||||
<CheckIcon />
|
||||
<span className="ml-2">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("plan.cloud.servers", {
|
||||
serverQuantity,
|
||||
})}
|
||||
</span>
|
||||
<ul
|
||||
role="list"
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
)}
|
||||
>
|
||||
{[
|
||||
"Managed Hosting: No need to manage your own servers",
|
||||
"Unlimited Deployments",
|
||||
"Unlimited Databases",
|
||||
"Unlimited Applications",
|
||||
"Unlimited Users",
|
||||
"Remote Servers Monitoring",
|
||||
"Priority Support",
|
||||
].map((feature, index) => (
|
||||
<li
|
||||
key={`${feature}-${index}`}
|
||||
className="flex text-muted-foreground"
|
||||
>
|
||||
<CheckIcon />
|
||||
<span className="ml-2">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
No. of {serverQuantity} Servers (You bring the servers)
|
||||
</span>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openVideo}>
|
||||
<TooltipTrigger onClick={() => setOpenVideo(true)}>
|
||||
@@ -308,7 +305,7 @@ export function Pricing() {
|
||||
/>
|
||||
</div>
|
||||
<p className="text-primary mb-2 text-left">
|
||||
We Recommend to watch the video to understand the
|
||||
We recommend you to watch the video to understand the
|
||||
benefits of Dokploy Cloud
|
||||
</p>
|
||||
|
||||
@@ -366,7 +363,7 @@ export function Pricing() {
|
||||
className: "w-full",
|
||||
})}
|
||||
>
|
||||
{t("plan.cloud.go")}
|
||||
Subscribe
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,53 +5,51 @@ import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "primaryFeatures.applications",
|
||||
description: "primaryFeatures.applicationsDes",
|
||||
title: "Applications & Databases",
|
||||
description: "Centralize control of your applications and databases for enhanced security and efficiency, simplifying access and management across your infrastructure.",
|
||||
image: "/dashboard.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.compose",
|
||||
description: "primaryFeatures.composeDes",
|
||||
title: "Docker Compose",
|
||||
description: "Native Docker Compose support so you can manage complex applications and services with ease.",
|
||||
image: "/compose.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.multiserver",
|
||||
description: "primaryFeatures.multiserverDes",
|
||||
title: "Multiserver",
|
||||
description: "Deploy applications to multiple servers without the extra effort.",
|
||||
image: "/remote.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.logs",
|
||||
description: "primaryFeatures.logsDes",
|
||||
title: "Logs",
|
||||
description: "Monitor and manage your applications' logs with ease, ensuring efficient troubleshooting and optimal performance.",
|
||||
image: "/logs.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.monitoring",
|
||||
description: "primaryFeatures.monitoringDes",
|
||||
title: "Monitoring",
|
||||
description: "Monitor your systems' performance and health in real time, ensuring continuous and uninterrupted operation.",
|
||||
image: "/primary/monitoring.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.backups",
|
||||
description: "primaryFeatures.backupsDes",
|
||||
title: "Backups",
|
||||
description: "Implement automatic and secure backup solutions to protect your critical data and restore it quickly when necessary.",
|
||||
image: "/backups.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.traefik",
|
||||
description: "primaryFeatures.traefikDes",
|
||||
title: "Traefik",
|
||||
description: "Manage Traefik via File Editor to configure your own domain names, certificates, and more.",
|
||||
image: "/traefik.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.templates",
|
||||
description: "primaryFeatures.templatesDes",
|
||||
title: "Templates",
|
||||
description: "Deploy open source templates with one click.",
|
||||
image: "/templates.png",
|
||||
},
|
||||
];
|
||||
|
||||
export function SecondaryFeaturesSections() {
|
||||
const t = useTranslations("HomePage");
|
||||
const [tabOrientation, setTabOrientation] = useState<
|
||||
"horizontal" | "vertical"
|
||||
>("horizontal");
|
||||
@@ -84,23 +82,13 @@ export function SecondaryFeaturesSections() {
|
||||
aria-label="Features for running your books"
|
||||
className="relative overflow-hidden bg-black pb-28 pt-20 sm:py-32"
|
||||
>
|
||||
{/* <div class="absolute inset-0 h-full w-full bg-background bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]" /> */}
|
||||
|
||||
{/* <Image
|
||||
className="absolute left-1/2 top-1/2 max-w-none translate-x-[-44%] translate-y-[-42%]"
|
||||
src={backgroundImage}
|
||||
alt=""
|
||||
width={2245}
|
||||
height={1636}
|
||||
unoptimized
|
||||
/> */}
|
||||
<div className="mx-auto max-w-7xl max-lg:px-4 relative">
|
||||
<div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
|
||||
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl md:text-5xl">
|
||||
{t("primaryFeatures.title")}
|
||||
Comprehensive Control of Your Digital Ecosystem
|
||||
</h2>
|
||||
<p className="mt-6 text-lg tracking-tight text-muted-foreground">
|
||||
{t("primaryFeatures.des")}
|
||||
Simplify your project and data management, ensure robust monitoring, and secure your backups—all without the fuss over minute details.
|
||||
</p>
|
||||
</div>
|
||||
<Tab.Group
|
||||
@@ -148,7 +136,7 @@ export function SecondaryFeaturesSections() {
|
||||
)}
|
||||
>
|
||||
<span className="absolute inset-0 rounded-full" />
|
||||
{t(feature.title)}
|
||||
{feature.title}
|
||||
</Tab>
|
||||
</h3>
|
||||
<p
|
||||
@@ -156,7 +144,7 @@ export function SecondaryFeaturesSections() {
|
||||
"mt-2 hidden text-sm text-muted-foreground ",
|
||||
)}
|
||||
>
|
||||
{t(feature.description)}
|
||||
{feature.description}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
@@ -168,7 +156,7 @@ export function SecondaryFeaturesSections() {
|
||||
<div className="relative sm:px-6 ">
|
||||
<div className="absolute -inset-x-4 bottom-[-4.25rem] top-[-6.5rem] bg-card/60 ring-1 ring-inset ring-white/10 sm:inset-x-0 sm:rounded-t-xl" />
|
||||
<p className="relative mx-auto max-w-2xl text-base text-white sm:text-center mb-10">
|
||||
{t(feature.description)}
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import { PlusCircleIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { buttonVariants } from "./ui/button";
|
||||
import Ripple from "./ui/ripple";
|
||||
@@ -12,15 +11,14 @@ import {
|
||||
} from "./ui/tooltip";
|
||||
|
||||
export const Sponsors = () => {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<div className="mt-20 flex flex-col justify-center gap-y-10 w-full ">
|
||||
<div className="flex flex-col justify-start gap-4 px-4">
|
||||
<h3 className="mx-auto max-w-2xl font-display text-3xl font-medium tracking-tight text-primary sm:text-5xl text-center">
|
||||
{t("hero.sponsors.title")}
|
||||
Sponsors
|
||||
</h3>
|
||||
<p className="mx-auto max-w-2xl text-lg tracking-tight text-muted-foreground text-center">
|
||||
{t("hero.sponsors.description")}
|
||||
Dokploy is an open source project that is maintained by a community of volunteers. We would like to thank our sponsors for their support and contributions to the project, which help us to continue to develop and improve Dokploy.
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative flex h-[700px] w-full flex-col items-center justify-center overflow-hidden bg-background md:shadow-xl">
|
||||
|
||||
@@ -1,24 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { HandCoins, Users } from "lucide-react";
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useId } from "react";
|
||||
import NumberTicker from "./ui/number-ticker";
|
||||
|
||||
const statsValues = {
|
||||
githubStars: 23000,
|
||||
dockerDownloads: 2500000,
|
||||
contributors: 194,
|
||||
githubStars: 26000,
|
||||
dockerDownloads: 4000000,
|
||||
contributors: 200,
|
||||
sponsors: 50,
|
||||
};
|
||||
|
||||
export function StatsSection() {
|
||||
const [githubStars, setGithubStars] = useState(statsValues.githubStars);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchGitHubStars = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
"/api/github-stars?owner=dokploy&repo=dokploy",
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setGithubStars(data.stargazers_count);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching GitHub stars:", error);
|
||||
// Keep default value on error
|
||||
}
|
||||
};
|
||||
|
||||
fetchGitHubStars();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="py-20 lg:py-40 flex flex-col gap-10 px-4 ">
|
||||
<div className="mx-auto max-w-2xl md:text-center">
|
||||
<h2 className="font-display text-3xl tracking-tight sm:text-4xl text-center">
|
||||
Stats You Didn’t Ask For (But Secretly Love to See)
|
||||
Stats You Didn't Ask For (But Secretly Love to See)
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground text-center">
|
||||
Just a few numbers to show we’re not *completely* making this up.
|
||||
Just a few numbers to show we're not *completely* making this up.
|
||||
Turns out, Dokploy has actually helped a few people—who knew?
|
||||
</p>
|
||||
</div>
|
||||
@@ -35,9 +59,13 @@ export function StatsSection() {
|
||||
{feature.icon}
|
||||
</p>
|
||||
<p className="text-neutral-400 mt-4 text-base font-normal relative z-20">
|
||||
{feature.description}
|
||||
{typeof feature.description === "function"
|
||||
? feature.description(githubStars)
|
||||
: feature.description}
|
||||
</p>
|
||||
{feature.component}
|
||||
{typeof feature.component === "function"
|
||||
? feature.component(githubStars)
|
||||
: feature.component}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -48,15 +76,16 @@ export function StatsSection() {
|
||||
const grid = [
|
||||
{
|
||||
title: "GitHub Stars",
|
||||
description: `With over ${(statsValues.githubStars / 1000).toFixed(1)}k stars on GitHub, Dokploy is trusted by developers worldwide. Explore our repositories and join our community!`,
|
||||
description: (stars: number) =>
|
||||
`With over ${(stars / 1000).toFixed(1)}k stars on GitHub, Dokploy is trusted by developers worldwide. Explore our repositories and join our community!`,
|
||||
icon: (
|
||||
<svg aria-hidden="true" className="h-6 w-6 fill-white">
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
|
||||
</svg>
|
||||
),
|
||||
component: (
|
||||
component: (stars: number) => (
|
||||
<p className="whitespace-pre-wrap text-2xl !font-semibold tracking-tighter mt-4">
|
||||
<NumberTicker value={statsValues.githubStars} />+
|
||||
<NumberTicker value={stars} />+
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -83,7 +112,7 @@ const grid = [
|
||||
},
|
||||
{
|
||||
title: "Community Contributors",
|
||||
description: `Thanks to a growing base of over ${statsValues.contributors} contributors, Dokploy continues to thrive with valuable contributions from developers around the world.`,
|
||||
description: `Thanks to our growing base of over ${statsValues.contributors} contributors, Dokploy continues to thrive, with valuable contributions from developers around the world.`,
|
||||
icon: <Users className="h-6 w-6 stroke-white" />,
|
||||
component: (
|
||||
<p className="whitespace-pre-wrap text-2xl !font-semibold tracking-tighter mt-4">
|
||||
|
||||
122
apps/website/components/ui/dialog.tsx
Normal file
122
apps/website/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogHeader.displayName = "DialogHeader";
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogFooter.displayName = "DialogFooter";
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
@@ -45,6 +45,12 @@ const Ripple = React.memo(function Ripple({
|
||||
link: "https://www.lxaer.com?ref=dokploy",
|
||||
type: "hero",
|
||||
},
|
||||
{
|
||||
name: "LambdaTest",
|
||||
image: "https://www.lambdatest.com/blue-logo.png",
|
||||
link: "https://www.lambdatest.com/?utm_source=dokploy&utm_medium=sponsor",
|
||||
type: "premium",
|
||||
},
|
||||
];
|
||||
const premiumSponsors = [
|
||||
{
|
||||
@@ -193,6 +199,7 @@ const Ripple = React.memo(function Ripple({
|
||||
<AvatarImage
|
||||
src={item.image}
|
||||
alt={item.name}
|
||||
className="object-contain"
|
||||
/>
|
||||
<AvatarFallback>{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { routing } from "./routing";
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
let locale = await requestLocale;
|
||||
// Validate that the incoming `locale` parameter is valid
|
||||
if (!locale || !routing.locales.includes(locale as any)) {
|
||||
locale = routing.defaultLocale;
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../locales/${locale}.json`)).default,
|
||||
};
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { createSharedPathnamesNavigation } from "next-intl/navigation";
|
||||
import { defineRouting } from "next-intl/routing";
|
||||
|
||||
export const routing = defineRouting({
|
||||
// A list of all locales that are supported
|
||||
locales: ["en", "fr", "es", "zh-Hans"],
|
||||
|
||||
// Used when no locale matches
|
||||
defaultLocale: "en",
|
||||
localePrefix: "as-needed",
|
||||
});
|
||||
|
||||
// Lightweight wrappers around Next.js' navigation APIs
|
||||
// that will consider the routing configuration
|
||||
export const { Link, redirect, usePathname, useRouter } =
|
||||
createSharedPathnamesNavigation(routing);
|
||||
138
apps/website/lib/hubspot.ts
Normal file
138
apps/website/lib/hubspot.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
interface HubSpotFormField {
|
||||
objectTypeId: string;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface HubSpotFormData {
|
||||
fields: HubSpotFormField[];
|
||||
context: {
|
||||
pageUri: string;
|
||||
pageName: string;
|
||||
hutk?: string; // HubSpot UTK from cookies
|
||||
};
|
||||
}
|
||||
|
||||
interface ContactFormData {
|
||||
inquiryType: "support" | "sales" | "other";
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
company: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract HubSpot UTK (User Token) from cookies
|
||||
* This is used for tracking and attribution in HubSpot
|
||||
*/
|
||||
export function getHubSpotUTK(cookieHeader?: string): string | null {
|
||||
if (!cookieHeader) return null;
|
||||
|
||||
const name = "hubspotutk=";
|
||||
const decodedCookie = decodeURIComponent(cookieHeader);
|
||||
const cookieArray = decodedCookie.split(";");
|
||||
|
||||
for (let i = 0; i < cookieArray.length; i++) {
|
||||
const cookie = cookieArray[i].trim();
|
||||
if (cookie.indexOf(name) === 0) {
|
||||
return cookie.substring(name.length, cookie.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert contact form data to HubSpot form format
|
||||
*/
|
||||
export function formatContactDataForHubSpot(
|
||||
contactData: ContactFormData,
|
||||
hutk?: string | null,
|
||||
): HubSpotFormData {
|
||||
const formData: HubSpotFormData = {
|
||||
fields: [
|
||||
{
|
||||
objectTypeId: "0-1", // Contact object type
|
||||
name: "firstname",
|
||||
value: contactData.firstName,
|
||||
},
|
||||
{
|
||||
objectTypeId: "0-1",
|
||||
name: "lastname",
|
||||
value: contactData.lastName,
|
||||
},
|
||||
{
|
||||
objectTypeId: "0-1",
|
||||
name: "email",
|
||||
value: contactData.email,
|
||||
},
|
||||
{
|
||||
objectTypeId: "0-1",
|
||||
name: "message",
|
||||
value: contactData.message,
|
||||
},
|
||||
{
|
||||
objectTypeId: "0-2", // Company object type
|
||||
name: "name",
|
||||
value: contactData.company,
|
||||
},
|
||||
],
|
||||
context: {
|
||||
pageUri: "https://dokploy.com/contact",
|
||||
pageName: "Contact Us",
|
||||
},
|
||||
};
|
||||
|
||||
// Add HubSpot UTK if available
|
||||
if (hutk) {
|
||||
formData.context.hutk = hutk;
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit form data to HubSpot Forms API
|
||||
*/
|
||||
export async function submitToHubSpot(
|
||||
contactData: ContactFormData,
|
||||
hutk?: string | null,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const portalId = process.env.HUBSPOT_PORTAL_ID;
|
||||
const formGuid = process.env.HUBSPOT_FORM_GUID;
|
||||
|
||||
if (!portalId || !formGuid) {
|
||||
console.error(
|
||||
"HubSpot configuration missing: HUBSPOT_PORTAL_ID or HUBSPOT_FORM_GUID not set",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const formData = formatContactDataForHubSpot(contactData, hutk);
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.hsforms.com/submissions/v3/integration/submit/${portalId}/${formGuid}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("HubSpot API error:", response.status, errorText);
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log("HubSpot submission successful:", result);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error submitting to HubSpot:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"navigation": {
|
||||
"features": "Features",
|
||||
"faqs": "FAQ",
|
||||
"docs": "Docs",
|
||||
"pricing": "Pricing",
|
||||
"support": "Support",
|
||||
"dashboard": "Sign In",
|
||||
"discord": "Discord",
|
||||
"i18nButtonPlaceholder": "Language",
|
||||
"i18nFr": "Français",
|
||||
"i18nEn": "English",
|
||||
"i18nEs": "Español",
|
||||
"i18nZh-Hans": "简体中文",
|
||||
"blog": "Blog",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
"register": "Register"
|
||||
},
|
||||
"hero": {
|
||||
"cloud": "Introducing Dokploy Cloud",
|
||||
"deploy": "Deploy",
|
||||
"anywhere": "Anywhere",
|
||||
"with": "with Total Freedom and Ease.",
|
||||
"des": "Streamline your operations with our all-in-one platform — perfect for managing projects, data, and system health with simplicity and efficiency.",
|
||||
"featuredIn": "Featured in",
|
||||
"sponsors": {
|
||||
"title": "Sponsors",
|
||||
"description": "Dokploy is an open source project that is maintained by a community of volunteers. We would like to thank our sponsors for their support and contributions to the project, which help us to continue to develop and improve Dokploy.",
|
||||
"level": {
|
||||
"hero": "Hero Sponsors",
|
||||
"premium": "Premium Supporters",
|
||||
"supporting": "Supporting Members",
|
||||
"community": "Community Backers",
|
||||
"organizations": "Organizations",
|
||||
"individuals": "Individuals"
|
||||
}
|
||||
}
|
||||
},
|
||||
"primaryFeatures": {
|
||||
"title": "Comprehensive Control for Your Digital Ecosystem",
|
||||
"des": "Simplify your project and data management, ensure robust monitoring, and secure your backups—all without the fuss over minute details.",
|
||||
"projects": "Projects",
|
||||
"templates": "Templates",
|
||||
"templatesDes": "One click to deploy open source templates.",
|
||||
"logs": "Logs",
|
||||
"logsDes": "Monitor and manage your applications' logs with ease, ensuring efficient troubleshooting and optimal performance.",
|
||||
"projectsDes": "Manage and organize all your projects in one place, keeping detailed track of progress and resource allocation.",
|
||||
"applications": "Applications & Databases",
|
||||
"applicationsDes": "Centralize control over your applications and databases for enhanced security and efficiency, simplifying access and management across your infrastructure.",
|
||||
"compose": "Docker Compose",
|
||||
"composeDes": "Native Docker Compose support for manage complex applications and services with ease.",
|
||||
"multiserver": "Multiserver",
|
||||
"multiserverDes": "Deploy applications to multiple servers without effort.",
|
||||
"monitoring": "Monitoring",
|
||||
"monitoringDes": "Monitor your systems' performance and health in real time, ensuring continuous and uninterrupted operation.",
|
||||
"backups": "Backups",
|
||||
"backupsDes": "Implement automatic and secure backup solutions to protect your critical data and restore it quickly when necessary.",
|
||||
"traefik": "Traefik",
|
||||
"traefikDes": "Manage traefik via File Editor to configure your own domain names, certificates, and more."
|
||||
},
|
||||
"secondaryFeatures": {
|
||||
"title": "Advanced Management Tools",
|
||||
"des": "Elevate your infrastructure with tools that offer precise control, detailed monitoring, and enhanced security, ensuring seamless management and robust performance.",
|
||||
"templates": "Open Source Templates",
|
||||
"templatesSummary": "One click to deploy open source templates.",
|
||||
"templatesDes": "Deploy open source templates with one click, powered by Docker Compose, (Plausible, Calcom, Pocketbase, etc.)",
|
||||
"traefik": "Real-Time Traefik Configuration",
|
||||
"traefikSummary": "Modify Traefik settings on-the-fly via a graphical interface or API.",
|
||||
"traefikDes": "Users can adjust Traefik's configuration, including middleware, forwarding rules, and SSL certificates through an intuitive interface or API. This feature enables seamless traffic routing and security adjustments without the need to restart services",
|
||||
"users": "User Permission Management",
|
||||
"usersSummary": "Detailed control over user permissions for accessing and managing projects and services.",
|
||||
"usersDes": "Allows administrators to define specific roles and permissions for each user, including the ability to create, modify, or delete applications and databases. This feature ensures secure and efficient management of large and diverse teams.",
|
||||
"terminal": "Terminal Access",
|
||||
"terminalSummary": "Direct access to each container's and server terminal for advanced management.",
|
||||
"terminalDes": "Provides an interface to access the command line of any active container, allowing developers to execute commands, manage services, and troubleshoot directly from the dashboard"
|
||||
},
|
||||
"callToAction": {
|
||||
"title": "Unlock Your Deployment Potential with Dokploy Cloud",
|
||||
"des": "Say goodbye to infrastructure hassles—Dokploy Cloud handles it all. Effortlessly deploy, manage Docker containers, and secure your traffic with Traefik. Focus on building, we'll handle the rest.",
|
||||
"button": "Get Started Now"
|
||||
},
|
||||
"faq": {
|
||||
"title": "Frequently asked questions",
|
||||
"des": "If you can't find what you're looking for, please submit an issue through our GitHub repository or ask questions on our Discord.",
|
||||
"q1": "What is Dokploy?",
|
||||
"a1": "Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as a free alternative self-hostable solution to platforms like Heroku, Vercel, and Netlify.",
|
||||
"q2": "Why Choose Dokploy?",
|
||||
"a2": "Dokploy offers simplicity, flexibility, and speed in application deployment and management.",
|
||||
"q4": "Is it open source?",
|
||||
"a4": "Yes, Dokploy is open source and free to use.",
|
||||
"q5": "What types of languages can I deploy with Dokploy?",
|
||||
"a5": "Dokploy does not restrict programming languages. You are free to choose your preferred language and framework.",
|
||||
"q6": "How do I request a feature or report a bug?",
|
||||
"a6": "To request a feature or report a bug, please create an issue on our GitHub repository or ask in our Discord channel.",
|
||||
"q7": "Do you track the usage of Dokploy?",
|
||||
"a7": "No, we don't track any usage data.",
|
||||
"q8": "Are there any user forums or communities where I can interact with other users?",
|
||||
"a8": "Yes, we have active GitHub discussions and Discord where you can share ideas, ask for help, and connect with other users.",
|
||||
"q9": "What types of applications can I deploy with Dokploy?",
|
||||
"a9": "You can deploy any application that can be Dockerized, with no limits. Dokploy supports builds from Git repositories, Dockerfiles, Nixpacks, and Buildpacks like Heroku and Paketo.",
|
||||
"q10": "How does Dokploy handle database management?",
|
||||
"a10": "Dokploy supports multiple database systems including Postgres, MySQL, MariaDB, MongoDB, and Redis, providing tools for easy deployment and management and backups directly from the dashboard.",
|
||||
"q11": "How does Dokploy's Open Source plan work?",
|
||||
"a11": "You can host Dokploy UI on your own infrastructure and you will be responsible for the maintenance and updates.",
|
||||
"q12": "Do I need to provide my own server for the managed plan?",
|
||||
"a12": "Yes, in the managed plan, you provide your own server e.g. (Hetzner, Hostinger, AWS, ETC.) VPS, and we manage the Dokploy UI infrastructure for you.",
|
||||
"q13": "What happens if I need more than one server?",
|
||||
"a13": "The first server costs $4.50/month, if you buy more than one it will be $3.50/month per server.",
|
||||
"q14": "Is there a limit on the number of deployments?",
|
||||
"a14": "No, there is no limit on the number of deployments in any of the plans.",
|
||||
"q15": "What happens if I exceed my purchased server limit?",
|
||||
"a15": "The most recently added servers will be deactivated. You won't be able to create services on inactive servers until they are reactivated.",
|
||||
"q16": "Do you offer a refunds?",
|
||||
"a16": "We do not offer refunds. However, you can cancel your subscription at any time. Feel free to try our open-source version for free before making a purchase.",
|
||||
"q17": "What kind of support do you offer?",
|
||||
"a17": "We offer community support for the open source version and priority support for paid plans (Via Discord or Email at support@dokploy.com).",
|
||||
"q18": "What's the catch on the Paid Plan?",
|
||||
"a18": "Nothing, you link your server(VPS) to your account and you can deploy unlimited applications, databases, and users and you get unlimited updates, deployments, backups and more."
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "Copyright © {year} Dokploy. All rights reserved."
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"title": "Oops! Looks like you're lost.",
|
||||
"des": "Let's get you back",
|
||||
"action": "home"
|
||||
},
|
||||
"Link": {
|
||||
"docs": {
|
||||
"intro": "https://docs.dokploy.com/docs/core",
|
||||
"install": "https://docs.dokploy.com/docs/core/installation"
|
||||
}
|
||||
},
|
||||
"Pricing": {
|
||||
"swirlyDoodleTitle": "Simple & Affordable,",
|
||||
"restTitle": "Pricing.",
|
||||
"description": "Deploy Smarter, Scale Faster – Without Breaking the Bank",
|
||||
"billingCycle": {
|
||||
"monthly": "Monthly",
|
||||
"annual": "Annual"
|
||||
},
|
||||
"plan": {
|
||||
"free": {
|
||||
"title": "Free",
|
||||
"subTitle": "Open Source",
|
||||
"section": {
|
||||
"title": "Dokploy Open Source",
|
||||
"description": "Manage your own infrastructure, installing Dokploy UI on your own server."
|
||||
},
|
||||
"features": {
|
||||
"f1": "Complete Flexibility: Install Dokploy UI on your own infrastructure",
|
||||
"f2": "Self-hosted Infrastructure",
|
||||
"f3": "Community Support",
|
||||
"f4": "Access to Core Features",
|
||||
"f5": "Access to All Updates",
|
||||
"f9": "Unlimited Servers"
|
||||
},
|
||||
"go": "Installation"
|
||||
},
|
||||
"cloud": {
|
||||
"title": "Recommended",
|
||||
"section": {
|
||||
"title": "Dokploy Plan",
|
||||
"description": " to manage Dokploy UI infrastructure, we take care of it for you."
|
||||
},
|
||||
"servers": "{serverQuantity} Servers (You bring the servers)",
|
||||
"features": {
|
||||
"f1": "Managed Hosting: No need to manage your own servers",
|
||||
"f2": "Unlimited Deployments",
|
||||
"f3": "Unlimited Databases",
|
||||
"f4": "Unlimited Applications",
|
||||
"f5": "Unlimited Users",
|
||||
"f6": "Remote Servers Monitoring",
|
||||
"f7": "Priority Support",
|
||||
"f8": "New Updates"
|
||||
},
|
||||
"go": "Subscribe"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "Frequently asked questions",
|
||||
"description": "If you can't find what you're looking for, please send us an email to",
|
||||
"q1": "How does Dokploy's Open Source plan work?",
|
||||
"a1": "You can host Dokploy UI on your own infrastructure and you will be responsible for the maintenance and updates.",
|
||||
"q2": "Do I need to provide my own server for the managed plan?",
|
||||
"a2": "Yes, in the managed plan, you provide your own server eg(Hetzner, Hostinger, AWS, ETC.) VPS, and we manage the Dokploy UI infrastructure for you.",
|
||||
"q3": "What happens if I need more than one server?",
|
||||
"a3": "The first server costs $4.50/month, if you buy more than one it will be $3.50/month per server.",
|
||||
"q4": "Is there a limit on the number of deployments?",
|
||||
"a4": "No, there is no limit on the number of deployments in any of the plans.",
|
||||
"q5": "What happens if I exceed my purchased server limit?",
|
||||
"a5": "The most recently added servers will be deactivated. You won't be able to create services on inactive servers until they are reactivated.",
|
||||
"q6": "Do you offer a refunds?",
|
||||
"a6": "We do not offer refunds. However, you can cancel your subscription at any time. Feel free to try our open-source version for free before making a purchase.",
|
||||
"q7": "What kind of support do you offer?",
|
||||
"a7": "We offer community support for the open source version and priority support for paid plans.",
|
||||
"q8": "Is Dokploy open-source?",
|
||||
"a8": "Yes, Dokploy is fully open-source. You can contribute or modify it as needed for your projects."
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "Blog",
|
||||
"description": "Latest news, updates, and articles from Dokploy",
|
||||
"noPosts": "No posts available",
|
||||
"noResults": "No posts found matching your criteria",
|
||||
"searchPlaceholder": "Search posts...",
|
||||
"allTags": "All Tags",
|
||||
"relatedPosts": "Related Posts",
|
||||
"tagDescription": "Posts tagged with",
|
||||
"foundPosts": "{count, plural, =0 {No posts found} one {# post found} other {# posts found}}",
|
||||
"tagTitle": "Posts tagged with {tag}",
|
||||
"backToBlog": "Back to Blog",
|
||||
"tags": "Tags",
|
||||
"postsTaggedWith": "Posts tagged with"
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"navigation": {
|
||||
"features": "Características",
|
||||
"faqs": "Preguntas frecuentes",
|
||||
"docs": "Documentación",
|
||||
"pricing": "Precios",
|
||||
"support": "Soporte",
|
||||
"dashboard": "Acceder",
|
||||
"discord": "Discord",
|
||||
"i18nButtonPlaceholder": "Lenguaje",
|
||||
"i18nFr": "Français",
|
||||
"i18nEn": "English",
|
||||
"i18nEs": "Español",
|
||||
"i18nZh-Hans": "简体中文",
|
||||
"blog": "Blog",
|
||||
"home": "Inicio",
|
||||
"login": "Iniciar sesión",
|
||||
"register": "Registro"
|
||||
},
|
||||
"hero": {
|
||||
"cloud": "Introduciendo Dokploy Cloud",
|
||||
"deploy": "Despliegue",
|
||||
"anywhere": "en Cualquier lugar",
|
||||
"with": "con Total Libertad y Facilidad.",
|
||||
"des": "Simplifique sus operaciones con nuestra plataforma en un solo lugar — perfecta para gestionar proyectos, datos y salud del sistema con simplicidad y eficiencia.",
|
||||
"featuredIn": "Destacado en",
|
||||
"sponsors": {
|
||||
"title": "Patrocinadores",
|
||||
"description": "Dokploy es un proyecto de código abierto que es mantenido por una comunidad de voluntarios. Gracias a nuestros patrocinadores por su apoyo y contribuciones al proyecto, esto nos ayuda a continuar desarrollando y mejorando Dokploy.",
|
||||
"level": {
|
||||
"hero": "Patrocinadores Hero",
|
||||
"premium": "Patrocinadores Premium",
|
||||
"supporting": "Miembros de Apoyo",
|
||||
"community": "Comunidad",
|
||||
"organizations": "Organizaciones",
|
||||
"individuals": "Individuos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"primaryFeatures": {
|
||||
"title": "Control integral para tu ecosistema digital.",
|
||||
"des": "Simplifica la gestión de tus proyectos y datos, garantiza una supervisión robusta y asegura tus copias de seguridad, todo sin complicaciones ni preocuparte por los mínimos detalles.",
|
||||
"projects": "Proyectos",
|
||||
"templates": "Plantillas",
|
||||
"templatesDes": "En un solo clic puedes desplegar plantillas de código abierto.",
|
||||
"logs": "Registros",
|
||||
"logsDes": "Supervisa y gestiona los registros de tus aplicaciones con facilidad, garantizando una resolución de problemas eficiente y un rendimiento óptimo.",
|
||||
"projectsDes": "Gestiona y organiza todos tus proyectos en un solo lugar, manteniendo un seguimiento detallado del progreso y la asignación de recursos.",
|
||||
"applications": "Aplicaciones & Bases de datos",
|
||||
"applicationsDes": "Centraliza el control de tus aplicaciones y bases de datos para mejorar la seguridad y la eficiencia, simplificando el acceso y la gestión en toda tu infraestructura.",
|
||||
"compose": "Docker Compose",
|
||||
"composeDes": "Centraliza el control de tus aplicaciones y bases de datos para mejorar la seguridad y la eficiencia, simplificando el acceso y la gestión en toda tu infraestructura.",
|
||||
"multiserver": "Multiservidor",
|
||||
"multiserverDes": "Despliega aplicaciones en múltiples servidores sin esfuerzo.",
|
||||
"monitoring": "Supervisión",
|
||||
"monitoringDes": "Supervisa el rendimiento y la salud de tus sistemas en tiempo real, garantizando una operación continua e ininterrumpida.",
|
||||
"backups": "Copias de seguridad",
|
||||
"backupsDes": "Implementa soluciones de copia de seguridad automáticas y seguras para proteger tus datos críticos y restaurarlos rápidamente cuando sea necesario.",
|
||||
"traefik": "Traefik",
|
||||
"traefikDes": "Gestiona Traefik mediante el Editor de Archivos para configurar tus propios nombres de dominio, certificados y mucho más."
|
||||
},
|
||||
"secondaryFeatures": {
|
||||
"title": "Herramientas de Gestión Avanzadas",
|
||||
"des": "Eleva tu infraestructura con herramientas que ofrecen control preciso, supervisión detallada y mayor seguridad, garantizando una gestión fluida y un rendimiento sólido.",
|
||||
"templates": "Plantillas de Código Abierto",
|
||||
"templatesSummary": "En un solo clic puedes desplegar plantillas de código abierto.",
|
||||
"templatesDes": "Despliega plantillas de código abierto con un solo clic, impulsadas por Docker Compose (Plausible, Calcom, Pocketbase, etc.).",
|
||||
"traefik": "Configuración de Traefik en Tiempo Real",
|
||||
"traefikSummary": "Modifica la configuración de Traefik al instante mediante una interfaz gráfica o una API.",
|
||||
"traefikDes": "Los usuarios pueden ajustar la configuración de Traefik, incluyendo middleware, reglas de reenvío y certificados SSL, a través de una interfaz intuitiva o una API. Esta funcionalidad permite realizar ajustes en el enrutamiento del tráfico y la seguridad sin necesidad de reiniciar los servicios.",
|
||||
"users": "Gestión de Permisos de Usuario",
|
||||
"usersSummary": "Control detallado sobre los permisos de usuario para acceder y gestionar proyectos y servicios.",
|
||||
"usersDes": "Permite a los administradores definir roles y permisos específicos para cada usuario, incluyendo la capacidad de crear, modificar o eliminar aplicaciones y bases de datos. Esta funcionalidad garantiza una gestión segura y eficiente de equipos grandes y diversos.",
|
||||
"terminal": "Acceso a la Terminal",
|
||||
"terminalSummary": "Acceso directo a la terminal de cada contenedor y servidor para una gestión avanzada.",
|
||||
"terminalDes": "Proporciona una interfaz para acceder a la línea de comandos de cualquier contenedor activo, permitiendo a los desarrolladores ejecutar comandos, gestionar servicios y solucionar problemas directamente desde el panel de control."
|
||||
},
|
||||
"callToAction": {
|
||||
"title": "Desbloquea tu potencial de despliegue con Dokploy Cloud.",
|
||||
"des": "Despídete de las complicaciones de infraestructura—Dokploy Cloud lo gestiona todo. Despliega y gestiona contenedores Docker sin esfuerzo y asegura tu tráfico con Traefik. Tú céntrate en construir, nosotros nos encargamos del resto.",
|
||||
"button": "Comenzar Ahora"
|
||||
},
|
||||
"faq": {
|
||||
"title": "Preguntas frecuentes",
|
||||
"des": "Si no encuentras lo que estás buscando, por favor envía un problema a través de nuestro repositorio de GitHub o haz tus preguntas en nuestro Discord.",
|
||||
"q1": "¿Qué es Dokploy?",
|
||||
"a1": "Dokploy es una solución de despliegue estable y fácil de usar, diseñada para simplificar el proceso de gestión de aplicaciones. Piensa en Dokploy como una alternativa gratuita y autohospedable a plataformas como Heroku, Vercel y Netlify.",
|
||||
"q2": "¿Por qué usar Dokploy?",
|
||||
"a2": "Dokploy ofrece simplicidad, flexibilidad y rapidez en el despliegue y la gestión de aplicaciones.",
|
||||
"q4": "¿Dokploy es de código abierto?",
|
||||
"a4": "Sí, Dokploy es de código abierto y gratuito.",
|
||||
"q5": "¿Qué tipos de lenguajes puedo desplegar con Dokploy?",
|
||||
"a5": "Dokploy no restringe lenguajes de programación. Puedes elegir el lenguaje y el framework que prefieras.",
|
||||
"q6": "¿Cómo puedo solicitar una característica o reportar un error?",
|
||||
"a6": "Para solicitar una característica o reportar un error, por favor crea un problema en nuestro repositorio de GitHub o haz preguntas en nuestro Discord.",
|
||||
"q7": "¿Se rastrea el uso de Dokploy?",
|
||||
"a7": "No, no rastreamos ningún dato de uso.",
|
||||
"q8": "¿Hay foros o comunidades donde pueda interactuar con otros usuarios?",
|
||||
"a8": "Sí, tenemos discusiones activas en GitHub y Discord donde puedes compartir ideas, solicitar ayuda y conectarte con otros usuarios.",
|
||||
"q9": "¿Qué tipos de aplicaciones puedo desplegar con Dokploy?",
|
||||
"a9": "Puedes desplegar cualquier aplicación que pueda ser Dockerizada, sin limites. Dokploy soporta builds desde repositorios Git, Dockerfiles, Nixpacks y Buildpacks como Heroku y Paketo.",
|
||||
"q10": "¿Cómo gestiona Dokploy el manejo de bases de datos?",
|
||||
"a10": "Dokploy soporta múltiples sistemas de base de datos, incluyendo Postgres, MySQL, MariaDB, MongoDB y Redis, proporcionando herramientas para despliegue, gestión y copias de seguridad directamente desde el panel de control.",
|
||||
"q11": "¿Cómo funciona el plan de código abierto de Dokploy?",
|
||||
"a11": "Puedes hospedar la UI de Dokploy en tu propia infraestructura, tu serás responsable del mantenimiento y las actualizaciones.",
|
||||
"q12": "¿Necesito proporcionar mi propio servidor para el plan gestionado?",
|
||||
"a12": "Sí, en el plan gestionado, proporcionas tu propio servidor, por ejemplo: (Hetzner, Hostinger, AWS, ETC.) VPS, y gestionamos la infraestructura de la UI de Dokploy para ti.",
|
||||
"q13": "¿Qué sucede si necesito más de un servidor?",
|
||||
"a13": "El primer servidor cuesta $4.50/mes, si compras más de uno, será $3.50/mes por servidor.",
|
||||
"q14": "¿Hay un límite en el número de despliegues?",
|
||||
"a14": "No, no hay límite en el número de despliegues en ningún plan.",
|
||||
"q15": "¿Qué sucede si excedo el límite de servidor comprado?",
|
||||
"a15": "Los servidores recientemente agregados se desactivarán. No podrás crear servicios en servidores inactivos hasta que se reactiven.",
|
||||
"q16": "¿Ofrecen reembolsos?",
|
||||
"a16": "No, no ofrecemos reembolsos. Sin embargo, puedes cancelar tu suscripción en cualquier momento. ¡Sienteté libre de probar nuestra versión de código abierto antes de hacer un pedido!",
|
||||
"q17": "¿Qué tipo de soporte ofrecéis?",
|
||||
"a17": "Ofrecemos soporte comunitario para la versión de código abierto y soporte de prioridad para los planes de pago (a través de Discord o Email a support@dokploy.com).",
|
||||
"q18": "¿Qué sucede con el plan de pago?",
|
||||
"a18": "Nada, solo vinculas tu servidor(VPS) a tu cuenta y puedes desplegar aplicaciones, bases de datos y usuarios ilimitados y obtienes actualizaciones, despliegues, copias de seguridad y más."
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "Copyright © {year} Dokploy. Todos los derechos reservados."
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"title": "Oops! Parece que te has perdido.",
|
||||
"des": "Vamos a ponerte de nuevo en el camino",
|
||||
"action": "home"
|
||||
},
|
||||
"Link": {
|
||||
"docs": {
|
||||
"intro": "https://docs.dokploy.com/docs/core",
|
||||
"install": "https://docs.dokploy.com/docs/core/installation"
|
||||
}
|
||||
},
|
||||
"Pricing": {
|
||||
"swirlyDoodleTitle": "Simple & Asequible,",
|
||||
"restTitle": "Precios.",
|
||||
"description": "Despliegue Inteligente, Escalado Rápido – Sin Arruinarse",
|
||||
"billingCycle": {
|
||||
"monthly": "Mensual",
|
||||
"annual": "Anual"
|
||||
},
|
||||
"plan": {
|
||||
"free": {
|
||||
"title": "Gratis",
|
||||
"subTitle": "Código Abierto",
|
||||
"section": {
|
||||
"title": "Dokploy es de Código Abierto",
|
||||
"description": "Gestiona tu propia infraestructura, instalando Dokploy UI en tu propio servidor."
|
||||
},
|
||||
"features": {
|
||||
"f1": "Flexibilidad Total: Instala Dokploy UI en tu propia infraestructura",
|
||||
"f2": "Infraestructura autohospedada",
|
||||
"f3": "Soporte comunitario",
|
||||
"f4": "Acceso a características básicas",
|
||||
"f5": "Acceso a todas las actualizaciones",
|
||||
"f9": "Servidores ilimitados"
|
||||
},
|
||||
"go": "Instalación"
|
||||
},
|
||||
"cloud": {
|
||||
"title": "Recomendado",
|
||||
"section": {
|
||||
"title": "Dokploy Plan",
|
||||
"description": "Gestionamos la infraestructura de la UI de Dokploy por ti."
|
||||
},
|
||||
"servers": "{serverQuantity} Servidores (Tienes que proporcionar los servidores)",
|
||||
"features": {
|
||||
"f1": "Gestión de Hosting: No tienes que preocuparte por la gestión de los servidores",
|
||||
"f2": "Despliegues ilimitados",
|
||||
"f3": "Bases de datos ilimitadas",
|
||||
"f4": "Aplicaciones ilimitadas",
|
||||
"f5": "Usuarios ilimitados",
|
||||
"f6": "Monitoreo de servidores remotos",
|
||||
"f7": "Soporte prioritario",
|
||||
"f8": "Nuevas actualizaciones"
|
||||
},
|
||||
"go": "Suscribirme"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "Preguntas Frecuentes",
|
||||
"description": "Si no encuentras lo que estás buscando, por favor envía un problema a través de nuestro repositorio de GitHub o haz preguntas en nuestro Discord.",
|
||||
"q1": "¿Cómo funciona el plan de código abierto de Dokploy?",
|
||||
"a1": "Puedes hospedar la UI de Dokploy en tu propia infraestructura, tu serás responsable del mantenimiento y las actualizaciones.",
|
||||
"q2": "¿Necesito proporcionar mi propio servidor para el plan gestionado?",
|
||||
"a2": "Sí, en el plan gestionado, proporcionas tu propio servidor, por ejemplo: (Hetzner, Hostinger, AWS, ETC.) VPS, y gestionamos la infraestructura de la UI de Dokploy para ti.",
|
||||
"q3": "¿Qué sucede si necesito más de un servidor?",
|
||||
"a3": "El primer servidor cuesta $4.50/mes, si compras más de uno, será $3.50/mes por servidor.",
|
||||
"q4": "¿Hay un límite en el número de despliegues?",
|
||||
"a4": "No, no hay límite en el número de despliegues en ningún plan.",
|
||||
"q5": "¿Qué sucede si excedo el límite de servidor comprado?",
|
||||
"a5": "Los servidores recientemente agregados se desactivarán. No podrás crear servicios en servidores inactivos hasta que se reactiven.",
|
||||
"q6": "¿Ofrecen reembolsos?",
|
||||
"a6": "No, no ofrecemos reembolsos. Sin embargo, puedes cancelar tu suscripción en cualquier momento. ¡Sienteté libre de probar nuestra versión de código abierto antes de hacer un pedido!",
|
||||
"q7": "¿Qué tipo de soporte ofrecéis?",
|
||||
"a7": "Ofrecemos soporte comunitario para la versión de código abierto y soporte de prioridad para los planes de pago (a través de Discord o Email a support@dokploy.com).",
|
||||
"q8": "¿Qué sucede con el plan de pago?",
|
||||
"a8": "Nada, solo vinculas tu servidor(VPS) a tu cuenta y puedes desplegar aplicaciones, bases de datos y usuarios ilimitados y obtienes actualizaciones, despliegues, copias de seguridad y más."
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "Blog",
|
||||
"description": "Noticias, actualizaciones y artículos de Dokploy",
|
||||
"noPosts": "No hay publicaciones disponibles",
|
||||
"noResults": "No se encontraron publicaciones que coincidan con tus criterios",
|
||||
"searchPlaceholder": "Buscar publicaciones...",
|
||||
"allTags": "Todas las etiquetas",
|
||||
"relatedPosts": "Publicaciones Relacionadas",
|
||||
"tagDescription": "Publicaciones etiquetadas con",
|
||||
"foundPosts": "{count, plural, =0 {No posts found} one {# post found} other {# posts found}}",
|
||||
"tagTitle": "Publicaciones etiquetadas con {tag}",
|
||||
"backToBlog": "Volver al Blog",
|
||||
"tags": "Etiquetas",
|
||||
"postsTaggedWith": "Publicaciones etiquetadas con"
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"navigation": {
|
||||
"features": "Fonctionnalités",
|
||||
"faqs": "Questions",
|
||||
"docs": "Documentation",
|
||||
"pricing": "Tarifs",
|
||||
"support": "Support",
|
||||
"dashboard": "S'inscrire",
|
||||
"discord": "Discord",
|
||||
"i18nButtonPlaceholder": "Langue",
|
||||
"i18nFr": "Français",
|
||||
"i18nEn": "English",
|
||||
"i18nEs": "Español",
|
||||
"i18nZh-Hans": "简体中文",
|
||||
"blog": "Blog",
|
||||
"home": "Accueil",
|
||||
"login": "Connexion",
|
||||
"register": "S'inscrire"
|
||||
},
|
||||
"hero": {
|
||||
"cloud": "Présentation de Dokploy Cloud",
|
||||
"deploy": "Deployer",
|
||||
"anywhere": "N'importe où",
|
||||
"with": "en toute liberté et facilement.",
|
||||
"des": "Optimisez vos opérations grâce à notre plateforme tout-en-un : idéale pour gérer vos projets, données et l'état de vos systèmes avec simplicité et efficacité.",
|
||||
"featuredIn": "En vedette sur",
|
||||
"sponsors": {
|
||||
"title": "Partenaires",
|
||||
"description": "Dokploy est un projet open source qui est maintenu par une communauté de volontaires. Nous voudrions remercier nos partenaires pour leur soutien et leurs contributions au projet, qui nous aide à continuer à développer et à améliorer Dokploy.",
|
||||
"level": {
|
||||
"hero": "Partenaires spéciaux",
|
||||
"premium": "Soutiens premium",
|
||||
"supporting": "Membres soutenants",
|
||||
"community": "Soutiens de la communauté",
|
||||
"organizations": "Organisations",
|
||||
"individuals": "Particuliers"
|
||||
}
|
||||
}
|
||||
},
|
||||
"primaryFeatures": {
|
||||
"title": "Contrôle complet de votre écosystème numérique",
|
||||
"des": "Simplifiez la gestion de vos projets et données, assurez une surveillance robuste et sécurisez vos sauvegardes — tout cela sans vous soucier des détails minutieux.",
|
||||
"projects": "Projets",
|
||||
"templates": "Templates",
|
||||
"templatesDes": "Un clic pour déployer des templates open source.",
|
||||
"logs": "Logs",
|
||||
"logsDes": "Surveillez et gérez les logs de vos applications avec facilité, en assurant un dépannage efficace et des performances optimales.",
|
||||
"projectsDes": "Gérez et organisez tous vos projets au même endroit, en suivant de manière détaillée l'avancement et l'allocation des ressources.",
|
||||
"applications": "Applications & Bases de données",
|
||||
"applicationsDes": "Centralisez le contrôle de vos applications et bases de données pour améliorer la sécurité et l'efficacité, simplifiant l'accès et la gestion sur toute votre infrastructure.",
|
||||
"compose": "Docker Compose",
|
||||
"composeDes": "Support natif de Docker Compose pour gérer des applications et services complexes avec facilité.",
|
||||
"multiserver": "Multi Serveur",
|
||||
"multiserverDes": "Déployez des applications sur plusieurs serveurs sans effort.",
|
||||
"monitoring": "Surveillance",
|
||||
"monitoringDes": "Surveillez les performances et la santé de vos systèmes en temps réel, garantissant une opération continue et ininterrompue.",
|
||||
"backups": "Sauvegardes",
|
||||
"backupsDes": "Mettez en place des solutions de sauvegarde automatiques et sécurisées pour protéger vos données critiques et les restaurer rapidement en cas de besoin.",
|
||||
"traefik": "Traefik",
|
||||
"traefikDes": "Gérez Traefik via l'éditeur de fichiers pour configurer vos propres noms de domaine, certificats, et plus encore."
|
||||
},
|
||||
"secondaryFeatures": {
|
||||
"title": "Outils de gestion avancés",
|
||||
"des": "Améliorez votre infrastructure avec des outils offrant un contrôle précis, une surveillance détaillée et une sécurité renforcée, garantissant une gestion fluide et des performances robustes.",
|
||||
"templates": "Templates Open Source",
|
||||
"templatesSummary": "Déployez des templates open source en un clic.",
|
||||
"templatesDes": "Déployez des templates open source en un clic, grâce à Docker Compose (Plausible, Calcom, Pocketbase, etc.).",
|
||||
"traefik": "Configuration Traefik en temps réel",
|
||||
"traefikSummary": "Modifiez les paramètres de Traefik à la volée via une interface graphique ou une API.",
|
||||
"traefikDes": "Les utilisateurs peuvent ajuster la configuration de Traefik, y compris les middlewares, règles de redirection et certificats SSL, via une interface intuitive ou une API. Cette fonctionnalité permet un routage du trafic et des ajustements de sécurité sans redémarrer les services.",
|
||||
"users": "Gestion des permissions utilisateurs",
|
||||
"usersSummary": "Contrôle détaillé des permissions des utilisateurs pour accéder et gérer les projets et services.",
|
||||
"usersDes": "Permet aux administrateurs de définir des rôles et des permissions spécifiques pour chaque utilisateur, y compris la création, modification ou suppression d'applications et de bases de données. Cette fonctionnalité assure une gestion sécurisée et efficace des équipes larges et diversifiées.",
|
||||
"terminal": "Accès au terminal",
|
||||
"terminalSummary": "Accès direct au terminal de chaque conteneur et serveur pour une gestion avancée.",
|
||||
"terminalDes": "Fournit une interface permettant d'accéder à la ligne de commande de tout conteneur actif, offrant aux développeurs la possibilité d'exécuter des commandes, de gérer des services et de résoudre les problèmes directement depuis le tableau de bord."
|
||||
},
|
||||
"callToAction": {
|
||||
"title": "Libérez le potentiel de vos déploiements avec Dokploy Cloud",
|
||||
"des": "Dites adieu aux tracas d'infrastructure — Dokploy Cloud s'occupe de tout. Déployez et gérez facilement vos conteneurs Docker et sécurisez votre trafic avec Traefik. Concentrez-vous sur la création, nous nous chargeons du reste.",
|
||||
"button": "Commencer maintenant"
|
||||
},
|
||||
"faq": {
|
||||
"title": "Questions fréquemment posées",
|
||||
"des": "Si vous ne trouvez pas ce que vous cherchez, veuillez soumettre un problème via notre dépôt GitHub ou poser vos questions sur notre Discord.",
|
||||
"q1": "Qu'est-ce que Dokploy ?",
|
||||
"a1": "Dokploy est une solution de déploiement stable et facile à utiliser, conçue pour simplifier la gestion des applications. Pensez à Dokploy comme une alternative gratuite et auto-hébergeable à des plateformes comme Heroku, Vercel et Netlify.",
|
||||
"q2": "Pourquoi choisir Dokploy ?",
|
||||
"a2": "Dokploy offre simplicité, flexibilité et rapidité dans le déploiement et la gestion des applications.",
|
||||
"q4": "Est-ce open source ?",
|
||||
"a4": "Oui, Dokploy est open source et gratuit à utiliser.",
|
||||
"q5": "Quels types de langages puis-je déployer avec Dokploy ?",
|
||||
"a5": "Dokploy ne restreint pas les langages de programmation. Vous êtes libre de choisir votre langage et framework préférés.",
|
||||
"q6": "Comment demander une fonctionnalité ou signaler un bug ?",
|
||||
"a6": "Pour demander une fonctionnalité ou signaler un bug, veuillez créer un problème sur notre dépôt GitHub ou poser la question sur notre chaîne Discord.",
|
||||
"q7": "Suivez-vous l'utilisation de Dokploy ?",
|
||||
"a7": "Non, nous ne suivons aucune donnée d'utilisation.",
|
||||
"q8": "Existe-t-il des forums ou communautés où je peux interagir avec d'autres utilisateurs ?",
|
||||
"a8": "Oui, nous avons des discussions actives sur GitHub et Discord où vous pouvez partager des idées, demander de l'aide et vous connecter avec d'autres utilisateurs.",
|
||||
"q9": "Quels types d'applications puis-je déployer avec Dokploy ?",
|
||||
"a9": "Vous pouvez déployer toute application pouvant être Dockerisée, sans aucune limite. Dokploy prend en charge les builds à partir de dépôts Git, Dockerfiles, Nixpacks et Buildpacks comme Heroku et Paketo.",
|
||||
"q10": "Comment Dokploy gère-t-il la gestion des bases de données ?",
|
||||
"a10": "Dokploy prend en charge plusieurs systèmes de bases de données, notamment Postgres, MySQL, MariaDB, MongoDB et Redis, avec des outils pour un déploiement facile, une gestion et des sauvegardes directement depuis le tableau de bord.",
|
||||
"q11": "Comment fonctionne le plan Open Source de Dokploy ?",
|
||||
"a11": "Vous pouvez héberger l'interface utilisateur de Dokploy sur votre propre infrastructure et vous serez responsable de sa maintenance et de ses mises à jour.",
|
||||
"q12": "Dois-je fournir mon propre serveur pour le plan géré ?",
|
||||
"a12": "Oui, dans le plan géré, vous fournissez votre propre serveur (par exemple, Hetzner, Hostinger, AWS, etc.) VPS, et nous gérons l'infrastructure de l'interface utilisateur de Dokploy pour vous.",
|
||||
"q13": "Que se passe-t-il si j'ai besoin de plus d'un serveur ?",
|
||||
"a13": "Le premier serveur coûte 4,50 $/mois, et si vous en achetez plus d'un, cela coûtera 3,50 $/mois par serveur.",
|
||||
"q14": "Y a-t-il une limite au nombre de déploiements ?",
|
||||
"a14": "Non, il n'y a aucune limite au nombre de déploiements dans aucun des plans.",
|
||||
"q15": "Que se passe-t-il si je dépasse la limite de serveurs achetés ?",
|
||||
"a15": "Les serveurs ajoutés le plus récemment seront désactivés. Vous ne pourrez pas créer de services sur des serveurs inactifs tant qu'ils ne seront pas réactivés.",
|
||||
"q16": "Proposez-vous des remboursements ?",
|
||||
"a16": "Nous ne proposons pas de remboursements. Cependant, vous pouvez annuler votre abonnement à tout moment. N'hésitez pas à essayer notre version open source gratuitement avant d'effectuer un achat.",
|
||||
"q17": "Quel type de support proposez-vous ?",
|
||||
"a17": "Nous offrons un support communautaire pour la version open source et un support prioritaire pour les plans payants (via Discord ou email à support@dokploy.com).",
|
||||
"q18": "Quel est le piège du plan payant ?",
|
||||
"a18": "Aucun. Vous connectez votre serveur (VPS) à votre compte et vous pouvez déployer des applications, bases de données et utilisateurs illimités tout en bénéficiant de mises à jour, déploiements, sauvegardes et bien plus encore."
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "Copyright © {year} Dokploy. Tous droits réservés."
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"title": "Oups ! On dirait que vous êtes perdu.",
|
||||
"des": "Revenons là où vous étiez",
|
||||
"action": "par ici"
|
||||
},
|
||||
"Link": {
|
||||
"docs": {
|
||||
"intro": "https://docs.dokploy.com/docs/core",
|
||||
"install": "https://docs.dokploy.com/docs/core/installation"
|
||||
}
|
||||
},
|
||||
"Pricing": {
|
||||
"swirlyDoodleTitle": "Simple et abordable,",
|
||||
"restTitle": "Tarifs.",
|
||||
"description": "Déployer plus intelligemment, S'étendre plus rapidement – Sans se ruiner",
|
||||
"billingCycle": {
|
||||
"monthly": "Mensuel",
|
||||
"annual": "Annuel"
|
||||
},
|
||||
"plan": {
|
||||
"free": {
|
||||
"title": "Gratuit",
|
||||
"subTitle": "Open Source",
|
||||
"section": {
|
||||
"title": "Dokploy Open Source",
|
||||
"description": "Gérer sa propre infrastructure installer dokploy ui sur son propre serveur."
|
||||
},
|
||||
"features": {
|
||||
"f1": "Complete Flexibility: Install Dokploy UI on your own infrastructure",
|
||||
"f2": "Infrastructure Auto-Hébergée",
|
||||
"f3": "Support de la communauté",
|
||||
"f4": "Accès à toutes les fonctionnalités",
|
||||
"f5": "Accès à toutes les mises à jour",
|
||||
"f9": "Serveurs Illimitées"
|
||||
},
|
||||
"go": "Installation"
|
||||
},
|
||||
"cloud": {
|
||||
"title": "Recommandé",
|
||||
"section": {
|
||||
"title": "Offre Dokploy&",
|
||||
"description": " pour gérer l'infrastructure de Dokploy UI, nous nous en occupons pour vous."
|
||||
},
|
||||
"servers": "{serverQuantity} Serveurs (Vous fournissez les serveurs)",
|
||||
"features": {
|
||||
"f1": "Hébergement géré: Pas besoin de gérer vos propres serveurs",
|
||||
"f2": "Déploiements Illimités",
|
||||
"f3": "Bases de données Illimitées",
|
||||
"f4": "Applications Illimitées",
|
||||
"f5": "Utilisateurs Illimités",
|
||||
"f6": "Support Prioritaire",
|
||||
"f7": "Nouvelles mises à jour"
|
||||
},
|
||||
"go": "S'abonner"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "Questions fréquemment posées",
|
||||
"description": "Si vous ne trouvez pas ce que vous cherchez, veuillez nous envoyer un e-mail à",
|
||||
"q1": "Comment fonctionne l'offre Open Source de Dokploy ?",
|
||||
"a1": "Vous pouvez héberger l'interface utilisateur de Dokploy sur votre propre infrastructure et vous serez responsable de la maintenance et des mises à jour.",
|
||||
"q2": "Dois-je fournir mon propre serveur pour l'offre gérée ?",
|
||||
"a2": "Oui, dans l'offre gérée, vous fournissez votre propre serveur (par exemple, Hetzner, Hostinger, AWS, etc.) VPS, et nous gérons l'infrastructure de l'interface utilisateur de Dokploy pour vous.",
|
||||
"q3": "Que se passe-t-il si j'ai besoin de plus d'un serveur ?",
|
||||
"a3": "Le premier serveur coûte 4,50 $/mois. Si vous en achetez plus d'un, il vous reviendra à 3,50 $/mois par serveur.",
|
||||
"q4": "Y a-t-il une limite de déploiements ?",
|
||||
"a4": "Non, il n'y a aucune limite de déploiements dans aucun des plans.",
|
||||
"q5": "Que se passe-t-il si je dépasse la limite du serveur que j'ai acheté ?",
|
||||
"a5": "Les derniers serveurs ajoutés seront désactivés. Vous ne pourrez pas créer de services sur les serveurs inactifs tant qu'ils n'auront pas été réactivés.",
|
||||
"q6": "Proposez-vous des remboursements ?",
|
||||
"a6": "Nous n'acceptons pas les remboursements. Cependant, vous pouvez annuler votre abonnement à tout moment. N'hésitez pas à essayer gratuitement la version open-source avant de passer à l'achat.",
|
||||
"q7": "Quel type de support proposez-vous ?",
|
||||
"a7": "Nous offrons un support communautaire pour la version open-source et un support prioritaire pour les offres payantes.",
|
||||
"q8": "Dokploy est-il open-source ?",
|
||||
"a8": "Oui, Dokploy est entièrement open-source. Vous pouvez contribuer ou modifier selon vos besoins pour vos projets."
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "Blog",
|
||||
"description": "Dernières nouvelles, mises à jour et articles de Dokploy",
|
||||
"noPosts": "Aucun article disponible",
|
||||
"noResults": "Aucun article ne correspond à vos critères",
|
||||
"searchPlaceholder": "Rechercher des articles...",
|
||||
"allTags": "Tous les tags",
|
||||
"relatedPosts": "Articles similaires",
|
||||
"tagDescription": "Articles tagués avec"
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"navigation": {
|
||||
"features": "特性",
|
||||
"faqs": "FAQ",
|
||||
"docs": "文档",
|
||||
"pricing": "价格",
|
||||
"support": "赞助",
|
||||
"dashboard": "控制台",
|
||||
"discord": "Discord",
|
||||
"i18nButtonPlaceholder": "语言",
|
||||
"i18nFr": "Français",
|
||||
"i18nEn": "English",
|
||||
"i18nEs": "Español",
|
||||
"i18nZh-Hans": "简体中文",
|
||||
"blog": "博客",
|
||||
"home": "首页",
|
||||
"login": "登录",
|
||||
"register": "注册"
|
||||
},
|
||||
"hero": {
|
||||
"cloud": "隆重介绍 Dokploy 云",
|
||||
"deploy": "部署在",
|
||||
"anywhere": "任何设施之上",
|
||||
"with": "",
|
||||
"des": "以前所未有的简洁和高效提供一站式项目、数据的管理以及系统监控。",
|
||||
"featuredIn": "发布于",
|
||||
"sponsors": {
|
||||
"title": "赞助名单",
|
||||
"description": "Dokploy 是由社区成员共同支持的完全免费的开源项目,您的慷慨解囊将帮助我们继续开发和改进 Dokploy。",
|
||||
"level": {
|
||||
"hero": "特别赞助",
|
||||
"premium": "金牌赞助",
|
||||
"supporting": "银牌赞助",
|
||||
"community": "铜牌赞助",
|
||||
"organizations": "组织赞助",
|
||||
"individuals": "个人赞助"
|
||||
}
|
||||
}
|
||||
},
|
||||
"primaryFeatures": {
|
||||
"title": "全面掌控您的基础设施",
|
||||
"des": "Dokploy 不仅简化您的项目部署和数据管理流程,同时还提供完备的数据备份,一切只在弹指间。",
|
||||
"projects": "项目",
|
||||
"logs": "日志",
|
||||
"templates": "模板",
|
||||
"templatesDes": "一键部署热门开源项目。",
|
||||
"logsDes": "轻松监控和处理您的应用程序日志,确保高效故障排除和最佳性能。",
|
||||
"projectsDes": "您所有的项目和所需的一切信息都将归集一处。",
|
||||
"applications": "应用和数据库",
|
||||
"applicationsDes": "Dokploy 通过集中管理您的应用和数据库来提升安全性和效率,并大大简化了跨基础设施的访问和管理。",
|
||||
"compose": "Docker Compose",
|
||||
"composeDes": "Dokploy 提供对复杂应用和服务的原生 Docker Compose 支持。",
|
||||
"multiserver": "多服务器部署",
|
||||
"multiserverDes": "毫不费力地将应用程序部署到多个服务器上。",
|
||||
"monitoring": "监控",
|
||||
"monitoringDes": "随时掌控你的系统性能和应用健康,确保服务无间断运行。",
|
||||
"backups": "备份",
|
||||
"backupsDes": "使用 Dokploy 的自动备份功能来保护您的数据,并在需要时极速还原。",
|
||||
"traefik": "Traefik",
|
||||
"traefikDes": "使用我们的编辑器轻松管理你的域名、证书和其他设置。"
|
||||
},
|
||||
"secondaryFeatures": {
|
||||
"title": "先进的管理工具",
|
||||
"des": "借助业界优秀的开源工具,Dokploy 不仅可以为您的基础设施提供精准的控制、详尽的监测、一流的安全,还能确保无缝的体验和强悍的性能。",
|
||||
"templates": "开源模板",
|
||||
"templatesSummary": "一键部署热门开源项目",
|
||||
"templatesDes": "基于 Docker Compose 一键部署热门开源自建项目(Plausible 分析, Calcom 日历, Pocketbase, 等等.)",
|
||||
"traefik": "实时 Traefik 配置",
|
||||
"traefikSummary": "通过图形界面或API动态修改 Traefik 配置",
|
||||
"traefikDes": "用户可通过直观的界面或API调整Traefik的配置,包括中间件、转发规则及SSL证书。此功能使得流量路由和安全设置的调整无需重启服务即可无缝进行。",
|
||||
"users": "多用户权限管理",
|
||||
"usersSummary": "对用户访问和管理项目及服务的权限进行细致控制",
|
||||
"usersDes": "允许管理员为每个用户定义特定角色和权限,包括创建、修改或删除应用程序和数据库的能力。借助这一功能,Dokploy 为大型团队进行安全且高效的管理提供了有力支持。",
|
||||
"terminal": "终端访问",
|
||||
"terminalSummary": "从图形界面直接访问容器或者服务的终端",
|
||||
"terminalDes": "DokPloy 内置的界面可以帮助开发者直接访问容器终端并执行任何命令。 "
|
||||
},
|
||||
"callToAction": {
|
||||
"title": "释放自主部署的全部潜力",
|
||||
"des": "借助我们的 PaaS 软件来简化部署流程,使用 Traefik 轻松管理 Docker 容器和流量,立即提高基础设施的效率和安全性。",
|
||||
"button": "现在就部署"
|
||||
},
|
||||
"faq": {
|
||||
"title": "常问问题",
|
||||
"des": "如果您要寻找的问题不在下方,请通过我们的 GitHub 仓库提交 issue 或在我们的 Discord 提出问题。",
|
||||
"q1": "什么是 dokploy?",
|
||||
"a1": "Dokploy 是一个稳定易用的自部署解决方案,设计用于简化项目部署过程。您可以把 DokPloy 想象成一个自部署的 Vercel/Netlify/Heroku 平台。",
|
||||
"q2": "为什么选择 Dokploy?",
|
||||
"a2": "简洁, 灵活, 还有高效!",
|
||||
"q3": "真的免费?",
|
||||
"a3": "是的, dokploy 完全免费。不管是个人使用还是团队使用,都不收取费用。",
|
||||
"q4": "这是开源的吗?",
|
||||
"a4": "Dokploy 的所有代码皆可在我们的托管平台找到。",
|
||||
"q5": "Dokploy 可以支持部署什么编程语言的项目?",
|
||||
"a5": "Dokploy 对编程语言没有限制, 您可以部署任何您喜爱的语言或者框架。",
|
||||
"q6": "想提出新的功能需求或者遇到了问题?",
|
||||
"a6": "您可以前往我们的 Github 打开一个新的 issue,或者在我们的 GitHub讨论区 或 Discord 中发帖。",
|
||||
"q7": "Dokploy是否内置遥测?",
|
||||
"a7": "不,Dokploy不收集任何使用数据。",
|
||||
"q8": "有什么用户社群可以便于我和其他用户交流吗?",
|
||||
"a8": "您可以在我们的GitHub讨论区自由讨论,如果您愿意还可以加入我们的 Discord。",
|
||||
"q9": "dokploy 可以支持部署哪种类型的应用?",
|
||||
"a9": "Dokploy 支持很多应用,不仅包括 Docker 镜像,也可以从 Git Repository 直接启动,DokPloy 提供了丰富的自定义构建工具,如 Nixpacks, Dockerfiles, 抑或是 Buildpacks (就像 Heroku 和 Paketo)",
|
||||
"q10": "Dokploy 支持哪些数据库?",
|
||||
"a10": "Dokploy 支持 Postgres, MySQL, MariaDB, MongoDB, 以及 Redis。我们还提供方便的图形化管理工具以供使用。",
|
||||
"q11": "Dokploy 的开源版本是什么?",
|
||||
"a11": "您可以免费安装 Dokploy 开源版本,并自行负责 Dokploy 的日后维护工作",
|
||||
"q12": "付费计划还需要自己的服务器吗?",
|
||||
"a12": "是的,在付费计划中,您依然需要提供您用于实际运行服务的服务器(如阿里云、腾讯云、AWS 等),我们会为您管理 Dokploy 控制面板。",
|
||||
"q13": "如果我需要管理更多的服务器怎么办?",
|
||||
"a13": "第一台服务器的费用为 $4.5/月,之后每台的费用是 $3.5/月。",
|
||||
"q14": "部署服务的数量有限制吗?",
|
||||
"a14": "不管您如何使用 Dokploy,我们都不会限制您部署服务的数量。",
|
||||
"q15": "如果我意外超过了最大服务器数量怎么办?",
|
||||
"a15": "最新添加的服务器将被停用,您将不能向其创建服务,直到它们被重新激活。",
|
||||
"q16": "关于退款服务的政策?",
|
||||
"a16": "您可以随时取消您的订阅,但请原谅我们无法提供退款服务,您可以无限试用开源版本,然后再购买。",
|
||||
"q17": "关于技术支持?",
|
||||
"a17": "付费计划可以得到优先的技术支持,开源版本则是由社区提供技术支持。",
|
||||
"q18": "Dokploy 开源吗?",
|
||||
"a18": "是的,Dokploy 完全开源,您可以参与贡献或者是 Fork 后自行修改以用于您的私人需求。"
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "版权属于 © {year} Dokploy, 保留所有权利"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"title": "糟糕!看起来你迷路了。",
|
||||
"des": "让我们送你",
|
||||
"action": "回家"
|
||||
},
|
||||
"Link": {
|
||||
"docs": {
|
||||
"intro": "https://docs.dokploy.com/docs/core",
|
||||
"install": "https://docs.dokploy.com/docs/core/installation"
|
||||
}
|
||||
},
|
||||
"Pricing": {
|
||||
"swirlyDoodleTitle": "简洁明了的",
|
||||
"restTitle": "定价",
|
||||
"description": "更聪明的部署方式,更快的扩容速度,以及不会让你破产的账单。",
|
||||
"billingCycle": {
|
||||
"monthly": "按月",
|
||||
"annual": "按年"
|
||||
},
|
||||
"plan": {
|
||||
"free": {
|
||||
"title": "免费",
|
||||
"subTitle": "开源版本",
|
||||
"section": {
|
||||
"title": "部署Dokploy的开源版本",
|
||||
"description": "自行管理您的基础设施,不收取任何费用"
|
||||
},
|
||||
"features": {
|
||||
"f1": "灵活的架构,您可以单独部署 Dokploy 管理端",
|
||||
"f2": "无限部署",
|
||||
"f3": "您负责维护基础设施",
|
||||
"f4": "来自社区的有限支持",
|
||||
"f5": "所有功能可用",
|
||||
"f6": "Dokploy 集成",
|
||||
"f7": "基础备份服务",
|
||||
"f8": "跟随开源版本更新",
|
||||
"f9": "无限服务器数量"
|
||||
},
|
||||
"go": "立即开始"
|
||||
},
|
||||
"cloud": {
|
||||
"title": "推荐(年付更优惠)",
|
||||
"section": {
|
||||
"title": "Dokploy 云",
|
||||
"description": "使用我们的云服务,一站式管理您所有的部署。"
|
||||
},
|
||||
"servers": "{serverQuantity}台受控服务器",
|
||||
"features": {
|
||||
"f1": "由 Dokploy 云提供支持的独立控制面板",
|
||||
"f2": "无限部署",
|
||||
"f3": "无限数据库",
|
||||
"f4": "无限应用",
|
||||
"f5": "无限用户",
|
||||
"f6": "优先技术支持",
|
||||
"f7": "最新的功能特性"
|
||||
},
|
||||
"go": "订阅"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "常见问题",
|
||||
"description": "如果您的问题不在以下列表,请随时向我们致信",
|
||||
"q1": "Dokploy 的开源版本是什么?",
|
||||
"a1": "您可以免费安装 Dokploy 开源版本,并自行负责 Dokploy 的日后维护工作",
|
||||
"q2": "付费计划还需要自己的服务器吗?",
|
||||
"a2": "是的,在付费计划中,您依然需要提供您用于实际运行服务的服务器(如阿里云、腾讯云、AWS 等),我们会为您管理 Dokploy 控制面板。",
|
||||
"q3": "如果我需要管理更多的服务器怎么办?",
|
||||
"a3": "第一台服务器的费用为 $4.5/月,之后每台的费用是 $3.5/月。",
|
||||
"q4": "部署服务的数量有限制吗?",
|
||||
"a4": "不管您如何使用 Dokploy,我们都不会限制您部署服务的数量。",
|
||||
"q5": "如果我意外超过了最大服务器数量怎么办?",
|
||||
"a5": "最新添加的服务器将被停用,您将不能向其创建服务,直到它们被重新激活。",
|
||||
"q6": "关于退款服务的政策?",
|
||||
"a6": "您可以随时取消您的订阅,但请原谅我们无法提供退款服务,您可以无限试用开源版本,然后再购买。",
|
||||
"q7": "关于技术支持?",
|
||||
"a7": "付费计划可以得到优先的技术支持,开源版本则是由社区提供技术支持。",
|
||||
"q8": "Dokploy 开源吗?",
|
||||
"a8": "是的,Dokploy 完全开源,您可以参与贡献或者是 Fork 后自行修改以用于您的私人需求。"
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "博客",
|
||||
"description": "Dokploy 的最新消息、更新和文章",
|
||||
"noPosts": "暂无文章",
|
||||
"noResults": "没有找到符合条件的文章",
|
||||
"searchPlaceholder": "搜索文章...",
|
||||
"allTags": "所有标签",
|
||||
"relatedPosts": "相关文章",
|
||||
"tagDescription": "标签文章",
|
||||
"readMore": "阅读更多",
|
||||
"backToBlog": "返回博客",
|
||||
"tags": "标签",
|
||||
"postsTaggedWith": "标签为",
|
||||
"foundPosts": "{count, plural, =0 {未找到文章} other {找到 # 篇文章}}",
|
||||
"tagTitle": "标签为 {tag} 的文章"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import createMiddleware from "next-intl/middleware";
|
||||
|
||||
export default createMiddleware({
|
||||
locales: ["en", "fr", "es", "zh-Hans"],
|
||||
|
||||
// Used when no locale matches
|
||||
defaultLocale: "en",
|
||||
localePrefix: "as-needed",
|
||||
});
|
||||
|
||||
export const config = {
|
||||
// Match only internationalized pathnames
|
||||
matcher: ["/((?!api|_next|.*\\..*).*)"],
|
||||
};
|
||||
@@ -1,6 +1,4 @@
|
||||
const createNextIntlPlugin = require("next-intl/plugin");
|
||||
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
// Internationalization removed
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
@@ -12,13 +10,13 @@ const nextConfig = {
|
||||
},
|
||||
images: {
|
||||
domains: [
|
||||
"static.ghost.org",
|
||||
"testing-ghost-8423be-31-220-108-27.traefik.me",
|
||||
"images.unsplash.com",
|
||||
"www.gravatar.com",
|
||||
"cms.dokploy.com",
|
||||
'static.ghost.org',
|
||||
'testing-ghost-8423be-31-220-108-27.traefik.me',
|
||||
'images.unsplash.com',
|
||||
'www.gravatar.com',
|
||||
'cms.dokploy.com',
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = withNextIntl(nextConfig);
|
||||
module.exports = nextConfig
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
"@types/turndown": "^5.0.5",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"axios": "^1.8.1",
|
||||
"resend": "6.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.3.19",
|
||||
"lucide-react": "0.364.0",
|
||||
"next": "15.4.5",
|
||||
"next-intl": "^3.26.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
|
||||
3168
apps/website/pnpm-lock.yaml
generated
3168
apps/website/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,7 @@ install_dokploy() {
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
curl -sSL https://get.docker.com | sh -s -- --version 28.5.0
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
@@ -156,11 +156,11 @@ install_dokploy() {
|
||||
--restart always \
|
||||
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
|
||||
-v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-p 80:80/tcp \
|
||||
-p 443:443/tcp \
|
||||
-p 443:443/udp \
|
||||
traefik:v3.5.0
|
||||
traefik:v3.6.1
|
||||
|
||||
docker network connect dokploy-network dokploy-traefik
|
||||
|
||||
@@ -169,13 +169,14 @@ install_dokploy() {
|
||||
# --name dokploy-traefik \
|
||||
# --constraint 'node.role==manager' \
|
||||
# --network dokploy-network \
|
||||
# --security-opt no-new-privileges:true \
|
||||
# --mount type=bind,source=/etc/dokploy/traefik/traefik.yml,target=/etc/traefik/traefik.yml \
|
||||
# --mount type=bind,source=/etc/dokploy/traefik/dynamic,target=/etc/dokploy/traefik/dynamic \
|
||||
# --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
# --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock,readonly \
|
||||
# --publish mode=host,published=443,target=443 \
|
||||
# --publish mode=host,published=80,target=80 \
|
||||
# --publish mode=host,published=443,target=443,protocol=udp \
|
||||
# traefik:v3.5.0
|
||||
# traefik:v3.6.1
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
@@ -218,4 +219,4 @@ if [ "$1" = "update" ]; then
|
||||
update_dokploy
|
||||
else
|
||||
install_dokploy
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -36,7 +36,7 @@ install_dokploy() {
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
curl -sSL https://get.docker.com | sh -s -- --version 28.5.0
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
@@ -158,11 +158,11 @@ install_dokploy() {
|
||||
--restart always \
|
||||
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
|
||||
-v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-p 80:80/tcp \
|
||||
-p 443:443/tcp \
|
||||
-p 443:443/udp \
|
||||
traefik:v3.5.0
|
||||
traefik:v3.6.1
|
||||
|
||||
docker network connect dokploy-network dokploy-traefik
|
||||
|
||||
@@ -177,7 +177,7 @@ install_dokploy() {
|
||||
# --publish mode=host,published=443,target=443 \
|
||||
# --publish mode=host,published=80,target=80 \
|
||||
# --publish mode=host,published=443,target=443,protocol=udp \
|
||||
# traefik:v3.5.0
|
||||
# traefik:v3.6.1
|
||||
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
@@ -221,4 +221,4 @@ if [ "$1" = "update" ]; then
|
||||
update_dokploy
|
||||
else
|
||||
install_dokploy
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -52,7 +52,7 @@ install_dokploy() {
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
curl -sSL https://get.docker.com | sh -s -- --version 28.5.0
|
||||
fi
|
||||
|
||||
# Check if running in Proxmox LXC container and set endpoint mode
|
||||
@@ -185,11 +185,11 @@ install_dokploy() {
|
||||
--restart always \
|
||||
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
|
||||
-v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-p 80:80/tcp \
|
||||
-p 443:443/tcp \
|
||||
-p 443:443/udp \
|
||||
traefik:v3.5.0
|
||||
traefik:v3.6.1
|
||||
|
||||
docker network connect dokploy-network dokploy-traefik
|
||||
|
||||
@@ -205,7 +205,7 @@ install_dokploy() {
|
||||
# --publish mode=host,published=443,target=443 \
|
||||
# --publish mode=host,published=80,target=80 \
|
||||
# --publish mode=host,published=443,target=443,protocol=udp \
|
||||
# traefik:v3.5.0
|
||||
# traefik:v3.6.1
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
|
||||
91
pnpm-lock.yaml
generated
91
pnpm-lock.yaml
generated
@@ -163,9 +163,6 @@ importers:
|
||||
next:
|
||||
specifier: 15.4.5
|
||||
version: 15.4.5(@babel/core@7.26.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next-intl:
|
||||
specifier: ^3.26.5
|
||||
version: 3.26.5(next@15.4.5(@babel/core@7.26.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
|
||||
prettier:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
@@ -193,6 +190,9 @@ importers:
|
||||
remark-toc:
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0
|
||||
resend:
|
||||
specifier: 6.1.2
|
||||
version: 6.1.2
|
||||
satori:
|
||||
specifier: ^0.12.1
|
||||
version: 0.12.1
|
||||
@@ -844,21 +844,6 @@ packages:
|
||||
'@floating-ui/utils@0.2.9':
|
||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||
|
||||
'@formatjs/ecma402-abstract@2.0.0':
|
||||
resolution: {integrity: sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==}
|
||||
|
||||
'@formatjs/fast-memoize@2.2.0':
|
||||
resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==}
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.7.8':
|
||||
resolution: {integrity: sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==}
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.2':
|
||||
resolution: {integrity: sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==}
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.4':
|
||||
resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.7':
|
||||
resolution: {integrity: sha512-GGFtfHGQVFe/niOZp24Kal5b2i36eE2bNL0xi9Sg/yd0TR8aLjcteApZdHmismP5QQax1cMnZM9yWySUUjJteA==}
|
||||
|
||||
@@ -2653,9 +2638,6 @@ packages:
|
||||
inline-style-parser@0.2.3:
|
||||
resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==}
|
||||
|
||||
intl-messageformat@10.5.14:
|
||||
resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==}
|
||||
|
||||
invariant@2.2.4:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
|
||||
@@ -3097,12 +3079,6 @@ packages:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
next-intl@3.26.5:
|
||||
resolution: {integrity: sha512-EQlCIfY0jOhRldiFxwSXG+ImwkQtDEfQeSOEQp6ieAGSLWGlgjdb/Ck/O7wMfC430ZHGeUKVKax8KGusTPKCgg==}
|
||||
peerDependencies:
|
||||
next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
||||
|
||||
next-themes@0.4.3:
|
||||
resolution: {integrity: sha512-nG84VPkTdUHR2YeD89YchvV4I9RbiMAql3GiLEQlPvq1ioaqPaIReK+yMRdg/zgiXws620qS1rU30TiWmmG9lA==}
|
||||
peerDependencies:
|
||||
@@ -3534,6 +3510,15 @@ packages:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
resend@6.1.2:
|
||||
resolution: {integrity: sha512-C9Q+YkRe57P8MQlkHG3yatSR/B6sqBGA06Ri2DveJfkz9Vm16182FC/iHB0K6IAfmqZ4yRrFebFw1EPuktLtSg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@react-email/render': '*'
|
||||
peerDependenciesMeta:
|
||||
'@react-email/render':
|
||||
optional: true
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3861,11 +3846,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-intl@3.26.5:
|
||||
resolution: {integrity: sha512-OdsJnC/znPvHCHLQH/duvQNXnP1w0hPfS+tkSi3mAbfjYBGh4JnyfdwkQBfIVf7t8gs9eSX/CntxUMvtKdG2MQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
||||
|
||||
use-sidecar@1.1.2:
|
||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4449,30 +4429,6 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.9': {}
|
||||
|
||||
'@formatjs/ecma402-abstract@2.0.0':
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.5.4
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/fast-memoize@2.2.0':
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.7.8':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.0.0
|
||||
'@formatjs/icu-skeleton-parser': 1.8.2
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.2':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.0.0
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.4':
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.7':
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
@@ -6661,13 +6617,6 @@ snapshots:
|
||||
|
||||
inline-style-parser@0.2.3: {}
|
||||
|
||||
intl-messageformat@10.5.14:
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.0.0
|
||||
'@formatjs/fast-memoize': 2.2.0
|
||||
'@formatjs/icu-messageformat-parser': 2.7.8
|
||||
tslib: 2.6.3
|
||||
|
||||
invariant@2.2.4:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@@ -7342,14 +7291,6 @@ snapshots:
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
next-intl@3.26.5(next@15.4.5(@babel/core@7.26.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.5.7
|
||||
negotiator: 1.0.0
|
||||
next: 15.4.5(@babel/core@7.26.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
use-intl: 3.26.5(react@18.2.0)
|
||||
|
||||
next-themes@0.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
@@ -7806,6 +7747,8 @@ snapshots:
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
resend@6.1.2: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
@@ -8229,12 +8172,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.5
|
||||
|
||||
use-intl@3.26.5(react@18.2.0):
|
||||
dependencies:
|
||||
'@formatjs/fast-memoize': 2.2.0
|
||||
intl-messageformat: 10.5.14
|
||||
react: 18.2.0
|
||||
|
||||
use-sidecar@1.1.2(@types/react@18.3.5)(react@18.2.0):
|
||||
dependencies:
|
||||
detect-node-es: 1.1.0
|
||||
|
||||
Reference in New Issue
Block a user