mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-06 22:45:24 +02:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
029eed7755 | ||
|
|
f017536396 | ||
|
|
ba5505cf81 | ||
|
|
5ff5da9ff9 | ||
|
|
e96a8ea4ad | ||
|
|
42864d2472 | ||
|
|
ae25ea265c | ||
|
|
0755f28307 | ||
|
|
3d10d48425 | ||
|
|
6e79183f6a | ||
|
|
2318fb062a | ||
|
|
14b4bc9d85 | ||
|
|
4c72f1894c | ||
|
|
ebd632df04 | ||
|
|
4878ed2b6f | ||
|
|
1d3ab2bafa | ||
|
|
7cb7cfa2a8 | ||
|
|
6009697710 | ||
|
|
6be86b49bb | ||
|
|
1e81244e0b | ||
|
|
f659ea463d | ||
|
|
caf57276a4 | ||
|
|
15c6c7e657 | ||
|
|
8be0db385a |
21
.github/pull_request_template.md
vendored
Normal file
21
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
## What is this PR about?
|
||||||
|
|
||||||
|
Please describe in a short paragraph what this PR is about.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
Before submitting this PR, please make sure that:
|
||||||
|
|
||||||
|
- [ ] You created a dedicated branch based on the `canary` branch.
|
||||||
|
- [ ] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
|
||||||
|
- [ ] You have tested this PR in your local instance.
|
||||||
|
|
||||||
|
## Issues related (if applicable)
|
||||||
|
|
||||||
|
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`, `fixes #ISSUE_NUMBER`, `resolves #ISSUE_NUMBER`
|
||||||
|
|
||||||
|
Example: `closes #123`
|
||||||
|
|
||||||
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
|
If you include a video or screenshot, would be awesome so we can see the changes in action.
|
||||||
4
.github/workflows/create-pr.yml
vendored
4
.github/workflows/create-pr.yml
vendored
@@ -19,17 +19,14 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Get version from package.json
|
- name: Get version from package.json
|
||||||
id: package_version
|
|
||||||
run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV
|
run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get latest GitHub tag
|
- name: Get latest GitHub tag
|
||||||
id: latest_tag
|
|
||||||
run: |
|
run: |
|
||||||
LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1)
|
LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1)
|
||||||
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
||||||
echo $LATEST_TAG
|
echo $LATEST_TAG
|
||||||
- name: Compare versions
|
- name: Compare versions
|
||||||
id: compare_versions
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then
|
if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then
|
||||||
VERSION_CHANGED="true"
|
VERSION_CHANGED="true"
|
||||||
@@ -42,7 +39,6 @@ jobs:
|
|||||||
echo "Latest tag: ${{ env.LATEST_TAG }}"
|
echo "Latest tag: ${{ env.LATEST_TAG }}"
|
||||||
echo "Version changed: $VERSION_CHANGED"
|
echo "Version changed: $VERSION_CHANGED"
|
||||||
- name: Check if a PR already exists
|
- name: Check if a PR already exists
|
||||||
id: check_pr
|
|
||||||
run: |
|
run: |
|
||||||
PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length')
|
PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length')
|
||||||
echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV
|
echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ pnpm run dokploy:dev
|
|||||||
|
|
||||||
Go to http://localhost:3000 to see the development server
|
Go to http://localhost:3000 to see the development server
|
||||||
|
|
||||||
Note: this project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off.
|
> [!NOTE]
|
||||||
|
> This project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -117,10 +118,10 @@ In the case you lost your password, you can reset it using the following command
|
|||||||
pnpm run reset-password
|
pnpm run reset-password
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to test the webhooks on development mode using localtunnel, make sure to install `localtunnel`
|
If you want to test the webhooks on development mode using localtunnel, make sure to install [`localtunnel`](https://localtunnel.app/)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bunx lt --port 3000
|
pnpm dlx localtunnel --port 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
If you run into permission issues of docker run the following command
|
If you run into permission issues of docker run the following command
|
||||||
@@ -152,7 +153,7 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.
|
|||||||
|
|
||||||
## Pull Request
|
## Pull Request
|
||||||
|
|
||||||
- The `main` branch is the source of truth and should always reflect the latest stable release.
|
- The `canary` branch is the source of truth and should always reflect the latest stable release.
|
||||||
- Create a new branch for each feature or bug fix.
|
- Create a new branch for each feature or bug fix.
|
||||||
- Make sure to add tests for your changes.
|
- Make sure to add tests for your changes.
|
||||||
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
|
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
|
||||||
@@ -161,6 +162,12 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.
|
|||||||
- If your pull request fixes an open issue, please reference the issue in the pull request description.
|
- If your pull request fixes an open issue, please reference the issue in the pull request description.
|
||||||
- Once your pull request is merged, you will be automatically added as a contributor to the project.
|
- Once your pull request is merged, you will be automatically added as a contributor to the project.
|
||||||
|
|
||||||
|
**Important Considerations for Pull Requests:**
|
||||||
|
|
||||||
|
- **Focus and Scope:** Each Pull Request should ideally address a single, well-defined problem or introduce one new feature. This greatly facilitates review and reduces the chances of introducing unintended side effects.
|
||||||
|
- **Avoid Unfocused Changes:** Please avoid submitting Pull Requests that contain only minor changes such as whitespace adjustments, IDE-generated formatting, or removal of unused variables, unless these are part of a larger, clearly defined refactor or a dedicated "cleanup" Pull Request that addresses a specific `good first issue` or maintenance task.
|
||||||
|
- **Issue Association:** For any significant change, it's highly recommended to open an issue first to discuss the proposed solution with the community and maintainers. This ensures alignment and avoids duplicated effort. If your PR resolves an existing issue, please link it in the description (e.g., `Fixes #123`, `Closes #456`).
|
||||||
|
|
||||||
Thank you for your contribution!
|
Thank you for your contribution!
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { HelpCircle, Settings } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -26,12 +32,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { HelpCircle, Settings } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const HealthCheckSwarmSchema = z
|
const HealthCheckSwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -181,21 +181,38 @@ const addSwarmSettings = z.object({
|
|||||||
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
|
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
id: string;
|
||||||
|
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddSwarmSettings = ({ applicationId }: Props) => {
|
export const AddSwarmSettings = ({ id, type }: Props) => {
|
||||||
const { data, refetch } = api.application.one.useQuery(
|
const queryMap = {
|
||||||
{
|
postgres: () =>
|
||||||
applicationId,
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
},
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
{
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
enabled: !!applicationId,
|
mariadb: () =>
|
||||||
},
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
);
|
application: () =>
|
||||||
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
};
|
||||||
|
const { data, refetch } = queryMap[type]
|
||||||
|
? queryMap[type]()
|
||||||
|
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||||
|
|
||||||
const { mutateAsync, isError, error, isLoading } =
|
const mutationMap = {
|
||||||
api.application.update.useMutation();
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
|
redis: () => api.redis.update.useMutation(),
|
||||||
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
|
application: () => api.application.update.useMutation(),
|
||||||
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutateAsync, isError, error, isLoading } = mutationMap[type]
|
||||||
|
? mutationMap[type]()
|
||||||
|
: api.mongo.update.useMutation();
|
||||||
|
|
||||||
const form = useForm<AddSwarmSettings>({
|
const form = useForm<AddSwarmSettings>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -244,7 +261,12 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async (data: AddSwarmSettings) => {
|
const onSubmit = async (data: AddSwarmSettings) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId: id || "",
|
||||||
|
postgresId: id || "",
|
||||||
|
redisId: id || "",
|
||||||
|
mysqlId: id || "",
|
||||||
|
mariadbId: id || "",
|
||||||
|
mongoId: id || "",
|
||||||
healthCheckSwarm: data.healthCheckSwarm,
|
healthCheckSwarm: data.healthCheckSwarm,
|
||||||
restartPolicySwarm: data.restartPolicySwarm,
|
restartPolicySwarm: data.restartPolicySwarm,
|
||||||
placementSwarm: data.placementSwarm,
|
placementSwarm: data.placementSwarm,
|
||||||
@@ -270,7 +292,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
Swarm Settings
|
Swarm Settings
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-5xl p-0">
|
<DialogContent className="sm:max-w-5xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Swarm Settings</DialogTitle>
|
<DialogTitle>Swarm Settings</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -278,10 +300,10 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
<div className="px-4">
|
<div>
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info">
|
||||||
Changing settings such as placements may cause the logs/monitoring
|
Changing settings such as placements may cause the logs/monitoring,
|
||||||
to be unavailable.
|
backups and other features to be unavailable.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -289,13 +311,13 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
<form
|
<form
|
||||||
id="hook-form-add-permissions"
|
id="hook-form-add-permissions"
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative"
|
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative mt-4"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="healthCheckSwarm"
|
name="healthCheckSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Health Check</FormLabel>
|
<FormLabel>Health Check</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -351,7 +373,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="restartPolicySwarm"
|
name="restartPolicySwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Restart Policy</FormLabel>
|
<FormLabel>Restart Policy</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -405,7 +427,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="placementSwarm"
|
name="placementSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Placement</FormLabel>
|
<FormLabel>Placement</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -471,7 +493,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="updateConfigSwarm"
|
name="updateConfigSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Update Config</FormLabel>
|
<FormLabel>Update Config</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -529,7 +551,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="rollbackConfigSwarm"
|
name="rollbackConfigSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Rollback Config</FormLabel>
|
<FormLabel>Rollback Config</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -587,7 +609,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="modeSwarm"
|
name="modeSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Mode</FormLabel>
|
<FormLabel>Mode</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -650,7 +672,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="networkSwarm"
|
name="networkSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Network</FormLabel>
|
<FormLabel>Network</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -709,7 +731,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="labelsSwarm"
|
name="labelsSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Labels</FormLabel>
|
<FormLabel>Labels</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Server } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -26,43 +33,57 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Server } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { AddSwarmSettings } from "./modify-swarm-settings";
|
import { AddSwarmSettings } from "./modify-swarm-settings";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
id: string;
|
||||||
|
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddRedirectchema = z.object({
|
const AddRedirectchema = z.object({
|
||||||
replicas: z.number().min(1, "Replicas must be at least 1"),
|
replicas: z.number().min(1, "Replicas must be at least 1"),
|
||||||
registryId: z.string(),
|
registryId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddCommand = z.infer<typeof AddRedirectchema>;
|
type AddCommand = z.infer<typeof AddRedirectchema>;
|
||||||
|
|
||||||
export const ShowClusterSettings = ({ applicationId }: Props) => {
|
export const ShowClusterSettings = ({ id, type }: Props) => {
|
||||||
const { data } = api.application.one.useQuery(
|
const queryMap = {
|
||||||
{
|
postgres: () =>
|
||||||
applicationId,
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
},
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
{ enabled: !!applicationId },
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
);
|
mariadb: () =>
|
||||||
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
|
application: () =>
|
||||||
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
};
|
||||||
|
const { data, refetch } = queryMap[type]
|
||||||
|
? queryMap[type]()
|
||||||
|
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||||
const { data: registries } = api.registry.all.useQuery();
|
const { data: registries } = api.registry.all.useQuery();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const mutationMap = {
|
||||||
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
|
redis: () => api.redis.update.useMutation(),
|
||||||
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
|
application: () => api.application.update.useMutation(),
|
||||||
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = api.application.update.useMutation();
|
const { mutateAsync, isLoading } = mutationMap[type]
|
||||||
|
? mutationMap[type]()
|
||||||
|
: api.mongo.update.useMutation();
|
||||||
|
|
||||||
const form = useForm<AddCommand>({
|
const form = useForm<AddCommand>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
registryId: data?.registryId || "",
|
...(type === "application" && data && "registryId" in data
|
||||||
|
? {
|
||||||
|
registryId: data?.registryId || "",
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
replicas: data?.replicas || 1,
|
replicas: data?.replicas || 1,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(AddRedirectchema),
|
resolver: zodResolver(AddRedirectchema),
|
||||||
@@ -71,7 +92,11 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.command) {
|
if (data?.command) {
|
||||||
form.reset({
|
form.reset({
|
||||||
registryId: data?.registryId || "",
|
...(type === "application" && data && "registryId" in data
|
||||||
|
? {
|
||||||
|
registryId: data?.registryId || "",
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
replicas: data?.replicas || 1,
|
replicas: data?.replicas || 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -79,18 +104,25 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async (data: AddCommand) => {
|
const onSubmit = async (data: AddCommand) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId: id || "",
|
||||||
registryId:
|
postgresId: id || "",
|
||||||
data?.registryId === "none" || !data?.registryId
|
redisId: id || "",
|
||||||
? null
|
mysqlId: id || "",
|
||||||
: data?.registryId,
|
mariadbId: id || "",
|
||||||
|
mongoId: id || "",
|
||||||
|
...(type === "application"
|
||||||
|
? {
|
||||||
|
registryId:
|
||||||
|
data?.registryId === "none" || !data?.registryId
|
||||||
|
? null
|
||||||
|
: data?.registryId,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
replicas: data?.replicas,
|
replicas: data?.replicas,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Command Updated");
|
toast.success("Command Updated");
|
||||||
await utils.application.one.invalidate({
|
await refetch();
|
||||||
applicationId,
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error updating the command");
|
toast.error("Error updating the command");
|
||||||
@@ -103,10 +135,10 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-xl">Cluster Settings</CardTitle>
|
<CardTitle className="text-xl">Cluster Settings</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Add the registry and the replicas of the application
|
Modify swarm settings for the service.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<AddSwarmSettings applicationId={applicationId} />
|
<AddSwarmSettings id={id} type={type} />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info">
|
||||||
@@ -144,58 +176,62 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{registries && registries?.length === 0 ? (
|
{type === "application" && (
|
||||||
<div className="pt-10">
|
|
||||||
<div className="flex flex-col items-center gap-3">
|
|
||||||
<Server className="size-8 text-muted-foreground" />
|
|
||||||
<span className="text-base text-muted-foreground">
|
|
||||||
To use a cluster feature, you need to configure at least a
|
|
||||||
registry first. Please, go to{" "}
|
|
||||||
<Link
|
|
||||||
href="/dashboard/settings/cluster"
|
|
||||||
className="text-foreground"
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</Link>{" "}
|
|
||||||
to do so.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<FormField
|
{registries && registries?.length === 0 ? (
|
||||||
control={form.control}
|
<div className="pt-10">
|
||||||
name="registryId"
|
<div className="flex flex-col items-center gap-3">
|
||||||
render={({ field }) => (
|
<Server className="size-8 text-muted-foreground" />
|
||||||
<FormItem>
|
<span className="text-base text-muted-foreground">
|
||||||
<FormLabel>Select a registry</FormLabel>
|
To use a cluster feature, you need to configure at least
|
||||||
<Select
|
a registry first. Please, go to{" "}
|
||||||
onValueChange={field.onChange}
|
<Link
|
||||||
defaultValue={field.value}
|
href="/dashboard/settings/cluster"
|
||||||
>
|
className="text-foreground"
|
||||||
<SelectTrigger>
|
>
|
||||||
<SelectValue placeholder="Select a registry" />
|
Settings
|
||||||
</SelectTrigger>
|
</Link>{" "}
|
||||||
<SelectContent>
|
to do so.
|
||||||
<SelectGroup>
|
</span>
|
||||||
{registries?.map((registry) => (
|
</div>
|
||||||
<SelectItem
|
</div>
|
||||||
key={registry.registryId}
|
) : (
|
||||||
value={registry.registryId}
|
<>
|
||||||
>
|
<FormField
|
||||||
{registry.registryName}
|
control={form.control}
|
||||||
</SelectItem>
|
name="registryId"
|
||||||
))}
|
render={({ field }) => (
|
||||||
<SelectItem value={"none"}>None</SelectItem>
|
<FormItem>
|
||||||
<SelectLabel>
|
<FormLabel>Select a registry</FormLabel>
|
||||||
Registries ({registries?.length})
|
<Select
|
||||||
</SelectLabel>
|
onValueChange={field.onChange}
|
||||||
</SelectGroup>
|
defaultValue={field.value}
|
||||||
</SelectContent>
|
>
|
||||||
</Select>
|
<SelectTrigger>
|
||||||
</FormItem>
|
<SelectValue placeholder="Select a registry" />
|
||||||
)}
|
</SelectTrigger>
|
||||||
/>
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{registries?.map((registry) => (
|
||||||
|
<SelectItem
|
||||||
|
key={registry.registryId}
|
||||||
|
value={registry.registryId}
|
||||||
|
>
|
||||||
|
{registry.registryName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value={"none"}>None</SelectItem>
|
||||||
|
<SelectLabel>
|
||||||
|
Registries ({registries?.length})
|
||||||
|
</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm, useWatch } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -26,12 +32,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const AddPortSchema = z.object({
|
const AddPortSchema = z.object({
|
||||||
publishedPort: z.number().int().min(1).max(65535),
|
publishedPort: z.number().int().min(1).max(65535),
|
||||||
@@ -80,6 +80,11 @@ export const HandlePorts = ({
|
|||||||
resolver: zodResolver(AddPortSchema),
|
resolver: zodResolver(AddPortSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const publishMode = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "publishMode",
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset({
|
form.reset({
|
||||||
publishedPort: data?.publishedPort ?? 0,
|
publishedPort: data?.publishedPort ?? 0,
|
||||||
@@ -253,6 +258,16 @@ export const HandlePorts = ({
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{publishMode === "host" && (
|
||||||
|
<AlertBlock type="warning" className="mt-4">
|
||||||
|
<strong>Host Mode Limitation:</strong> When using Host publish
|
||||||
|
mode, Docker Swarm has limitations that prevent proper container
|
||||||
|
updates during deployments. Old containers may not be replaced
|
||||||
|
automatically. Consider using Ingress mode instead, or be prepared
|
||||||
|
to manually stop/start the application after deployments.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Loader2, Puzzle, RefreshCw } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -10,9 +13,6 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Loader2, Puzzle, RefreshCw } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
@@ -79,7 +79,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row gap-2 justify-end">
|
<div className="flex flex-row gap-2 justify-end my-4">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
import { Download as DownloadIcon, Loader2, Pause, Play } from "lucide-react";
|
||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Download as DownloadIcon, Loader2 } from "lucide-react";
|
|
||||||
import React, { useEffect, useRef } from "react";
|
|
||||||
import { LineCountFilter } from "./line-count-filter";
|
import { LineCountFilter } from "./line-count-filter";
|
||||||
import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter";
|
import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter";
|
||||||
import { StatusLogsFilter } from "./status-logs-filter";
|
import { StatusLogsFilter } from "./status-logs-filter";
|
||||||
import { TerminalLine } from "./terminal-line";
|
import { TerminalLine } from "./terminal-line";
|
||||||
import { type LogLine, getLogType, parseLogs } from "./utils";
|
import { getLogType, type LogLine, parseLogs } from "./utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
containerId: string;
|
containerId: string;
|
||||||
@@ -61,6 +62,9 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
const [showTimestamp, setShowTimestamp] = React.useState(true);
|
const [showTimestamp, setShowTimestamp] = React.useState(true);
|
||||||
const [since, setSince] = React.useState<TimeFilter>("all");
|
const [since, setSince] = React.useState<TimeFilter>("all");
|
||||||
const [typeFilter, setTypeFilter] = React.useState<string[]>([]);
|
const [typeFilter, setTypeFilter] = React.useState<string[]>([]);
|
||||||
|
const [isPaused, setIsPaused] = React.useState(false);
|
||||||
|
const [messageBuffer, setMessageBuffer] = React.useState<string[]>([]);
|
||||||
|
const isPausedRef = useRef(false);
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
|
||||||
@@ -85,15 +89,38 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
const handleLines = (lines: number) => {
|
const handleLines = (lines: number) => {
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
setLines(lines);
|
setLines(lines);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSince = (value: TimeFilter) => {
|
const handleSince = (value: TimeFilter) => {
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
setSince(value);
|
setSince(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePauseResume = () => {
|
||||||
|
if (isPaused) {
|
||||||
|
// Resume: Apply all buffered messages
|
||||||
|
if (messageBuffer.length > 0) {
|
||||||
|
const bufferedContent = messageBuffer.join("");
|
||||||
|
setRawLogs((prev) => {
|
||||||
|
const updated = prev + bufferedContent;
|
||||||
|
const splitLines = updated.split("\n");
|
||||||
|
if (splitLines.length > lines) {
|
||||||
|
return splitLines.slice(-lines).join("\n");
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
setMessageBuffer([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newPausedState = !isPaused;
|
||||||
|
setIsPaused(newPausedState);
|
||||||
|
isPausedRef.current = newPausedState;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerId) return;
|
if (!containerId) return;
|
||||||
|
|
||||||
@@ -102,6 +129,10 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
|
// Reset pause state when container changes
|
||||||
|
setIsPaused(false);
|
||||||
|
isPausedRef.current = false;
|
||||||
|
|
||||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
const params = new globalThis.URLSearchParams({
|
const params = new globalThis.URLSearchParams({
|
||||||
@@ -140,14 +171,22 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
|
|
||||||
ws.onmessage = (e) => {
|
ws.onmessage = (e) => {
|
||||||
if (!isCurrentConnection) return;
|
if (!isCurrentConnection) return;
|
||||||
setRawLogs((prev) => {
|
|
||||||
const updated = prev + e.data;
|
if (isPausedRef.current) {
|
||||||
const splitLines = updated.split("\n");
|
// When paused, buffer the messages instead of displaying them
|
||||||
if (splitLines.length > lines) {
|
setMessageBuffer((prev) => [...prev, e.data]);
|
||||||
return splitLines.slice(-lines).join("\n");
|
} else {
|
||||||
}
|
// When not paused, display messages normally
|
||||||
return updated;
|
setRawLogs((prev) => {
|
||||||
});
|
const updated = prev + e.data;
|
||||||
|
const splitLines = updated.split("\n");
|
||||||
|
if (splitLines.length > lines) {
|
||||||
|
return splitLines.slice(-lines).join("\n");
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
if (noDataTimeout) clearTimeout(noDataTimeout);
|
if (noDataTimeout) clearTimeout(noDataTimeout);
|
||||||
};
|
};
|
||||||
@@ -210,9 +249,15 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sync isPausedRef with isPaused state
|
||||||
|
useEffect(() => {
|
||||||
|
isPausedRef.current = isPaused;
|
||||||
|
}, [isPaused]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
}, [containerId]);
|
}, [containerId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -260,17 +305,48 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<div className="flex gap-2">
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
className="h-9 sm:w-auto w-full"
|
size="sm"
|
||||||
onClick={handleDownload}
|
className="h-9"
|
||||||
disabled={filteredLogs.length === 0 || !data?.Name}
|
onClick={handlePauseResume}
|
||||||
>
|
title={isPaused ? "Resume logs" : "Pause logs"}
|
||||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
>
|
||||||
Download logs
|
{isPaused ? (
|
||||||
</Button>
|
<Play className="mr-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Pause className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
{isPaused ? "Resume" : "Pause"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-9 sm:w-auto w-full"
|
||||||
|
onClick={handleDownload}
|
||||||
|
disabled={filteredLogs.length === 0 || !data?.Name}
|
||||||
|
>
|
||||||
|
<DownloadIcon className="mr-2 h-4 w-4" />
|
||||||
|
Download logs
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isPaused && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Pause className="h-4 w-4" />
|
||||||
|
<span>
|
||||||
|
Logs paused
|
||||||
|
{messageBuffer.length > 0 && (
|
||||||
|
<span className="ml-1 font-medium">
|
||||||
|
({messageBuffer.length} messages buffered)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
ArrowUpDown,
|
||||||
|
BookIcon,
|
||||||
|
ExternalLinkIcon,
|
||||||
|
FolderInput,
|
||||||
|
Loader2,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
Search,
|
||||||
|
TrashIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
@@ -31,20 +45,14 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
Select,
|
||||||
BookIcon,
|
SelectContent,
|
||||||
ExternalLinkIcon,
|
SelectItem,
|
||||||
FolderInput,
|
SelectTrigger,
|
||||||
Loader2,
|
SelectValue,
|
||||||
MoreHorizontalIcon,
|
} from "@/components/ui/select";
|
||||||
Search,
|
import { api } from "@/utils/api";
|
||||||
TrashIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { HandleProject } from "./handle-project";
|
import { HandleProject } from "./handle-project";
|
||||||
import { ProjectEnvironment } from "./project-environment";
|
import { ProjectEnvironment } from "./project-environment";
|
||||||
|
|
||||||
@@ -54,15 +62,65 @@ export const ShowProjects = () => {
|
|||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
const { mutateAsync } = api.project.remove.useMutation();
|
const { mutateAsync } = api.project.remove.useMutation();
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [sortBy, setSortBy] = useState<string>(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
return localStorage.getItem("projectsSort") || "createdAt-desc";
|
||||||
|
}
|
||||||
|
return "createdAt-desc";
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("projectsSort", sortBy);
|
||||||
|
}, [sortBy]);
|
||||||
|
|
||||||
const filteredProjects = useMemo(() => {
|
const filteredProjects = useMemo(() => {
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
return data.filter(
|
|
||||||
|
// First filter by search query
|
||||||
|
const filtered = data.filter(
|
||||||
(project) =>
|
(project) =>
|
||||||
project.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
project.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
project.description?.toLowerCase().includes(searchQuery.toLowerCase()),
|
project.description?.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||||
);
|
);
|
||||||
}, [data, searchQuery]);
|
|
||||||
|
// Then sort the filtered results
|
||||||
|
const [field, direction] = sortBy.split("-");
|
||||||
|
return [...filtered].sort((a, b) => {
|
||||||
|
let comparison = 0;
|
||||||
|
switch (field) {
|
||||||
|
case "name":
|
||||||
|
comparison = a.name.localeCompare(b.name);
|
||||||
|
break;
|
||||||
|
case "createdAt":
|
||||||
|
comparison =
|
||||||
|
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||||
|
break;
|
||||||
|
case "services": {
|
||||||
|
const aTotalServices =
|
||||||
|
a.mariadb.length +
|
||||||
|
a.mongo.length +
|
||||||
|
a.mysql.length +
|
||||||
|
a.postgres.length +
|
||||||
|
a.redis.length +
|
||||||
|
a.applications.length +
|
||||||
|
a.compose.length;
|
||||||
|
const bTotalServices =
|
||||||
|
b.mariadb.length +
|
||||||
|
b.mongo.length +
|
||||||
|
b.mysql.length +
|
||||||
|
b.postgres.length +
|
||||||
|
b.redis.length +
|
||||||
|
b.applications.length +
|
||||||
|
b.compose.length;
|
||||||
|
comparison = aTotalServices - bTotalServices;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
comparison = 0;
|
||||||
|
}
|
||||||
|
return direction === "asc" ? comparison : -comparison;
|
||||||
|
});
|
||||||
|
}, [data, searchQuery, sortBy]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -98,14 +156,40 @@ export const ShowProjects = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="w-full relative">
|
<div className="flex max-sm:flex-col gap-4 items-center w-full">
|
||||||
<Input
|
<div className="flex-1 relative max-sm:w-full">
|
||||||
placeholder="Filter projects..."
|
<Input
|
||||||
value={searchQuery}
|
placeholder="Filter projects..."
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
value={searchQuery}
|
||||||
className="pr-10"
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
className="pr-10"
|
||||||
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
/>
|
||||||
|
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 min-w-48 max-sm:w-full">
|
||||||
|
<ArrowUpDown className="size-4 text-muted-foreground" />
|
||||||
|
<Select value={sortBy} onValueChange={setSortBy}>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue placeholder="Sort by..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="name-asc">Name (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="name-desc">Name (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="createdAt-desc">
|
||||||
|
Newest first
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="createdAt-asc">
|
||||||
|
Oldest first
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="services-desc">
|
||||||
|
Most services
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="services-asc">
|
||||||
|
Least services
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{filteredProjects?.length === 0 && (
|
{filteredProjects?.length === 0 && (
|
||||||
<div className="mt-6 flex h-[50vh] w-full flex-col items-center justify-center space-y-4">
|
<div className="mt-6 flex h-[50vh] w-full flex-col items-center justify-center space-y-4">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
|
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
|
||||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||||
|
import { ShowClusterSettings } from "../application/advanced/cluster/show-cluster-settings";
|
||||||
import { RebuildDatabase } from "./rebuild-database";
|
import { RebuildDatabase } from "./rebuild-database";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -12,6 +13,7 @@ export const ShowDatabaseAdvancedSettings = ({ id, type }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-5">
|
<div className="flex w-full flex-col gap-5">
|
||||||
<ShowCustomCommand id={id} type={type} />
|
<ShowCustomCommand id={id} type={type} />
|
||||||
|
<ShowClusterSettings id={id} type={type} />
|
||||||
<ShowVolumes id={id} type={type} />
|
<ShowVolumes id={id} type={type} />
|
||||||
<ShowResources id={id} type={type} />
|
<ShowResources id={id} type={type} />
|
||||||
<RebuildDatabase id={id} type={type} />
|
<RebuildDatabase id={id} type={type} />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface ChatwootWidgetProps {
|
|||||||
launcherTitle?: string;
|
launcherTitle?: string;
|
||||||
darkMode?: boolean;
|
darkMode?: boolean;
|
||||||
hideMessageBubble?: boolean;
|
hideMessageBubble?: boolean;
|
||||||
placement?: "right" | "left";
|
placement?: "left" | "right";
|
||||||
showPopoutButton?: boolean;
|
showPopoutButton?: boolean;
|
||||||
widgetStyle?: "standard" | "bubble";
|
widgetStyle?: "standard" | "bubble";
|
||||||
};
|
};
|
||||||
@@ -41,7 +41,7 @@ export const ChatwootWidget = ({
|
|||||||
position: "right",
|
position: "right",
|
||||||
};
|
};
|
||||||
|
|
||||||
(window as any).chatwootSDKReady = () => {
|
window.chatwootSDKReady = () => {
|
||||||
window.chatwootSDK?.run({ websiteToken, baseUrl });
|
window.chatwootSDK?.run({ websiteToken, baseUrl });
|
||||||
|
|
||||||
const trySetUser = () => {
|
const trySetUser = () => {
|
||||||
@@ -63,7 +63,7 @@ export const ChatwootWidget = ({
|
|||||||
<Script
|
<Script
|
||||||
src={`${baseUrl}/packs/js/sdk.js`}
|
src={`${baseUrl}/packs/js/sdk.js`}
|
||||||
strategy="lazyOnload"
|
strategy="lazyOnload"
|
||||||
onLoad={() => (window as any).chatwootSDKReady?.()}
|
onLoad={() => window.chatwootSDKReady?.()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface Props extends React.ComponentPropsWithoutRef<"div"> {
|
interface Props extends React.ComponentPropsWithoutRef<"div"> {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
type?: "info" | "success" | "warning" | "error";
|
type?: "info" | "success" | "warning" | "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
|
|
||||||
|
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
info: {
|
info: {
|
||||||
className: "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400",
|
className: "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { Fragment } from "react";
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
@@ -7,8 +9,6 @@ import {
|
|||||||
} from "@/components/ui/breadcrumb";
|
} from "@/components/ui/breadcrumb";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||||
import Link from "next/link";
|
|
||||||
import { Fragment } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
list: {
|
list: {
|
||||||
@@ -26,7 +26,7 @@ export const BreadcrumbSidebar = ({ list }: Props) => {
|
|||||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
{list.map((item, _index) => (
|
{list.map((item, index) => (
|
||||||
<Fragment key={item.name}>
|
<Fragment key={item.name}>
|
||||||
<BreadcrumbItem className="block">
|
<BreadcrumbItem className="block">
|
||||||
<BreadcrumbLink href={item.href} asChild={!!item.href}>
|
<BreadcrumbLink href={item.href} asChild={!!item.href}>
|
||||||
@@ -37,7 +37,7 @@ export const BreadcrumbSidebar = ({ list }: Props) => {
|
|||||||
)}
|
)}
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
{_index + 1 < list.length && (
|
{index + 1 < list.length && (
|
||||||
<BreadcrumbSeparator className="block" />
|
<BreadcrumbSeparator className="block" />
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { json } from "@codemirror/lang-json";
|
|
||||||
import { yaml } from "@codemirror/lang-yaml";
|
|
||||||
import { StreamLanguage } from "@codemirror/language";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
autocompletion,
|
||||||
type Completion,
|
type Completion,
|
||||||
type CompletionContext,
|
type CompletionContext,
|
||||||
type CompletionResult,
|
type CompletionResult,
|
||||||
autocompletion,
|
|
||||||
} from "@codemirror/autocomplete";
|
} from "@codemirror/autocomplete";
|
||||||
|
import { json } from "@codemirror/lang-json";
|
||||||
|
import { yaml } from "@codemirror/lang-yaml";
|
||||||
|
import { StreamLanguage } from "@codemirror/language";
|
||||||
import { properties } from "@codemirror/legacy-modes/mode/properties";
|
import { properties } from "@codemirror/legacy-modes/mode/properties";
|
||||||
import { shell } from "@codemirror/legacy-modes/mode/shell";
|
import { shell } from "@codemirror/legacy-modes/mode/shell";
|
||||||
import { EditorView } from "@codemirror/view";
|
import { EditorView } from "@codemirror/view";
|
||||||
import { githubDark, githubLight } from "@uiw/codemirror-theme-github";
|
import { githubDark, githubLight } from "@uiw/codemirror-theme-github";
|
||||||
import CodeMirror, { type ReactCodeMirrorProps } from "@uiw/react-codemirror";
|
import CodeMirror, { type ReactCodeMirrorProps } from "@uiw/react-codemirror";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
// Docker Compose completion options
|
// Docker Compose completion options
|
||||||
const dockerComposeServices = [
|
const dockerComposeServices = [
|
||||||
@@ -101,9 +100,7 @@ function dockerComposeComplete(
|
|||||||
context: CompletionContext,
|
context: CompletionContext,
|
||||||
): CompletionResult | null {
|
): CompletionResult | null {
|
||||||
const word = context.matchBefore(/\w*/);
|
const word = context.matchBefore(/\w*/);
|
||||||
if (!word) return null;
|
if (!word || (!word.text && !context.explicit)) return null;
|
||||||
|
|
||||||
if (!word.text && !context.explicit) return null;
|
|
||||||
|
|
||||||
// Check if we're at the root level
|
// Check if we're at the root level
|
||||||
const line = context.state.doc.lineAt(context.pos);
|
const line = context.state.doc.lineAt(context.pos);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { format, formatDistanceToNow } from "date-fns";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -5,7 +6,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { format, formatDistanceToNow } from "date-fns";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
date: string;
|
date: string;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@@ -5,8 +7,6 @@ import {
|
|||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { TerminalLine } from "../dashboard/docker/logs/terminal-line";
|
import { TerminalLine } from "../dashboard/docker/logs/terminal-line";
|
||||||
import type { LogLine } from "../dashboard/docker/logs/utils";
|
import type { LogLine } from "../dashboard/docker/logs/utils";
|
||||||
|
|
||||||
@@ -43,11 +43,11 @@ export const DrawerLogs = ({ isOpen, onClose, filteredLogs }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Sheet
|
<Sheet
|
||||||
open={!!isOpen}
|
open={!!isOpen}
|
||||||
onOpenChange={(_open) => {
|
onOpenChange={() => {
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SheetContent className="sm:max-w-[740px] flex flex-col">
|
<SheetContent className="sm:max-w-[740px] flex flex-col">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Deployment Logs</SheetTitle>
|
<SheetTitle>Deployment Logs</SheetTitle>
|
||||||
<SheetDescription>Details of the request log entry.</SheetDescription>
|
<SheetDescription>Details of the request log entry.</SheetDescription>
|
||||||
|
|||||||
@@ -13,10 +13,13 @@ export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
|||||||
setIsPasswordVisible((prevVisibility) => !prevVisibility);
|
setIsPasswordVisible((prevVisibility) => !prevVisibility);
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputType = isPasswordVisible ? "text" : "password";
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center space-x-2">
|
<div className="flex w-full items-center space-x-2">
|
||||||
<Input ref={inputRef} type={inputType} {...props} />
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
type={isPasswordVisible ? "text" : "password"}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -27,10 +30,10 @@ export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
|||||||
<Clipboard className="size-4 text-muted-foreground" />
|
<Clipboard className="size-4 text-muted-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={togglePasswordVisibility} variant={"secondary"}>
|
<Button onClick={togglePasswordVisibility} variant={"secondary"}>
|
||||||
{inputType === "password" ? (
|
{isPasswordVisible ? (
|
||||||
<EyeIcon className="size-4 text-muted-foreground" />
|
|
||||||
) : (
|
|
||||||
<EyeOffIcon className="size-4 text-muted-foreground" />
|
<EyeOffIcon className="size-4 text-muted-foreground" />
|
||||||
|
) : (
|
||||||
|
<EyeIcon className="size-4 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
45
apps/dokploy/drizzle/0105_clumsy_quicksilver.sql
Normal file
45
apps/dokploy/drizzle/0105_clumsy_quicksilver.sql
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
ALTER TABLE "postgres" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;
|
||||||
6418
apps/dokploy/drizzle/meta/0105_snapshot.json
Normal file
6418
apps/dokploy/drizzle/meta/0105_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -736,6 +736,13 @@
|
|||||||
"when": 1754207407121,
|
"when": 1754207407121,
|
||||||
"tag": "0104_omniscient_randall",
|
"tag": "0104_omniscient_randall",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 105,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1754259281559,
|
||||||
|
"tag": "0105_clumsy_quicksilver",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.24.6",
|
"version": "v0.24.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import { GlobeIcon, HelpCircle, ServerOff } from "lucide-react";
|
||||||
|
import type {
|
||||||
|
GetServerSidePropsContext,
|
||||||
|
InferGetServerSidePropsType,
|
||||||
|
} from "next";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { type ReactElement, useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import superjson from "superjson";
|
||||||
import { ShowClusterSettings } from "@/components/dashboard/application/advanced/cluster/show-cluster-settings";
|
import { ShowClusterSettings } from "@/components/dashboard/application/advanced/cluster/show-cluster-settings";
|
||||||
import { AddCommand } from "@/components/dashboard/application/advanced/general/add-command";
|
import { AddCommand } from "@/components/dashboard/application/advanced/general/add-command";
|
||||||
import { ShowPorts } from "@/components/dashboard/application/advanced/ports/show-port";
|
import { ShowPorts } from "@/components/dashboard/application/advanced/ports/show-port";
|
||||||
@@ -39,20 +53,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { appRouter } from "@/server/api/root";
|
import { appRouter } from "@/server/api/root";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
|
||||||
import copy from "copy-to-clipboard";
|
|
||||||
import { GlobeIcon, HelpCircle, ServerOff } from "lucide-react";
|
|
||||||
import type {
|
|
||||||
GetServerSidePropsContext,
|
|
||||||
InferGetServerSidePropsType,
|
|
||||||
} from "next";
|
|
||||||
import Head from "next/head";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { type ReactElement, useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import superjson from "superjson";
|
|
||||||
|
|
||||||
type TabState =
|
type TabState =
|
||||||
| "projects"
|
| "projects"
|
||||||
@@ -345,7 +345,10 @@ const Service = (
|
|||||||
<TabsContent value="advanced">
|
<TabsContent value="advanced">
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
<AddCommand applicationId={applicationId} />
|
<AddCommand applicationId={applicationId} />
|
||||||
<ShowClusterSettings applicationId={applicationId} />
|
<ShowClusterSettings
|
||||||
|
id={applicationId}
|
||||||
|
type="application"
|
||||||
|
/>
|
||||||
|
|
||||||
<ShowResources id={applicationId} type="application" />
|
<ShowResources id={applicationId} type="application" />
|
||||||
<ShowVolumes id={applicationId} type="application" />
|
<ShowVolumes id={applicationId} type="application" />
|
||||||
|
|||||||
@@ -24,7 +24,25 @@ import { redirects } from "./redirects";
|
|||||||
import { registry } from "./registry";
|
import { registry } from "./registry";
|
||||||
import { security } from "./security";
|
import { security } from "./security";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import { applicationStatus, certificateType, triggerType } from "./shared";
|
import {
|
||||||
|
applicationStatus,
|
||||||
|
certificateType,
|
||||||
|
type HealthCheckSwarm,
|
||||||
|
HealthCheckSwarmSchema,
|
||||||
|
type LabelsSwarm,
|
||||||
|
LabelsSwarmSchema,
|
||||||
|
type NetworkSwarm,
|
||||||
|
NetworkSwarmSchema,
|
||||||
|
type PlacementSwarm,
|
||||||
|
PlacementSwarmSchema,
|
||||||
|
type RestartPolicySwarm,
|
||||||
|
RestartPolicySwarmSchema,
|
||||||
|
type ServiceModeSwarm,
|
||||||
|
ServiceModeSwarmSchema,
|
||||||
|
triggerType,
|
||||||
|
type UpdateConfigSwarm,
|
||||||
|
UpdateConfigSwarmSchema,
|
||||||
|
} from "./shared";
|
||||||
import { sshKeys } from "./ssh-key";
|
import { sshKeys } from "./ssh-key";
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
export const sourceType = pgEnum("sourceType", [
|
export const sourceType = pgEnum("sourceType", [
|
||||||
@@ -46,64 +64,6 @@ export const buildType = pgEnum("buildType", [
|
|||||||
"railpack",
|
"railpack",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export interface HealthCheckSwarm {
|
|
||||||
Test?: string[] | undefined;
|
|
||||||
Interval?: number | undefined;
|
|
||||||
Timeout?: number | undefined;
|
|
||||||
StartPeriod?: number | undefined;
|
|
||||||
Retries?: number | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RestartPolicySwarm {
|
|
||||||
Condition?: string | undefined;
|
|
||||||
Delay?: number | undefined;
|
|
||||||
MaxAttempts?: number | undefined;
|
|
||||||
Window?: number | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlacementSwarm {
|
|
||||||
Constraints?: string[] | undefined;
|
|
||||||
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
|
|
||||||
MaxReplicas?: number | undefined;
|
|
||||||
Platforms?:
|
|
||||||
| Array<{
|
|
||||||
Architecture: string;
|
|
||||||
OS: string;
|
|
||||||
}>
|
|
||||||
| undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateConfigSwarm {
|
|
||||||
Parallelism: number;
|
|
||||||
Delay?: number | undefined;
|
|
||||||
FailureAction?: string | undefined;
|
|
||||||
Monitor?: number | undefined;
|
|
||||||
MaxFailureRatio?: number | undefined;
|
|
||||||
Order: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServiceModeSwarm {
|
|
||||||
Replicated?: { Replicas?: number | undefined } | undefined;
|
|
||||||
Global?: {} | undefined;
|
|
||||||
ReplicatedJob?:
|
|
||||||
| {
|
|
||||||
MaxConcurrent?: number | undefined;
|
|
||||||
TotalCompletions?: number | undefined;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
GlobalJob?: {} | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NetworkSwarm {
|
|
||||||
Target?: string | undefined;
|
|
||||||
Aliases?: string[] | undefined;
|
|
||||||
DriverOpts?: { [key: string]: string } | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LabelsSwarm {
|
|
||||||
[name: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const applications = pgTable("application", {
|
export const applications = pgTable("application", {
|
||||||
applicationId: text("applicationId")
|
applicationId: text("applicationId")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -283,94 +243,6 @@ export const applicationsRelations = relations(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const HealthCheckSwarmSchema = z
|
|
||||||
.object({
|
|
||||||
Test: z.array(z.string()).optional(),
|
|
||||||
Interval: z.number().optional(),
|
|
||||||
Timeout: z.number().optional(),
|
|
||||||
StartPeriod: z.number().optional(),
|
|
||||||
Retries: z.number().optional(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const RestartPolicySwarmSchema = z
|
|
||||||
.object({
|
|
||||||
Condition: z.string().optional(),
|
|
||||||
Delay: z.number().optional(),
|
|
||||||
MaxAttempts: z.number().optional(),
|
|
||||||
Window: z.number().optional(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const PreferenceSchema = z
|
|
||||||
.object({
|
|
||||||
Spread: z.object({
|
|
||||||
SpreadDescriptor: z.string(),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const PlatformSchema = z
|
|
||||||
.object({
|
|
||||||
Architecture: z.string(),
|
|
||||||
OS: z.string(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const PlacementSwarmSchema = z
|
|
||||||
.object({
|
|
||||||
Constraints: z.array(z.string()).optional(),
|
|
||||||
Preferences: z.array(PreferenceSchema).optional(),
|
|
||||||
MaxReplicas: z.number().optional(),
|
|
||||||
Platforms: z.array(PlatformSchema).optional(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const UpdateConfigSwarmSchema = z
|
|
||||||
.object({
|
|
||||||
Parallelism: z.number(),
|
|
||||||
Delay: z.number().optional(),
|
|
||||||
FailureAction: z.string().optional(),
|
|
||||||
Monitor: z.number().optional(),
|
|
||||||
MaxFailureRatio: z.number().optional(),
|
|
||||||
Order: z.string(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const ReplicatedSchema = z
|
|
||||||
.object({
|
|
||||||
Replicas: z.number().optional(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const ReplicatedJobSchema = z
|
|
||||||
.object({
|
|
||||||
MaxConcurrent: z.number().optional(),
|
|
||||||
TotalCompletions: z.number().optional(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const ServiceModeSwarmSchema = z
|
|
||||||
.object({
|
|
||||||
Replicated: ReplicatedSchema.optional(),
|
|
||||||
Global: z.object({}).optional(),
|
|
||||||
ReplicatedJob: ReplicatedJobSchema.optional(),
|
|
||||||
GlobalJob: z.object({}).optional(),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const NetworkSwarmSchema = z.array(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
Target: z.string().optional(),
|
|
||||||
Aliases: z.array(z.string()).optional(),
|
|
||||||
DriverOpts: z.object({}).optional(),
|
|
||||||
})
|
|
||||||
.strict(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const LabelsSwarmSchema = z.record(z.string());
|
|
||||||
|
|
||||||
const createSchema = createInsertSchema(applications, {
|
const createSchema = createInsertSchema(applications, {
|
||||||
appName: z.string(),
|
appName: z.string(),
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -7,7 +7,23 @@ import { backups } from "./backups";
|
|||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
import { projects } from "./project";
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import { applicationStatus } from "./shared";
|
import {
|
||||||
|
applicationStatus,
|
||||||
|
type HealthCheckSwarm,
|
||||||
|
HealthCheckSwarmSchema,
|
||||||
|
type LabelsSwarm,
|
||||||
|
LabelsSwarmSchema,
|
||||||
|
type NetworkSwarm,
|
||||||
|
NetworkSwarmSchema,
|
||||||
|
type PlacementSwarm,
|
||||||
|
PlacementSwarmSchema,
|
||||||
|
type RestartPolicySwarm,
|
||||||
|
RestartPolicySwarmSchema,
|
||||||
|
type ServiceModeSwarm,
|
||||||
|
ServiceModeSwarmSchema,
|
||||||
|
type UpdateConfigSwarm,
|
||||||
|
UpdateConfigSwarmSchema,
|
||||||
|
} from "./shared";
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
export const mariadb = pgTable("mariadb", {
|
export const mariadb = pgTable("mariadb", {
|
||||||
@@ -38,6 +54,15 @@ export const mariadb = pgTable("mariadb", {
|
|||||||
applicationStatus: applicationStatus("applicationStatus")
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
|
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||||
|
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||||
|
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||||
|
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -83,6 +108,14 @@ const createSchema = createInsertSchema(mariadb, {
|
|||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
|
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||||
|
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMariaDB = createSchema
|
export const apiCreateMariaDB = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
import { boolean, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -7,7 +7,23 @@ import { backups } from "./backups";
|
|||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
import { projects } from "./project";
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import { applicationStatus } from "./shared";
|
import {
|
||||||
|
applicationStatus,
|
||||||
|
type HealthCheckSwarm,
|
||||||
|
HealthCheckSwarmSchema,
|
||||||
|
type LabelsSwarm,
|
||||||
|
LabelsSwarmSchema,
|
||||||
|
type NetworkSwarm,
|
||||||
|
NetworkSwarmSchema,
|
||||||
|
type PlacementSwarm,
|
||||||
|
PlacementSwarmSchema,
|
||||||
|
type RestartPolicySwarm,
|
||||||
|
RestartPolicySwarmSchema,
|
||||||
|
type ServiceModeSwarm,
|
||||||
|
ServiceModeSwarmSchema,
|
||||||
|
type UpdateConfigSwarm,
|
||||||
|
UpdateConfigSwarmSchema,
|
||||||
|
} from "./shared";
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
export const mongo = pgTable("mongo", {
|
export const mongo = pgTable("mongo", {
|
||||||
@@ -34,6 +50,15 @@ export const mongo = pgTable("mongo", {
|
|||||||
applicationStatus: applicationStatus("applicationStatus")
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
|
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||||
|
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||||
|
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||||
|
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -79,6 +104,14 @@ const createSchema = createInsertSchema(mongo, {
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
replicaSets: z.boolean().default(false),
|
replicaSets: z.boolean().default(false),
|
||||||
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
|
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||||
|
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMongo = createSchema
|
export const apiCreateMongo = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -7,7 +7,23 @@ import { backups } from "./backups";
|
|||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
import { projects } from "./project";
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import { applicationStatus } from "./shared";
|
import {
|
||||||
|
applicationStatus,
|
||||||
|
type HealthCheckSwarm,
|
||||||
|
HealthCheckSwarmSchema,
|
||||||
|
type LabelsSwarm,
|
||||||
|
LabelsSwarmSchema,
|
||||||
|
type NetworkSwarm,
|
||||||
|
NetworkSwarmSchema,
|
||||||
|
type PlacementSwarm,
|
||||||
|
PlacementSwarmSchema,
|
||||||
|
type RestartPolicySwarm,
|
||||||
|
RestartPolicySwarmSchema,
|
||||||
|
type ServiceModeSwarm,
|
||||||
|
ServiceModeSwarmSchema,
|
||||||
|
type UpdateConfigSwarm,
|
||||||
|
UpdateConfigSwarmSchema,
|
||||||
|
} from "./shared";
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
export const mysql = pgTable("mysql", {
|
export const mysql = pgTable("mysql", {
|
||||||
@@ -36,6 +52,15 @@ export const mysql = pgTable("mysql", {
|
|||||||
applicationStatus: applicationStatus("applicationStatus")
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
|
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||||
|
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||||
|
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||||
|
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -81,6 +106,14 @@ const createSchema = createInsertSchema(mysql, {
|
|||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
|
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||||
|
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMySql = createSchema
|
export const apiCreateMySql = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -7,7 +7,23 @@ import { backups } from "./backups";
|
|||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
import { projects } from "./project";
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import { applicationStatus } from "./shared";
|
import {
|
||||||
|
applicationStatus,
|
||||||
|
type HealthCheckSwarm,
|
||||||
|
HealthCheckSwarmSchema,
|
||||||
|
type LabelsSwarm,
|
||||||
|
LabelsSwarmSchema,
|
||||||
|
type NetworkSwarm,
|
||||||
|
NetworkSwarmSchema,
|
||||||
|
type PlacementSwarm,
|
||||||
|
PlacementSwarmSchema,
|
||||||
|
type RestartPolicySwarm,
|
||||||
|
RestartPolicySwarmSchema,
|
||||||
|
type ServiceModeSwarm,
|
||||||
|
ServiceModeSwarmSchema,
|
||||||
|
type UpdateConfigSwarm,
|
||||||
|
UpdateConfigSwarmSchema,
|
||||||
|
} from "./shared";
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
export const postgres = pgTable("postgres", {
|
export const postgres = pgTable("postgres", {
|
||||||
@@ -35,6 +51,16 @@ export const postgres = pgTable("postgres", {
|
|||||||
applicationStatus: applicationStatus("applicationStatus")
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
|
|
||||||
|
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||||
|
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||||
|
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||||
|
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -78,6 +104,14 @@ const createSchema = createInsertSchema(postgres, {
|
|||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
|
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||||
|
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreatePostgres = createSchema
|
export const apiCreatePostgres = createSchema
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
import { projects } from "./project";
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import { applicationStatus } from "./shared";
|
import {
|
||||||
|
applicationStatus,
|
||||||
|
type HealthCheckSwarm,
|
||||||
|
HealthCheckSwarmSchema,
|
||||||
|
type LabelsSwarm,
|
||||||
|
LabelsSwarmSchema,
|
||||||
|
type NetworkSwarm,
|
||||||
|
NetworkSwarmSchema,
|
||||||
|
type PlacementSwarm,
|
||||||
|
PlacementSwarmSchema,
|
||||||
|
type RestartPolicySwarm,
|
||||||
|
RestartPolicySwarmSchema,
|
||||||
|
type ServiceModeSwarm,
|
||||||
|
ServiceModeSwarmSchema,
|
||||||
|
type UpdateConfigSwarm,
|
||||||
|
UpdateConfigSwarmSchema,
|
||||||
|
} from "./shared";
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
export const redis = pgTable("redis", {
|
export const redis = pgTable("redis", {
|
||||||
@@ -35,6 +51,15 @@ export const redis = pgTable("redis", {
|
|||||||
applicationStatus: applicationStatus("applicationStatus")
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
|
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||||
|
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||||
|
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||||
|
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
projectId: text("projectId")
|
projectId: text("projectId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => projects.projectId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
@@ -73,6 +98,14 @@ const createSchema = createInsertSchema(redis, {
|
|||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
|
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||||
|
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateRedis = createSchema
|
export const apiCreateRedis = createSchema
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { pgEnum } from "drizzle-orm/pg-core";
|
import { pgEnum } from "drizzle-orm/pg-core";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const applicationStatus = pgEnum("applicationStatus", [
|
export const applicationStatus = pgEnum("applicationStatus", [
|
||||||
"idle",
|
"idle",
|
||||||
@@ -14,3 +15,149 @@ export const certificateType = pgEnum("certificateType", [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export const triggerType = pgEnum("triggerType", ["push", "tag"]);
|
export const triggerType = pgEnum("triggerType", ["push", "tag"]);
|
||||||
|
|
||||||
|
export interface HealthCheckSwarm {
|
||||||
|
Test?: string[] | undefined;
|
||||||
|
Interval?: number | undefined;
|
||||||
|
Timeout?: number | undefined;
|
||||||
|
StartPeriod?: number | undefined;
|
||||||
|
Retries?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RestartPolicySwarm {
|
||||||
|
Condition?: string | undefined;
|
||||||
|
Delay?: number | undefined;
|
||||||
|
MaxAttempts?: number | undefined;
|
||||||
|
Window?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlacementSwarm {
|
||||||
|
Constraints?: string[] | undefined;
|
||||||
|
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
|
||||||
|
MaxReplicas?: number | undefined;
|
||||||
|
Platforms?:
|
||||||
|
| Array<{
|
||||||
|
Architecture: string;
|
||||||
|
OS: string;
|
||||||
|
}>
|
||||||
|
| undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateConfigSwarm {
|
||||||
|
Parallelism: number;
|
||||||
|
Delay?: number | undefined;
|
||||||
|
FailureAction?: string | undefined;
|
||||||
|
Monitor?: number | undefined;
|
||||||
|
MaxFailureRatio?: number | undefined;
|
||||||
|
Order: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceModeSwarm {
|
||||||
|
Replicated?: { Replicas?: number | undefined } | undefined;
|
||||||
|
Global?: {} | undefined;
|
||||||
|
ReplicatedJob?:
|
||||||
|
| {
|
||||||
|
MaxConcurrent?: number | undefined;
|
||||||
|
TotalCompletions?: number | undefined;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
GlobalJob?: {} | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetworkSwarm {
|
||||||
|
Target?: string | undefined;
|
||||||
|
Aliases?: string[] | undefined;
|
||||||
|
DriverOpts?: { [key: string]: string } | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LabelsSwarm {
|
||||||
|
[name: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HealthCheckSwarmSchema = z
|
||||||
|
.object({
|
||||||
|
Test: z.array(z.string()).optional(),
|
||||||
|
Interval: z.number().optional(),
|
||||||
|
Timeout: z.number().optional(),
|
||||||
|
StartPeriod: z.number().optional(),
|
||||||
|
Retries: z.number().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const RestartPolicySwarmSchema = z
|
||||||
|
.object({
|
||||||
|
Condition: z.string().optional(),
|
||||||
|
Delay: z.number().optional(),
|
||||||
|
MaxAttempts: z.number().optional(),
|
||||||
|
Window: z.number().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const PreferenceSchema = z
|
||||||
|
.object({
|
||||||
|
Spread: z.object({
|
||||||
|
SpreadDescriptor: z.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const PlatformSchema = z
|
||||||
|
.object({
|
||||||
|
Architecture: z.string(),
|
||||||
|
OS: z.string(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const PlacementSwarmSchema = z
|
||||||
|
.object({
|
||||||
|
Constraints: z.array(z.string()).optional(),
|
||||||
|
Preferences: z.array(PreferenceSchema).optional(),
|
||||||
|
MaxReplicas: z.number().optional(),
|
||||||
|
Platforms: z.array(PlatformSchema).optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const UpdateConfigSwarmSchema = z
|
||||||
|
.object({
|
||||||
|
Parallelism: z.number(),
|
||||||
|
Delay: z.number().optional(),
|
||||||
|
FailureAction: z.string().optional(),
|
||||||
|
Monitor: z.number().optional(),
|
||||||
|
MaxFailureRatio: z.number().optional(),
|
||||||
|
Order: z.string(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const ReplicatedSchema = z
|
||||||
|
.object({
|
||||||
|
Replicas: z.number().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const ReplicatedJobSchema = z
|
||||||
|
.object({
|
||||||
|
MaxConcurrent: z.number().optional(),
|
||||||
|
TotalCompletions: z.number().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const ServiceModeSwarmSchema = z
|
||||||
|
.object({
|
||||||
|
Replicated: ReplicatedSchema.optional(),
|
||||||
|
Global: z.object({}).optional(),
|
||||||
|
ReplicatedJob: ReplicatedJobSchema.optional(),
|
||||||
|
GlobalJob: z.object({}).optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const NetworkSwarmSchema = z.array(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
Target: z.string().optional(),
|
||||||
|
Aliases: z.array(z.string()).optional(),
|
||||||
|
DriverOpts: z.object({}).optional(),
|
||||||
|
})
|
||||||
|
.strict(),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LabelsSwarmSchema = z.record(z.string());
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ const { handler, api } = betterAuth({
|
|||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
logger: {
|
||||||
|
disabled: process.env.NODE_ENV === "production",
|
||||||
|
},
|
||||||
...(!IS_CLOUD && {
|
...(!IS_CLOUD && {
|
||||||
async trustedOrigins() {
|
async trustedOrigins() {
|
||||||
const admin = await db.query.member.findFirst({
|
const admin = await db.query.member.findFirst({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
|
|||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
|
generateConfigContainer,
|
||||||
generateFileMounts,
|
generateFileMounts,
|
||||||
generateVolumeMounts,
|
generateVolumeMounts,
|
||||||
prepareEnvironmentVariables,
|
prepareEnvironmentVariables,
|
||||||
@@ -34,6 +35,17 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
const defaultMariadbEnv = `MARIADB_DATABASE="${databaseName}"\nMARIADB_USER="${databaseUser}"\nMARIADB_PASSWORD="${databasePassword}"\nMARIADB_ROOT_PASSWORD="${databaseRootPassword}"${
|
const defaultMariadbEnv = `MARIADB_DATABASE="${databaseName}"\nMARIADB_USER="${databaseUser}"\nMARIADB_PASSWORD="${databasePassword}"\nMARIADB_ROOT_PASSWORD="${databaseRootPassword}"${
|
||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
HealthCheck,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
|
Labels,
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
|
UpdateConfig,
|
||||||
|
Networks,
|
||||||
|
} = generateConfigContainer(mariadb);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
memoryReservation,
|
memoryReservation,
|
||||||
@@ -54,6 +66,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
Name: appName,
|
Name: appName,
|
||||||
TaskTemplate: {
|
TaskTemplate: {
|
||||||
ContainerSpec: {
|
ContainerSpec: {
|
||||||
|
HealthCheck,
|
||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
@@ -63,20 +76,17 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
Args: ["-c", command],
|
Args: ["-c", command],
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
Labels,
|
||||||
},
|
},
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
Placement: {
|
|
||||||
Constraints: ["node.role==manager"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mode: {
|
|
||||||
Replicated: {
|
|
||||||
Replicas: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
Mode: "dnsrr",
|
Mode: "dnsrr",
|
||||||
Ports: externalPort
|
Ports: externalPort
|
||||||
@@ -90,6 +100,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
|
UpdateConfig,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const service = docker.getService(appName);
|
const service = docker.getService(appName);
|
||||||
@@ -97,6 +108,10 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
await service.update({
|
await service.update({
|
||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
|
TaskTemplate: {
|
||||||
|
...settings.TaskTemplate,
|
||||||
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
|
|||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
|
generateConfigContainer,
|
||||||
generateFileMounts,
|
generateFileMounts,
|
||||||
generateVolumeMounts,
|
generateVolumeMounts,
|
||||||
prepareEnvironmentVariables,
|
prepareEnvironmentVariables,
|
||||||
@@ -81,6 +82,17 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
HealthCheck,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
|
Labels,
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
|
UpdateConfig,
|
||||||
|
Networks,
|
||||||
|
} = generateConfigContainer(mongo);
|
||||||
|
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
memoryReservation,
|
memoryReservation,
|
||||||
@@ -102,6 +114,7 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
Name: appName,
|
Name: appName,
|
||||||
TaskTemplate: {
|
TaskTemplate: {
|
||||||
ContainerSpec: {
|
ContainerSpec: {
|
||||||
|
HealthCheck,
|
||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
@@ -116,20 +129,17 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
Args: ["-c", command],
|
Args: ["-c", command],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
Labels,
|
||||||
},
|
},
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
Placement: {
|
|
||||||
Constraints: ["node.role==manager"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mode: {
|
|
||||||
Replicated: {
|
|
||||||
Replicas: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
Mode: "dnsrr",
|
Mode: "dnsrr",
|
||||||
Ports: externalPort
|
Ports: externalPort
|
||||||
@@ -143,6 +153,7 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
|
UpdateConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -151,6 +162,10 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
await service.update({
|
await service.update({
|
||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
|
TaskTemplate: {
|
||||||
|
...settings.TaskTemplate,
|
||||||
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
|
|||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
|
generateConfigContainer,
|
||||||
generateFileMounts,
|
generateFileMounts,
|
||||||
generateVolumeMounts,
|
generateVolumeMounts,
|
||||||
prepareEnvironmentVariables,
|
prepareEnvironmentVariables,
|
||||||
@@ -40,6 +41,17 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
: `MYSQL_DATABASE="${databaseName}"\nMYSQL_ROOT_PASSWORD="${databaseRootPassword}"${
|
: `MYSQL_DATABASE="${databaseName}"\nMYSQL_ROOT_PASSWORD="${databaseRootPassword}"${
|
||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
HealthCheck,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
|
Labels,
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
|
UpdateConfig,
|
||||||
|
Networks,
|
||||||
|
} = generateConfigContainer(mysql);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
memoryReservation,
|
memoryReservation,
|
||||||
@@ -60,6 +72,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
Name: appName,
|
Name: appName,
|
||||||
TaskTemplate: {
|
TaskTemplate: {
|
||||||
ContainerSpec: {
|
ContainerSpec: {
|
||||||
|
HealthCheck,
|
||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
@@ -69,20 +82,17 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
Args: ["-c", command],
|
Args: ["-c", command],
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
Labels,
|
||||||
},
|
},
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
Placement: {
|
|
||||||
Constraints: ["node.role==manager"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mode: {
|
|
||||||
Replicated: {
|
|
||||||
Replicas: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
Mode: "dnsrr",
|
Mode: "dnsrr",
|
||||||
Ports: externalPort
|
Ports: externalPort
|
||||||
@@ -96,6 +106,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
|
UpdateConfig,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const service = docker.getService(appName);
|
const service = docker.getService(appName);
|
||||||
@@ -103,6 +114,10 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
await service.update({
|
await service.update({
|
||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
|
TaskTemplate: {
|
||||||
|
...settings.TaskTemplate,
|
||||||
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
|
|||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
|
generateConfigContainer,
|
||||||
generateFileMounts,
|
generateFileMounts,
|
||||||
generateVolumeMounts,
|
generateVolumeMounts,
|
||||||
prepareEnvironmentVariables,
|
prepareEnvironmentVariables,
|
||||||
@@ -33,6 +34,17 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
const defaultPostgresEnv = `POSTGRES_DB="${databaseName}"\nPOSTGRES_USER="${databaseUser}"\nPOSTGRES_PASSWORD="${databasePassword}"${
|
const defaultPostgresEnv = `POSTGRES_DB="${databaseName}"\nPOSTGRES_USER="${databaseUser}"\nPOSTGRES_PASSWORD="${databasePassword}"${
|
||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
HealthCheck,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
|
Labels,
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
|
UpdateConfig,
|
||||||
|
Networks,
|
||||||
|
} = generateConfigContainer(postgres);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
memoryReservation,
|
memoryReservation,
|
||||||
@@ -53,6 +65,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
Name: appName,
|
Name: appName,
|
||||||
TaskTemplate: {
|
TaskTemplate: {
|
||||||
ContainerSpec: {
|
ContainerSpec: {
|
||||||
|
HealthCheck,
|
||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
@@ -62,20 +75,17 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
Args: ["-c", command],
|
Args: ["-c", command],
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
Labels,
|
||||||
},
|
},
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
Placement: {
|
|
||||||
Constraints: ["node.role==manager"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mode: {
|
|
||||||
Replicated: {
|
|
||||||
Replicas: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
Mode: "dnsrr",
|
Mode: "dnsrr",
|
||||||
Ports: externalPort
|
Ports: externalPort
|
||||||
@@ -89,6 +99,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
|
UpdateConfig,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const service = docker.getService(appName);
|
const service = docker.getService(appName);
|
||||||
@@ -96,6 +107,10 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
await service.update({
|
await service.update({
|
||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
|
TaskTemplate: {
|
||||||
|
...settings.TaskTemplate,
|
||||||
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.log("error", error);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
|
|||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
|
generateConfigContainer,
|
||||||
generateFileMounts,
|
generateFileMounts,
|
||||||
generateVolumeMounts,
|
generateVolumeMounts,
|
||||||
prepareEnvironmentVariables,
|
prepareEnvironmentVariables,
|
||||||
@@ -31,6 +32,17 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
const defaultRedisEnv = `REDIS_PASSWORD="${databasePassword}"${
|
const defaultRedisEnv = `REDIS_PASSWORD="${databasePassword}"${
|
||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
HealthCheck,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
|
Labels,
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
|
UpdateConfig,
|
||||||
|
Networks,
|
||||||
|
} = generateConfigContainer(redis);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
memoryReservation,
|
memoryReservation,
|
||||||
@@ -51,6 +63,7 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
Name: appName,
|
Name: appName,
|
||||||
TaskTemplate: {
|
TaskTemplate: {
|
||||||
ContainerSpec: {
|
ContainerSpec: {
|
||||||
|
HealthCheck,
|
||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
@@ -59,20 +72,17 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
"-c",
|
"-c",
|
||||||
command ? command : `redis-server --requirepass ${databasePassword}`,
|
command ? command : `redis-server --requirepass ${databasePassword}`,
|
||||||
],
|
],
|
||||||
|
Labels,
|
||||||
},
|
},
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
Placement: {
|
|
||||||
Constraints: ["node.role==manager"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mode: {
|
|
||||||
Replicated: {
|
|
||||||
Replicas: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
Mode: "dnsrr",
|
Mode: "dnsrr",
|
||||||
Ports: externalPort
|
Ports: externalPort
|
||||||
@@ -86,6 +96,7 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
|
UpdateConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -94,6 +105,10 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
await service.update({
|
await service.update({
|
||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
|
TaskTemplate: {
|
||||||
|
...settings.TaskTemplate,
|
||||||
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
|
|||||||
@@ -348,7 +348,9 @@ export const calculateResources = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateConfigContainer = (application: ApplicationNested) => {
|
export const generateConfigContainer = (
|
||||||
|
application: Partial<ApplicationNested>,
|
||||||
|
) => {
|
||||||
const {
|
const {
|
||||||
healthCheckSwarm,
|
healthCheckSwarm,
|
||||||
restartPolicySwarm,
|
restartPolicySwarm,
|
||||||
@@ -362,7 +364,7 @@ export const generateConfigContainer = (application: ApplicationNested) => {
|
|||||||
networkSwarm,
|
networkSwarm,
|
||||||
} = application;
|
} = application;
|
||||||
|
|
||||||
const haveMounts = mounts.length > 0;
|
const haveMounts = mounts && mounts.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...(healthCheckSwarm && {
|
...(healthCheckSwarm && {
|
||||||
|
|||||||
Reference in New Issue
Block a user