mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge branch 'canary' into typo-fix
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
ImportIcon,
|
||||
Loader2,
|
||||
Trash2,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { toast } from "sonner";
|
||||
@@ -24,6 +25,13 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider";
|
||||
@@ -39,6 +47,8 @@ export const ShowGitProviders = () => {
|
||||
const { data, isPending, refetch } = api.gitProvider.getAll.useQuery();
|
||||
const { mutateAsync, isPending: isRemoving } =
|
||||
api.gitProvider.remove.useMutation();
|
||||
const { mutateAsync: toggleShare, isPending: isToggling } =
|
||||
api.gitProvider.toggleShare.useMutation();
|
||||
const url = useUrl();
|
||||
|
||||
const getGitlabUrl = (
|
||||
@@ -154,10 +164,62 @@ export const ShowGitProviders = () => {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{!gitProvider.isOwner && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-xs"
|
||||
>
|
||||
<Users className="size-3 mr-1" />
|
||||
Shared
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
{gitProvider.isOwner && (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1.5 mr-2">
|
||||
<Users className="size-4 text-muted-foreground" />
|
||||
<Switch
|
||||
disabled={isToggling}
|
||||
checked={
|
||||
gitProvider.sharedWithOrganization
|
||||
}
|
||||
onCheckedChange={async (
|
||||
checked,
|
||||
) => {
|
||||
await toggleShare({
|
||||
gitProviderId:
|
||||
gitProvider.gitProviderId,
|
||||
sharedWithOrganization: checked,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
checked
|
||||
? "Provider shared with organization"
|
||||
: "Provider unshared",
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error updating sharing",
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Share with entire organization
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{isBitbucket &&
|
||||
gitProvider.bitbucket?.appPassword &&
|
||||
!gitProvider.bitbucket?.apiToken ? (
|
||||
@@ -222,62 +284,71 @@ export const ShowGitProviders = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isGithub && haveGithubRequirements && (
|
||||
<EditGithubProvider
|
||||
githubId={gitProvider.github?.githubId}
|
||||
/>
|
||||
)}
|
||||
{gitProvider.isOwner && (
|
||||
<>
|
||||
{isGithub && haveGithubRequirements && (
|
||||
<EditGithubProvider
|
||||
githubId={gitProvider.github?.githubId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isGitlab && (
|
||||
<EditGitlabProvider
|
||||
gitlabId={gitProvider.gitlab?.gitlabId}
|
||||
/>
|
||||
)}
|
||||
{isGitlab && (
|
||||
<EditGitlabProvider
|
||||
gitlabId={gitProvider.gitlab?.gitlabId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isBitbucket && (
|
||||
<EditBitbucketProvider
|
||||
bitbucketId={
|
||||
gitProvider.bitbucket?.bitbucketId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isBitbucket && (
|
||||
<EditBitbucketProvider
|
||||
bitbucketId={
|
||||
gitProvider.bitbucket?.bitbucketId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isGitea && (
|
||||
<EditGiteaProvider
|
||||
giteaId={gitProvider.gitea?.giteaId}
|
||||
/>
|
||||
)}
|
||||
{isGitea && (
|
||||
<EditGiteaProvider
|
||||
giteaId={gitProvider.gitea?.giteaId}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DialogAction
|
||||
title="Delete Git Provider"
|
||||
description="Are you sure you want to delete this Git Provider?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
gitProviderId: gitProvider.gitProviderId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
"Git Provider deleted successfully",
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error deleting Git Provider",
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10"
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DialogAction
|
||||
title="Delete Git Provider"
|
||||
description={
|
||||
gitProvider.sharedWithOrganization
|
||||
? "This provider is shared with the organization. Deleting it will remove access for all members. Are you sure?"
|
||||
: "Are you sure you want to delete this Git Provider?"
|
||||
}
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
gitProviderId:
|
||||
gitProvider.gitProviderId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
"Git Provider deleted successfully",
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error deleting Git Provider",
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10"
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,7 @@ const notificationBaseSchema = z.object({
|
||||
appDeploy: z.boolean().default(false),
|
||||
appBuildError: z.boolean().default(false),
|
||||
databaseBackup: z.boolean().default(false),
|
||||
dokployBackup: z.boolean().default(false),
|
||||
volumeBackup: z.boolean().default(false),
|
||||
dokployRestart: z.boolean().default(false),
|
||||
dockerCleanup: z.boolean().default(false),
|
||||
@@ -355,6 +356,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
webhookUrl: notification.slack?.webhookUrl,
|
||||
@@ -369,6 +371,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
botToken: notification.telegram?.botToken,
|
||||
messageThreadId: notification.telegram?.messageThreadId || "",
|
||||
@@ -384,6 +387,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.discord?.webhookUrl,
|
||||
@@ -398,6 +402,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
smtpServer: notification.email?.smtpServer,
|
||||
@@ -416,6 +421,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
apiKey: notification.resend?.apiKey,
|
||||
@@ -431,6 +437,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
appToken: notification.gotify?.appToken,
|
||||
@@ -446,6 +453,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
accessToken: notification.ntfy?.accessToken || "",
|
||||
@@ -462,6 +470,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.mattermost?.webhookUrl,
|
||||
@@ -477,6 +486,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.lark?.webhookUrl,
|
||||
name: notification.name,
|
||||
@@ -490,6 +500,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.teams?.webhookUrl,
|
||||
@@ -503,6 +514,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
type: notification.notificationType,
|
||||
endpoint: notification.custom?.endpoint || "",
|
||||
headers: notification.custom?.headers
|
||||
@@ -524,6 +536,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
userKey: notification.pushover?.userKey,
|
||||
@@ -562,6 +575,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy,
|
||||
dokployRestart,
|
||||
databaseBackup,
|
||||
dokployBackup,
|
||||
volumeBackup,
|
||||
dockerCleanup,
|
||||
serverThreshold,
|
||||
@@ -573,6 +587,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
channel: data.channel,
|
||||
@@ -588,6 +603,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
botToken: data.botToken,
|
||||
messageThreadId: data.messageThreadId || "",
|
||||
@@ -604,6 +620,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
decoration: data.decoration,
|
||||
@@ -619,6 +636,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
smtpServer: data.smtpServer,
|
||||
smtpPort: data.smtpPort,
|
||||
@@ -638,6 +656,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
apiKey: data.apiKey,
|
||||
fromAddress: data.fromAddress,
|
||||
@@ -654,6 +673,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
serverUrl: data.serverUrl,
|
||||
appToken: data.appToken,
|
||||
@@ -670,6 +690,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
serverUrl: data.serverUrl,
|
||||
accessToken: data.accessToken || "",
|
||||
@@ -686,6 +707,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
channel: data.channel || undefined,
|
||||
@@ -702,6 +724,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
name: data.name,
|
||||
@@ -716,6 +739,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
name: data.name,
|
||||
@@ -742,6 +766,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
endpoint: data.endpoint,
|
||||
headers: headersRecord,
|
||||
@@ -761,6 +786,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
userKey: data.userKey,
|
||||
apiToken: data.apiToken,
|
||||
@@ -1856,6 +1882,27 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dokployBackup"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Dokploy Backup</FormLabel>
|
||||
<FormDescription>
|
||||
Trigger the action when a dokploy backup is created.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="volumeBackup"
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { EnterpriseFeatureLocked } from "@/components/proprietary/enterprise-feature-gate";
|
||||
import { api, type RouterOutputs } from "@/utils/api";
|
||||
|
||||
/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */
|
||||
@@ -170,6 +171,7 @@ const addPermissions = z.object({
|
||||
accessedProjects: z.array(z.string()).optional(),
|
||||
accessedEnvironments: z.array(z.string()).optional(),
|
||||
accessedServices: z.array(z.string()).optional(),
|
||||
accessedGitProviders: z.array(z.string()).optional(),
|
||||
canCreateProjects: z.boolean().optional().default(false),
|
||||
canCreateServices: z.boolean().optional().default(false),
|
||||
canDeleteProjects: z.boolean().optional().default(false),
|
||||
@@ -196,6 +198,15 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
const { data: projects } = api.project.allForPermissions.useQuery(undefined, {
|
||||
enabled: isOpen,
|
||||
});
|
||||
const { data: haveValidLicense } =
|
||||
api.licenseKey.haveValidLicenseKey.useQuery();
|
||||
|
||||
const { data: gitProviders } = api.gitProvider.allForPermissions.useQuery(
|
||||
undefined,
|
||||
{
|
||||
enabled: isOpen && !!haveValidLicense,
|
||||
},
|
||||
);
|
||||
|
||||
const { data, refetch } = api.user.one.useQuery(
|
||||
{
|
||||
@@ -214,6 +225,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
accessedProjects: [],
|
||||
accessedEnvironments: [],
|
||||
accessedServices: [],
|
||||
accessedGitProviders: [],
|
||||
canDeleteEnvironments: false,
|
||||
canCreateProjects: false,
|
||||
canCreateServices: false,
|
||||
@@ -235,6 +247,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedEnvironments: data.accessedEnvironments || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
accessedGitProviders: data.accessedGitProviders || [],
|
||||
canCreateProjects: data.canCreateProjects,
|
||||
canCreateServices: data.canCreateServices,
|
||||
canDeleteProjects: data.canDeleteProjects,
|
||||
@@ -262,6 +275,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedEnvironments: data.accessedEnvironments || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
accessedGitProviders: data.accessedGitProviders || [],
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||
@@ -870,6 +884,78 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{haveValidLicense ? (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accessedGitProviders"
|
||||
render={() => (
|
||||
<FormItem className="md:col-span-2">
|
||||
<div className="mb-4">
|
||||
<FormLabel className="text-base">Git Providers</FormLabel>
|
||||
<FormDescription>
|
||||
Select the Git Providers that the user can access
|
||||
</FormDescription>
|
||||
</div>
|
||||
{gitProviders?.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No git providers found
|
||||
</p>
|
||||
)}
|
||||
<div className="grid md:grid-cols-1 gap-2">
|
||||
{gitProviders?.map((provider) => (
|
||||
<FormField
|
||||
key={provider.gitProviderId}
|
||||
control={form.control}
|
||||
name="accessedGitProviders"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center space-x-3 space-y-0 rounded-lg border p-3">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(
|
||||
provider.gitProviderId,
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
field.onChange([
|
||||
...(field.value || []),
|
||||
provider.gitProviderId,
|
||||
]);
|
||||
} else {
|
||||
field.onChange(
|
||||
field.value?.filter(
|
||||
(v) => v !== provider.gitProviderId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel className="text-sm cursor-pointer">
|
||||
{provider.name}
|
||||
</FormLabel>
|
||||
<span className="text-xs text-muted-foreground capitalize">
|
||||
({provider.providerType})
|
||||
</span>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className="md:col-span-2">
|
||||
<EnterpriseFeatureLocked
|
||||
compact
|
||||
title="Git Provider Assignment"
|
||||
description="Assign specific Git Providers to users with an Enterprise license."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2">
|
||||
<Button
|
||||
isLoading={isPending}
|
||||
|
||||
1
apps/dokploy/drizzle/0156_fair_vargas.sql
Normal file
1
apps/dokploy/drizzle/0156_fair_vargas.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "notification" ADD COLUMN "dokployBackup" boolean DEFAULT false NOT NULL;
|
||||
2
apps/dokploy/drizzle/0157_stiff_misty_knight.sql
Normal file
2
apps/dokploy/drizzle/0157_stiff_misty_knight.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "member" ADD COLUMN "accessedGitProviders" text[] DEFAULT ARRAY[]::text[] NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "git_provider" ADD COLUMN "sharedWithOrganization" boolean DEFAULT false NOT NULL;
|
||||
8250
apps/dokploy/drizzle/meta/0156_snapshot.json
Normal file
8250
apps/dokploy/drizzle/meta/0156_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
8264
apps/dokploy/drizzle/meta/0157_snapshot.json
Normal file
8264
apps/dokploy/drizzle/meta/0157_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1093,6 +1093,20 @@
|
||||
"when": 1774794547865,
|
||||
"tag": "0155_careless_clea",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 156,
|
||||
"version": "7",
|
||||
"when": 1774910955774,
|
||||
"tag": "0156_fair_vargas",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 157,
|
||||
"version": "7",
|
||||
"when": 1775246622387,
|
||||
"tag": "0157_stiff_misty_knight",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -545,52 +545,42 @@ export const backupRouter = createTRPCRouter({
|
||||
}
|
||||
const destination = await findDestinationById(input.destinationId);
|
||||
const queue: string[] = [];
|
||||
const done = false;
|
||||
if (input.backupType === "database") {
|
||||
if (input.databaseType === "postgres") {
|
||||
const postgres = await findPostgresById(input.databaseId);
|
||||
|
||||
restorePostgresBackup(postgres, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
let done = false;
|
||||
const onLog = (log: string) => queue.push(log);
|
||||
const runRestore = async () => {
|
||||
if (input.backupType === "database") {
|
||||
if (input.databaseType === "postgres") {
|
||||
const postgres = await findPostgresById(input.databaseId);
|
||||
await restorePostgresBackup(postgres, destination, input, onLog);
|
||||
} else if (input.databaseType === "mysql") {
|
||||
const mysql = await findMySqlById(input.databaseId);
|
||||
await restoreMySqlBackup(mysql, destination, input, onLog);
|
||||
} else if (input.databaseType === "mariadb") {
|
||||
const mariadb = await findMariadbById(input.databaseId);
|
||||
await restoreMariadbBackup(mariadb, destination, input, onLog);
|
||||
} else if (input.databaseType === "mongo") {
|
||||
const mongo = await findMongoById(input.databaseId);
|
||||
await restoreMongoBackup(mongo, destination, input, onLog);
|
||||
} else if (input.databaseType === "libsql") {
|
||||
const libsql = await findLibsqlById(input.databaseId);
|
||||
await restoreLibsqlBackup(libsql, destination, input, onLog);
|
||||
} else if (input.databaseType === "web-server") {
|
||||
await restoreWebServerBackup(destination, input.backupFile, onLog);
|
||||
}
|
||||
} else if (input.backupType === "compose") {
|
||||
const compose = await findComposeById(input.databaseId);
|
||||
await restoreComposeBackup(compose, destination, input, onLog);
|
||||
}
|
||||
|
||||
if (input.databaseType === "mysql") {
|
||||
const mysql = await findMySqlById(input.databaseId);
|
||||
restoreMySqlBackup(mysql, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "mariadb") {
|
||||
const mariadb = await findMariadbById(input.databaseId);
|
||||
restoreMariadbBackup(mariadb, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "mongo") {
|
||||
const mongo = await findMongoById(input.databaseId);
|
||||
restoreMongoBackup(mongo, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "libsql") {
|
||||
const libsql = await findLibsqlById(input.databaseId);
|
||||
restoreLibsqlBackup(libsql, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "web-server") {
|
||||
restoreWebServerBackup(destination, input.backupFile, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (input.backupType === "compose") {
|
||||
const compose = await findComposeById(input.databaseId);
|
||||
restoreComposeBackup(compose, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
};
|
||||
runRestore()
|
||||
.catch((error) => {
|
||||
onLog(
|
||||
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
}
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
yield queue.shift()!;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
createBitbucket,
|
||||
findBitbucketById,
|
||||
getAccessibleGitProviderIds,
|
||||
getBitbucketBranches,
|
||||
getBitbucketRepositories,
|
||||
testBitbucketConnection,
|
||||
@@ -54,6 +55,8 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
return await findBitbucketById(input.bitbucketId);
|
||||
}),
|
||||
bitbucketProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.bitbucket.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -67,7 +70,7 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
return (
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId)
|
||||
);
|
||||
});
|
||||
return result;
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
import { findGitProviderById, removeGitProvider } from "@dokploy/server";
|
||||
import {
|
||||
findGitProviderById,
|
||||
getAccessibleGitProviderIds,
|
||||
removeGitProvider,
|
||||
updateGitProvider,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { desc, eq, inArray } from "drizzle-orm";
|
||||
import {
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
withPermission,
|
||||
} from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
|
||||
import {
|
||||
apiRemoveGitProvider,
|
||||
apiToggleShareGitProvider,
|
||||
gitProvider,
|
||||
} from "@/server/db/schema";
|
||||
|
||||
export const gitProviderRouter = createTRPCRouter({
|
||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
||||
return await db.query.gitProvider.findMany({
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
if (accessibleIds.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = await db.query.gitProvider.findMany({
|
||||
with: {
|
||||
gitlab: true,
|
||||
bitbucket: true,
|
||||
@@ -20,12 +36,65 @@ export const gitProviderRouter = createTRPCRouter({
|
||||
gitea: true,
|
||||
},
|
||||
orderBy: desc(gitProvider.createdAt),
|
||||
where: and(
|
||||
eq(gitProvider.userId, ctx.session.userId),
|
||||
eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
),
|
||||
where: inArray(gitProvider.gitProviderId, [...accessibleIds]),
|
||||
});
|
||||
|
||||
return results.map((r) => ({
|
||||
...r,
|
||||
isOwner: r.userId === ctx.session.userId,
|
||||
}));
|
||||
}),
|
||||
|
||||
toggleShare: protectedProcedure
|
||||
.input(apiToggleShareGitProvider)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const provider = await findGitProviderById(input.gitProviderId);
|
||||
|
||||
if (
|
||||
provider.userId !== ctx.session.userId ||
|
||||
provider.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Only the owner can share this provider",
|
||||
});
|
||||
}
|
||||
|
||||
await audit(ctx, {
|
||||
action: "update",
|
||||
resourceType: "gitProvider",
|
||||
resourceId: provider.gitProviderId,
|
||||
resourceName: provider.name ?? provider.gitProviderId,
|
||||
});
|
||||
|
||||
return await updateGitProvider(input.gitProviderId, {
|
||||
sharedWithOrganization: input.sharedWithOrganization,
|
||||
});
|
||||
}),
|
||||
|
||||
allForPermissions: withPermission("member", "update")
|
||||
.use(async ({ ctx, next }) => {
|
||||
const licensed = await hasValidLicense(ctx.session.activeOrganizationId);
|
||||
if (!licensed) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Valid enterprise license required",
|
||||
});
|
||||
}
|
||||
return next();
|
||||
})
|
||||
.query(async ({ ctx }) => {
|
||||
return await db.query.gitProvider.findMany({
|
||||
columns: {
|
||||
gitProviderId: true,
|
||||
name: true,
|
||||
providerType: true,
|
||||
},
|
||||
orderBy: desc(gitProvider.createdAt),
|
||||
where: eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
});
|
||||
}),
|
||||
|
||||
remove: withPermission("gitProviders", "delete")
|
||||
.input(apiRemoveGitProvider)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
createGitea,
|
||||
findGiteaById,
|
||||
getAccessibleGitProviderIds,
|
||||
getGiteaBranches,
|
||||
getGiteaRepositories,
|
||||
haveGiteaRequirements,
|
||||
@@ -57,6 +58,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
giteaProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.gitea.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -67,7 +70,7 @@ export const giteaRouter = createTRPCRouter({
|
||||
(provider) =>
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId,
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId),
|
||||
);
|
||||
|
||||
const filtered = result
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
findGithubById,
|
||||
getAccessibleGitProviderIds,
|
||||
getGithubBranches,
|
||||
getGithubRepositories,
|
||||
haveGithubRequirements,
|
||||
@@ -35,6 +36,8 @@ export const githubRouter = createTRPCRouter({
|
||||
return await getGithubBranches(input);
|
||||
}),
|
||||
githubProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.github.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -45,7 +48,7 @@ export const githubRouter = createTRPCRouter({
|
||||
(provider) =>
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId,
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId),
|
||||
);
|
||||
|
||||
const filtered = result
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
createGitlab,
|
||||
findGitlabById,
|
||||
getAccessibleGitProviderIds,
|
||||
getGitlabBranches,
|
||||
getGitlabRepositories,
|
||||
haveGitlabRequirements,
|
||||
@@ -54,6 +55,8 @@ export const gitlabRouter = createTRPCRouter({
|
||||
return await findGitlabById(input.gitlabId);
|
||||
}),
|
||||
gitlabProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.gitlab.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -64,7 +67,7 @@ export const gitlabRouter = createTRPCRouter({
|
||||
return (
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId)
|
||||
);
|
||||
});
|
||||
const filtered = result
|
||||
|
||||
@@ -246,11 +246,15 @@ export const libsqlRouter = createTRPCRouter({
|
||||
deployment: ["create"],
|
||||
});
|
||||
const queue: string[] = [];
|
||||
const done = false;
|
||||
let done = false;
|
||||
|
||||
deployLibsql(input.libsqlId, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -229,11 +229,15 @@ export const mongoRouter = createTRPCRouter({
|
||||
deployment: ["create"],
|
||||
});
|
||||
const queue: string[] = [];
|
||||
const done = false;
|
||||
let done = false;
|
||||
|
||||
deployMongo(input.mongoId, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -230,11 +230,15 @@ export const mysqlRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
const queue: string[] = [];
|
||||
const done = false;
|
||||
let done = false;
|
||||
|
||||
deployMySql(input.mysqlId, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -234,11 +234,15 @@ export const postgresRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
const queue: string[] = [];
|
||||
const done = false;
|
||||
let done = false;
|
||||
|
||||
deployPostgres(input.postgresId, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -143,7 +143,12 @@ export const licenseKeyRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
await deactivateLicenseKey(currentUser.licenseKey);
|
||||
try {
|
||||
await deactivateLicenseKey(currentUser.licenseKey);
|
||||
} catch (err) {
|
||||
console.error("Failed to deactivate license key remotely:", err);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(user)
|
||||
.set({
|
||||
|
||||
@@ -252,11 +252,15 @@ export const redisRouter = createTRPCRouter({
|
||||
deployment: ["create"],
|
||||
});
|
||||
const queue: string[] = [];
|
||||
const done = false;
|
||||
let done = false;
|
||||
|
||||
deployRedis(input.redisId, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
updateSchedule,
|
||||
} from "@dokploy/server/services/schedule";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { asc, desc, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { removeJob, schedule } from "@/server/utils/backup";
|
||||
@@ -157,6 +157,7 @@ export const scheduleRouter = createTRPCRouter({
|
||||
};
|
||||
return db.query.schedules.findMany({
|
||||
where: where[input.scheduleType],
|
||||
orderBy: [asc(schedules.createdAt)],
|
||||
with: {
|
||||
application: true,
|
||||
server: true,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
updateUser,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||
import {
|
||||
account,
|
||||
apiAssignPermissions,
|
||||
@@ -344,12 +345,19 @@ export const userRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const { id, ...rest } = input;
|
||||
const { id, accessedGitProviders, ...rest } = input;
|
||||
|
||||
const licensed = await hasValidLicense(
|
||||
ctx.session?.activeOrganizationId || "",
|
||||
);
|
||||
|
||||
await db
|
||||
.update(member)
|
||||
.set({
|
||||
...rest,
|
||||
...(licensed && accessedGitProviders !== undefined
|
||||
? { accessedGitProviders }
|
||||
: {}),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
|
||||
Reference in New Issue
Block a user