diff --git a/LICENSE.MD b/LICENSE.MD index 6cbef2c6d..bcef8b36e 100644 --- a/LICENSE.MD +++ b/LICENSE.MD @@ -1,8 +1,13 @@ -# License +Copyright 2026-present Dokploy Technology, Inc. -## Core License (Apache License 2.0) +Portions of this software are licensed as follows: -Copyright 2025 Mauricio Siu. +* All content that resides under a "/proprietary" directory of this repository, if that directory exists, is licensed under the license defined in "LICENSE_PROPRIETARY". +* Content outside of the above mentioned directories or restrictions above is available under the "Apache License 2.0" license as defined below. + +## Apache License 2.0 + +Copyright 2026-present Dokploy Technology, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,12 +20,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -## Additional Terms for Specific Features -The following additional terms apply to the multi-node support, Docker Compose file, Preview Deployments and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License: - -- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server, will always be free to use in the self-hosted version. -- **Restriction on Resale**: The multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent. -- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server features must be distributed freely and cannot be sold or offered as a service. - -For further inquiries or permissions, please contact us directly. diff --git a/LICENSE_PROPRIETARY.md b/LICENSE_PROPRIETARY.md new file mode 100644 index 000000000..0f4957575 --- /dev/null +++ b/LICENSE_PROPRIETARY.md @@ -0,0 +1,11 @@ +The Dokploy Source Available license (DSAL) version 1.0 + +Copyright (c) 2026-present Dokploy Technology, Inc. + +With regard to the Dokploy Software:This software and associated documentation files (the "Software") may only beused in production, if you (and any entity that you represent) have agreed to, and are in compliance with, a valid commercial agreement from Dokploy.Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Dokploy and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid Dokploy Source Available License. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes, without requiring a subscription. You agree that Dokploy and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to theforegoing, it is forbidden to copy, merge, publish, distribute, sublicense,and/or sell the Software. + +This Dokploy Source Available license applies only to the part of this Software that is in a /proprietary folder. The full text of this License shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE. + +For all third party components incorporated into the Dokploy Software, thosecomponents are licensed under the original license provided by the owner of the applicable component. \ No newline at end of file diff --git a/README.md b/README.md index 23fcd0c9d..e97735597 100644 --- a/README.md +++ b/README.md @@ -68,53 +68,21 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). [Github Sponsors](https://github.com/sponsors/Siumauricio) - +## Sponsors - - -### Hero Sponsors 🎖 - -
- - - - - -### Premium Supporters 🥇 - - - - - - - -### Elite Contributors 🥈 - - - -### Supporting Members 🥉 - - +| Sponsor | Logo | Supporter Level | +|---------|:----:|----------------| +| [Hostinger](https://www.hostinger.com/vps-hosting?ref=dokploy) |
| 🎖 Hero Sponsor |
+| [LX Aer](https://www.lxaer.com/?ref=dokploy) |
| 🎖 Hero Sponsor |
+| [LinkDR](https://linkdr.com/?ref=dokploy) |
| 🎖 Hero Sponsor |
+| [Awesome Tools](https://awesome.tools/) |
| 🎖 Hero Sponsor |
+| [Supafort](https://supafort.com/?ref=dokploy) |
| 🥇 Premium Supporter |
+| [Agentdock](https://agentdock.ai/?ref=dokploy) |
| 🥇 Premium Supporter |
+| [AmericanCloud](https://americancloud.com/?ref=dokploy) |
| 🥈 Elite Contributor |
+| [Tolgee](https://tolgee.io/?utm_source=github_dokploy&utm_medium=banner&utm_campaign=dokploy) |
| 🥈 Elite Contributor |
+| [Cloudblast](https://cloudblast.io/?ref=dokploy) |
| 🥉 Supporting Member |
### Community Backers 🤝
diff --git a/apps/dokploy/.env.production.example b/apps/dokploy/.env.production.example
index 41e934c3a..560faf9e6 100644
--- a/apps/dokploy/.env.production.example
+++ b/apps/dokploy/.env.production.example
@@ -1,3 +1,2 @@
-DATABASE_URL="postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy"
PORT=3000
NODE_ENV=production
\ No newline at end of file
diff --git a/apps/dokploy/__test__/env/environment-access-fallback.test.ts b/apps/dokploy/__test__/env/environment-access-fallback.test.ts
new file mode 100644
index 000000000..a4b56393a
--- /dev/null
+++ b/apps/dokploy/__test__/env/environment-access-fallback.test.ts
@@ -0,0 +1,294 @@
+import { describe, expect, it } from "vitest";
+
+// Type definitions matching the project structure
+type Environment = {
+ environmentId: string;
+ name: string;
+ isDefault: boolean;
+};
+
+type Project = {
+ projectId: string;
+ name: string;
+ environments: Environment[];
+};
+
+/**
+ * Helper function that selects the appropriate environment for a user
+ * This matches the logic used in search-command.tsx and show.tsx
+ */
+function selectAccessibleEnvironment(
+ project: Project | null | undefined,
+): Environment | null {
+ if (!project || !project.environments || project.environments.length === 0) {
+ return null;
+ }
+
+ // Find default environment from accessible environments, or fall back to first accessible environment
+ const defaultEnvironment =
+ project.environments.find((environment) => environment.isDefault) ||
+ project.environments[0];
+
+ return defaultEnvironment || null;
+}
+
+describe("Environment Access Fallback", () => {
+ describe("selectAccessibleEnvironment", () => {
+ it("should return default environment when user has access to it", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-prod",
+ name: "production",
+ isDefault: true,
+ },
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-prod");
+ expect(result?.isDefault).toBe(true);
+ });
+
+ it("should return first accessible environment when user doesn't have access to default", () => {
+ // Simulating filtered environments (user only has access to development)
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ // Note: production is not in the list because user doesn't have access
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev");
+ expect(result?.name).toBe("development");
+ });
+
+ it("should return first environment when no default is marked but environments exist", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev");
+ });
+
+ it("should return null when project has no accessible environments", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).toBeNull();
+ });
+
+ it("should return null when project is null", () => {
+ const result = selectAccessibleEnvironment(null);
+
+ expect(result).toBeNull();
+ });
+
+ it("should return null when project is undefined", () => {
+ const result = selectAccessibleEnvironment(undefined);
+
+ expect(result).toBeNull();
+ });
+
+ it("should handle project with single accessible environment", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev");
+ });
+
+ it("should prioritize default environment even when it's not first in the array", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-prod",
+ name: "production",
+ isDefault: true,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-prod");
+ expect(result?.isDefault).toBe(true);
+ });
+
+ it("should handle multiple default environments by returning the first one found", () => {
+ // Edge case: multiple environments marked as default (shouldn't happen, but test it)
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-prod-1",
+ name: "production-1",
+ isDefault: true,
+ },
+ {
+ environmentId: "env-prod-2",
+ name: "production-2",
+ isDefault: true,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.isDefault).toBe(true);
+ // Should return the first default found
+ expect(result?.environmentId).toBe("env-prod-1");
+ });
+
+ it("should work correctly when user has access to multiple environments including default", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-prod",
+ name: "production",
+ isDefault: true,
+ },
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-prod");
+ expect(result?.isDefault).toBe(true);
+ });
+
+ it("should handle real-world scenario: user with only development access", () => {
+ // This simulates the exact bug we're fixing:
+ // User has access to development but not production (default)
+ // The filtered environments array only contains development
+ const project: Project = {
+ projectId: "proj-1",
+ name: "My Project",
+ environments: [
+ // Only development is accessible (production was filtered out)
+ {
+ environmentId: "env-dev-123",
+ name: "development",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev-123");
+ expect(result?.name).toBe("development");
+ // Should not be null even though it's not the default
+ });
+ });
+
+ describe("Environment selection edge cases", () => {
+ it("should handle project with environments property as undefined", () => {
+ const project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: undefined,
+ } as unknown as Project;
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).toBeNull();
+ });
+
+ it("should handle project with null environments array", () => {
+ const project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: null,
+ } as unknown as Project;
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).toBeNull();
+ });
+ });
+});
diff --git a/apps/dokploy/__test__/env/stack-environment.test.ts b/apps/dokploy/__test__/env/stack-environment.test.ts
new file mode 100644
index 000000000..13f5adb53
--- /dev/null
+++ b/apps/dokploy/__test__/env/stack-environment.test.ts
@@ -0,0 +1,184 @@
+import { getEnviromentVariablesObject } from "@dokploy/server/index";
+import { describe, expect, it } from "vitest";
+
+const projectEnv = `
+ENVIRONMENT=staging
+DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
+PORT=3000
+`;
+
+const environmentEnv = `
+NODE_ENV=development
+API_URL=https://api.dev.example.com
+REDIS_URL=redis://localhost:6379
+DATABASE_NAME=dev_database
+SECRET_KEY=env-secret-123
+`;
+
+describe("getEnviromentVariablesObject with environment variables (Stack compose)", () => {
+ it("resolves environment variables correctly for Stack compose", () => {
+ const serviceEnv = `
+FOO=\${{environment.NODE_ENV}}
+BAR=\${{environment.API_URL}}
+BAZ=test
+`;
+
+ const result = getEnviromentVariablesObject(
+ serviceEnv,
+ projectEnv,
+ environmentEnv,
+ );
+
+ expect(result).toEqual({
+ FOO: "development",
+ BAR: "https://api.dev.example.com",
+ BAZ: "test",
+ });
+ });
+
+ it("resolves both project and environment variables for Stack compose", () => {
+ const serviceEnv = `
+ENVIRONMENT=\${{project.ENVIRONMENT}}
+NODE_ENV=\${{environment.NODE_ENV}}
+API_URL=\${{environment.API_URL}}
+DATABASE_URL=\${{project.DATABASE_URL}}
+SERVICE_PORT=4000
+`;
+
+ const result = getEnviromentVariablesObject(
+ serviceEnv,
+ projectEnv,
+ environmentEnv,
+ );
+
+ expect(result).toEqual({
+ ENVIRONMENT: "staging",
+ NODE_ENV: "development",
+ API_URL: "https://api.dev.example.com",
+ DATABASE_URL: "postgres://postgres:postgres@localhost:5432/project_db",
+ SERVICE_PORT: "4000",
+ });
+ });
+
+ it("handles multiple environment references in single value for Stack compose", () => {
+ const multiRefEnv = `
+HOST=localhost
+PORT=5432
+USERNAME=postgres
+PASSWORD=secret123
+`;
+
+ const serviceEnv = `
+DATABASE_URL=postgresql://\${{environment.USERNAME}}:\${{environment.PASSWORD}}@\${{environment.HOST}}:\${{environment.PORT}}/mydb
+`;
+
+ const result = getEnviromentVariablesObject(serviceEnv, "", multiRefEnv);
+
+ expect(result).toEqual({
+ DATABASE_URL: "postgresql://postgres:secret123@localhost:5432/mydb",
+ });
+ });
+
+ it("throws error for undefined environment variables in Stack compose", () => {
+ const serviceWithUndefined = `
+UNDEFINED_VAR=\${{environment.UNDEFINED_VAR}}
+`;
+
+ expect(() =>
+ getEnviromentVariablesObject(serviceWithUndefined, "", environmentEnv),
+ ).toThrow("Invalid environment variable: environment.UNDEFINED_VAR");
+ });
+
+ it("allows service variables to override environment variables in Stack compose", () => {
+ const serviceOverrideEnv = `
+NODE_ENV=production
+API_URL=\${{environment.API_URL}}
+`;
+
+ const result = getEnviromentVariablesObject(
+ serviceOverrideEnv,
+ "",
+ environmentEnv,
+ );
+
+ expect(result).toEqual({
+ NODE_ENV: "production",
+ API_URL: "https://api.dev.example.com",
+ });
+ });
+
+ it("resolves complex references with project, environment, and service variables for Stack compose", () => {
+ const complexServiceEnv = `
+FULL_DATABASE_URL=\${{project.DATABASE_URL}}/\${{environment.DATABASE_NAME}}
+API_ENDPOINT=\${{environment.API_URL}}/\${{project.ENVIRONMENT}}/api
+SERVICE_NAME=my-service
+COMPLEX_VAR=\${{SERVICE_NAME}}-\${{environment.NODE_ENV}}-\${{project.ENVIRONMENT}}
+`;
+
+ const result = getEnviromentVariablesObject(
+ complexServiceEnv,
+ projectEnv,
+ environmentEnv,
+ );
+
+ expect(result).toEqual({
+ FULL_DATABASE_URL:
+ "postgres://postgres:postgres@localhost:5432/project_db/dev_database",
+ API_ENDPOINT: "https://api.dev.example.com/staging/api",
+ SERVICE_NAME: "my-service",
+ COMPLEX_VAR: "my-service-development-staging",
+ });
+ });
+
+ it("maintains precedence: service > environment > project in Stack compose", () => {
+ const conflictingProjectEnv = `
+NODE_ENV=production-project
+API_URL=https://project.api.com
+DATABASE_NAME=project_db
+`;
+
+ const conflictingEnvironmentEnv = `
+NODE_ENV=development-environment
+API_URL=https://environment.api.com
+DATABASE_NAME=env_db
+`;
+
+ const serviceWithConflicts = `
+NODE_ENV=service-override
+PROJECT_ENV=\${{project.NODE_ENV}}
+ENV_VAR=\${{environment.API_URL}}
+DB_NAME=\${{environment.DATABASE_NAME}}
+`;
+
+ const result = getEnviromentVariablesObject(
+ serviceWithConflicts,
+ conflictingProjectEnv,
+ conflictingEnvironmentEnv,
+ );
+
+ expect(result).toEqual({
+ NODE_ENV: "service-override",
+ PROJECT_ENV: "production-project",
+ ENV_VAR: "https://environment.api.com",
+ DB_NAME: "env_db",
+ });
+ });
+
+ it("handles empty environment variables in Stack compose", () => {
+ const serviceWithEmpty = `
+SERVICE_VAR=test
+PROJECT_VAR=\${{project.ENVIRONMENT}}
+`;
+
+ const result = getEnviromentVariablesObject(
+ serviceWithEmpty,
+ projectEnv,
+ "",
+ );
+
+ expect(result).toEqual({
+ SERVICE_VAR: "test",
+ PROJECT_VAR: "staging",
+ });
+ });
+});
diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx
index 739bd87a5..ee427feca 100644
--- a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx
@@ -1,205 +1,106 @@
-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 { Settings } from "lucide-react";
+import { useState } from "react";
import { AlertBlock } from "@/components/shared/alert-block";
-import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
- DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import { api } from "@/utils/api";
+import { cn } from "@/lib/utils";
+import {
+ EndpointSpecForm,
+ HealthCheckForm,
+ LabelsForm,
+ ModeForm,
+ PlacementForm,
+ RestartPolicyForm,
+ RollbackConfigForm,
+ StopGracePeriodForm,
+ UpdateConfigForm,
+} from "./swarm-forms";
-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 EndpointPortConfigSwarmSchema = z
- .object({
- Protocol: z.string().optional(),
- TargetPort: z.number().optional(),
- PublishedPort: z.number().optional(),
- PublishMode: z.string().optional(),
- })
- .strict();
-
-const EndpointSpecSwarmSchema = z
- .object({
- Mode: z.string().optional(),
- Ports: z.array(EndpointPortConfigSwarmSchema).optional(),
- })
- .strict();
-
-const createStringToJSONSchema = (schema: z.ZodTypeAny) => {
- return z
- .string()
- .transform((str, ctx) => {
- if (str === null || str === "") {
- return null;
- }
- try {
- return JSON.parse(str);
- } catch {
- ctx.addIssue({ code: "custom", message: "Invalid JSON format" });
- return z.NEVER;
- }
- })
- .superRefine((data, ctx) => {
- if (data === null) {
- return;
- }
-
- if (Object.keys(data).length === 0) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "Object cannot be empty",
- });
- return;
- }
-
- const parseResult = schema.safeParse(data);
- if (!parseResult.success) {
- for (const error of parseResult.error.issues) {
- const path = error.path.join(".");
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `${path} ${error.message}`,
- });
- }
- }
- });
+type MenuItem = {
+ id: string;
+ label: string;
+ description: string;
+ docDescription: string;
};
-const addSwarmSettings = z.object({
- healthCheckSwarm: createStringToJSONSchema(HealthCheckSwarmSchema).nullable(),
- restartPolicySwarm: createStringToJSONSchema(
- RestartPolicySwarmSchema,
- ).nullable(),
- placementSwarm: createStringToJSONSchema(PlacementSwarmSchema).nullable(),
- updateConfigSwarm: createStringToJSONSchema(
- UpdateConfigSwarmSchema,
- ).nullable(),
- rollbackConfigSwarm: createStringToJSONSchema(
- UpdateConfigSwarmSchema,
- ).nullable(),
- modeSwarm: createStringToJSONSchema(ServiceModeSwarmSchema).nullable(),
- labelsSwarm: createStringToJSONSchema(LabelsSwarmSchema).nullable(),
- networkSwarm: createStringToJSONSchema(NetworkSwarmSchema).nullable(),
- stopGracePeriodSwarm: z.bigint().nullable(),
- endpointSpecSwarm: createStringToJSONSchema(
- EndpointSpecSwarmSchema,
- ).nullable(),
-});
-
-type AddSwarmSettings = z.infer