mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-30 11:35:22 +02:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
385fbf4af5 | ||
|
|
44e75ee7e1 | ||
|
|
6b4d6eac1d | ||
|
|
175e84f50e | ||
|
|
efb646c43d | ||
|
|
fa950dae39 | ||
|
|
712ad25e7a | ||
|
|
35a41e774e | ||
|
|
c2ac193fbe | ||
|
|
ce3c89a715 | ||
|
|
96f7206a1d | ||
|
|
b7ace886f3 | ||
|
|
5dc330eaa3 | ||
|
|
b7f5bee2f8 | ||
|
|
19ee5f073b | ||
|
|
1fd4a6ae80 | ||
|
|
3c8a412014 | ||
|
|
eee617719b | ||
|
|
fc611946a6 | ||
|
|
af13c84968 | ||
|
|
ddb78ef8dd |
@@ -46,6 +46,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
|
|||||||
<div className="flex flex-col pt-2 relative">
|
<div className="flex flex-col pt-2 relative">
|
||||||
<div className="flex flex-col gap-6 max-h-[35rem] min-h-[10rem] overflow-y-auto">
|
<div className="flex flex-col gap-6 max-h-[35rem] min-h-[10rem] overflow-y-auto">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
lineWrapping
|
||||||
value={data || "Empty"}
|
value={data || "Empty"}
|
||||||
disabled
|
disabled
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
|
|||||||
<FormLabel>Traefik config</FormLabel>
|
<FormLabel>Traefik config</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
lineWrapping
|
||||||
wrapperClassName="h-[35rem] font-mono"
|
wrapperClassName="h-[35rem] font-mono"
|
||||||
placeholder={`http:
|
placeholder={`http:
|
||||||
routers:
|
routers:
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
|||||||
<div className="flex flex-row items-center gap-2 flex-wrap">
|
<div className="flex flex-row items-center gap-2 flex-wrap">
|
||||||
<span>Webhook URL: </span>
|
<span>Webhook URL: </span>
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
<span className="text-muted-foreground">
|
<span className="break-all text-muted-foreground">
|
||||||
{`${url}/api/deploy/${data?.refreshToken}`}
|
{`${url}/api/deploy/${data?.refreshToken}`}
|
||||||
</span>
|
</span>
|
||||||
<RefreshToken applicationId={applicationId} />
|
<RefreshToken applicationId={applicationId} />
|
||||||
@@ -72,7 +72,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
|||||||
{deployments?.map((deployment) => (
|
{deployments?.map((deployment) => (
|
||||||
<div
|
<div
|
||||||
key={deployment.deploymentId}
|
key={deployment.deploymentId}
|
||||||
className="flex items-center justify-between rounded-lg border p-4"
|
className="flex items-center justify-between rounded-lg border p-4 gap-2"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
|
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
|
||||||
@@ -87,7 +87,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
|||||||
{deployment.title}
|
{deployment.title}
|
||||||
</span>
|
</span>
|
||||||
{deployment.description && (
|
{deployment.description && (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="break-all text-sm text-muted-foreground">
|
||||||
{deployment.description}
|
{deployment.description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ export const ShowTraefikFile = ({ path }: Props) => {
|
|||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
lineWrapping
|
||||||
wrapperClassName="h-[35rem] font-mono"
|
wrapperClassName="h-[35rem] font-mono"
|
||||||
placeholder={`http:
|
placeholder={`http:
|
||||||
routers:
|
routers:
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ interface Props {
|
|||||||
|
|
||||||
export const AddTemplate = ({ projectId }: Props) => {
|
export const AddTemplate = ({ projectId }: Props) => {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const { data } = api.compose.templates.useQuery();
|
const { data } = api.compose.templates.useQuery();
|
||||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
const { data: tags, isLoading: isLoadingTags } =
|
const { data: tags, isLoading: isLoadingTags } =
|
||||||
@@ -75,14 +76,14 @@ export const AddTemplate = ({ projectId }: Props) => {
|
|||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger className="w-full">
|
<DialogTrigger className="w-full">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer space-x-3"
|
className="w-full cursor-pointer space-x-3"
|
||||||
onSelect={(e) => e.preventDefault()}
|
onSelect={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<PuzzleIcon className="size-4 text-muted-foreground" />
|
<PuzzleIcon className="size-4 text-muted-foreground" />
|
||||||
<span>Templates</span>
|
<span>Template</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl p-0">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl p-0">
|
||||||
@@ -283,6 +284,7 @@ export const AddTemplate = ({ projectId }: Props) => {
|
|||||||
utils.project.one.invalidate({
|
utils.project.one.invalidate({
|
||||||
projectId,
|
projectId,
|
||||||
});
|
});
|
||||||
|
setOpen(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|||||||
@@ -9,36 +9,11 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { format } from "date-fns";
|
||||||
import { BadgeCheck } from "lucide-react";
|
import { BadgeCheck } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { RemoveGithubApp } from "./remove-github-app";
|
import { RemoveGithubApp } from "./remove-github-app";
|
||||||
export const generateName = () => {
|
|
||||||
const n1 = ["Blue", "Green", "Red", "Orange", "Violet", "Indigo", "Yellow"];
|
|
||||||
const n2 = [
|
|
||||||
"One",
|
|
||||||
"Two",
|
|
||||||
"Three",
|
|
||||||
"Four",
|
|
||||||
"Five",
|
|
||||||
"Six",
|
|
||||||
"Seven",
|
|
||||||
"Eight",
|
|
||||||
"Nine",
|
|
||||||
"Zero",
|
|
||||||
];
|
|
||||||
return `Dokploy-${n1[Math.round(Math.random() * (n1.length - 1))]}-${
|
|
||||||
n2[Math.round(Math.random() * (n2.length - 1))]
|
|
||||||
}`;
|
|
||||||
};
|
|
||||||
function slugify(text: string) {
|
|
||||||
return text
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[\s\^&*()+=!]+/g, "-")
|
|
||||||
.replace(/[\$.,*+~()'"!:@^&]+/g, "")
|
|
||||||
.replace(/-+/g, "-")
|
|
||||||
.replace(/^-+|-+$/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GithubSetup = () => {
|
export const GithubSetup = () => {
|
||||||
const [isOrganization, setIsOrganization] = useState(false);
|
const [isOrganization, setIsOrganization] = useState(false);
|
||||||
@@ -52,10 +27,9 @@ export const GithubSetup = () => {
|
|||||||
const manifest = JSON.stringify(
|
const manifest = JSON.stringify(
|
||||||
{
|
{
|
||||||
redirect_url: `${origin}/api/redirect?authId=${data?.authId}`,
|
redirect_url: `${origin}/api/redirect?authId=${data?.authId}`,
|
||||||
name: generateName(),
|
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`,
|
||||||
url: origin,
|
url: origin,
|
||||||
hook_attributes: {
|
hook_attributes: {
|
||||||
// JUST FOR TESTING
|
|
||||||
url: `${url}/api/deploy/github`,
|
url: `${url}/api/deploy/github`,
|
||||||
// url: `${origin}/api/webhook`, // Aquí especificas la URL del endpoint de tu webhook
|
// url: `${origin}/api/webhook`, // Aquí especificas la URL del endpoint de tu webhook
|
||||||
},
|
},
|
||||||
@@ -95,8 +69,8 @@ export const GithubSetup = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-end gap-4 flex-wrap">
|
<div className="flex items-end gap-4 flex-wrap">
|
||||||
<RemoveGithubApp />
|
<RemoveGithubApp />
|
||||||
{/* <Link
|
<Link
|
||||||
href={`https://github.com/settings/apps/${data?.githubAppName}`}
|
href={`${data?.githubAppName}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className={buttonVariants({
|
className={buttonVariants({
|
||||||
className: "w-fit",
|
className: "w-fit",
|
||||||
@@ -104,7 +78,7 @@ export const GithubSetup = () => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span className="text-sm">Manage Github App</span>
|
<span className="text-sm">Manage Github App</span>
|
||||||
</Link> */}
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -119,9 +93,9 @@ export const GithubSetup = () => {
|
|||||||
|
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row gap-4">
|
||||||
<Link
|
<Link
|
||||||
href={`https://github.com/apps/${slugify(
|
href={`${
|
||||||
data.githubAppName,
|
data.githubAppName
|
||||||
)}/installations/new?state=gh_setup:${data?.authId}`}
|
}/installations/new?state=gh_setup:${data?.authId}`}
|
||||||
className={buttonVariants({ className: "w-fit" })}
|
className={buttonVariants({ className: "w-fit" })}
|
||||||
>
|
>
|
||||||
Install Github App
|
Install Github App
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const ShowDestinations = () => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">SSH Keys</CardTitle>
|
<CardTitle className="text-xl">SSH Keys</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Use SSH to beeing able cloning from private repositories.
|
Use SSH to be able to clone from private repositories.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2 pt-4">
|
<CardContent className="space-y-2 pt-4">
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const addPermissions = z.object({
|
|||||||
canAccessToTraefikFiles: z.boolean().optional().default(false),
|
canAccessToTraefikFiles: z.boolean().optional().default(false),
|
||||||
canAccessToDocker: z.boolean().optional().default(false),
|
canAccessToDocker: z.boolean().optional().default(false),
|
||||||
canAccessToAPI: z.boolean().optional().default(false),
|
canAccessToAPI: z.boolean().optional().default(false),
|
||||||
|
canAccessToSSHKeys: z.boolean().optional().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddPermissions = z.infer<typeof addPermissions>;
|
type AddPermissions = z.infer<typeof addPermissions>;
|
||||||
@@ -82,6 +83,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
||||||
canAccessToDocker: data.canAccessToDocker,
|
canAccessToDocker: data.canAccessToDocker,
|
||||||
canAccessToAPI: data.canAccessToAPI,
|
canAccessToAPI: data.canAccessToAPI,
|
||||||
|
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form, form.formState.isSubmitSuccessful, form.reset, data]);
|
}, [form, form.formState.isSubmitSuccessful, form.reset, data]);
|
||||||
@@ -98,6 +100,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
accesedServices: data.accesedServices || [],
|
accesedServices: data.accesedServices || [],
|
||||||
canAccessToDocker: data.canAccessToDocker,
|
canAccessToDocker: data.canAccessToDocker,
|
||||||
canAccessToAPI: data.canAccessToAPI,
|
canAccessToAPI: data.canAccessToAPI,
|
||||||
|
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Permissions updated");
|
toast.success("Permissions updated");
|
||||||
@@ -270,6 +273,26 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="canAccessToSSHKeys"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Access to SSH Keys</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Allow to users to access to the SSH Keys section
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accesedProjects"
|
name="accesedProjects"
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ export const ShowMainTraefikConfig = ({ children }: Props) => {
|
|||||||
<FormLabel>Traefik config</FormLabel>
|
<FormLabel>Traefik config</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
lineWrapping
|
||||||
wrapperClassName="h-[35rem] font-mono"
|
wrapperClassName="h-[35rem] font-mono"
|
||||||
placeholder={`providers:
|
placeholder={`providers:
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export const ShowServerTraefikConfig = ({ children }: Props) => {
|
|||||||
<FormLabel>Traefik config</FormLabel>
|
<FormLabel>Traefik config</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
lineWrapping
|
||||||
wrapperClassName="h-[35rem] font-mono"
|
wrapperClassName="h-[35rem] font-mono"
|
||||||
placeholder={`http:
|
placeholder={`http:
|
||||||
routers:
|
routers:
|
||||||
|
|||||||
@@ -79,6 +79,16 @@ export const SettingsLayout = ({ children }: Props) => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
...(user?.canAccessToSSHKeys
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: "SSH Keys",
|
||||||
|
label: "",
|
||||||
|
icon: KeyRound,
|
||||||
|
href: "/dashboard/settings/ssh-keys",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { json } from "@codemirror/lang-json";
|
|||||||
import { yaml } from "@codemirror/lang-yaml";
|
import { yaml } from "@codemirror/lang-yaml";
|
||||||
import { StreamLanguage } from "@codemirror/language";
|
import { StreamLanguage } from "@codemirror/language";
|
||||||
import { properties } from "@codemirror/legacy-modes/mode/properties";
|
import { properties } from "@codemirror/legacy-modes/mode/properties";
|
||||||
|
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";
|
||||||
@@ -10,6 +11,7 @@ interface Props extends ReactCodeMirrorProps {
|
|||||||
wrapperClassName?: string;
|
wrapperClassName?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
language?: "yaml" | "json" | "properties";
|
language?: "yaml" | "json" | "properties";
|
||||||
|
lineWrapping?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeEditor = ({
|
export const CodeEditor = ({
|
||||||
@@ -36,6 +38,7 @@ export const CodeEditor = ({
|
|||||||
: language === "json"
|
: language === "json"
|
||||||
? json()
|
? json()
|
||||||
: StreamLanguage.define(properties),
|
: StreamLanguage.define(properties),
|
||||||
|
props.lineWrapping ? EditorView.lineWrapping : [],
|
||||||
]}
|
]}
|
||||||
{...props}
|
{...props}
|
||||||
editable={!props.disabled}
|
editable={!props.disabled}
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0030_little_kabuki.sql
Normal file
1
apps/dokploy/drizzle/0030_little_kabuki.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "user" ADD COLUMN "canAccessToSSHKeys" boolean DEFAULT false NOT NULL;
|
||||||
3030
apps/dokploy/drizzle/meta/0030_snapshot.json
Normal file
3030
apps/dokploy/drizzle/meta/0030_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -211,6 +211,13 @@
|
|||||||
"when": 1722578386823,
|
"when": 1722578386823,
|
||||||
"tag": "0029_colossal_zodiak",
|
"tag": "0029_colossal_zodiak",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 30,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1723608499147,
|
||||||
|
"tag": "0030_little_kabuki",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.6.2",
|
"version": "v0.6.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
"@codemirror/lang-yaml": "^6.1.1",
|
"@codemirror/lang-yaml": "^6.1.1",
|
||||||
"@codemirror/language": "^6.10.1",
|
"@codemirror/language": "^6.10.1",
|
||||||
"@codemirror/legacy-modes": "6.4.0",
|
"@codemirror/legacy-modes": "6.4.0",
|
||||||
|
"@codemirror/view": "6.29.0",
|
||||||
"@dokploy/trpc-openapi": "0.0.4",
|
"@dokploy/trpc-openapi": "0.0.4",
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^8.4.1",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default async function handler(
|
|||||||
.update(admins)
|
.update(admins)
|
||||||
.set({
|
.set({
|
||||||
githubAppId: data.id,
|
githubAppId: data.id,
|
||||||
githubAppName: data.name,
|
githubAppName: data.html_url,
|
||||||
githubClientId: data.client_id,
|
githubClientId: data.client_id,
|
||||||
githubClientSecret: data.client_secret,
|
githubClientSecret: data.client_secret,
|
||||||
githubWebhookSecret: data.webhook_secret,
|
githubWebhookSecret: data.webhook_secret,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { ShowDestinations } from "@/components/dashboard/settings/ssh-keys/show-ssh-keys";
|
import { ShowDestinations } from "@/components/dashboard/settings/ssh-keys/show-ssh-keys";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||||
|
import { appRouter } from "@/server/api/root";
|
||||||
import { validateRequest } from "@/server/auth/auth";
|
import { validateRequest } from "@/server/auth/auth";
|
||||||
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import React, { type ReactElement } from "react";
|
import React, { type ReactElement } from "react";
|
||||||
|
import superjson from "superjson";
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return (
|
return (
|
||||||
@@ -26,7 +29,7 @@ export async function getServerSideProps(
|
|||||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||||
) {
|
) {
|
||||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||||
if (!user || user.rol === "user") {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
@@ -34,8 +37,45 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const { req, res, resolvedUrl } = ctx;
|
||||||
|
const helpers = createServerSideHelpers({
|
||||||
|
router: appRouter,
|
||||||
|
ctx: {
|
||||||
|
req: req as any,
|
||||||
|
res: res as any,
|
||||||
|
db: null as any,
|
||||||
|
session: session,
|
||||||
|
user: user,
|
||||||
|
},
|
||||||
|
transformer: superjson,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
try {
|
||||||
props: {},
|
await helpers.project.all.prefetch();
|
||||||
};
|
const auth = await helpers.auth.get.fetch();
|
||||||
|
|
||||||
|
if (auth.rol === "user") {
|
||||||
|
const user = await helpers.user.byAuthId.fetch({
|
||||||
|
authId: auth.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user.canAccessToSSHKeys) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
trpcState: helpers.dehydrate(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from "@/server/utils/docker/utils";
|
} from "@/server/utils/docker/utils";
|
||||||
import { recreateDirectory } from "@/server/utils/filesystem/directory";
|
import { recreateDirectory } from "@/server/utils/filesystem/directory";
|
||||||
import { sendDockerCleanupNotifications } from "@/server/utils/notifications/docker-cleanup";
|
import { sendDockerCleanupNotifications } from "@/server/utils/notifications/docker-cleanup";
|
||||||
|
import { execAsync } from "@/server/utils/process/execAsync";
|
||||||
import { spawnAsync } from "@/server/utils/process/spawnAsync";
|
import { spawnAsync } from "@/server/utils/process/spawnAsync";
|
||||||
import {
|
import {
|
||||||
readConfig,
|
readConfig,
|
||||||
@@ -49,14 +50,10 @@ import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
|
|||||||
|
|
||||||
export const settingsRouter = createTRPCRouter({
|
export const settingsRouter = createTRPCRouter({
|
||||||
reloadServer: adminProcedure.mutation(async () => {
|
reloadServer: adminProcedure.mutation(async () => {
|
||||||
await spawnAsync("docker", [
|
const { stdout } = await execAsync(
|
||||||
"service",
|
"docker service inspect dokploy --format '{{.ID}}'",
|
||||||
"update",
|
);
|
||||||
"--force",
|
await execAsync(`docker service update --force ${stdout.trim()}`);
|
||||||
"--image",
|
|
||||||
getDokployImage(),
|
|
||||||
"dokploy",
|
|
||||||
]);
|
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
reloadTraefik: adminProcedure.mutation(async () => {
|
reloadTraefik: adminProcedure.mutation(async () => {
|
||||||
|
|||||||
@@ -34,21 +34,23 @@ export const sshRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
remove: adminProcedure.input(apiRemoveSshKey).mutation(async ({ input }) => {
|
remove: protectedProcedure
|
||||||
try {
|
.input(apiRemoveSshKey)
|
||||||
return await removeSSHKeyById(input.sshKeyId);
|
.mutation(async ({ input }) => {
|
||||||
} catch (error) {
|
try {
|
||||||
throw new TRPCError({
|
return await removeSSHKeyById(input.sshKeyId);
|
||||||
code: "BAD_REQUEST",
|
} catch (error) {
|
||||||
message: "Error to delete this ssh key",
|
throw new TRPCError({
|
||||||
});
|
code: "BAD_REQUEST",
|
||||||
}
|
message: "Error to delete this ssh key",
|
||||||
}),
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
one: protectedProcedure.input(apiFindOneSshKey).query(async ({ input }) => {
|
one: protectedProcedure.input(apiFindOneSshKey).query(async ({ input }) => {
|
||||||
const sshKey = await findSSHKeyById(input.sshKeyId);
|
const sshKey = await findSSHKeyById(input.sshKeyId);
|
||||||
return sshKey;
|
return sshKey;
|
||||||
}),
|
}),
|
||||||
all: adminProcedure.query(async () => {
|
all: protectedProcedure.query(async () => {
|
||||||
return await db.query.sshKeys.findMany({});
|
return await db.query.sshKeys.findMany({});
|
||||||
}),
|
}),
|
||||||
generate: protectedProcedure
|
generate: protectedProcedure
|
||||||
@@ -56,15 +58,17 @@ export const sshRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await generateSSHKey(input.type);
|
return await generateSSHKey(input.type);
|
||||||
}),
|
}),
|
||||||
update: adminProcedure.input(apiUpdateSshKey).mutation(async ({ input }) => {
|
update: protectedProcedure
|
||||||
try {
|
.input(apiUpdateSshKey)
|
||||||
return await updateSSHKeyById(input);
|
.mutation(async ({ input }) => {
|
||||||
} catch (error) {
|
try {
|
||||||
throw new TRPCError({
|
return await updateSSHKeyById(input);
|
||||||
code: "BAD_REQUEST",
|
} catch (error) {
|
||||||
message: "Error to update this ssh key",
|
throw new TRPCError({
|
||||||
cause: error,
|
code: "BAD_REQUEST",
|
||||||
});
|
message: "Error to update this ssh key",
|
||||||
}
|
cause: error,
|
||||||
}),
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const users = pgTable("user", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
|
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
|
||||||
|
canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
|
||||||
canCreateServices: boolean("canCreateServices").notNull().default(false),
|
canCreateServices: boolean("canCreateServices").notNull().default(false),
|
||||||
canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
|
canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
|
||||||
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
|
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
|
||||||
@@ -107,6 +108,7 @@ export const apiAssignPermissions = createSchema
|
|||||||
canAccessToTraefikFiles: true,
|
canAccessToTraefikFiles: true,
|
||||||
canAccessToDocker: true,
|
canAccessToDocker: true,
|
||||||
canAccessToAPI: true,
|
canAccessToAPI: true,
|
||||||
|
canAccessToSSHKeys: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,9 @@ export const initializePostgres = async () => {
|
|||||||
Ports: [
|
Ports: [
|
||||||
{
|
{
|
||||||
TargetPort: 5432,
|
TargetPort: 5432,
|
||||||
...(process.env.NODE_ENV === "development"
|
PublishedPort: process.env.NODE_ENV === "development" ? 5432 : 0,
|
||||||
? { PublishedPort: 5432 }
|
|
||||||
: {}),
|
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
PublishMode: "host",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,10 +33,9 @@ export const initializeRedis = async () => {
|
|||||||
Ports: [
|
Ports: [
|
||||||
{
|
{
|
||||||
TargetPort: 6379,
|
TargetPort: 6379,
|
||||||
...(process.env.NODE_ENV === "development"
|
PublishedPort: process.env.NODE_ENV === "development" ? 6379 : 0,
|
||||||
? { PublishedPort: 6379 }
|
|
||||||
: {}),
|
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
PublishMode: "host",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ docker service create \
|
|||||||
--network dokploy-network \
|
--network dokploy-network \
|
||||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||||
|
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||||
--publish published=3000,target=3000,mode=host \
|
--publish published=3000,target=3000,mode=host \
|
||||||
--update-parallelism 1 \
|
--update-parallelism 1 \
|
||||||
--update-order stop-first \
|
--update-order stop-first \
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ docker service create \
|
|||||||
--network dokploy-network \
|
--network dokploy-network \
|
||||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||||
|
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||||
--publish published=3000,target=3000,mode=host \
|
--publish published=3000,target=3000,mode=host \
|
||||||
--update-parallelism 1 \
|
--update-parallelism 1 \
|
||||||
--update-order stop-first \
|
--update-order stop-first \
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ docker service create \
|
|||||||
--network dokploy-network \
|
--network dokploy-network \
|
||||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||||
|
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||||
--publish published=3000,target=3000,mode=host \
|
--publish published=3000,target=3000,mode=host \
|
||||||
--update-parallelism 1 \
|
--update-parallelism 1 \
|
||||||
--update-order stop-first \
|
--update-order stop-first \
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"format-and-lint": "biome check .",
|
"format-and-lint": "biome check .",
|
||||||
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
|
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
|
||||||
"format-and-lint:fix": "biome check . --write",
|
"format-and-lint:fix": "biome check . --write",
|
||||||
"prepare": "node .husky/install.mjs"
|
"prepare": "node ./.config/.husky/install.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv": "16.4.5",
|
"dotenv": "16.4.5",
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -114,6 +114,9 @@ importers:
|
|||||||
'@codemirror/legacy-modes':
|
'@codemirror/legacy-modes':
|
||||||
specifier: 6.4.0
|
specifier: 6.4.0
|
||||||
version: 6.4.0
|
version: 6.4.0
|
||||||
|
'@codemirror/view':
|
||||||
|
specifier: 6.29.0
|
||||||
|
version: 6.29.0
|
||||||
'@dokploy/trpc-openapi':
|
'@dokploy/trpc-openapi':
|
||||||
specifier: 0.0.4
|
specifier: 0.0.4
|
||||||
version: 0.0.4(@trpc/server@10.45.2)(zod@3.23.8)
|
version: 0.0.4(@trpc/server@10.45.2)(zod@3.23.8)
|
||||||
|
|||||||
Reference in New Issue
Block a user