mirror of
https://github.com/Dokploy/website
synced 2025-06-26 18:16:01 +00:00
feat: add website
This commit is contained in:
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1,2 +0,0 @@
|
||||
# These owners will be the default owners for everything in
|
||||
* @siumauricio
|
||||
25
.github/workflows/pull-request.yml
vendored
25
.github/workflows/pull-request.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Pull request
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.18.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run Build
|
||||
run: pnpm build
|
||||
13
README.md
13
README.md
@@ -1,2 +1,15 @@
|
||||
# Dokploy Website
|
||||
|
||||
Main Landing Page of Dokploy
|
||||
|
||||
Run development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open http://localhost:3000 with your browser to see the result.
|
||||
|
||||
5
app/[locale]/[...rest]/page.tsx
Normal file
5
app/[locale]/[...rest]/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default function CatchAll() {
|
||||
notFound();
|
||||
}
|
||||
106
app/[locale]/layout.tsx
Normal file
106
app/[locale]/layout.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import clsx from "clsx";
|
||||
import { Inter, Lexend } from "next/font/google";
|
||||
import "@/styles/tailwind.css";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages } from "next-intl/server";
|
||||
|
||||
import { Footer } from "@/components/Footer";
|
||||
import { Header } from "@/components/Header";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "Dokploy - Effortless Deployment Solutions",
|
||||
template: "%s | Simplify Your DevOps",
|
||||
},
|
||||
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: "http://dokploy.com/og.png",
|
||||
},
|
||||
],
|
||||
},
|
||||
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",
|
||||
},
|
||||
};
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-inter",
|
||||
});
|
||||
|
||||
const lexend = Lexend({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-lexend",
|
||||
});
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: { locale: string };
|
||||
}) {
|
||||
const { locale } = params;
|
||||
const messages = await getMessages();
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
className={clsx("h-full scroll-smooth", inter.variable, lexend.variable)}
|
||||
>
|
||||
<head>
|
||||
<script
|
||||
defer
|
||||
src="https://umami.dokploy.com/script.js"
|
||||
data-website-id="7d1422e4-3776-4870-8145-7d7b2075d470"
|
||||
/>
|
||||
</head>
|
||||
{/* <GoogleAnalytics /> */}
|
||||
<body className="flex h-full flex-col">
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
5
app/[locale]/not-found.tsx
Normal file
5
app/[locale]/not-found.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SlimLayout } from "@/components/SlimLayout";
|
||||
|
||||
export default function NotFound() {
|
||||
return <SlimLayout />;
|
||||
}
|
||||
20
app/[locale]/page.tsx
Normal file
20
app/[locale]/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { CallToAction } from "@/components/CallToAction";
|
||||
import { Faqs } from "@/components/Faqs";
|
||||
import { Hero } from "@/components/Hero";
|
||||
import { PrimaryFeatures } from "@/components/PrimaryFeatures";
|
||||
import { SecondaryFeatures } from "@/components/SecondaryFeatures";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<main>
|
||||
<Hero />
|
||||
<PrimaryFeatures />
|
||||
<SecondaryFeatures />
|
||||
<CallToAction />
|
||||
{/* <Testimonials /> */}
|
||||
<Faqs />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
app/[locale]/pricing/page.tsx
Normal file
9
app/[locale]/pricing/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Pricing } from "@/components/pricing";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Pricing />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
111
app/[locale]/privacy/page.tsx
Normal file
111
app/[locale]/privacy/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl font-bold text-center mb-6">Privacy</h1>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<p>
|
||||
At Dokploy, we are committed to protecting your privacy. This Privacy
|
||||
Policy explains how we collect, use, and safeguard your personal
|
||||
information when you use our website and services.
|
||||
</p>
|
||||
<p>
|
||||
By using Dokploy, you agree to the collection and use of information
|
||||
in accordance with this Privacy Policy. If you do not agree with these
|
||||
practices, please do not use our services.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
1. Information We Collect
|
||||
</h2>
|
||||
<p className="">
|
||||
We only collect limited, non-personal data through Umami Analytics, a
|
||||
privacy-focused analytics tool. No personal identifying information
|
||||
(PII) is collected. The data we collect includes:
|
||||
</p>
|
||||
<ul className="list-disc list-inside mb-4">
|
||||
<li>Website usage statistics (e.g., page views, session duration)</li>
|
||||
<li>Anonymized IP addresses</li>
|
||||
<li>Referring websites</li>
|
||||
<li>Browser and device type</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
2. How We Use the Information
|
||||
</h2>
|
||||
<p className="mb-4">
|
||||
The information we collect is used solely for improving the
|
||||
functionality and user experience of our platform. Specifically, we
|
||||
use it to:
|
||||
</p>
|
||||
<ul className="list-disc list-inside mb-4">
|
||||
<li>Monitor traffic and website performance</li>
|
||||
<li>Optimize the user experience</li>
|
||||
<li>Understand how users interact with our platform</li>
|
||||
</ul>
|
||||
<p>
|
||||
Additionally, we use a single cookie to manage user sessions, which is
|
||||
necessary for the proper functioning of the platform.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-semibold mb-4">3. Data Security</h2>
|
||||
<p className="">
|
||||
We take reasonable precautions to protect your data. Since we do not
|
||||
collect personal information, the risk of data misuse is minimized.
|
||||
Umami Analytics is privacy-friendly and does not rely on cookies or
|
||||
store PII.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">4. Third-Party Services</h2>
|
||||
|
||||
<p>
|
||||
We do not share your data with any third-party services other than
|
||||
Umami Analytics. We do not sell, trade, or transfer your data to
|
||||
outside parties.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">5. Cookies</h2>
|
||||
<p className="mb-4">
|
||||
Dokploy does not use cookies to track user activity. Umami Analytics
|
||||
is cookie-free and does not require any tracking cookies for its
|
||||
functionality.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
6. Changes to This Privacy Policy
|
||||
</h2>
|
||||
<p className="">
|
||||
We may update this Privacy Policy from time to time. Any changes will
|
||||
be posted on this page, and it is your responsibility to review this
|
||||
policy periodically.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">12. Contact Information</h2>
|
||||
<p className="mb-4">
|
||||
If you have any questions or concerns regarding these Privacy Policy,
|
||||
please contact us at:
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
Email:
|
||||
<a
|
||||
href="mailto:support@dokploy.com"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
support@dokploy.com
|
||||
</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
205
app/[locale]/terms/page.tsx
Normal file
205
app/[locale]/terms/page.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl font-bold text-center mb-6">
|
||||
Terms and Conditions
|
||||
</h1>
|
||||
|
||||
<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.
|
||||
</p>
|
||||
<p>
|
||||
By accessing or using our services, you agree to be bound by the
|
||||
following terms. If you do not agree with these terms, please do not
|
||||
use our website or services.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mb-4">1. Definitions</h2>
|
||||
<p className="">
|
||||
Website: Refers to the website of Dokploy (
|
||||
<a
|
||||
href="https://dokploy.com"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
https://dokploy.com
|
||||
</a>
|
||||
) and its subdomains.
|
||||
</p>
|
||||
<p>
|
||||
Services: The platform and related services offered by Dokploy for
|
||||
deploying and managing applications using Docker and other related
|
||||
tools.
|
||||
</p>
|
||||
<p>User: Any individual or organization using Dokploy.</p>
|
||||
<p>
|
||||
Subscription: The paid plan for using additional features, resources,
|
||||
or server capacity.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">2. Service Description</h2>
|
||||
<p className="mb-4">
|
||||
Dokploy is a platform that allows users to deploy and manage web
|
||||
applications on their own servers using custom builders and Docker
|
||||
technology. Dokploy offers both free and paid services, including
|
||||
subscriptions for adding additional servers, features, or increased
|
||||
capacity.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
3. User Responsibilities
|
||||
</h2>
|
||||
<p className="">
|
||||
Users are responsible for maintaining the security of their accounts,
|
||||
servers, and applications deployed through Dokploy.
|
||||
</p>
|
||||
<p className="">
|
||||
Users must not use the platform for illegal activities, including but
|
||||
not limited to distributing malware, violating intellectual property
|
||||
rights, or engaging in cyberattacks.
|
||||
</p>
|
||||
<p className="">
|
||||
Users must comply with all local, state, and international laws in
|
||||
connection with their use of Dokploy.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
4. Subscription and Payment
|
||||
</h2>
|
||||
<ul className="list-disc list-inside mb-4">
|
||||
<li>
|
||||
By purchasing a subscription, users agree to the pricing and payment
|
||||
terms detailed on the website or via Paddle (our payment processor).
|
||||
</li>
|
||||
<li>
|
||||
Subscriptions renew automatically unless canceled by the user before
|
||||
the renewal date.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">5. Refund Policy</h2>
|
||||
<p className="mb-4">
|
||||
Due to the nature of our digital services, Dokploy operates on a
|
||||
no-refund policy for any paid subscriptions, except where required by
|
||||
law. We offer a self-hosted version of Dokploy with the same core
|
||||
functionalities, which users can deploy and use without any cost. We
|
||||
recommend users try the self-hosted version to evaluate the platform
|
||||
before committing to a paid subscription.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
6. Limitations of Liability
|
||||
</h2>
|
||||
<p className="">
|
||||
Dokploy is provided "as is" without any warranties, express or
|
||||
implied, including but not limited to the availability, reliability,
|
||||
or accuracy of the service.
|
||||
</p>
|
||||
<p className="">
|
||||
Users are fully responsible for any modifications made to their remote
|
||||
servers or the environment where Dokploy is deployed. Any changes to
|
||||
the server configuration, system settings, security policies, or other
|
||||
environments that deviate from the recommended use of Dokploy may
|
||||
result in compatibility issues, performance degradation, or security
|
||||
vulnerabilities. Additionally, Dokploy may not function properly on
|
||||
unsupported operating systems or environments. We do not guarantee the
|
||||
platform will operate correctly or reliably under modified server
|
||||
conditions or on unsupported systems, and we will not be held liable
|
||||
for any disruptions, malfunctions, or damages resulting from such
|
||||
changes or unsupported configurations.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
7. Service Modifications and Downtime
|
||||
</h2>
|
||||
<p className="mb-4">
|
||||
While we strive to provide uninterrupted service, there may be periods
|
||||
of downtime due to scheduled maintenance or upgrades to our
|
||||
infrastructure, such as server maintenance or system improvements. We
|
||||
will provide notice to users ahead of any planned maintenance.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
8. Intellectual Property
|
||||
</h2>
|
||||
<p className="">
|
||||
Dokploy retains all intellectual property rights to the platform,
|
||||
including code, design, and content.
|
||||
</p>
|
||||
<p className="">
|
||||
Users are granted a limited, non-exclusive, and non-transferable
|
||||
license to use Dokploy in accordance with these terms.
|
||||
</p>
|
||||
<p className="">
|
||||
Users may not modify, reverse-engineer, or distribute any part of the
|
||||
platform without express permission.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">9. Termination</h2>
|
||||
<p className="mb-4">
|
||||
Dokploy reserves the right to suspend or terminate access to the
|
||||
platform for users who violate these terms or engage in harmful
|
||||
behavior.
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
Users may terminate their account at any time by contacting support.
|
||||
Upon termination, access to the platform will be revoked, and any
|
||||
stored data may be permanently deleted.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">10. Changes to Terms</h2>
|
||||
<p className="mb-4">
|
||||
Dokploy reserves the right to update these Terms & Conditions at any
|
||||
time. Changes will be effective immediately upon posting on the
|
||||
website. It is the user's responsibility to review these terms
|
||||
periodically.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">11. Governing Law</h2>
|
||||
<p className="mb-4">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="">
|
||||
<h2 className="text-2xl font-semibold mb-4">12. Contact Information</h2>
|
||||
<p className="mb-4">
|
||||
If you have any questions or concerns regarding these Terms, you can
|
||||
reach us at:
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
Email:
|
||||
<a
|
||||
href="mailto:support@dokploy.com"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
support@dokploy.com
|
||||
</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
101
app/layout.tsx
101
app/layout.tsx
@@ -1,98 +1,11 @@
|
||||
import { Inter, Lexend } from "next/font/google";
|
||||
import clsx from "clsx";
|
||||
import "@/styles/tailwind.css";
|
||||
import type { Metadata } from "next";
|
||||
import GoogleAnalytics from "@/components/analitycs/google";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "Dokploy - Effortless Deployment Solutions",
|
||||
template: "%s | Simplify Your DevOps",
|
||||
},
|
||||
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: "http://dokploy.com/og.png",
|
||||
},
|
||||
],
|
||||
},
|
||||
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",
|
||||
},
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-inter",
|
||||
});
|
||||
|
||||
const lexend = Lexend({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-lexend",
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={clsx("h-full scroll-smooth ", inter.variable, lexend.variable)}
|
||||
>
|
||||
<GoogleAnalytics />
|
||||
<body className="flex h-full flex-col">{children}</body>
|
||||
<a
|
||||
className="fixed bottom-0 right-0 m-4"
|
||||
href="https://www.producthunt.com/posts/dokploy?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=454418&theme=light"
|
||||
alt="Dokploy - Open-source alternative to Heroku, Vercel, and Netlify. | Product Hunt"
|
||||
width="250"
|
||||
height="54"
|
||||
/>
|
||||
</a>
|
||||
</html>
|
||||
);
|
||||
// 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 function RootLayout({ children }: Props) {
|
||||
return children;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
import Link from "next/link";
|
||||
"use client";
|
||||
|
||||
// import { Button } from "../components/Button";
|
||||
import { Logo } from "../components/shared/Logo";
|
||||
import { SlimLayout } from "../components/SlimLayout";
|
||||
import NextError from "next/error";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<SlimLayout>
|
||||
<div className="flex">
|
||||
<Link href="/" aria-label="Home">
|
||||
<Logo className="h-10 w-auto" />
|
||||
</Link>
|
||||
</div>
|
||||
<p className="mt-20 text-sm font-medium text-gray-700">404</p>
|
||||
<h1 className="mt-3 text-lg font-semibold text-gray-900">
|
||||
Page not found
|
||||
</h1>
|
||||
<p className="mt-3 text-sm text-gray-700">
|
||||
Sorry, we couldn’t find the page you’re looking for.
|
||||
</p>
|
||||
{/* <Button href="/" className="mt-10">
|
||||
Go back home
|
||||
</Button> */}
|
||||
</SlimLayout>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<NextError statusCode={404} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
25
app/page.tsx
25
app/page.tsx
@@ -1,25 +0,0 @@
|
||||
import { CallToAction } from "../components/CallToAction";
|
||||
import { Faqs } from "../components/Faqs";
|
||||
import { Footer } from "../components/Footer";
|
||||
import { Header } from "../components/Header";
|
||||
import { Hero } from "../components/Hero";
|
||||
import { PrimaryFeatures } from "../components/PrimaryFeatures";
|
||||
import { SecondaryFeatures } from "../components/SecondaryFeatures";
|
||||
import { Testimonials } from "../components/Testimonials";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<main>
|
||||
<Hero />
|
||||
<PrimaryFeatures />
|
||||
<SecondaryFeatures />
|
||||
<CallToAction />
|
||||
{/* <Testimonials /> */}
|
||||
<Faqs />
|
||||
<Footer />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/styles/tailwind.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/styles/tailwind.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { Container } from "@/components/Container";
|
||||
import { Button } from "./ui/button";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
export function CallToAction() {
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
return (
|
||||
<section
|
||||
id="get-started-today"
|
||||
className="relative overflow-hidden py-10 bg-black border-y border-border/30"
|
||||
className="relative overflow-hidden border-y border-border/30 bg-black py-10"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 2000 1000"
|
||||
@@ -27,25 +30,23 @@ export function CallToAction() {
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<Container className="z-30 relative">
|
||||
<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">
|
||||
Unlock Your Deployment Potential
|
||||
{t("callToAction.title")}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
Streamline your deployments with our PaaS. Effortlessly manage
|
||||
Docker containers and traffic with Traefik. Boost your
|
||||
infrastructure's efficiency and security today
|
||||
{t("callToAction.des")}
|
||||
</p>
|
||||
|
||||
<Button className="rounded-full mt-10" asChild>
|
||||
<Button className="mt-10 rounded-full" asChild>
|
||||
<Link
|
||||
href="https://docs.dokploy.com/get-started/installation"
|
||||
href={linkT("docs.install")}
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row gap-2 items-center"
|
||||
className="flex flex-row items-center gap-2"
|
||||
>
|
||||
Get Started Now
|
||||
{t("callToAction.button")}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,63 +1,57 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Container } from "./Container";
|
||||
|
||||
const faqs = [
|
||||
[
|
||||
{
|
||||
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.q1",
|
||||
answer: "faq.a1",
|
||||
},
|
||||
{
|
||||
question: "Why Choose Dokploy?",
|
||||
answer: "Simplicity, Flexibility, and Fast",
|
||||
question: "faq.q2",
|
||||
answer: "faq.a2",
|
||||
},
|
||||
{
|
||||
question: "Is free?",
|
||||
answer:
|
||||
"Yes, dokploy is totally free. You can use it for personal projects, small teams, or even for large-scale applications.",
|
||||
question: "faq.q3",
|
||||
answer: "faq.a3",
|
||||
},
|
||||
{
|
||||
question: "Is it open source?",
|
||||
answer: "Yes, dokploy is open source and free to use.",
|
||||
question: "faq.q4",
|
||||
answer: "faq.a4",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question: "What type of applications can i deploy with dokploy?",
|
||||
answer:
|
||||
"Dokploy is a great choice for any type of application. You can deploy your code to dokploy and manage it from the dashboard. We support a wide range of languages and frameworks, so you can choose the one that best fits your needs.",
|
||||
question: "faq.q5",
|
||||
answer: "faq.a5",
|
||||
},
|
||||
{
|
||||
question: "How do I request a feature or report a bug?",
|
||||
answer:
|
||||
"Currently we are working on fixing bug fixes, but we will be releasing new features soon. You can also request features or report bugs.",
|
||||
question: "faq.q6",
|
||||
answer: "faq.a6",
|
||||
},
|
||||
{
|
||||
question: "Do you track the usage of Dokploy?",
|
||||
answer: "No, we don't track any usage data.",
|
||||
question: "faq.q7",
|
||||
answer: "faq.a7",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question:
|
||||
"Are there any user forums or communities where I can interact with other users?",
|
||||
answer:
|
||||
"Yes, we have active github discussions where you can share ideas, ask for help, and connect with other users.",
|
||||
question: "faq.q8",
|
||||
answer: "faq.a8",
|
||||
},
|
||||
{
|
||||
question: "What types of applications can I deploy with Dokploy?",
|
||||
answer:
|
||||
"Dokploy supports a variety of applications, including those built with Docker, as well as applications from any Git repository, offering custom builds with Nixpacks, Dockerfiles, or Buildpacks like Heroku and Paketo.",
|
||||
question: "faq.q9",
|
||||
answer: "faq.a9",
|
||||
},
|
||||
{
|
||||
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 directly from the dashboard.",
|
||||
question: "faq.q10",
|
||||
answer: "faq.a10",
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export function Faqs() {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<section
|
||||
id="faqs"
|
||||
@@ -70,11 +64,10 @@ export function Faqs() {
|
||||
id="faq-title"
|
||||
className="font-display text-3xl tracking-tight text-primary sm:text-4xl"
|
||||
>
|
||||
Frequently asked questions
|
||||
{t("faq.title")}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
If you can’t find what you’re looking for, email our support team
|
||||
and if you’re lucky someone will get back to you.
|
||||
{t("faq.des")}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 lg:max-w-none lg:grid-cols-3">
|
||||
@@ -84,10 +77,10 @@ export function Faqs() {
|
||||
{column.map((faq, faqIndex) => (
|
||||
<li key={faqIndex}>
|
||||
<h3 className="font-display text-lg leading-7 text-primary">
|
||||
{faq.question}
|
||||
{t(faq.question)}
|
||||
</h3>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
{faq.answer}
|
||||
{t(faq.answer)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -1,15 +1,48 @@
|
||||
import Link from "next/link";
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "@/components/ui/select";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import type { SVGProps } from "react";
|
||||
import { Container } from "./Container";
|
||||
import { Logo } from "./shared/Logo";
|
||||
import { NavLink } from "./NavLink";
|
||||
import { Logo } from "./shared/Logo";
|
||||
import { buttonVariants } from "./ui/button";
|
||||
|
||||
const I18nIcon = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth={0}
|
||||
viewBox="0 0 512 512"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="none"
|
||||
d="m478.33 433.6-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362 368 281.65 401.17 362zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73 39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93.92 1.19 1.83 2.35 2.74 3.51-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59 22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export function Footer() {
|
||||
const router = useRouter();
|
||||
const locale = useLocale();
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
|
||||
return (
|
||||
<footer className="bg-black">
|
||||
<Container>
|
||||
<div className="py-16">
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Logo className="mx-auto h-10 w-auto" />
|
||||
<span className="text-center text-sm font-medium text-primary">
|
||||
Dokploy
|
||||
@@ -17,22 +50,19 @@ export function Footer() {
|
||||
</div>
|
||||
|
||||
<nav className="mt-10 text-sm" aria-label="quick links">
|
||||
<div className="-my-1 flex justify-center gap-6 flex-wrap">
|
||||
<NavLink href="/#features">Features</NavLink>
|
||||
<NavLink href="/#faqs">Faqs</NavLink>
|
||||
<NavLink
|
||||
href="https://docs.dokploy.com/get-started/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
<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>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
|
||||
<div className="flex gap-x-6">
|
||||
<div className="flex gap-x-6 items-center">
|
||||
<Link
|
||||
href="https://twitter.com/Siumauricio"
|
||||
href="https://x.com/getdokploy"
|
||||
className="group"
|
||||
aria-label="Dokploy on Twitter"
|
||||
>
|
||||
@@ -55,10 +85,35 @@ 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",
|
||||
});
|
||||
}}
|
||||
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="zh-Hans">
|
||||
{t("navigation.i18nZh-Hans")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<p className="mt-6 text-sm text-muted-foreground sm:mt-0">
|
||||
Copyright © {new Date().getFullYear()} Dokploy. All rights
|
||||
reserved.
|
||||
{t("footer.copyright", {
|
||||
year: new Date().getFullYear(),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
import Link from "next/link";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { Container } from "./Container";
|
||||
import { Logo } from "./shared/Logo";
|
||||
import { NavLink } from "./NavLink";
|
||||
import { Button, buttonVariants } from "./ui/button";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { trackGAEvent } from "./analitycs";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { 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";
|
||||
import { trackGAEvent } from "./analitycs";
|
||||
import { Logo } from "./shared/Logo";
|
||||
import { Button, buttonVariants } from "./ui/button";
|
||||
|
||||
function MobileNavLink({
|
||||
href,
|
||||
@@ -63,7 +64,27 @@ function MobileNavIcon({ open }: { open: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
const I18nIcon = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth={0}
|
||||
viewBox="0 0 512 512"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="none"
|
||||
d="m478.33 433.6-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362 368 281.65 401.17 362zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73 39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93.92 1.19 1.83 2.35 2.74 3.51-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59 22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
function MobileNavigation() {
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
return (
|
||||
<Popover>
|
||||
<Popover.Button
|
||||
@@ -74,7 +95,7 @@ function MobileNavigation() {
|
||||
</Popover.Button>
|
||||
<Transition.Root>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
as={Fragment as any}
|
||||
enter="duration-150 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -84,8 +105,9 @@ function MobileNavigation() {
|
||||
>
|
||||
<Popover.Overlay className="fixed inset-0 bg-background/50" />
|
||||
</Transition.Child>
|
||||
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
as={Fragment as any}
|
||||
enter="duration-150 ease-out"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
@@ -95,16 +117,12 @@ function MobileNavigation() {
|
||||
>
|
||||
<Popover.Panel
|
||||
as="div"
|
||||
className="absolute inset-x-0 top-full mt-4 flex origin-top flex-col rounded-2xl bg-background border border-border p-4 text-lg tracking-tight text-primary shadow-xl ring-1 ring-border/5"
|
||||
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="/#features">Features</MobileNavLink>
|
||||
<MobileNavLink href="/#testimonials">Testimonials</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">Faqs</MobileNavLink>
|
||||
<MobileNavLink
|
||||
href="https://docs.dokploy.com/get-started/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
<MobileNavLink href="/pricing">Pricing</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">{t("navigation.faqs")}</MobileNavLink>
|
||||
<MobileNavLink href={linkT("docs.intro")} target="_blank">
|
||||
{t("navigation.docs")}
|
||||
</MobileNavLink>
|
||||
</Popover.Panel>
|
||||
</Transition.Child>
|
||||
@@ -114,23 +132,22 @@ function MobileNavigation() {
|
||||
}
|
||||
|
||||
export function Header() {
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
|
||||
return (
|
||||
<header className="py-10 bg-background">
|
||||
<header className="bg-background py-10">
|
||||
<Container>
|
||||
<nav className="relative z-50 flex justify-between">
|
||||
<div className="flex items-center md:gap-x-12">
|
||||
<Link href="#" aria-label="Home">
|
||||
<Link href="/" aria-label="Home">
|
||||
<Logo className="h-10 w-auto" />
|
||||
</Link>
|
||||
<div className="hidden md:flex md:gap-x-6">
|
||||
<NavLink href="/#features">Features</NavLink>
|
||||
{/* <NavLink href="/#testimonials">Testimonials</NavLink> */}
|
||||
<NavLink href="/#faqs">Faqs</NavLink>
|
||||
<NavLink
|
||||
href="https://docs.dokploy.com/get-started/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,28 +160,19 @@ export function Header() {
|
||||
href="https://opencollective.com/dokploy"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="text-sm font-semibold">Support </span>
|
||||
<HeartIcon className="size-4 text-red-500 fill-red-600 animate-heartbeat " />
|
||||
<span className="text-sm font-semibold">
|
||||
{t("navigation.support")}{" "}
|
||||
</span>
|
||||
<HeartIcon className="animate-heartbeat size-4 fill-red-600 text-red-500 " />
|
||||
</Link>
|
||||
<Button
|
||||
className="rounded-full bg-[#5965F2] hover:bg-[#4A55E0]"
|
||||
asChild
|
||||
>
|
||||
<Button className="rounded-xl" asChild>
|
||||
<Link
|
||||
href="https://discord.gg/2tBnJ3jDJc"
|
||||
href="https://app.dokploy.com"
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row gap-2 items-center text-white"
|
||||
// className="flex flex-row items-center gap-2 text-white"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
className="h-6 w-6 fill-white"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<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"></path>
|
||||
</svg>
|
||||
Discord
|
||||
{t("navigation.dashboard")}
|
||||
</Link>
|
||||
</Button>
|
||||
<div className="-mr-1 md:hidden">
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowRight, ArrowRightIcon, Check, Copy } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { Container } from "./Container";
|
||||
import { Button } from "./ui/button";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Container } from "./Container";
|
||||
import AnimatedShinyText from "./ui/animated-shiny-text";
|
||||
import { Button } from "./ui/button";
|
||||
import { HoverBorderGradient } from "./ui/hover-border-gradient";
|
||||
|
||||
const ProductHunt = () => {
|
||||
return (
|
||||
@@ -38,6 +42,7 @@ const ProductHunt = () => {
|
||||
};
|
||||
|
||||
export function Hero() {
|
||||
const t = useTranslations("HomePage");
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -47,7 +52,7 @@ export function Hero() {
|
||||
return () => clearTimeout(timer);
|
||||
}, [isCopied]);
|
||||
return (
|
||||
<Container className="pb-16 pt-20 text-center lg:pt-32 bg-black">
|
||||
<Container className="bg-black pb-16 pt-20 text-center lg:pt-32">
|
||||
<div className="absolute inset-0">
|
||||
<svg viewBox="0 0 2000 1000" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="b" x="0" y="0" width="2000" height="1000">
|
||||
@@ -67,8 +72,23 @@ export function Hero() {
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<h1 className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl">
|
||||
Deploy{" "}
|
||||
<Link href="/pricing" className="relative z-10 mb-4 inline-block">
|
||||
<div className="flex items-center justify-center">
|
||||
<div
|
||||
className={cn(
|
||||
"group rounded-full border border-black/5 bg-neutral-100 text-sm font-medium text-white transition-all ease-in hover:cursor-pointer hover:bg-neutral-200 dark:border-white/5 dark:bg-neutral-900 dark:hover:bg-neutral-800",
|
||||
)}
|
||||
>
|
||||
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 text-neutral-800 transition ease-out hover:text-neutral-900 hover:duration-300 hover:dark:text-neutral-400">
|
||||
<span>🚀 {t("hero.cloud")} </span>
|
||||
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||
</AnimatedShinyText>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<h1 className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl">
|
||||
{t("hero.deploy")}{" "}
|
||||
<span className="relative whitespace-nowrap text-primary">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -78,18 +98,16 @@ export function Hero() {
|
||||
>
|
||||
<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"> Anywhere</span>
|
||||
<span className="relative"> {t("hero.anywhere")}</span>
|
||||
</span>{" "}
|
||||
with Total Freedom and Ease.
|
||||
{t("hero.with")}
|
||||
</h1>
|
||||
<p className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-muted-foreground">
|
||||
Streamline your operations with our all-in-one platform—perfect for
|
||||
managing projects, data, and system health with simplicity and
|
||||
efficiency.
|
||||
{t("hero.des")}
|
||||
</p>
|
||||
<div className="flex flex-col gap-6 md:gap-10">
|
||||
<div className="mt-10 flex justify-center gap-6 items-center flex-wrap md:flex-nowrap">
|
||||
<code className="font-sans border p-3 rounded-xl flex flex-row gap-4 items-center">
|
||||
<div className="mt-10 flex flex-wrap items-center justify-center gap-6 md:flex-nowrap">
|
||||
<code className="flex flex-row items-center gap-4 rounded-xl border p-3 font-sans">
|
||||
curl -sSL https://dokploy.com/install.sh | sh
|
||||
<button
|
||||
type="button"
|
||||
@@ -125,12 +143,14 @@ export function Hero() {
|
||||
Discord
|
||||
</Link>
|
||||
</Button> */}
|
||||
<Button className="rounded-xl" asChild>
|
||||
</div>
|
||||
<div className="mx-auto flex w-full max-w-sm flex-wrap items-center justify-center gap-3 md:flex-nowrap">
|
||||
<Button className="w-full rounded-xl" asChild>
|
||||
<Link
|
||||
href="https://github.com/dokploy/dokploy"
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row gap-2 items-center"
|
||||
className="flex flex-row items-center gap-2"
|
||||
>
|
||||
<svg aria-hidden="true" className="h-6 w-6 fill-black">
|
||||
<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" />
|
||||
@@ -138,12 +158,33 @@ export function Hero() {
|
||||
Github
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full rounded-xl bg-[#5965F2] hover:bg-[#4A55E0]"
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
href="https://discord.gg/2tBnJ3jDJc"
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row items-center gap-2 text-white"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
className="h-6 w-6 fill-white"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<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")}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-16 flex justify-center gap-x-8 flex-row sm:gap-x-0 sm:gap-y-10 xl:gap-x-12 xl:gap-y-0 rounded-lg">
|
||||
<div className="mt-16 flex flex-row justify-center gap-x-8 rounded-lg sm:gap-x-0 sm:gap-y-10 xl:gap-x-12 xl:gap-y-0">
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
width="460"
|
||||
height="215"
|
||||
src="https://www.youtube-nocookie.com/embed/mznYKPvhcfw?si=vHvqP3HKy0V3XkOZ"
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
@@ -152,9 +193,11 @@ export function Hero() {
|
||||
className="rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 ">
|
||||
<p className="font-display text-base text-primary">Featured in</p>
|
||||
<ShowSponsors />
|
||||
{/* <div className="mt-16">
|
||||
<p className="font-display text-base text-primary">
|
||||
{t("hero.featuredIn")}
|
||||
</p>
|
||||
<ul className="mt-8 flex items-center justify-center gap-x-8 sm:flex-col sm:gap-x-0 sm:gap-y-10 xl:flex-row xl:gap-x-12 xl:gap-y-0">
|
||||
{[
|
||||
[
|
||||
@@ -173,8 +216,183 @@ export function Hero() {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export const ShowSponsors = () => {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<div className="mt-20 flex flex-col justify-center gap-y-10">
|
||||
<div className="flex flex-col justify-start gap-4">
|
||||
<h1 className="mx-auto max-w-2xl font-display text-3xl font-medium tracking-tight text-primary sm:text-5xl">
|
||||
{t("hero.sponsors.title")}
|
||||
</h1>
|
||||
<p className="mx-auto max-w-2xl text-lg tracking-tight text-muted-foreground">
|
||||
{t("hero.sponsors.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-start gap-4 md:gap-6">
|
||||
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||
{t("hero.sponsors.level.hero")} 🎖
|
||||
</h2>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<a
|
||||
href="https://www.hostinger.com/vps-hosting?ref=dokploy"
|
||||
target="_blank"
|
||||
className="flex-shrink-0"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/hostinger.jpg"
|
||||
alt="hostinger.com"
|
||||
className="h-auto w-[190px] rounded-xl"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.lxaer.com?ref=dokploy"
|
||||
target="_blank"
|
||||
className="flex-shrink-0"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/lxaer.png"
|
||||
alt="lxaer.com"
|
||||
className="h-auto w-[70px] rounded-xl"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||
{t("hero.sponsors.level.premium")} 🥇
|
||||
</h2>
|
||||
<div className="flex flex-col items-center justify-start gap-4 md:gap-6">
|
||||
<a
|
||||
href="https://supafort.com/?ref=dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://supafort.com/build/q-4Ht4rBZR.webp"
|
||||
alt="Supafort.com"
|
||||
className="rounded-xl"
|
||||
width="190"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||
{t("hero.sponsors.level.supporting")} 🥉
|
||||
</h2>
|
||||
<div className="flex flex-row gap-10">
|
||||
<a
|
||||
href="https://lightspeed.run/?ref=dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://github.com/lightspeedrun.png"
|
||||
className="rounded-xl"
|
||||
width="60px"
|
||||
alt="Lightspeed.run"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://cloudblast.io/?ref=dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://cloudblast.io/img/logo-icon.193cf13e.svg"
|
||||
className="rounded-xl"
|
||||
width="250px"
|
||||
alt="Cloudblast.io"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="justify-star flex flex-col items-center gap-4 md:gap-8">
|
||||
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||
{t("hero.sponsors.level.community")} 🤝
|
||||
</h2>
|
||||
<div className="flex flex-row gap-10">
|
||||
<a
|
||||
href="https://steamsets.com/?ref=dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4"
|
||||
className="rounded-xl"
|
||||
width="60px"
|
||||
alt="Steamsets.com"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://rivo.gg/?ref=dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://avatars.githubusercontent.com/u/126797452?s=200&v=4"
|
||||
className="rounded-xl"
|
||||
width="60px"
|
||||
alt="Rivo.gg"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://photoquest.wedding/?ref=dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://photoquest.wedding/favicon/android-chrome-512x512.png"
|
||||
className="rounded-xl"
|
||||
width="60px"
|
||||
alt="Rivo.gg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||
{t("hero.sponsors.level.organizations")}
|
||||
</h2>
|
||||
<div className="flex flex-row gap-10">
|
||||
<a
|
||||
href="https://opencollective.com/dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://opencollective.com/dokploy/organizations.svg?width=890"
|
||||
alt="Organization Sponsors"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||
{t("hero.sponsors.level.individuals")}
|
||||
</h2>
|
||||
<div className="flex flex-row gap-10">
|
||||
<a
|
||||
href="https://opencollective.com/dokploy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://opencollective.com/dokploy/individuals.svg?width=890"
|
||||
alt="Individual Sponsors"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { trackGAEvent } from "./analitycs";
|
||||
|
||||
export function NavLink({
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,52 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Container } from "./Container";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Container } from "./Container";
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Projects",
|
||||
description:
|
||||
"Manage and organize all your projects in one place, keeping detailed track of progress and resource allocation.",
|
||||
title: "primaryFeatures.projects",
|
||||
description: "primaryFeatures.projectsDes",
|
||||
image: "/primary/projects.png",
|
||||
},
|
||||
{
|
||||
title: "Applications & Databases",
|
||||
description:
|
||||
"Centralize control over your applications and databases for enhanced security and efficiency, simplifying access and management across your infrastructure.",
|
||||
title: "primaryFeatures.applications",
|
||||
description: "primaryFeatures.applicationsDes",
|
||||
image: "/primary/applications.png",
|
||||
},
|
||||
{
|
||||
title: "Docker Compose",
|
||||
description:
|
||||
"Native Docker Compose support for manage complex applications and services with ease.",
|
||||
title: "primaryFeatures.compose",
|
||||
description: "primaryFeatures.composeDes",
|
||||
image: "/primary/compose.png",
|
||||
},
|
||||
{
|
||||
title: "Multi Node",
|
||||
description:
|
||||
"Scale applications to multiples nodes using docker swarm to manage the cluster.",
|
||||
title: "primaryFeatures.multinode",
|
||||
description: "primaryFeatures.multinodeDes",
|
||||
image: "/primary/multinode.png",
|
||||
},
|
||||
{
|
||||
title: "Monitoring",
|
||||
description:
|
||||
"Monitor your systems' performance and health in real time, ensuring continuous and uninterrupted operation.",
|
||||
title: "primaryFeatures.monitoring",
|
||||
description: "primaryFeatures.monitoringDes",
|
||||
image: "/primary/monitoring.png",
|
||||
},
|
||||
{
|
||||
title: "Backups",
|
||||
description:
|
||||
"Implement automatic and secure backup solutions to protect your critical data and restore it quickly when necessary.",
|
||||
title: "primaryFeatures.backups",
|
||||
description: "primaryFeatures.backupsDes",
|
||||
image: "/primary/backups.png",
|
||||
},
|
||||
];
|
||||
|
||||
export function PrimaryFeatures() {
|
||||
const t = useTranslations("HomePage");
|
||||
const [tabOrientation, setTabOrientation] = useState<
|
||||
"horizontal" | "vertical"
|
||||
>("horizontal");
|
||||
@@ -92,11 +88,10 @@ export function PrimaryFeatures() {
|
||||
<Container className="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">
|
||||
Comprehensive Control for Your Digital Ecosystem
|
||||
{t("primaryFeatures.title")}
|
||||
</h2>
|
||||
<p className="mt-6 text-lg tracking-tight text-muted-foreground">
|
||||
Simplify your project and data management, ensure robust monitoring,
|
||||
and secure your backups—all without the fuss over minute details.
|
||||
{t("primaryFeatures.des")}
|
||||
</p>
|
||||
</div>
|
||||
<Tab.Group
|
||||
@@ -118,14 +113,14 @@ export function PrimaryFeatures() {
|
||||
initial={false}
|
||||
key={`feature-${featureIndex}`}
|
||||
className={cn(
|
||||
"group relative rounded-full px-4 py-1 lg:rounded-l-xl lg:rounded-r-none lg:p-6 transition-colors ",
|
||||
"group relative rounded-full px-4 py-1 transition-colors lg:rounded-l-xl lg:rounded-r-none lg:p-6 ",
|
||||
)}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{selectedIndex === featureIndex && (
|
||||
<motion.span
|
||||
layoutId="tab"
|
||||
className="absolute inset-0 z-10 bg-white/5 rounded-full mix-blend-difference lg:rounded-l-xl lg:rounded-r-none"
|
||||
className="absolute inset-0 z-10 rounded-full bg-white/5 mix-blend-difference lg:rounded-l-xl lg:rounded-r-none"
|
||||
initial={{ opacity: 1 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
@@ -140,19 +135,19 @@ export function PrimaryFeatures() {
|
||||
<h3>
|
||||
<Tab
|
||||
className={cn(
|
||||
"font-display text-lg ui-not-focus-visible:outline-none text-primary",
|
||||
"font-display text-lg text-primary ui-not-focus-visible:outline-none",
|
||||
)}
|
||||
>
|
||||
<span className="absolute inset-0 rounded-full lg:rounded-l-xl lg:rounded-r-none" />
|
||||
{feature.title}
|
||||
{t(feature.title)}
|
||||
</Tab>
|
||||
</h3>
|
||||
<p
|
||||
className={cn(
|
||||
"mt-2 hidden text-sm lg:block text-muted-foreground",
|
||||
"mt-2 hidden text-sm text-muted-foreground lg:block",
|
||||
)}
|
||||
>
|
||||
{feature.description}
|
||||
{t(feature.description)}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
@@ -164,7 +159,7 @@ export function PrimaryFeatures() {
|
||||
<div className="relative sm:px-6 lg:hidden">
|
||||
<div className="absolute -inset-x-4 bottom-[-4.25rem] top-[-6.5rem] bg-white/10 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">
|
||||
{feature.description}
|
||||
{t(feature.description)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -178,7 +173,7 @@ export function PrimaryFeatures() {
|
||||
bounce: 0.2,
|
||||
duration: 0.6,
|
||||
}}
|
||||
className="mt-10 h-[24rem] lg:h-[40rem] w-[45rem] overflow-hidden rounded-xl shadow-xl border sm:w-auto lg:mt-0 lg:w-[67.8125rem]"
|
||||
className="mt-10 h-[24rem] w-[45rem] overflow-hidden rounded-xl border shadow-xl sm:w-auto lg:mt-0 lg:h-[40rem] lg:w-[67.8125rem]"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { Container } from "./Container";
|
||||
import { motion } from "framer-motion";
|
||||
import { Layers, Terminal, Users } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Container } from "./Container";
|
||||
interface Feature {
|
||||
name: React.ReactNode;
|
||||
summary: string;
|
||||
@@ -15,10 +16,9 @@ interface Feature {
|
||||
|
||||
const features: Array<Feature> = [
|
||||
{
|
||||
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.)",
|
||||
name: "secondaryFeatures.templates",
|
||||
summary: "secondaryFeatures.templatesSummary",
|
||||
description: "secondaryFeatures.templatesDes",
|
||||
image: "/secondary/templates.png",
|
||||
icon: function ReportingIcon() {
|
||||
return (
|
||||
@@ -29,11 +29,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
name: "secondaryFeatures.traefik",
|
||||
summary: "secondaryFeatures.traefikSummary",
|
||||
description: "secondaryFeatures.traefikDes",
|
||||
image: "/secondary/traefik.png",
|
||||
icon: function ReportingIcon() {
|
||||
return (
|
||||
@@ -217,11 +215,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
},
|
||||
{
|
||||
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.",
|
||||
name: "secondaryFeatures.users",
|
||||
summary: "secondaryFeatures.usersSummary",
|
||||
description: "secondaryFeatures.usersDes",
|
||||
image: "/secondary/users.png",
|
||||
icon: function InventoryIcon() {
|
||||
return (
|
||||
@@ -232,11 +228,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
name: "secondaryFeatures.terminal",
|
||||
summary: "secondaryFeatures.terminalSummary",
|
||||
description: "secondaryFeatures.terminalDes",
|
||||
image: "/secondary/terminal.png",
|
||||
icon: function ContactsIcon() {
|
||||
return (
|
||||
@@ -257,18 +251,19 @@ function Feature({
|
||||
feature: Feature;
|
||||
isActive: boolean;
|
||||
}) {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
className,
|
||||
!isActive ? "opacity-75 hover:opacity-100 " : "rounded-xl",
|
||||
" p-4 relative",
|
||||
" relative p-4",
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"size-9 rounded-lg flex items-center justify-center",
|
||||
"flex size-9 items-center justify-center rounded-lg",
|
||||
isActive ? "bg-border" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
@@ -277,7 +272,7 @@ function Feature({
|
||||
{isActive && (
|
||||
<motion.span
|
||||
layoutId="bubble"
|
||||
className="absolute inset-0 z-10 bg-white/5 mix-blend-difference rounded-xl"
|
||||
className="absolute inset-0 z-10 rounded-xl bg-white/5 mix-blend-difference"
|
||||
transition={{
|
||||
type: "spring",
|
||||
bounce: 0.2,
|
||||
@@ -294,10 +289,10 @@ function Feature({
|
||||
{feature.name}
|
||||
</h3>
|
||||
<p className="mt-2 font-display text-xl text-foreground">
|
||||
{feature.summary}
|
||||
{t(feature.summary)}
|
||||
</p>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
{feature.description}
|
||||
{t(feature.description)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -327,6 +322,7 @@ function FeaturesMobile() {
|
||||
}
|
||||
|
||||
function FeaturesDesktop() {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<Tab.Group as="div" className="hidden lg:mt-20 lg:block">
|
||||
{({ selectedIndex }) => (
|
||||
@@ -340,7 +336,7 @@ function FeaturesDesktop() {
|
||||
name: (
|
||||
<Tab className="ui-not-focus-visible:outline-none">
|
||||
<span className="absolute inset-0" />
|
||||
{feature.name}
|
||||
{t(feature.name)}
|
||||
</Tab>
|
||||
),
|
||||
}}
|
||||
@@ -359,7 +355,9 @@ function FeaturesDesktop() {
|
||||
"px-5 transition duration-500 ease-in-out ui-not-focus-visible:outline-none",
|
||||
featureIndex !== selectedIndex && "opacity-60",
|
||||
)}
|
||||
style={{ transform: `translateX(-${selectedIndex * 100}%)` }}
|
||||
style={{
|
||||
transform: `translateX(-${selectedIndex * 100}%)`,
|
||||
}}
|
||||
aria-hidden={featureIndex !== selectedIndex}
|
||||
>
|
||||
<div className="w-[52.75rem] overflow-hidden rounded-xl bg-red-500 shadow-lg shadow-slate-900/5 ring-1 ring-slate-500/10">
|
||||
@@ -382,21 +380,20 @@ function FeaturesDesktop() {
|
||||
}
|
||||
|
||||
export function SecondaryFeatures() {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<section
|
||||
id="secondary-features"
|
||||
aria-label="Features for simplifying everyday business tasks"
|
||||
className="pb-14 pt-20 sm:pb-20 sm:pt-32 lg:pb-32 bg-black"
|
||||
className="bg-black pb-14 pt-20 sm:pb-20 sm:pt-32 lg:pb-32"
|
||||
>
|
||||
<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">
|
||||
Advanced Management Tools
|
||||
{t("secondaryFeatures.title")}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
Elevate your infrastructure with tools that offer precise control,
|
||||
detailed monitoring, and enhanced security, ensuring seamless
|
||||
management and robust performance.
|
||||
{t("secondaryFeatures.des")}
|
||||
</p>
|
||||
</div>
|
||||
<FeaturesMobile />
|
||||
|
||||
@@ -1,31 +1,23 @@
|
||||
import { Header } from "./Header";
|
||||
import { Footer } from "./Footer";
|
||||
import Link from "next/link";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export function SlimLayout({ children }: { children: React.ReactNode }) {
|
||||
export function SlimLayout() {
|
||||
const t = useTranslations("404");
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Header />
|
||||
</div>
|
||||
<main className="text-center flex-auto items-center flex justify-center">
|
||||
<main className="flex flex-auto items-center justify-center text-center">
|
||||
<div>
|
||||
<h1 className="mb-4 text-6xl font-semibold text-primary">404</h1>
|
||||
<p className="mb-4 text-lg text-muted-foreground">
|
||||
Oops! Looks like you're lost.
|
||||
</p>
|
||||
<p className="mb-4 text-lg text-muted-foreground">{t("title")}</p>
|
||||
<p className="mt-4 text-muted-foreground">
|
||||
Let's get you back{" "}
|
||||
{t("des")}{" "}
|
||||
<Link href="/" className="text-primary">
|
||||
home
|
||||
{t("action")}
|
||||
</Link>
|
||||
.
|
||||
p{" "}
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
<div>
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
40
components/ui/animated-shiny-text.tsx
Normal file
40
components/ui/animated-shiny-text.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { CSSProperties, FC, ReactNode } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AnimatedShinyTextProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
shimmerWidth?: number;
|
||||
}
|
||||
|
||||
const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({
|
||||
children,
|
||||
className,
|
||||
shimmerWidth = 100,
|
||||
}) => {
|
||||
return (
|
||||
<p
|
||||
style={
|
||||
{
|
||||
"--shiny-width": `${shimmerWidth}px`,
|
||||
} as CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
"mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70",
|
||||
|
||||
// Shine effect
|
||||
"animate-shiny-text bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shiny-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
|
||||
|
||||
// Shine gradient
|
||||
"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
|
||||
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnimatedShinyText;
|
||||
36
components/ui/badge.tsx
Normal file
36
components/ui/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
|
||||
100
components/ui/hover-border-gradient.tsx
Normal file
100
components/ui/hover-border-gradient.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
import type React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
type Direction = "TOP" | "LEFT" | "BOTTOM" | "RIGHT";
|
||||
|
||||
export function HoverBorderGradient({
|
||||
children,
|
||||
containerClassName,
|
||||
className,
|
||||
as: Tag = "button",
|
||||
duration = 1,
|
||||
clockwise = true,
|
||||
...props
|
||||
}: React.PropsWithChildren<
|
||||
{
|
||||
as?: React.ElementType;
|
||||
containerClassName?: string;
|
||||
className?: string;
|
||||
duration?: number;
|
||||
clockwise?: boolean;
|
||||
} & React.HTMLAttributes<HTMLElement>
|
||||
>) {
|
||||
const [hovered, setHovered] = useState<boolean>(false);
|
||||
const [direction, setDirection] = useState<Direction>("TOP");
|
||||
|
||||
const rotateDirection = (currentDirection: Direction): Direction => {
|
||||
const directions: Direction[] = ["TOP", "LEFT", "BOTTOM", "RIGHT"];
|
||||
const currentIndex = directions.indexOf(currentDirection);
|
||||
const nextIndex = clockwise
|
||||
? (currentIndex - 1 + directions.length) % directions.length
|
||||
: (currentIndex + 1) % directions.length;
|
||||
return directions[nextIndex];
|
||||
};
|
||||
|
||||
const movingMap: Record<Direction, string> = {
|
||||
TOP: "radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||
LEFT: "radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||
BOTTOM:
|
||||
"radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||
RIGHT:
|
||||
"radial-gradient(16.2% 41.199999999999996% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||
};
|
||||
|
||||
const highlight =
|
||||
"radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)";
|
||||
|
||||
useEffect(() => {
|
||||
if (!hovered) {
|
||||
const interval = setInterval(() => {
|
||||
setDirection((prevState) => rotateDirection(prevState));
|
||||
}, duration * 1000);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [hovered]);
|
||||
return (
|
||||
<Tag
|
||||
onMouseEnter={(event: React.MouseEvent<HTMLDivElement>) => {
|
||||
setHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
className={cn(
|
||||
"relative flex rounded-full border content-center bg-black/20 hover:bg-black/10 transition duration-500 dark:bg-white/20 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit",
|
||||
containerClassName,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"w-auto text-white z-10 bg-black px-4 py-2 rounded-[inherit]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<motion.div
|
||||
className={cn(
|
||||
"flex-none inset-0 overflow-hidden absolute z-0 rounded-[inherit]",
|
||||
)}
|
||||
style={{
|
||||
filter: "blur(2px)",
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
initial={{ background: movingMap[direction] }}
|
||||
animate={{
|
||||
background: hovered
|
||||
? [movingMap[direction], highlight]
|
||||
: movingMap[direction],
|
||||
}}
|
||||
transition={{ ease: "linear", duration: duration ?? 1 }}
|
||||
/>
|
||||
<div className="bg-black absolute z-1 flex-none inset-[2px] rounded-[100px]" />
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
69
components/ui/input.tsx
Normal file
69
components/ui/input.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as React from "react";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, errorMessage, type, ...props }, ref) => {
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
// bg-gray
|
||||
"flex h-10 w-full rounded-md bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<span className="text-sm text-red-600 text-secondary-foreground">
|
||||
{errorMessage}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
const NumberInput = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, errorMessage, ...props }, ref) => {
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
className={cn("text-left", className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
value={props.value === undefined ? undefined : String(props.value)}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === "") {
|
||||
props.onChange?.(e);
|
||||
} else {
|
||||
const number = Number.parseInt(value, 10);
|
||||
if (!Number.isNaN(number)) {
|
||||
const syntheticEvent = {
|
||||
...e,
|
||||
target: {
|
||||
...e.target,
|
||||
value: number,
|
||||
},
|
||||
};
|
||||
props.onChange?.(
|
||||
syntheticEvent as unknown as React.ChangeEvent<HTMLInputElement>,
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
NumberInput.displayName = "NumberInput";
|
||||
|
||||
export { Input, NumberInput };
|
||||
160
components/ui/select.tsx
Normal file
160
components/ui/select.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
));
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
));
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
};
|
||||
29
components/ui/switch.tsx
Normal file
29
components/ui/switch.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
));
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||
|
||||
export { Switch };
|
||||
53
components/ui/tabs.tsx
Normal file
53
components/ui/tabs.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
12
i18n/request.tsx
Normal file
12
i18n/request.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { notFound } from "next/navigation";
|
||||
import { routing } from "./routing";
|
||||
|
||||
export default getRequestConfig(async ({ locale }) => {
|
||||
// Validate that the incoming `locale` parameter is valid
|
||||
if (!routing.locales.includes(locale as any)) notFound();
|
||||
|
||||
return {
|
||||
messages: (await import(`../locales/${locale}.json`)).default,
|
||||
};
|
||||
});
|
||||
16
i18n/routing.ts
Normal file
16
i18n/routing.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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", "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);
|
||||
176
locales/en.json
Normal file
176
locales/en.json
Normal file
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"navigation": {
|
||||
"features": "Features",
|
||||
"faqs": "FAQ",
|
||||
"docs": "Docs",
|
||||
"pricing": "Pricing",
|
||||
"support": "Support",
|
||||
"dashboard": "Dashboard",
|
||||
"discord": "Discord",
|
||||
"i18nButtonPlaceholder": "Language",
|
||||
"i18nEn": "English",
|
||||
"i18nZh-Hans": "简体中文"
|
||||
},
|
||||
"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",
|
||||
"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": "Compose",
|
||||
"composeDes": "Native Docker Compose support for manage complex applications and services with ease.",
|
||||
"multinode": "Multinode",
|
||||
"multinodeDes": "Scale applications to multiples nodes using docker swarm to manage the cluster.",
|
||||
"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."
|
||||
},
|
||||
"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",
|
||||
"des": "Streamline your deployments with our PaaS. Effortlessly manage Docker containers and traffic with Traefik. Boost your infrastructure's efficiency and security today",
|
||||
"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.",
|
||||
"q3": "Is Dokploy free?",
|
||||
"a3": "Yes, Dokploy is totally free. You can use it for personal projects, small teams, or even for large-scale applications.",
|
||||
"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. We are currently focused on addressing existing bugs and plan to release new features soon.",
|
||||
"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 where you can share ideas, ask for help, and connect with other users.",
|
||||
"q9": "What types of applications can I deploy with Dokploy?",
|
||||
"a9": "Dokploy supports a variety of applications, including those built with Docker, as well as applications from any Git repository, offering custom builds with Nixpacks, Dockerfiles, or 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 directly from the dashboard."
|
||||
},
|
||||
"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/get-started/introduction",
|
||||
"install": "https://docs.dokploy.com/en/docs/core/get-started/introduction"
|
||||
}
|
||||
},
|
||||
"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": "Manager your own infrastructure installing dokploy ui in your own server."
|
||||
},
|
||||
"features": {
|
||||
"f1": "Complete Flexibility: Install Dokploy UI on your own infrastructure",
|
||||
"f2": "Unlimited Deployments",
|
||||
"f3": "Self-hosted Infrastructure",
|
||||
"f4": "Community Support",
|
||||
"f5": "Access to Core Features",
|
||||
"f6": "Dokploy Integration",
|
||||
"f7": "Basic Backups",
|
||||
"f8": "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": "Priority Support",
|
||||
"f3": "Future-Proof Features"
|
||||
},
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
176
locales/zh-Hans.json
Normal file
176
locales/zh-Hans.json
Normal file
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"navigation": {
|
||||
"features": "特性",
|
||||
"faqs": "FAQ",
|
||||
"docs": "文档",
|
||||
"pricing": "价格",
|
||||
"support": "赞助",
|
||||
"dashboard": "控制台",
|
||||
"discord": "Discord",
|
||||
"i18nButtonPlaceholder": "语言",
|
||||
"i18nEn": "English",
|
||||
"i18nZh-Hans": "简体中文"
|
||||
},
|
||||
"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": "项目",
|
||||
"projectsDes": "您所有的项目和所需的一切信息都将归集一处。",
|
||||
"applications": "应用和数据库",
|
||||
"applicationsDes": "Dokploy 通过集中管理您的应用和数据库来提升安全性和效率,并大大简化了跨基础设施的访问和管理。",
|
||||
"compose": "Docker Compose",
|
||||
"composeDes": "Dokploy 提供对复杂应用和服务的原生 Docker Compose 支持。",
|
||||
"multinode": "多节点",
|
||||
"multinodeDes": "Dokploy 使用 Docker Swarm 来管理集群,可自动将应用扩容至多个节点。",
|
||||
"monitoring": "监控",
|
||||
"monitoringDes": "随时掌控你的系统性能和应用健康,确保服务无间断运行。",
|
||||
"backups": "备份",
|
||||
"backupsDes": "使用 Dokploy 的自动备份功能来保护您的数据,并在需要时极速还原。"
|
||||
},
|
||||
"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。我们还提供方便的图形化管理工具以供使用。"
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "版权属于 © {year} Dokploy, 保留所有权利"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"title": "糟糕!看起来你迷路了。",
|
||||
"des": "让我们送你",
|
||||
"action": "回家"
|
||||
},
|
||||
"Link": {
|
||||
"docs": {
|
||||
"intro": "https://docs.dokploy.com/cn/docs/core/get-started/introduction",
|
||||
"install": "https://docs.dokploy.com/cn/docs/core/get-started/introduction"
|
||||
}
|
||||
},
|
||||
"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": "优先体验先行功能"
|
||||
},
|
||||
"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 后自行修改以用于您的私人需求。"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
middleware.ts
Normal file
9
middleware.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import createMiddleware from "next-intl/middleware";
|
||||
import { routing } from "./i18n/routing";
|
||||
|
||||
export default createMiddleware(routing);
|
||||
|
||||
export const config = {
|
||||
// Match only internationalized pathnames
|
||||
matcher: ["/((?!_next|.*\\..*).*)"],
|
||||
};
|
||||
@@ -1,8 +1,15 @@
|
||||
const createNextIntlPlugin = require("next-intl/plugin");
|
||||
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true
|
||||
}
|
||||
}
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig
|
||||
module.exports = withNextIntl(nextConfig);
|
||||
|
||||
4888
package-lock.json
generated
4888
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
81
package.json
81
package.json
@@ -1,41 +1,44 @@
|
||||
{
|
||||
"name": "dokploy-website",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"browserslist": "defaults, not ie <= 11",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@types/node": "20.4.6",
|
||||
"@types/react": "18.2.18",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.0.24",
|
||||
"lucide-react": "0.364.0",
|
||||
"next": "14.2.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.7.0",
|
||||
"eslint": "8.45.0",
|
||||
"eslint-config-next": "13.4.16",
|
||||
"prettier": "^3.0.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.2"
|
||||
}
|
||||
"name": "website",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"browserslist": "defaults, not ie <= 11",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@types/node": "20.4.6",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.0.24",
|
||||
"lucide-react": "0.364.0",
|
||||
"next": "14.2.2",
|
||||
"next-intl": "^3.19.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "5.1.6",
|
||||
"@radix-ui/react-tabs": "1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.7.0",
|
||||
"@types/react": "18.3.5",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"prettier": "^3.0.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
4272
pnpm-lock.yaml
generated
4272
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** @type {import('prettier').Options} */
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
semi: false,
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
}
|
||||
singleQuote: true,
|
||||
semi: false,
|
||||
tabWidth: 4,
|
||||
useTabs: true,
|
||||
plugins: ["prettier-plugin-tailwindcss"],
|
||||
};
|
||||
|
||||
201
public/canary.sh
201
public/canary.sh
@@ -1,92 +1,139 @@
|
||||
#!/bin/bash
|
||||
install_dokploy() {
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if is Mac OS
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if is Mac OS
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# check if is running inside a container
|
||||
if [ -f /.dockerenv ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if is running inside a container
|
||||
if [ -f /.dockerenv ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if something is running on port 80
|
||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 80" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if something is running on port 80
|
||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 80" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if something is running on port 443
|
||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 443" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if something is running on port 443
|
||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 443" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
|
||||
get_ip() {
|
||||
# Try to get IPv4
|
||||
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||
|
||||
if [ -n "$ipv4" ]; then
|
||||
echo "$ipv4"
|
||||
else
|
||||
# Try to get IPv6
|
||||
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||
if [ -n "$ipv6" ]; then
|
||||
echo "$ipv6"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||
|
||||
docker swarm init --advertise-addr $advertise_addr
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Swarm initialized"
|
||||
|
||||
docker network rm -f dokploy-network 2>/dev/null
|
||||
docker network create --driver overlay --attachable dokploy-network
|
||||
|
||||
echo "Network created"
|
||||
|
||||
mkdir -p /etc/dokploy
|
||||
|
||||
chmod 777 /etc/dokploy
|
||||
|
||||
docker pull dokploy/dokploy:canary
|
||||
|
||||
# Installation
|
||||
docker service create \
|
||||
--name dokploy \
|
||||
--replicas 1 \
|
||||
--network dokploy-network \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||
--publish published=3000,target=3000,mode=host \
|
||||
--update-parallelism 1 \
|
||||
--update-order stop-first \
|
||||
--constraint 'node.role == manager' \
|
||||
-e RELEASE_TAG=canary \
|
||||
-e ADVERTISE_ADDR=$advertise_addr \
|
||||
dokploy/dokploy:canary
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
BLUE="\033[0;34m"
|
||||
NC="\033[0m" # No Color
|
||||
|
||||
format_ip_for_url() {
|
||||
local ip="$1"
|
||||
if echo "$ip" | grep -q ':'; then
|
||||
# IPv6
|
||||
echo "[${ip}]"
|
||||
else
|
||||
# IPv4
|
||||
echo "${ip}"
|
||||
fi
|
||||
}
|
||||
|
||||
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||
echo ""
|
||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||
echo ""
|
||||
}
|
||||
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
update_dokploy() {
|
||||
echo "Updating Dokploy..."
|
||||
|
||||
# Pull the latest canary image
|
||||
docker pull dokploy/dokploy:canary
|
||||
|
||||
# Update the service
|
||||
docker service update --image dokploy/dokploy:canary dokploy
|
||||
|
||||
echo "Dokploy has been updated to the latest canary version."
|
||||
}
|
||||
|
||||
# Main script execution
|
||||
if [ "$1" = "update" ]; then
|
||||
update_dokploy
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
advertise_addr=$(curl -s ifconfig.me)
|
||||
|
||||
docker swarm init --advertise-addr $advertise_addr
|
||||
|
||||
echo "Swarm initialized"
|
||||
|
||||
docker network rm -f dokploy-network 2>/dev/null
|
||||
docker network create --driver overlay --attachable dokploy-network
|
||||
|
||||
echo "Network created"
|
||||
|
||||
mkdir -p /etc/dokploy
|
||||
|
||||
chmod -R 777 /etc/dokploy
|
||||
|
||||
docker pull dokploy/dokploy:canary
|
||||
|
||||
# Installation
|
||||
docker service create \
|
||||
--name dokploy \
|
||||
--replicas 1 \
|
||||
--network dokploy-network \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||
--publish published=3000,target=3000,mode=host \
|
||||
--update-parallelism 1 \
|
||||
--update-order stop-first \
|
||||
--constraint 'node.role == manager' \
|
||||
-e RELEASE_TAG=canary \
|
||||
dokploy/dokploy:canary
|
||||
|
||||
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
BLUE="\033[0;34m"
|
||||
NC="\033[0m" # No Color
|
||||
|
||||
|
||||
echo ""
|
||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||
printf "${YELLOW}Please go to http://${advertise_addr}:3000${NC}\n\n"
|
||||
echo ""
|
||||
install_dokploy
|
||||
fi
|
||||
@@ -1,93 +1,139 @@
|
||||
#!/bin/bash
|
||||
install_dokploy() {
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if is Mac OS
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if is Mac OS
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# check if is running inside a container
|
||||
if [ -f /.dockerenv ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if is running inside a container
|
||||
if [ -f /.dockerenv ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if something is running on port 80
|
||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 80" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if something is running on port 80
|
||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 80" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if something is running on port 443
|
||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 443" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if something is running on port 443
|
||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 443" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
|
||||
get_ip() {
|
||||
# Try to get IPv4
|
||||
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||
|
||||
if [ -n "$ipv4" ]; then
|
||||
echo "$ipv4"
|
||||
else
|
||||
# Try to get IPv6
|
||||
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||
if [ -n "$ipv6" ]; then
|
||||
echo "$ipv6"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||
|
||||
docker swarm init --advertise-addr $advertise_addr
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Swarm initialized"
|
||||
|
||||
docker network rm -f dokploy-network 2>/dev/null
|
||||
docker network create --driver overlay --attachable dokploy-network
|
||||
|
||||
echo "Network created"
|
||||
|
||||
mkdir -p /etc/dokploy
|
||||
|
||||
chmod 777 /etc/dokploy
|
||||
|
||||
docker pull dokploy/dokploy:feature
|
||||
|
||||
# Installation
|
||||
docker service create \
|
||||
--name dokploy \
|
||||
--replicas 1 \
|
||||
--network dokploy-network \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||
--publish published=3000,target=3000,mode=host \
|
||||
--update-parallelism 1 \
|
||||
--update-order stop-first \
|
||||
--constraint 'node.role == manager' \
|
||||
-e RELEASE_TAG=feature \
|
||||
-e ADVERTISE_ADDR=$advertise_addr \
|
||||
dokploy/dokploy:feature
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
BLUE="\033[0;34m"
|
||||
NC="\033[0m" # No Color
|
||||
|
||||
format_ip_for_url() {
|
||||
local ip="$1"
|
||||
if echo "$ip" | grep -q ':'; then
|
||||
# IPv6
|
||||
echo "[${ip}]"
|
||||
else
|
||||
# IPv4
|
||||
echo "${ip}"
|
||||
fi
|
||||
}
|
||||
|
||||
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||
echo ""
|
||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||
echo ""
|
||||
}
|
||||
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
update_dokploy() {
|
||||
echo "Updating Dokploy..."
|
||||
|
||||
# Pull the latest feature image
|
||||
docker pull dokploy/dokploy:feature
|
||||
|
||||
# Update the service
|
||||
docker service update --image dokploy/dokploy:feature dokploy
|
||||
|
||||
echo "Dokploy has been updated to the latest feature version."
|
||||
}
|
||||
|
||||
# Main script execution
|
||||
if [ "$1" = "update" ]; then
|
||||
update_dokploy
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
advertise_addr=$(curl -s ifconfig.me)
|
||||
|
||||
docker swarm init --advertise-addr $advertise_addr
|
||||
|
||||
echo "Swarm initialized"
|
||||
|
||||
docker network rm -f dokploy-network 2>/dev/null
|
||||
docker network create --driver overlay --attachable dokploy-network
|
||||
|
||||
echo "Network created"
|
||||
|
||||
mkdir -p /etc/dokploy
|
||||
|
||||
chmod -R 777 /etc/dokploy
|
||||
|
||||
docker pull dokploy/dokploy:feature
|
||||
|
||||
# Installation
|
||||
docker service create \
|
||||
--name dokploy \
|
||||
--replicas 1 \
|
||||
--network dokploy-network \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||
--publish published=3000,target=3000,mode=host \
|
||||
--update-parallelism 1 \
|
||||
--update-order stop-first \
|
||||
--constraint 'node.role == manager' \
|
||||
-e RELEASE_TAG=feature \
|
||||
dokploy/dokploy:feature
|
||||
|
||||
|
||||
public_ip=$(hostname -I | awk '{print $1}')
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
BLUE="\033[0;34m"
|
||||
NC="\033[0m" # No Color
|
||||
|
||||
|
||||
echo ""
|
||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||
printf "${YELLOW}Please go to http://${public_ip}:3000${NC}\n\n"
|
||||
echo ""
|
||||
install_dokploy
|
||||
fi
|
||||
@@ -1,118 +1,136 @@
|
||||
#!/bin/bash
|
||||
install_dokploy() {
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if is Mac OS
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if is Mac OS
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if is running inside a container
|
||||
if [ -f /.dockerenv ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if something is running on port 80
|
||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 80" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if is running inside a container
|
||||
if [ -f /.dockerenv ]; then
|
||||
echo "This script must be run on Linux" >&2
|
||||
exit 1
|
||||
fi
|
||||
# check if something is running on port 443
|
||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 443" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if something is running on port 80
|
||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 80" >&2
|
||||
exit 1
|
||||
fi
|
||||
command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# check if something is running on port 443
|
||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 443" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
else
|
||||
curl -sSL https://get.docker.com | sh
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
|
||||
get_ip() {
|
||||
# Try to get IPv4
|
||||
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||
|
||||
if [ -n "$ipv4" ]; then
|
||||
echo "$ipv4"
|
||||
if command_exists docker; then
|
||||
echo "Docker already installed"
|
||||
else
|
||||
# Try to get IPv6
|
||||
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||
if [ -n "$ipv6" ]; then
|
||||
echo "$ipv6"
|
||||
curl -sSL https://get.docker.com | sh
|
||||
fi
|
||||
|
||||
docker swarm leave --force 2>/dev/null
|
||||
|
||||
get_ip() {
|
||||
# Try to get IPv4
|
||||
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||
|
||||
if [ -n "$ipv4" ]; then
|
||||
echo "$ipv4"
|
||||
else
|
||||
# Try to get IPv6
|
||||
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||
if [ -n "$ipv6" ]; then
|
||||
echo "$ipv6"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||
|
||||
docker swarm init --advertise-addr $advertise_addr
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Swarm initialized"
|
||||
|
||||
docker network rm -f dokploy-network 2>/dev/null
|
||||
docker network create --driver overlay --attachable dokploy-network
|
||||
|
||||
echo "Network created"
|
||||
|
||||
mkdir -p /etc/dokploy
|
||||
|
||||
chmod 777 /etc/dokploy
|
||||
|
||||
docker pull dokploy/dokploy:latest
|
||||
|
||||
# Installation
|
||||
docker service create \
|
||||
--name dokploy \
|
||||
--replicas 1 \
|
||||
--network dokploy-network \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||
--publish published=3000,target=3000,mode=host \
|
||||
--update-parallelism 1 \
|
||||
--update-order stop-first \
|
||||
--constraint 'node.role == manager' \
|
||||
-e ADVERTISE_ADDR=$advertise_addr \
|
||||
dokploy/dokploy:latest
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
BLUE="\033[0;34m"
|
||||
NC="\033[0m" # No Color
|
||||
|
||||
format_ip_for_url() {
|
||||
local ip="$1"
|
||||
if echo "$ip" | grep -q ':'; then
|
||||
# IPv6
|
||||
echo "[${ip}]"
|
||||
else
|
||||
# IPv4
|
||||
echo "${ip}"
|
||||
fi
|
||||
}
|
||||
|
||||
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||
echo ""
|
||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||
}
|
||||
|
||||
advertise_addr=$(get_ip)
|
||||
update_dokploy() {
|
||||
echo "Updating Dokploy..."
|
||||
|
||||
# Pull the latest image
|
||||
docker pull dokploy/dokploy:latest
|
||||
|
||||
docker swarm init --advertise-addr $advertise_addr
|
||||
# Update the service
|
||||
docker service update --image dokploy/dokploy:latest dokploy
|
||||
|
||||
echo "Swarm initialized"
|
||||
|
||||
docker network rm -f dokploy-network 2>/dev/null
|
||||
docker network create --driver overlay --attachable dokploy-network
|
||||
|
||||
echo "Network created"
|
||||
|
||||
mkdir -p /etc/dokploy
|
||||
|
||||
chmod -R 777 /etc/dokploy
|
||||
|
||||
docker pull dokploy/dokploy:latest
|
||||
|
||||
# Installation
|
||||
docker service create \
|
||||
--name dokploy \
|
||||
--replicas 1 \
|
||||
--network dokploy-network \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||
--publish published=3000,target=3000,mode=host \
|
||||
--update-parallelism 1 \
|
||||
--update-order stop-first \
|
||||
--constraint 'node.role == manager' \
|
||||
dokploy/dokploy:latest
|
||||
|
||||
|
||||
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
BLUE="\033[0;34m"
|
||||
NC="\033[0m" # No Color
|
||||
|
||||
format_ip_for_url() {
|
||||
local ip="$1"
|
||||
if echo "$ip" | grep -q ':'; then
|
||||
# IPv6
|
||||
echo "[${ip}]"
|
||||
else
|
||||
# IPv4
|
||||
echo "${ip}"
|
||||
fi
|
||||
echo "Dokploy has been updated to the latest version."
|
||||
}
|
||||
|
||||
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||
echo ""
|
||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||
echo ""
|
||||
# Main script execution
|
||||
if [ "$1" = "update" ]; then
|
||||
update_dokploy
|
||||
else
|
||||
install_dokploy
|
||||
fi
|
||||
|
||||
BIN
public/og.png
BIN
public/og.png
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 443 KiB |
@@ -1,5 +1,5 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
import headlessuiPlugin from "@headlessui/tailwindcss";
|
||||
import type { Config } from "tailwindcss";
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
@@ -10,9 +10,6 @@ const config = {
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
// fontFamily: {
|
||||
// sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
// },
|
||||
fontSize: {
|
||||
xs: ["0.75rem", { lineHeight: "1rem" }],
|
||||
sm: ["0.875rem", { lineHeight: "1.5rem" }],
|
||||
@@ -75,7 +72,6 @@ const config = {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
|
||||
"4xl": "2rem",
|
||||
},
|
||||
fontFamily: {
|
||||
@@ -84,17 +80,34 @@ const config = {
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
from: {
|
||||
height: "0",
|
||||
},
|
||||
to: {
|
||||
height: "var(--radix-accordion-content-height)",
|
||||
},
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
from: {
|
||||
height: "var(--radix-accordion-content-height)",
|
||||
},
|
||||
to: {
|
||||
height: "0",
|
||||
},
|
||||
},
|
||||
"shiny-text": {
|
||||
"0%, 90%, 100%": {
|
||||
"background-position": "calc(-100% - var(--shiny-width)) 0",
|
||||
},
|
||||
"30%, 60%": {
|
||||
"background-position": "calc(100% + var(--shiny-width)) 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"shiny-text": "shiny-text 8s infinite",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user