This commit is contained in:
Jack 2026-09-11 13:14:52 -04:00 committed by GitHub
commit 8addbcebd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 197 additions and 51 deletions

View File

@ -0,0 +1,93 @@
import { type ReactNode, useState } from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
interface Props {
onConfirm: () => void;
needsConfirmation: boolean;
onValidate?: () => Promise<boolean> | boolean;
isLoading?: boolean;
disabled?: boolean;
children?: ReactNode;
}
export const ConfirmableProviderSave = ({
onConfirm,
needsConfirmation,
onValidate,
isLoading,
disabled,
children = "Save",
}: Props) => {
const [open, setOpen] = useState(false);
if (!needsConfirmation) {
return (
<Button
type="submit"
className="w-fit"
isLoading={isLoading}
disabled={disabled}
>
{children}
</Button>
);
}
const handleClick = async () => {
if (onValidate) {
const valid = await onValidate();
if (!valid) return;
}
setOpen(true);
};
return (
<>
<Button
type="button"
className="w-fit"
isLoading={isLoading}
disabled={disabled}
onClick={handleClick}
>
{children}
</Button>
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Switch provider and disable preview deployments?
</AlertDialogTitle>
<AlertDialogDescription>
Preview deployments only run on the GitHub provider. Switching
will disable preview deployments for this application; existing
previews will stop receiving updates.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
onClick={() => {
setOpen(false);
onConfirm();
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
};

View File

@ -48,6 +48,7 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { ConfirmableProviderSave } from "./confirmable-provider-save";
const BitbucketProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@ -74,9 +75,10 @@ interface Props {
}
export const SaveBitbucketProvider = ({ applicationId }: Props) => {
const utils = api.useUtils();
const { data: bitbucketProviders } =
api.bitbucket.bitbucketProviders.useQuery();
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { data } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isPending: isSavingBitbucketProvider } =
api.application.saveBitbucketProvider.useMutation();
@ -162,7 +164,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
})
.then(async () => {
toast.success("Service Provider Saved");
await refetch();
await utils.application.one.invalidate({ applicationId });
})
.catch(() => {
toast.error("Error saving the Bitbucket provider");
@ -519,13 +521,17 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
/>
</div>
<div className="flex w-full justify-end">
<Button
<ConfirmableProviderSave
needsConfirmation={
data?.sourceType === "github" &&
data?.isPreviewDeploymentsActive === true
}
isLoading={isSavingBitbucketProvider}
type="submit"
className="w-fit"
onValidate={() => form.trigger()}
onConfirm={form.handleSubmit(onSubmit)}
>
Save
</Button>
</ConfirmableProviderSave>
</div>
</form>
</Form>

View File

@ -3,7 +3,6 @@ import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
@ -14,6 +13,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { ConfirmableProviderSave } from "./confirmable-provider-save";
const DockerProviderSchema = z.object({
dockerImage: z.string().min(1, {
@ -31,7 +31,8 @@ interface Props {
}
export const SaveDockerProvider = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery({ applicationId });
const utils = api.useUtils();
const { data } = api.application.one.useQuery({ applicationId });
const { mutateAsync } = api.application.saveDockerProvider.useMutation();
const form = useForm<DockerProvider>({
@ -65,7 +66,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
})
.then(async () => {
toast.success("Docker Provider Saved");
await refetch();
await utils.application.one.invalidate({ applicationId });
})
.catch(() => {
toast.error("Error saving the Docker provider");
@ -149,13 +150,17 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
</div>
<div className="flex flex-row justify-end">
<Button
type="submit"
className="w-fit"
<ConfirmableProviderSave
needsConfirmation={
data?.sourceType === "github" &&
data?.isPreviewDeploymentsActive === true
}
isLoading={form.formState.isSubmitting}
onValidate={() => form.trigger()}
onConfirm={form.handleSubmit(onSubmit)}
>
Save{" "}
</Button>
Save
</ConfirmableProviderSave>
</div>
</form>
</Form>

View File

@ -16,13 +16,15 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { type UploadFile, uploadFileSchema } from "@/utils/schema";
import { ConfirmableProviderSave } from "./confirmable-provider-save";
interface Props {
applicationId: string;
}
export const SaveDragNDrop = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery({ applicationId });
const utils = api.useUtils();
const { data } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isPending } =
api.application.dropDeployment.useMutation();
@ -53,7 +55,7 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
await mutateAsync(formData)
.then(async () => {
toast.success("Deployment saved");
await refetch();
await utils.application.one.invalidate({ applicationId });
})
.catch(() => {
toast.error("Error saving the deployment");
@ -126,14 +128,18 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
</div>
<div className="flex flex-row justify-end">
<Button
type="submit"
className="w-fit"
<ConfirmableProviderSave
needsConfirmation={
data?.sourceType === "github" &&
data?.isPreviewDeploymentsActive === true
}
isLoading={isPending}
disabled={!zip || isPending}
onValidate={() => form.trigger()}
onConfirm={form.handleSubmit(onSubmit)}
>
Deploy{" "}
</Button>
Deploy
</ConfirmableProviderSave>
</div>
</form>
</Form>

View File

@ -36,6 +36,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { ConfirmableProviderSave } from "./confirmable-provider-save";
const GitProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@ -58,7 +59,8 @@ interface Props {
}
export const SaveGitProvider = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery({ applicationId });
const utils = api.useUtils();
const { data } = api.application.one.useQuery({ applicationId });
const { data: sshKeys } = api.sshKey.allForApps.useQuery();
const router = useRouter();
@ -102,7 +104,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
})
.then(async () => {
toast.success("Git Provider Saved");
await refetch();
await utils.application.one.invalidate({ applicationId });
})
.catch(() => {
toast.error("Error saving the Git provider");
@ -316,9 +318,17 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
</div>
<div className="flex flex-row justify-end">
<Button type="submit" className="w-fit" isLoading={isPending}>
<ConfirmableProviderSave
needsConfirmation={
data?.sourceType === "github" &&
data?.isPreviewDeploymentsActive === true
}
isLoading={isPending}
onValidate={() => form.trigger()}
onConfirm={form.handleSubmit(onSubmit)}
>
Save
</Button>
</ConfirmableProviderSave>
</div>
</form>
</Form>

View File

@ -48,6 +48,7 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { ConfirmableProviderSave } from "./confirmable-provider-save";
interface GiteaRepository {
name: string;
@ -89,8 +90,9 @@ interface Props {
}
export const SaveGiteaProvider = ({ applicationId }: Props) => {
const utils = api.useUtils();
const { data: giteaProviders } = api.gitea.giteaProviders.useQuery();
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { data } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isPending: isSavingGiteaProvider } =
api.application.saveGiteaProvider.useMutation();
@ -177,7 +179,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
})
.then(async () => {
toast.success("Service Provider Saved");
await refetch();
await utils.application.one.invalidate({ applicationId });
})
.catch(() => {
toast.error("Error saving the Gitea provider");
@ -548,13 +550,17 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
/>
</div>
<div className="flex w-full justify-end">
<Button
<ConfirmableProviderSave
needsConfirmation={
data?.sourceType === "github" &&
data?.isPreviewDeploymentsActive === true
}
isLoading={isSavingGiteaProvider}
type="submit"
className="w-fit"
onValidate={() => form.trigger()}
onConfirm={form.handleSubmit(onSubmit)}
>
Save
</Button>
</ConfirmableProviderSave>
</div>
</form>
</Form>

View File

@ -74,8 +74,9 @@ interface Props {
}
export const SaveGithubProvider = ({ applicationId }: Props) => {
const utils = api.useUtils();
const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { data } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isPending: isSavingGithubProvider } =
api.application.saveGithubProvider.useMutation();
@ -160,7 +161,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
})
.then(async () => {
toast.success("Service Provider Saved");
await refetch();
await utils.application.one.invalidate({ applicationId });
})
.catch(() => {
toast.error("Error saving the github provider");

View File

@ -48,6 +48,7 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { ConfirmableProviderSave } from "./confirmable-provider-save";
const GitlabProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@ -75,8 +76,9 @@ interface Props {
}
export const SaveGitlabProvider = ({ applicationId }: Props) => {
const utils = api.useUtils();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { data } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isPending: isSavingGitlabProvider } =
api.application.saveGitlabProvider.useMutation();
@ -172,7 +174,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
})
.then(async () => {
toast.success("Service Provider Saved");
await refetch();
await utils.application.one.invalidate({ applicationId });
})
.catch(() => {
toast.error("Error saving the gitlab provider");
@ -538,13 +540,17 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
/>
</div>
<div className="flex w-full justify-end">
<Button
<ConfirmableProviderSave
needsConfirmation={
data?.sourceType === "github" &&
data?.isPreviewDeploymentsActive === true
}
isLoading={isSavingGitlabProvider}
type="submit"
className="w-fit"
onValidate={() => form.trigger()}
onConfirm={form.handleSubmit(onSubmit)}
>
Save
</Button>
</ConfirmableProviderSave>
</div>
</form>
</Form>

View File

@ -257,11 +257,12 @@ const Service = (
Deployments
</TabsTrigger>
)}
{permissions?.deployment.read && (
<TabsTrigger value="preview-deployments">
Preview Deployments
</TabsTrigger>
)}
{permissions?.deployment.read &&
data?.sourceType === "github" && (
<TabsTrigger value="preview-deployments">
Preview Deployments
</TabsTrigger>
)}
{permissions?.schedule.read && (
<TabsTrigger value="schedules">Schedules</TabsTrigger>
)}
@ -398,13 +399,19 @@ const Service = (
</div>
</TabsContent>
)}
{permissions?.deployment.read && (
<TabsContent value="preview-deployments" className="w-full">
<div className="flex flex-col gap-4 pt-2.5">
<ShowPreviewDeployments applicationId={applicationId} />
</div>
</TabsContent>
)}
{permissions?.deployment.read &&
data?.sourceType === "github" && (
<TabsContent
value="preview-deployments"
className="w-full"
>
<div className="flex flex-col gap-4 pt-2.5">
<ShowPreviewDeployments
applicationId={applicationId}
/>
</div>
</TabsContent>
)}
{permissions?.domain.read && (
<TabsContent value="domains" className="w-full">
<div className="flex flex-col gap-4 pt-2.5">

View File

@ -578,6 +578,7 @@ export const applicationRouter = createTRPCRouter({
gitlabPathNamespace: input.gitlabPathNamespace,
watchPaths: input.watchPaths,
enableSubmodules: input.enableSubmodules,
isPreviewDeploymentsActive: false,
});
const application = await findApplicationById(input.applicationId);
await audit(ctx, {
@ -605,6 +606,7 @@ export const applicationRouter = createTRPCRouter({
bitbucketId: input.bitbucketId,
watchPaths: input.watchPaths,
enableSubmodules: input.enableSubmodules,
isPreviewDeploymentsActive: false,
});
const application = await findApplicationById(input.applicationId);
await audit(ctx, {
@ -631,6 +633,7 @@ export const applicationRouter = createTRPCRouter({
giteaId: input.giteaId,
watchPaths: input.watchPaths,
enableSubmodules: input.enableSubmodules,
isPreviewDeploymentsActive: false,
});
const application = await findApplicationById(input.applicationId);
await audit(ctx, {
@ -654,6 +657,7 @@ export const applicationRouter = createTRPCRouter({
sourceType: "docker",
applicationStatus: "idle",
registryUrl: input.registryUrl,
isPreviewDeploymentsActive: false,
});
const application = await findApplicationById(input.applicationId);
await audit(ctx, {
@ -679,6 +683,7 @@ export const applicationRouter = createTRPCRouter({
applicationStatus: "idle",
watchPaths: input.watchPaths,
enableSubmodules: input.enableSubmodules,
isPreviewDeploymentsActive: false,
});
const application = await findApplicationById(input.applicationId);
await audit(ctx, {
@ -934,6 +939,7 @@ export const applicationRouter = createTRPCRouter({
await updateApplication(applicationId, {
sourceType: "drop",
dropBuildPath: dropBuildPath || "",
isPreviewDeploymentsActive: false,
});
await unzipDrop(zipFile, app);