mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-08 07:25:22 +02:00
Compare commits
12 Commits
v0.24.12
...
feat/label
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09b2492585 | ||
|
|
ca1fa7c4f7 | ||
|
|
112b898d98 | ||
|
|
15e62961e8 | ||
|
|
429c1e4cd8 | ||
|
|
1904a3d1e9 | ||
|
|
025d439f71 | ||
|
|
9baafb83ff | ||
|
|
1f9ef473f1 | ||
|
|
a0bbf7be23 | ||
|
|
a5bc384d77 | ||
|
|
f2ae39aa86 |
@@ -1,4 +1,5 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -27,9 +28,15 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Settings2 } from "lucide-react";
|
import { HelpCircle, Plus, Settings2, X } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -42,6 +49,7 @@ const schema = z
|
|||||||
wildcardDomain: z.string(),
|
wildcardDomain: z.string(),
|
||||||
port: z.number(),
|
port: z.number(),
|
||||||
previewLimit: z.number(),
|
previewLimit: z.number(),
|
||||||
|
previewLabels: z.array(z.string()).optional(),
|
||||||
previewHttps: z.boolean(),
|
previewHttps: z.boolean(),
|
||||||
previewPath: z.string(),
|
previewPath: z.string(),
|
||||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
|
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
|
||||||
@@ -81,6 +89,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
wildcardDomain: "*.traefik.me",
|
wildcardDomain: "*.traefik.me",
|
||||||
port: 3000,
|
port: 3000,
|
||||||
previewLimit: 3,
|
previewLimit: 3,
|
||||||
|
previewLabels: [],
|
||||||
previewHttps: false,
|
previewHttps: false,
|
||||||
previewPath: "/",
|
previewPath: "/",
|
||||||
previewCertificateType: "none",
|
previewCertificateType: "none",
|
||||||
@@ -102,6 +111,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
buildArgs: data.previewBuildArgs || "",
|
buildArgs: data.previewBuildArgs || "",
|
||||||
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
||||||
port: data.previewPort || 3000,
|
port: data.previewPort || 3000,
|
||||||
|
previewLabels: data.previewLabels || [],
|
||||||
previewLimit: data.previewLimit || 3,
|
previewLimit: data.previewLimit || 3,
|
||||||
previewHttps: data.previewHttps || false,
|
previewHttps: data.previewHttps || false,
|
||||||
previewPath: data.previewPath || "/",
|
previewPath: data.previewPath || "/",
|
||||||
@@ -119,6 +129,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
previewBuildArgs: formData.buildArgs,
|
previewBuildArgs: formData.buildArgs,
|
||||||
previewWildcard: formData.wildcardDomain,
|
previewWildcard: formData.wildcardDomain,
|
||||||
previewPort: formData.port,
|
previewPort: formData.port,
|
||||||
|
previewLabels: formData.previewLabels,
|
||||||
applicationId,
|
applicationId,
|
||||||
previewLimit: formData.previewLimit,
|
previewLimit: formData.previewLimit,
|
||||||
previewHttps: formData.previewHttps,
|
previewHttps: formData.previewHttps,
|
||||||
@@ -200,6 +211,90 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="previewLabels"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="md:col-span-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FormLabel>Preview Labels</FormLabel>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
Add a labels that will trigger a preview
|
||||||
|
deployment for a pull request. If no labels
|
||||||
|
are specified, all pull requests will trigger
|
||||||
|
a preview deployment.
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2 mb-2">
|
||||||
|
{field.value?.map((label, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
variant="secondary"
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
<X
|
||||||
|
className="size-3 cursor-pointer hover:text-destructive"
|
||||||
|
onClick={() => {
|
||||||
|
const newLabels = [...(field.value || [])];
|
||||||
|
newLabels.splice(index, 1);
|
||||||
|
field.onChange(newLabels);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Enter a label (e.g. enhancements, needs-review)"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
const input = e.currentTarget;
|
||||||
|
const label = input.value.trim();
|
||||||
|
if (label) {
|
||||||
|
field.onChange([
|
||||||
|
...(field.value || []),
|
||||||
|
label,
|
||||||
|
]);
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => {
|
||||||
|
const input = document.querySelector(
|
||||||
|
'input[placeholder*="Enter a label"]',
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const label = input.value.trim();
|
||||||
|
if (label) {
|
||||||
|
field.onChange([...(field.value || []), label]);
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="previewLimit"
|
name="previewLimit"
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0106_purple_maggott.sql
Normal file
1
apps/dokploy/drizzle/0106_purple_maggott.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "previewLabels" text[];
|
||||||
6424
apps/dokploy/drizzle/meta/0106_snapshot.json
Normal file
6424
apps/dokploy/drizzle/meta/0106_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -743,6 +743,13 @@
|
|||||||
"when": 1754259281559,
|
"when": 1754259281559,
|
||||||
"tag": "0105_clumsy_quicksilver",
|
"tag": "0105_clumsy_quicksilver",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 106,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1754912062243,
|
||||||
|
"tag": "0106_purple_maggott",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
import { db } from "@/server/db";
|
|
||||||
import { applications, compose, github } from "@/server/db/schema";
|
|
||||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
|
||||||
import { deploy } from "@/server/utils/deploy";
|
|
||||||
import {
|
import {
|
||||||
IS_CLOUD,
|
|
||||||
checkUserRepositoryPermissions,
|
checkUserRepositoryPermissions,
|
||||||
createPreviewDeployment,
|
createPreviewDeployment,
|
||||||
createSecurityBlockedComment,
|
createSecurityBlockedComment,
|
||||||
findGithubById,
|
findGithubById,
|
||||||
findPreviewDeploymentByApplicationId,
|
findPreviewDeploymentByApplicationId,
|
||||||
findPreviewDeploymentsByPullRequestId,
|
findPreviewDeploymentsByPullRequestId,
|
||||||
|
IS_CLOUD,
|
||||||
removePreviewDeployment,
|
removePreviewDeployment,
|
||||||
shouldDeploy,
|
shouldDeploy,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { Webhooks } from "@octokit/webhooks";
|
import { Webhooks } from "@octokit/webhooks";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { applications, compose, github } from "@/server/db/schema";
|
||||||
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
|
import { myQueue } from "@/server/queues/queueSetup";
|
||||||
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
@@ -343,7 +343,9 @@ export default async function handler(
|
|||||||
if (
|
if (
|
||||||
action === "opened" ||
|
action === "opened" ||
|
||||||
action === "synchronize" ||
|
action === "synchronize" ||
|
||||||
action === "reopened"
|
action === "reopened" ||
|
||||||
|
action === "labeled" ||
|
||||||
|
action === "unlabeled"
|
||||||
) {
|
) {
|
||||||
const repository = githubBody?.repository?.name;
|
const repository = githubBody?.repository?.name;
|
||||||
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
||||||
@@ -442,6 +444,19 @@ export default async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const app of secureApps) {
|
for (const app of secureApps) {
|
||||||
|
// check for labels
|
||||||
|
if (app?.previewLabels && app?.previewLabels?.length > 0) {
|
||||||
|
let hasLabel = false;
|
||||||
|
const labels = githubBody?.pull_request?.labels;
|
||||||
|
for (const label of labels) {
|
||||||
|
if (app?.previewLabels?.includes(label.name)) {
|
||||||
|
hasLabel = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasLabel) continue;
|
||||||
|
}
|
||||||
|
|
||||||
const previewLimit = app?.previewLimit || 0;
|
const previewLimit = app?.previewLimit || 0;
|
||||||
if (app?.previewDeployments?.length > previewLimit) {
|
if (app?.previewDeployments?.length > previewLimit) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export const applications = pgTable("application", {
|
|||||||
previewEnv: text("previewEnv"),
|
previewEnv: text("previewEnv"),
|
||||||
watchPaths: text("watchPaths").array(),
|
watchPaths: text("watchPaths").array(),
|
||||||
previewBuildArgs: text("previewBuildArgs"),
|
previewBuildArgs: text("previewBuildArgs"),
|
||||||
|
previewLabels: text("previewLabels").array(),
|
||||||
previewWildcard: text("previewWildcard"),
|
previewWildcard: text("previewWildcard"),
|
||||||
previewPort: integer("previewPort").default(3000),
|
previewPort: integer("previewPort").default(3000),
|
||||||
previewHttps: boolean("previewHttps").notNull().default(false),
|
previewHttps: boolean("previewHttps").notNull().default(false),
|
||||||
@@ -308,6 +309,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
||||||
previewRequireCollaboratorPermissions: z.boolean().optional(),
|
previewRequireCollaboratorPermissions: z.boolean().optional(),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
|
previewLabels: z.array(z.string()).optional(),
|
||||||
cleanCache: z.boolean().optional(),
|
cleanCache: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user