mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-10 08:25:22 +02:00
Merge branch 'canary' into feat/internal-path-routing
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
# License
|
||||
|
||||
## Core License (Apache License 2.0)
|
||||
|
||||
Copyright 2024 Mauricio Siu.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
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, 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, 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, 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.
|
||||
@@ -130,7 +130,7 @@ const createStringToJSONSchema = (schema: z.ZodTypeAny) => {
|
||||
}
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
ctx.addIssue({ code: "custom", message: "Invalid JSON format" });
|
||||
return z.NEVER;
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ export const ShowImport = ({ composeId }: Props) => {
|
||||
composeId,
|
||||
});
|
||||
setShowModal(false);
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("Error importing template");
|
||||
}
|
||||
};
|
||||
@@ -126,7 +126,7 @@ export const ShowImport = ({ composeId }: Props) => {
|
||||
});
|
||||
setTemplateInfo(result);
|
||||
setShowModal(true);
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("Error processing template");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,6 +35,9 @@ import { z } from "zod";
|
||||
|
||||
const AddPortSchema = z.object({
|
||||
publishedPort: z.number().int().min(1).max(65535),
|
||||
publishMode: z.enum(["ingress", "host"], {
|
||||
required_error: "Publish mode is required",
|
||||
}),
|
||||
targetPort: z.number().int().min(1).max(65535),
|
||||
protocol: z.enum(["tcp", "udp"], {
|
||||
required_error: "Protocol is required",
|
||||
@@ -80,6 +83,7 @@ export const HandlePorts = ({
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
publishedPort: data?.publishedPort ?? 0,
|
||||
publishMode: data?.publishMode ?? "ingress",
|
||||
targetPort: data?.targetPort ?? 0,
|
||||
protocol: data?.protocol ?? "tcp",
|
||||
});
|
||||
@@ -165,6 +169,32 @@ export const HandlePorts = ({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="publishMode"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="md:col-span-2">
|
||||
<FormLabel>Published Port Mode</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a publish mode for the port" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value={"ingress"}>Ingress</SelectItem>
|
||||
<SelectItem value={"host"}>Host</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetPort"
|
||||
|
||||
@@ -60,7 +60,7 @@ export const ShowPorts = ({ applicationId }: Props) => {
|
||||
{data?.ports.map((port) => (
|
||||
<div key={port.portId}>
|
||||
<div className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Published Port</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
@@ -68,7 +68,13 @@ export const ShowPorts = ({ applicationId }: Props) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium"> Target Port</span>
|
||||
<span className="font-medium">Published Port Mode</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{port?.publishMode?.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Target Port</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{port.targetPort}
|
||||
</span>
|
||||
|
||||
@@ -77,7 +77,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
||||
composeId,
|
||||
});
|
||||
})
|
||||
.catch((_e) => {
|
||||
.catch(() => {
|
||||
toast.error("Error updating the Compose config");
|
||||
});
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
||||
.then(() => {
|
||||
refetch();
|
||||
})
|
||||
.catch((_err) => {});
|
||||
.catch(() => {});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||
projectId,
|
||||
});
|
||||
})
|
||||
.catch((_e) => {
|
||||
.catch(() => {
|
||||
toast.error("Error creating the service");
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1063,7 +1063,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
});
|
||||
}
|
||||
toast.success("Connection Success");
|
||||
} catch (_err) {
|
||||
} catch {
|
||||
toast.error("Error testing the provider");
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -63,7 +63,7 @@ export const Disable2FA = () => {
|
||||
toast.success("2FA disabled successfully");
|
||||
utils.user.get.invalidate();
|
||||
setIsOpen(false);
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
form.setError("password", {
|
||||
message: "Connection error. Please try again.",
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
|
||||
await refetch();
|
||||
}
|
||||
toast.success("Docker Cleanup updated");
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("Docker Cleanup Error");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
|
||||
try {
|
||||
await utils.settings.checkGPUStatus.invalidate({ serverId });
|
||||
await refetch();
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("Failed to refresh GPU status");
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
@@ -74,7 +74,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
|
||||
|
||||
try {
|
||||
await setupGPU.mutateAsync({ serverId });
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
// Error handling is done in mutation's onError
|
||||
}
|
||||
};
|
||||
|
||||
@@ -146,7 +146,7 @@ export const ShowInvitations = () => {
|
||||
{invitation.status === "pending" && (
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={(_e) => {
|
||||
onSelect={() => {
|
||||
copy(
|
||||
`${origin}/invitation?token=${invitation.id}`,
|
||||
);
|
||||
@@ -162,7 +162,7 @@ export const ShowInvitations = () => {
|
||||
{invitation.status === "pending" && (
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={async (_e) => {
|
||||
onSelect={async () => {
|
||||
const result =
|
||||
await authClient.organization.cancelInvitation(
|
||||
{
|
||||
@@ -189,7 +189,7 @@ export const ShowInvitations = () => {
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={async (_e) => {
|
||||
onSelect={async () => {
|
||||
await removeInvitation({
|
||||
invitationId: invitation.id,
|
||||
}).then(() => {
|
||||
|
||||
@@ -91,7 +91,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
|
||||
});
|
||||
toast.success(t("settings.server.webServer.traefik.portsUpdated"));
|
||||
setOpen(false);
|
||||
} catch (_error) {}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -17,7 +17,7 @@ const PopoverContent = React.forwardRef<
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
"z-50 w-full rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
2
apps/dokploy/drizzle/0099_wise_golden_guardian.sql
Normal file
2
apps/dokploy/drizzle/0099_wise_golden_guardian.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
CREATE TYPE "public"."publishModeType" AS ENUM('ingress', 'host');--> statement-breakpoint
|
||||
ALTER TABLE "port" ADD COLUMN "publishMode" "publishModeType" DEFAULT 'host' NOT NULL;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "4d928914-5268-4921-aa21-c1b087cc15cc",
|
||||
"id": "71f68c87-ddb4-4e8c-b9fc-1db7fbcedf56",
|
||||
"prevId": "edde8c54-b715-4db6-bc3a-85d435226083",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -1209,20 +1209,6 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'none'"
|
||||
},
|
||||
"internalPath": {
|
||||
"name": "internalPath",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'/'"
|
||||
},
|
||||
"stripPath": {
|
||||
"name": "stripPath",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
@@ -2847,6 +2833,14 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"publishMode": {
|
||||
"name": "publishMode",
|
||||
"type": "publishModeType",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'host'"
|
||||
},
|
||||
"targetPort": {
|
||||
"name": "targetPort",
|
||||
"type": "integer",
|
||||
@@ -5730,6 +5724,14 @@
|
||||
"udp"
|
||||
]
|
||||
},
|
||||
"public.publishModeType": {
|
||||
"name": "publishModeType",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"ingress",
|
||||
"host"
|
||||
]
|
||||
},
|
||||
"public.applicationStatus": {
|
||||
"name": "applicationStatus",
|
||||
"schema": "public",
|
||||
|
||||
@@ -698,8 +698,8 @@
|
||||
{
|
||||
"idx": 99,
|
||||
"version": "7",
|
||||
"when": 1751293280505,
|
||||
"tag": "0099_fast_the_order",
|
||||
"when": 1751693569786,
|
||||
"tag": "0099_wise_golden_guardian",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.23.6",
|
||||
"version": "v0.23.7",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -390,7 +390,7 @@ const Project = (
|
||||
break;
|
||||
}
|
||||
success++;
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error(`Error starting service ${serviceId}`);
|
||||
}
|
||||
}
|
||||
@@ -437,7 +437,7 @@ const Project = (
|
||||
break;
|
||||
}
|
||||
success++;
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error(`Error stopping service ${serviceId}`);
|
||||
}
|
||||
}
|
||||
@@ -1107,7 +1107,7 @@ export async function getServerSideProps(
|
||||
projectId: params?.projectId,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -413,7 +413,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -409,7 +409,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -338,7 +338,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -340,7 +340,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -324,7 +324,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -321,7 +321,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -328,7 +328,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -68,7 +68,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
|
||||
toast.success("Logged in successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("An error occurred while logging in");
|
||||
} finally {
|
||||
setIsLoginLoading(false);
|
||||
@@ -124,7 +124,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
|
||||
toast.success("Logged in successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("An error occurred while verifying 2FA code");
|
||||
} finally {
|
||||
setIsTwoFactorLoading(false);
|
||||
@@ -154,7 +154,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
|
||||
toast.success("Logged in successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("An error occurred while verifying backup code");
|
||||
} finally {
|
||||
setIsBackupCodeLoading(false);
|
||||
@@ -478,7 +478,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (_error) {}
|
||||
} catch {}
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
||||
@@ -133,7 +133,7 @@ const Invitation = ({
|
||||
|
||||
toast.success("Account created successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error("An error occurred while creating your account");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
containerRestart,
|
||||
findServerById,
|
||||
getConfig,
|
||||
getContainers,
|
||||
getContainersByAppLabel,
|
||||
@@ -9,6 +10,9 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
export const containerIdRegex = /^[a-zA-Z0-9.\-_]+$/;
|
||||
|
||||
export const dockerRouter = createTRPCRouter({
|
||||
getContainers: protectedProcedure
|
||||
@@ -17,14 +21,23 @@ export const dockerRouter = createTRPCRouter({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getContainers(input.serverId);
|
||||
}),
|
||||
|
||||
restartContainer: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
containerId: z.string().min(1),
|
||||
containerId: z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(containerIdRegex, "Invalid container id."),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
@@ -34,11 +47,20 @@ export const dockerRouter = createTRPCRouter({
|
||||
getConfig: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
containerId: z.string().min(1),
|
||||
containerId: z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(containerIdRegex, "Invalid container id."),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getConfig(input.containerId, input.serverId);
|
||||
}),
|
||||
|
||||
@@ -48,11 +70,17 @@ export const dockerRouter = createTRPCRouter({
|
||||
appType: z
|
||||
.union([z.literal("stack"), z.literal("docker-compose")])
|
||||
.optional(),
|
||||
appName: z.string().min(1),
|
||||
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getContainersByAppNameMatch(
|
||||
input.appName,
|
||||
input.appType,
|
||||
@@ -63,12 +91,18 @@ export const dockerRouter = createTRPCRouter({
|
||||
getContainersByAppLabel: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
appName: z.string().min(1),
|
||||
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||
serverId: z.string().optional(),
|
||||
type: z.enum(["standalone", "swarm"]),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getContainersByAppLabel(
|
||||
input.appName,
|
||||
input.type,
|
||||
@@ -79,22 +113,34 @@ export const dockerRouter = createTRPCRouter({
|
||||
getStackContainersByAppName: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
appName: z.string().min(1),
|
||||
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getStackContainersByAppName(input.appName, input.serverId);
|
||||
}),
|
||||
|
||||
getServiceContainersByAppName: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
appName: z.string().min(1),
|
||||
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getServiceContainersByAppName(input.appName, input.serverId);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -459,6 +459,15 @@ export const settingsRouter = createTRPCRouter({
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
|
||||
return readConfigInPath(input.path, input.serverId);
|
||||
}),
|
||||
getIp: protectedProcedure.query(async ({ ctx }) => {
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { findServerById } from "@dokploy/server";
|
||||
import { containerIdRegex } from "./docker";
|
||||
|
||||
export const swarmRouter = createTRPCRouter({
|
||||
getNodes: protectedProcedure
|
||||
@@ -14,12 +17,24 @@ export const swarmRouter = createTRPCRouter({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getSwarmNodes(input.serverId);
|
||||
}),
|
||||
getNodeInfo: protectedProcedure
|
||||
.input(z.object({ nodeId: z.string(), serverId: z.string().optional() }))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getNodeInfo(input.nodeId, input.serverId);
|
||||
}),
|
||||
getNodeApps: protectedProcedure
|
||||
@@ -28,17 +43,29 @@ export const swarmRouter = createTRPCRouter({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return getNodeApplications(input.serverId);
|
||||
}),
|
||||
getAppInfos: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
appName: z.string(),
|
||||
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
return await getApplicationInfo(input.appName, input.serverId);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -75,6 +75,24 @@ export const userRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
// If user not found in the organization, deny access
|
||||
if (!memberResult) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "User not found in this organization",
|
||||
});
|
||||
}
|
||||
|
||||
// Allow access if:
|
||||
// 1. User is requesting their own information
|
||||
// 2. User has owner role (admin permissions) AND user is in the same organization
|
||||
if (memberResult.userId !== ctx.user.id && ctx.user.role !== "owner") {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to access this user",
|
||||
});
|
||||
}
|
||||
|
||||
return memberResult;
|
||||
}),
|
||||
get: protectedProcedure.query(async ({ ctx }) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ export const isWSL = async () => {
|
||||
const { stdout } = await execAsync("uname -r");
|
||||
const isWSL = stdout.includes("microsoft");
|
||||
return isWSL;
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,7 +101,7 @@ export const setupDeploymentLogsWebSocketServer = (
|
||||
ws.close();
|
||||
});
|
||||
}
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
// @ts-ignore
|
||||
// const errorMessage = error?.message as unknown as string;
|
||||
// ws.send(errorMessage);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
initializeNetwork,
|
||||
initializeSwarm,
|
||||
} from "@dokploy/server/setup/setup";
|
||||
import { execAsync } from "@dokploy/server";
|
||||
(async () => {
|
||||
try {
|
||||
setupDirectories();
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
await initializeNetwork();
|
||||
createDefaultTraefikConfig();
|
||||
createDefaultServerTraefikConfig();
|
||||
await execAsync("docker pull traefik:v3.1.2");
|
||||
await initializeTraefik();
|
||||
await initializeRedis();
|
||||
await initializePostgres();
|
||||
|
||||
@@ -242,3 +242,8 @@
|
||||
background-color: var(--terminal-paste) !important;
|
||||
color: currentColor !important;
|
||||
}
|
||||
|
||||
.cm-content,
|
||||
.cm-lineWrapping {
|
||||
@apply font-mono;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright 2025 Mauricio Siu.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, 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 THE
|
||||
SOFTWARE.
|
||||
Reference in New Issue
Block a user