From 698104e7b7759d958411bd9dd1d8bd57102d1ba5 Mon Sep 17 00:00:00 2001 From: diego fabricio Date: Thu, 4 Dec 2025 11:33:29 -0500 Subject: [PATCH 01/54] fix(docker-logs): fix warning symbol detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added support for detecting warning symbols (⚠, ⚠️) in log messages --- apps/dokploy/components/dashboard/docker/logs/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/docker/logs/utils.ts b/apps/dokploy/components/dashboard/docker/logs/utils.ts index 5e97edfe2..d1af51077 100644 --- a/apps/dokploy/components/dashboard/docker/logs/utils.ts +++ b/apps/dokploy/components/dashboard/docker/logs/utils.ts @@ -108,7 +108,8 @@ export const getLogType = (message: string): LogStyle => { /(?:might|may|could)\s+(?:not|cause|lead\s+to)/i.test(lowerMessage) || /(?:!+\s*(?:warning|caution|attention)\s*!+)/i.test(lowerMessage) || /\b(?:deprecated|obsolete)\b/i.test(lowerMessage) || - /\b(?:unstable|experimental)\b/i.test(lowerMessage) + /\b(?:unstable|experimental)\b/i.test(lowerMessage) || + /⚠|⚠️/i.test(lowerMessage) ) { return LOG_STYLES.warning; } From d465fb4da1a240d6a1b302089669539df6ba1dd4 Mon Sep 17 00:00:00 2001 From: Vlad Vladov Date: Sun, 7 Dec 2025 13:45:14 +0200 Subject: [PATCH 02/54] feat(resources): Add number component to have better UX control over Docker resources --- .../application/advanced/show-resources.tsx | 65 +++++++++++--- apps/dokploy/components/ui/number-input.tsx | 84 +++++++++++++++++++ 2 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 apps/dokploy/components/ui/number-input.tsx diff --git a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx index 3beedcdbc..aea30e49b 100644 --- a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx @@ -21,7 +21,10 @@ import { FormLabel, FormMessage, } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; +import { + createConverter, + NumberInputWithSteps, +} from "@/components/ui/number-input"; import { Tooltip, TooltipContent, @@ -30,6 +33,23 @@ import { } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; +const CPU_STEP = 0.25; +const MEMORY_STEP_MB = 256; + +const formatNumber = (value: number, decimals = 2): string => + Number.isInteger(value) ? String(value) : value.toFixed(decimals); + +const cpuConverter = createConverter(1_000_000_000, (cpu) => + cpu <= 0 ? "" : `${formatNumber(cpu)} CPU`, +); + +const memoryConverter = createConverter(1024 * 1024, (mb) => { + if (mb <= 0) return ""; + return mb >= 1024 + ? `${formatNumber(mb / 1024)} GB` + : `${formatNumber(mb)} MB`; +}); + const addResourcesSchema = z.object({ memoryReservation: z.string().optional(), cpuLimit: z.string().optional(), @@ -51,6 +71,7 @@ interface Props { } type AddResources = z.infer; + export const ShowResources = ({ id, type }: Props) => { const queryMap = { postgres: () => @@ -163,16 +184,20 @@ export const ShowResources = ({ id, type }: Props) => {

Memory hard limit in bytes. Example: 1GB = - 1073741824 bytes + 1073741824 bytes. Use +/- buttons to adjust by + 256 MB.

- @@ -198,16 +223,20 @@ export const ShowResources = ({ id, type }: Props) => {

Memory soft limit in bytes. Example: 256MB = - 268435456 bytes + 268435456 bytes. Use +/- buttons to adjust by 256 + MB.

- @@ -234,17 +263,20 @@ export const ShowResources = ({ id, type }: Props) => {

CPU quota in units of 10^-9 CPUs. Example: 2 - CPUs = 2000000000 + CPUs = 2000000000. Use +/- buttons to adjust by + 0.25 CPU.

- @@ -271,14 +303,21 @@ export const ShowResources = ({ id, type }: Props) => {

CPU shares (relative weight). Example: 1 CPU = - 1000000000 + 1000000000. Use +/- buttons to adjust by 0.25 + CPU.

- + diff --git a/apps/dokploy/components/ui/number-input.tsx b/apps/dokploy/components/ui/number-input.tsx new file mode 100644 index 000000000..511b6dc9f --- /dev/null +++ b/apps/dokploy/components/ui/number-input.tsx @@ -0,0 +1,84 @@ +import { MinusIcon, PlusIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; + +export interface UnitConverter { + toValue: (raw: string | undefined) => number; + fromValue: (value: number) => string; + formatDisplay: (value: number) => string; +} + +export const createConverter = ( + multiplier: number, + formatDisplay: (value: number) => string, +): UnitConverter => ({ + toValue: (raw) => { + if (!raw) return 0; + const value = Number.parseInt(raw, 10); + return Number.isNaN(value) ? 0 : value / multiplier; + }, + fromValue: (value) => + value <= 0 ? "" : String(Math.round(value * multiplier)), + formatDisplay, +}); + +interface NumberInputWithStepsProps { + value: string | undefined; + onChange: (value: string) => void; + placeholder: string; + step: number; + converter: UnitConverter; +} + +export const NumberInputWithSteps = ({ + value, + onChange, + placeholder, + step, + converter, +}: NumberInputWithStepsProps) => { + const numericValue = converter.toValue(value); + const displayValue = converter.formatDisplay(numericValue); + + const handleIncrement = () => + onChange(converter.fromValue(numericValue + step)); + const handleDecrement = () => + onChange(converter.fromValue(Math.max(0, numericValue - step))); + + return ( +
+
+ + onChange(e.target.value)} + className="text-center" + /> + +
+ {displayValue && ( + + {displayValue} + + )} +
+ ); +}; From 8c889fc71e084bb997a4bab55c85189cd7d04047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=84=D1=8B=D1=80=D0=B0=D1=82=20=D1=91=D0=B7=D0=B4=D1=8D?= =?UTF-8?q?=D0=BD?= <31664778+fir4tozden@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:06:55 +0300 Subject: [PATCH 03/54] fix: some fixes in dockerSafeExec() --- packages/server/src/utils/docker/utils.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 5c7326e2d..f8518bd90 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -146,17 +146,17 @@ export const getContainerByName = (name: string): Promise => { }; /** - * Docker commands passed through this method are held during Docker's build or pull process. + * Docker commands sent using this method are held in a hold when Docker is busy. * * https://github.com/Dokploy/dokploy/pull/3064 - * https://github.com/fir4tozden */ -export const dockerSafeExec = (exec: string) => `CHECK_INTERVAL=10 +export const dockerSafeExec = (exec: string) => ` +CHECK_INTERVAL=10 echo "Preparing for execution..." while true; do - PROCESSES=$(ps aux | grep -E "docker build|docker pull" | grep -v grep) + PROCESSES=$(ps aux | grep -E "docker " | grep -v grep) if [ -z "$PROCESSES" ]; then echo "Docker is idle. Starting execution..." @@ -169,7 +169,8 @@ done ${exec} -echo "Execution completed."`; +echo "Execution completed." +`; export const cleanupContainers = async (serverId?: string) => { try { From c045c5328fef9c8774d10fcee519ae0148a3ae03 Mon Sep 17 00:00:00 2001 From: Oded Davidov Date: Fri, 12 Dec 2025 22:43:53 +0200 Subject: [PATCH 04/54] feat(schedules): add support for all IANA timezones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace limited 15-timezone list with comprehensive 421 IANA timezones - Add searchable timezone selector with region grouping for better UX - Create dedicated timezones.ts file following project conventions - Support all timezone offsets including 30-min and 45-min offsets Closes #2935 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../schedules/handle-schedules.tsx | 114 +++-- .../application/schedules/timezones.ts | 458 ++++++++++++++++++ 2 files changed, 529 insertions(+), 43 deletions(-) create mode 100644 apps/dokploy/components/dashboard/application/schedules/timezones.ts diff --git a/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx b/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx index 1cb3d34af..e85b1b004 100644 --- a/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx +++ b/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx @@ -1,5 +1,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { + CheckIcon, + ChevronsUpDown, DatabaseZap, Info, PenBoxIcon, @@ -13,6 +15,14 @@ import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; import { Dialog, DialogContent, @@ -31,6 +41,12 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, @@ -48,6 +64,7 @@ import { import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import type { CacheType } from "../domains/handle-domain"; +import { getTimezoneLabel, TIMEZONES } from "./timezones"; export const commonCronExpressions = [ { label: "Every minute", value: "* * * * *" }, @@ -60,30 +77,6 @@ export const commonCronExpressions = [ { label: "Custom", value: "custom" }, ]; -export const commonTimezones = [ - { label: "UTC (Coordinated Universal Time)", value: "UTC" }, - { label: "America/New_York (Eastern Time)", value: "America/New_York" }, - { label: "America/Chicago (Central Time)", value: "America/Chicago" }, - { label: "America/Denver (Mountain Time)", value: "America/Denver" }, - { label: "America/Los_Angeles (Pacific Time)", value: "America/Los_Angeles" }, - { - label: "America/Mexico_City (Central Mexico)", - value: "America/Mexico_City", - }, - { label: "America/Sao_Paulo (Brasilia Time)", value: "America/Sao_Paulo" }, - { label: "Europe/London (Greenwich Mean Time)", value: "Europe/London" }, - { label: "Europe/Paris (Central European Time)", value: "Europe/Paris" }, - { label: "Europe/Berlin (Central European Time)", value: "Europe/Berlin" }, - { label: "Asia/Tokyo (Japan Standard Time)", value: "Asia/Tokyo" }, - { label: "Asia/Shanghai (China Standard Time)", value: "Asia/Shanghai" }, - { label: "Asia/Dubai (Gulf Standard Time)", value: "Asia/Dubai" }, - { label: "Asia/Kolkata (India Standard Time)", value: "Asia/Kolkata" }, - { - label: "Australia/Sydney (Australian Eastern Time)", - value: "Australia/Sydney", - }, -]; - const formSchema = z .object({ name: z.string().min(1, "Name is required"), @@ -512,25 +505,60 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => { - + + + + + + + + + + + No timezone found. + + {Object.entries(TIMEZONES).map( + ([region, zones]) => ( + + {zones.map((tz) => ( + { + field.onChange(tz.value); + }} + > + {tz.value} + + + ))} + + ), + )} + + + + + Optional: Choose a timezone for the schedule execution time diff --git a/apps/dokploy/components/dashboard/application/schedules/timezones.ts b/apps/dokploy/components/dashboard/application/schedules/timezones.ts new file mode 100644 index 000000000..44891b909 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/schedules/timezones.ts @@ -0,0 +1,458 @@ +// Complete list of IANA timezones grouped by region +export const TIMEZONES: Record< + string, + Array<{ label: string; value: string }> +> = { + Common: [{ label: "UTC (Coordinated Universal Time)", value: "UTC" }], + Africa: [ + { label: "Abidjan", value: "Africa/Abidjan" }, + { label: "Accra", value: "Africa/Accra" }, + { label: "Addis Ababa", value: "Africa/Addis_Ababa" }, + { label: "Algiers", value: "Africa/Algiers" }, + { label: "Asmara", value: "Africa/Asmara" }, + { label: "Bamako", value: "Africa/Bamako" }, + { label: "Bangui", value: "Africa/Bangui" }, + { label: "Banjul", value: "Africa/Banjul" }, + { label: "Bissau", value: "Africa/Bissau" }, + { label: "Blantyre", value: "Africa/Blantyre" }, + { label: "Brazzaville", value: "Africa/Brazzaville" }, + { label: "Bujumbura", value: "Africa/Bujumbura" }, + { label: "Cairo", value: "Africa/Cairo" }, + { label: "Casablanca", value: "Africa/Casablanca" }, + { label: "Ceuta", value: "Africa/Ceuta" }, + { label: "Conakry", value: "Africa/Conakry" }, + { label: "Dakar", value: "Africa/Dakar" }, + { label: "Dar es Salaam", value: "Africa/Dar_es_Salaam" }, + { label: "Djibouti", value: "Africa/Djibouti" }, + { label: "Douala", value: "Africa/Douala" }, + { label: "El Aaiun", value: "Africa/El_Aaiun" }, + { label: "Freetown", value: "Africa/Freetown" }, + { label: "Gaborone", value: "Africa/Gaborone" }, + { label: "Harare", value: "Africa/Harare" }, + { label: "Johannesburg", value: "Africa/Johannesburg" }, + { label: "Juba", value: "Africa/Juba" }, + { label: "Kampala", value: "Africa/Kampala" }, + { label: "Khartoum", value: "Africa/Khartoum" }, + { label: "Kigali", value: "Africa/Kigali" }, + { label: "Kinshasa", value: "Africa/Kinshasa" }, + { label: "Lagos", value: "Africa/Lagos" }, + { label: "Libreville", value: "Africa/Libreville" }, + { label: "Lome", value: "Africa/Lome" }, + { label: "Luanda", value: "Africa/Luanda" }, + { label: "Lubumbashi", value: "Africa/Lubumbashi" }, + { label: "Lusaka", value: "Africa/Lusaka" }, + { label: "Malabo", value: "Africa/Malabo" }, + { label: "Maputo", value: "Africa/Maputo" }, + { label: "Maseru", value: "Africa/Maseru" }, + { label: "Mbabane", value: "Africa/Mbabane" }, + { label: "Mogadishu", value: "Africa/Mogadishu" }, + { label: "Monrovia", value: "Africa/Monrovia" }, + { label: "Nairobi", value: "Africa/Nairobi" }, + { label: "Ndjamena", value: "Africa/Ndjamena" }, + { label: "Niamey", value: "Africa/Niamey" }, + { label: "Nouakchott", value: "Africa/Nouakchott" }, + { label: "Ouagadougou", value: "Africa/Ouagadougou" }, + { label: "Porto-Novo", value: "Africa/Porto-Novo" }, + { label: "Sao Tome", value: "Africa/Sao_Tome" }, + { label: "Tripoli", value: "Africa/Tripoli" }, + { label: "Tunis", value: "Africa/Tunis" }, + { label: "Windhoek", value: "Africa/Windhoek" }, + ], + America: [ + { label: "Adak", value: "America/Adak" }, + { label: "Anchorage", value: "America/Anchorage" }, + { label: "Anguilla", value: "America/Anguilla" }, + { label: "Antigua", value: "America/Antigua" }, + { label: "Araguaina", value: "America/Araguaina" }, + { + label: "Argentina/Buenos Aires", + value: "America/Argentina/Buenos_Aires", + }, + { label: "Argentina/Catamarca", value: "America/Argentina/Catamarca" }, + { label: "Argentina/Cordoba", value: "America/Argentina/Cordoba" }, + { label: "Argentina/Jujuy", value: "America/Argentina/Jujuy" }, + { label: "Argentina/La Rioja", value: "America/Argentina/La_Rioja" }, + { label: "Argentina/Mendoza", value: "America/Argentina/Mendoza" }, + { + label: "Argentina/Rio Gallegos", + value: "America/Argentina/Rio_Gallegos", + }, + { label: "Argentina/Salta", value: "America/Argentina/Salta" }, + { label: "Argentina/San Juan", value: "America/Argentina/San_Juan" }, + { label: "Argentina/San Luis", value: "America/Argentina/San_Luis" }, + { label: "Argentina/Tucuman", value: "America/Argentina/Tucuman" }, + { label: "Argentina/Ushuaia", value: "America/Argentina/Ushuaia" }, + { label: "Aruba", value: "America/Aruba" }, + { label: "Asuncion", value: "America/Asuncion" }, + { label: "Atikokan", value: "America/Atikokan" }, + { label: "Bahia", value: "America/Bahia" }, + { label: "Bahia Banderas", value: "America/Bahia_Banderas" }, + { label: "Barbados", value: "America/Barbados" }, + { label: "Belem", value: "America/Belem" }, + { label: "Belize", value: "America/Belize" }, + { label: "Blanc-Sablon", value: "America/Blanc-Sablon" }, + { label: "Boa Vista", value: "America/Boa_Vista" }, + { label: "Bogota", value: "America/Bogota" }, + { label: "Boise", value: "America/Boise" }, + { label: "Cambridge Bay", value: "America/Cambridge_Bay" }, + { label: "Campo Grande", value: "America/Campo_Grande" }, + { label: "Cancun", value: "America/Cancun" }, + { label: "Caracas", value: "America/Caracas" }, + { label: "Cayenne", value: "America/Cayenne" }, + { label: "Cayman", value: "America/Cayman" }, + { label: "Chicago (Central Time)", value: "America/Chicago" }, + { label: "Chihuahua", value: "America/Chihuahua" }, + { label: "Ciudad Juarez", value: "America/Ciudad_Juarez" }, + { label: "Costa Rica", value: "America/Costa_Rica" }, + { label: "Creston", value: "America/Creston" }, + { label: "Cuiaba", value: "America/Cuiaba" }, + { label: "Curacao", value: "America/Curacao" }, + { label: "Danmarkshavn", value: "America/Danmarkshavn" }, + { label: "Dawson", value: "America/Dawson" }, + { label: "Dawson Creek", value: "America/Dawson_Creek" }, + { label: "Denver (Mountain Time)", value: "America/Denver" }, + { label: "Detroit", value: "America/Detroit" }, + { label: "Dominica", value: "America/Dominica" }, + { label: "Edmonton", value: "America/Edmonton" }, + { label: "Eirunepe", value: "America/Eirunepe" }, + { label: "El Salvador", value: "America/El_Salvador" }, + { label: "Fort Nelson", value: "America/Fort_Nelson" }, + { label: "Fortaleza", value: "America/Fortaleza" }, + { label: "Glace Bay", value: "America/Glace_Bay" }, + { label: "Goose Bay", value: "America/Goose_Bay" }, + { label: "Grand Turk", value: "America/Grand_Turk" }, + { label: "Grenada", value: "America/Grenada" }, + { label: "Guadeloupe", value: "America/Guadeloupe" }, + { label: "Guatemala", value: "America/Guatemala" }, + { label: "Guayaquil", value: "America/Guayaquil" }, + { label: "Guyana", value: "America/Guyana" }, + { label: "Halifax", value: "America/Halifax" }, + { label: "Havana", value: "America/Havana" }, + { label: "Hermosillo", value: "America/Hermosillo" }, + { label: "Indiana/Indianapolis", value: "America/Indiana/Indianapolis" }, + { label: "Indiana/Knox", value: "America/Indiana/Knox" }, + { label: "Indiana/Marengo", value: "America/Indiana/Marengo" }, + { label: "Indiana/Petersburg", value: "America/Indiana/Petersburg" }, + { label: "Indiana/Tell City", value: "America/Indiana/Tell_City" }, + { label: "Indiana/Vevay", value: "America/Indiana/Vevay" }, + { label: "Indiana/Vincennes", value: "America/Indiana/Vincennes" }, + { label: "Indiana/Winamac", value: "America/Indiana/Winamac" }, + { label: "Inuvik", value: "America/Inuvik" }, + { label: "Iqaluit", value: "America/Iqaluit" }, + { label: "Jamaica", value: "America/Jamaica" }, + { label: "Juneau", value: "America/Juneau" }, + { label: "Kentucky/Louisville", value: "America/Kentucky/Louisville" }, + { label: "Kentucky/Monticello", value: "America/Kentucky/Monticello" }, + { label: "Kralendijk", value: "America/Kralendijk" }, + { label: "La Paz", value: "America/La_Paz" }, + { label: "Lima", value: "America/Lima" }, + { label: "Los Angeles (Pacific Time)", value: "America/Los_Angeles" }, + { label: "Lower Princes", value: "America/Lower_Princes" }, + { label: "Maceio", value: "America/Maceio" }, + { label: "Managua", value: "America/Managua" }, + { label: "Manaus", value: "America/Manaus" }, + { label: "Marigot", value: "America/Marigot" }, + { label: "Martinique", value: "America/Martinique" }, + { label: "Matamoros", value: "America/Matamoros" }, + { label: "Mazatlan", value: "America/Mazatlan" }, + { label: "Menominee", value: "America/Menominee" }, + { label: "Merida", value: "America/Merida" }, + { label: "Metlakatla", value: "America/Metlakatla" }, + { label: "Mexico City (Central Mexico)", value: "America/Mexico_City" }, + { label: "Miquelon", value: "America/Miquelon" }, + { label: "Moncton", value: "America/Moncton" }, + { label: "Monterrey", value: "America/Monterrey" }, + { label: "Montevideo", value: "America/Montevideo" }, + { label: "Montserrat", value: "America/Montserrat" }, + { label: "Nassau", value: "America/Nassau" }, + { label: "New York (Eastern Time)", value: "America/New_York" }, + { label: "Nome", value: "America/Nome" }, + { label: "Noronha", value: "America/Noronha" }, + { label: "North Dakota/Beulah", value: "America/North_Dakota/Beulah" }, + { label: "North Dakota/Center", value: "America/North_Dakota/Center" }, + { + label: "North Dakota/New Salem", + value: "America/North_Dakota/New_Salem", + }, + { label: "Nuuk", value: "America/Nuuk" }, + { label: "Ojinaga", value: "America/Ojinaga" }, + { label: "Panama", value: "America/Panama" }, + { label: "Paramaribo", value: "America/Paramaribo" }, + { label: "Phoenix", value: "America/Phoenix" }, + { label: "Port-au-Prince", value: "America/Port-au-Prince" }, + { label: "Port of Spain", value: "America/Port_of_Spain" }, + { label: "Porto Velho", value: "America/Porto_Velho" }, + { label: "Puerto Rico", value: "America/Puerto_Rico" }, + { label: "Punta Arenas", value: "America/Punta_Arenas" }, + { label: "Rankin Inlet", value: "America/Rankin_Inlet" }, + { label: "Recife", value: "America/Recife" }, + { label: "Regina", value: "America/Regina" }, + { label: "Resolute", value: "America/Resolute" }, + { label: "Rio Branco", value: "America/Rio_Branco" }, + { label: "Santarem", value: "America/Santarem" }, + { label: "Santiago", value: "America/Santiago" }, + { label: "Santo Domingo", value: "America/Santo_Domingo" }, + { label: "Sao Paulo (Brasilia Time)", value: "America/Sao_Paulo" }, + { label: "Scoresbysund", value: "America/Scoresbysund" }, + { label: "Sitka", value: "America/Sitka" }, + { label: "St Barthelemy", value: "America/St_Barthelemy" }, + { label: "St Johns", value: "America/St_Johns" }, + { label: "St Kitts", value: "America/St_Kitts" }, + { label: "St Lucia", value: "America/St_Lucia" }, + { label: "St Thomas", value: "America/St_Thomas" }, + { label: "St Vincent", value: "America/St_Vincent" }, + { label: "Swift Current", value: "America/Swift_Current" }, + { label: "Tegucigalpa", value: "America/Tegucigalpa" }, + { label: "Thule", value: "America/Thule" }, + { label: "Tijuana", value: "America/Tijuana" }, + { label: "Toronto", value: "America/Toronto" }, + { label: "Tortola", value: "America/Tortola" }, + { label: "Vancouver", value: "America/Vancouver" }, + { label: "Whitehorse", value: "America/Whitehorse" }, + { label: "Winnipeg", value: "America/Winnipeg" }, + { label: "Yakutat", value: "America/Yakutat" }, + ], + Antarctica: [ + { label: "Casey", value: "Antarctica/Casey" }, + { label: "Davis", value: "Antarctica/Davis" }, + { label: "DumontDUrville", value: "Antarctica/DumontDUrville" }, + { label: "Macquarie", value: "Antarctica/Macquarie" }, + { label: "Mawson", value: "Antarctica/Mawson" }, + { label: "McMurdo", value: "Antarctica/McMurdo" }, + { label: "Palmer", value: "Antarctica/Palmer" }, + { label: "Rothera", value: "Antarctica/Rothera" }, + { label: "Syowa", value: "Antarctica/Syowa" }, + { label: "Troll", value: "Antarctica/Troll" }, + { label: "Vostok", value: "Antarctica/Vostok" }, + ], + Arctic: [{ label: "Longyearbyen", value: "Arctic/Longyearbyen" }], + Asia: [ + { label: "Aden", value: "Asia/Aden" }, + { label: "Almaty", value: "Asia/Almaty" }, + { label: "Amman", value: "Asia/Amman" }, + { label: "Anadyr", value: "Asia/Anadyr" }, + { label: "Aqtau", value: "Asia/Aqtau" }, + { label: "Aqtobe", value: "Asia/Aqtobe" }, + { label: "Ashgabat", value: "Asia/Ashgabat" }, + { label: "Atyrau", value: "Asia/Atyrau" }, + { label: "Baghdad", value: "Asia/Baghdad" }, + { label: "Bahrain", value: "Asia/Bahrain" }, + { label: "Baku", value: "Asia/Baku" }, + { label: "Bangkok", value: "Asia/Bangkok" }, + { label: "Barnaul", value: "Asia/Barnaul" }, + { label: "Beirut", value: "Asia/Beirut" }, + { label: "Bishkek", value: "Asia/Bishkek" }, + { label: "Brunei", value: "Asia/Brunei" }, + { label: "Chita", value: "Asia/Chita" }, + { label: "Choibalsan", value: "Asia/Choibalsan" }, + { label: "Colombo", value: "Asia/Colombo" }, + { label: "Damascus", value: "Asia/Damascus" }, + { label: "Dhaka", value: "Asia/Dhaka" }, + { label: "Dili", value: "Asia/Dili" }, + { label: "Dubai (Gulf Standard Time)", value: "Asia/Dubai" }, + { label: "Dushanbe", value: "Asia/Dushanbe" }, + { label: "Famagusta", value: "Asia/Famagusta" }, + { label: "Gaza", value: "Asia/Gaza" }, + { label: "Hebron", value: "Asia/Hebron" }, + { label: "Ho Chi Minh", value: "Asia/Ho_Chi_Minh" }, + { label: "Hong Kong", value: "Asia/Hong_Kong" }, + { label: "Hovd", value: "Asia/Hovd" }, + { label: "Irkutsk", value: "Asia/Irkutsk" }, + { label: "Jakarta", value: "Asia/Jakarta" }, + { label: "Jayapura", value: "Asia/Jayapura" }, + { label: "Jerusalem", value: "Asia/Jerusalem" }, + { label: "Kabul", value: "Asia/Kabul" }, + { label: "Kamchatka", value: "Asia/Kamchatka" }, + { label: "Karachi", value: "Asia/Karachi" }, + { label: "Kathmandu", value: "Asia/Kathmandu" }, + { label: "Khandyga", value: "Asia/Khandyga" }, + { label: "Kolkata (India Standard Time)", value: "Asia/Kolkata" }, + { label: "Krasnoyarsk", value: "Asia/Krasnoyarsk" }, + { label: "Kuala Lumpur", value: "Asia/Kuala_Lumpur" }, + { label: "Kuching", value: "Asia/Kuching" }, + { label: "Kuwait", value: "Asia/Kuwait" }, + { label: "Macau", value: "Asia/Macau" }, + { label: "Magadan", value: "Asia/Magadan" }, + { label: "Makassar", value: "Asia/Makassar" }, + { label: "Manila", value: "Asia/Manila" }, + { label: "Muscat", value: "Asia/Muscat" }, + { label: "Nicosia", value: "Asia/Nicosia" }, + { label: "Novokuznetsk", value: "Asia/Novokuznetsk" }, + { label: "Novosibirsk", value: "Asia/Novosibirsk" }, + { label: "Omsk", value: "Asia/Omsk" }, + { label: "Oral", value: "Asia/Oral" }, + { label: "Phnom Penh", value: "Asia/Phnom_Penh" }, + { label: "Pontianak", value: "Asia/Pontianak" }, + { label: "Pyongyang", value: "Asia/Pyongyang" }, + { label: "Qatar", value: "Asia/Qatar" }, + { label: "Qostanay", value: "Asia/Qostanay" }, + { label: "Qyzylorda", value: "Asia/Qyzylorda" }, + { label: "Riyadh", value: "Asia/Riyadh" }, + { label: "Sakhalin", value: "Asia/Sakhalin" }, + { label: "Samarkand", value: "Asia/Samarkand" }, + { label: "Seoul", value: "Asia/Seoul" }, + { label: "Shanghai (China Standard Time)", value: "Asia/Shanghai" }, + { label: "Singapore", value: "Asia/Singapore" }, + { label: "Srednekolymsk", value: "Asia/Srednekolymsk" }, + { label: "Taipei", value: "Asia/Taipei" }, + { label: "Tashkent", value: "Asia/Tashkent" }, + { label: "Tbilisi", value: "Asia/Tbilisi" }, + { label: "Tehran", value: "Asia/Tehran" }, + { label: "Thimphu", value: "Asia/Thimphu" }, + { label: "Tokyo (Japan Standard Time)", value: "Asia/Tokyo" }, + { label: "Tomsk", value: "Asia/Tomsk" }, + { label: "Ulaanbaatar", value: "Asia/Ulaanbaatar" }, + { label: "Urumqi", value: "Asia/Urumqi" }, + { label: "Ust-Nera", value: "Asia/Ust-Nera" }, + { label: "Vientiane", value: "Asia/Vientiane" }, + { label: "Vladivostok", value: "Asia/Vladivostok" }, + { label: "Yakutsk", value: "Asia/Yakutsk" }, + { label: "Yangon", value: "Asia/Yangon" }, + { label: "Yekaterinburg", value: "Asia/Yekaterinburg" }, + { label: "Yerevan", value: "Asia/Yerevan" }, + ], + Atlantic: [ + { label: "Azores", value: "Atlantic/Azores" }, + { label: "Bermuda", value: "Atlantic/Bermuda" }, + { label: "Canary", value: "Atlantic/Canary" }, + { label: "Cape Verde", value: "Atlantic/Cape_Verde" }, + { label: "Faroe", value: "Atlantic/Faroe" }, + { label: "Madeira", value: "Atlantic/Madeira" }, + { label: "Reykjavik", value: "Atlantic/Reykjavik" }, + { label: "South Georgia", value: "Atlantic/South_Georgia" }, + { label: "St Helena", value: "Atlantic/St_Helena" }, + { label: "Stanley", value: "Atlantic/Stanley" }, + ], + Australia: [ + { label: "Adelaide", value: "Australia/Adelaide" }, + { label: "Brisbane", value: "Australia/Brisbane" }, + { label: "Broken Hill", value: "Australia/Broken_Hill" }, + { label: "Darwin", value: "Australia/Darwin" }, + { label: "Eucla", value: "Australia/Eucla" }, + { label: "Hobart", value: "Australia/Hobart" }, + { label: "Lindeman", value: "Australia/Lindeman" }, + { label: "Lord Howe", value: "Australia/Lord_Howe" }, + { label: "Melbourne", value: "Australia/Melbourne" }, + { label: "Perth", value: "Australia/Perth" }, + { label: "Sydney (Australian Eastern Time)", value: "Australia/Sydney" }, + ], + Europe: [ + { label: "Amsterdam", value: "Europe/Amsterdam" }, + { label: "Andorra", value: "Europe/Andorra" }, + { label: "Astrakhan", value: "Europe/Astrakhan" }, + { label: "Athens", value: "Europe/Athens" }, + { label: "Belgrade", value: "Europe/Belgrade" }, + { label: "Berlin (Central European Time)", value: "Europe/Berlin" }, + { label: "Bratislava", value: "Europe/Bratislava" }, + { label: "Brussels", value: "Europe/Brussels" }, + { label: "Bucharest", value: "Europe/Bucharest" }, + { label: "Budapest", value: "Europe/Budapest" }, + { label: "Busingen", value: "Europe/Busingen" }, + { label: "Chisinau", value: "Europe/Chisinau" }, + { label: "Copenhagen", value: "Europe/Copenhagen" }, + { label: "Dublin", value: "Europe/Dublin" }, + { label: "Gibraltar", value: "Europe/Gibraltar" }, + { label: "Guernsey", value: "Europe/Guernsey" }, + { label: "Helsinki", value: "Europe/Helsinki" }, + { label: "Isle of Man", value: "Europe/Isle_of_Man" }, + { label: "Istanbul", value: "Europe/Istanbul" }, + { label: "Jersey", value: "Europe/Jersey" }, + { label: "Kaliningrad", value: "Europe/Kaliningrad" }, + { label: "Kirov", value: "Europe/Kirov" }, + { label: "Kyiv", value: "Europe/Kyiv" }, + { label: "Lisbon", value: "Europe/Lisbon" }, + { label: "Ljubljana", value: "Europe/Ljubljana" }, + { label: "London (Greenwich Mean Time)", value: "Europe/London" }, + { label: "Luxembourg", value: "Europe/Luxembourg" }, + { label: "Madrid", value: "Europe/Madrid" }, + { label: "Malta", value: "Europe/Malta" }, + { label: "Mariehamn", value: "Europe/Mariehamn" }, + { label: "Minsk", value: "Europe/Minsk" }, + { label: "Monaco", value: "Europe/Monaco" }, + { label: "Moscow", value: "Europe/Moscow" }, + { label: "Oslo", value: "Europe/Oslo" }, + { label: "Paris (Central European Time)", value: "Europe/Paris" }, + { label: "Podgorica", value: "Europe/Podgorica" }, + { label: "Prague", value: "Europe/Prague" }, + { label: "Riga", value: "Europe/Riga" }, + { label: "Rome", value: "Europe/Rome" }, + { label: "Samara", value: "Europe/Samara" }, + { label: "San Marino", value: "Europe/San_Marino" }, + { label: "Sarajevo", value: "Europe/Sarajevo" }, + { label: "Saratov", value: "Europe/Saratov" }, + { label: "Simferopol", value: "Europe/Simferopol" }, + { label: "Skopje", value: "Europe/Skopje" }, + { label: "Sofia", value: "Europe/Sofia" }, + { label: "Stockholm", value: "Europe/Stockholm" }, + { label: "Tallinn", value: "Europe/Tallinn" }, + { label: "Tirane", value: "Europe/Tirane" }, + { label: "Ulyanovsk", value: "Europe/Ulyanovsk" }, + { label: "Vaduz", value: "Europe/Vaduz" }, + { label: "Vatican", value: "Europe/Vatican" }, + { label: "Vienna", value: "Europe/Vienna" }, + { label: "Vilnius", value: "Europe/Vilnius" }, + { label: "Volgograd", value: "Europe/Volgograd" }, + { label: "Warsaw", value: "Europe/Warsaw" }, + { label: "Zagreb", value: "Europe/Zagreb" }, + { label: "Zurich", value: "Europe/Zurich" }, + ], + Indian: [ + { label: "Antananarivo", value: "Indian/Antananarivo" }, + { label: "Chagos", value: "Indian/Chagos" }, + { label: "Christmas", value: "Indian/Christmas" }, + { label: "Cocos", value: "Indian/Cocos" }, + { label: "Comoro", value: "Indian/Comoro" }, + { label: "Kerguelen", value: "Indian/Kerguelen" }, + { label: "Mahe", value: "Indian/Mahe" }, + { label: "Maldives", value: "Indian/Maldives" }, + { label: "Mauritius", value: "Indian/Mauritius" }, + { label: "Mayotte", value: "Indian/Mayotte" }, + { label: "Reunion", value: "Indian/Reunion" }, + ], + Pacific: [ + { label: "Apia", value: "Pacific/Apia" }, + { label: "Auckland", value: "Pacific/Auckland" }, + { label: "Bougainville", value: "Pacific/Bougainville" }, + { label: "Chatham", value: "Pacific/Chatham" }, + { label: "Chuuk", value: "Pacific/Chuuk" }, + { label: "Easter", value: "Pacific/Easter" }, + { label: "Efate", value: "Pacific/Efate" }, + { label: "Fakaofo", value: "Pacific/Fakaofo" }, + { label: "Fiji", value: "Pacific/Fiji" }, + { label: "Funafuti", value: "Pacific/Funafuti" }, + { label: "Galapagos", value: "Pacific/Galapagos" }, + { label: "Gambier", value: "Pacific/Gambier" }, + { label: "Guadalcanal", value: "Pacific/Guadalcanal" }, + { label: "Guam", value: "Pacific/Guam" }, + { label: "Honolulu", value: "Pacific/Honolulu" }, + { label: "Kanton", value: "Pacific/Kanton" }, + { label: "Kiritimati", value: "Pacific/Kiritimati" }, + { label: "Kosrae", value: "Pacific/Kosrae" }, + { label: "Kwajalein", value: "Pacific/Kwajalein" }, + { label: "Majuro", value: "Pacific/Majuro" }, + { label: "Marquesas", value: "Pacific/Marquesas" }, + { label: "Midway", value: "Pacific/Midway" }, + { label: "Nauru", value: "Pacific/Nauru" }, + { label: "Niue", value: "Pacific/Niue" }, + { label: "Norfolk", value: "Pacific/Norfolk" }, + { label: "Noumea", value: "Pacific/Noumea" }, + { label: "Pago Pago", value: "Pacific/Pago_Pago" }, + { label: "Palau", value: "Pacific/Palau" }, + { label: "Pitcairn", value: "Pacific/Pitcairn" }, + { label: "Pohnpei", value: "Pacific/Pohnpei" }, + { label: "Port Moresby", value: "Pacific/Port_Moresby" }, + { label: "Rarotonga", value: "Pacific/Rarotonga" }, + { label: "Saipan", value: "Pacific/Saipan" }, + { label: "Tahiti", value: "Pacific/Tahiti" }, + { label: "Tarawa", value: "Pacific/Tarawa" }, + { label: "Tongatapu", value: "Pacific/Tongatapu" }, + { label: "Wake", value: "Pacific/Wake" }, + { label: "Wallis", value: "Pacific/Wallis" }, + ], +}; + +// Helper to get display label for a timezone value +export function getTimezoneLabel(value: string | undefined): string { + if (!value) return "UTC (default)"; + return value; +} From 4c1005639414c798e0552268f38e0eecee92bd1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=84=D1=8B=D1=80=D0=B0=D1=82=20=D1=91=D0=B7=D0=B4=D1=8D?= =?UTF-8?q?=D0=BD?= <31664778+fir4tozden@users.noreply.github.com> Date: Sun, 14 Dec 2025 07:24:27 +0300 Subject: [PATCH 05/54] chore --- packages/server/src/utils/docker/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index f8518bd90..6f0e433b9 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -156,7 +156,7 @@ CHECK_INTERVAL=10 echo "Preparing for execution..." while true; do - PROCESSES=$(ps aux | grep -E "docker " | grep -v grep) + PROCESSES=$(ps aux | grep -E "^.*docker [a-z]" | grep -v grep) if [ -z "$PROCESSES" ]; then echo "Docker is idle. Starting execution..." From 5d427379434ffcaac7550dd85ea7577f5d0eacd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=84=D1=8B=D1=80=D0=B0=D1=82=20=D1=91=D0=B7=D0=B4=D1=8D?= =?UTF-8?q?=D0=BD?= <31664778+fir4tozden@users.noreply.github.com> Date: Sun, 14 Dec 2025 07:32:28 +0300 Subject: [PATCH 06/54] cepte --- packages/server/src/utils/docker/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 6f0e433b9..1f5e54978 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -156,7 +156,7 @@ CHECK_INTERVAL=10 echo "Preparing for execution..." while true; do - PROCESSES=$(ps aux | grep -E "^.*docker [a-z]" | grep -v grep) + PROCESSES=$(ps aux | grep -E "^.*docker [A-Za-z]" | grep -v grep) if [ -z "$PROCESSES" ]; then echo "Docker is idle. Starting execution..." From 19a7a80d43cd99c9f1a96da6fe37122bb599094f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=84=D1=8B=D1=80=D0=B0=D1=82=20=D1=91=D0=B7=D0=B4=D1=8D?= =?UTF-8?q?=D0=BD?= <31664778+fir4tozden@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:06:55 +0300 Subject: [PATCH 07/54] [BUG] fix: volume cleaning should not be performed --- packages/server/src/utils/docker/utils.ts | 44 +++++++++++++++-------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index d674a8840..a58ad441c 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -174,9 +174,9 @@ echo "Execution completed."`; const cleanupCommands = { containers: "docker container prune --force", images: "docker image prune --all --force", + volumes: "docker volume prune --all --force", builders: "docker builder prune --all --force", system: "docker system prune --all --force", - volumes: "docker volume prune --all --force", }; export const cleanupContainers = async (serverId?: string) => { @@ -257,24 +257,40 @@ export const cleanupSystem = async (serverId?: string) => { } }; +/** + * Volume cleanup should always be performed manually by the user. The reason is that during automatic cleanup, a volume may be deleted due to a stopped container, which is a dangerous situation. + * + * https://github.com/Dokploy/dokploy/pull/3266 + */ +const excludedCleanupAllCommands: (keyof typeof cleanupCommands)[] = ['volumes']; + export const cleanupAll = async (serverId?: string) => { - await cleanupContainers(serverId); - await cleanupImages(serverId); - await cleanupBuilders(serverId); - await cleanupSystem(serverId); + for (const [key, command] of Object.entries(cleanupCommands)) { + if (excludedCleanupAllCommands.includes(key)) continue; + + try { + if (serverId) { + await execAsyncRemote(serverId, dockerSafeExec(command)); + } else { + await execAsync(dockerSafeExec(command)); + } + } catch {} + } }; export const cleanupAllBackground = async (serverId?: string) => { Promise.allSettled( - Object.values(cleanupCommands).map(async (command) => { - try { - if (serverId) { - await execAsyncRemote(serverId, dockerSafeExec(command)); - } else { - await execAsync(dockerSafeExec(command)); - } - } catch (error) {} - }), + Object.entries(cleanupCommands) + .filter(([key]) => !excludedCleanupAllCommands.includes(key)) + .map(async ([, command]) => { + try { + if (serverId) { + await execAsyncRemote(serverId, dockerSafeExec(command)); + } else { + await execAsync(dockerSafeExec(command)); + } + } catch {} + }) ) .then((results) => { const failed = results.filter((r) => r.status === "rejected"); From ba5283039c669a3180df47cfeb9ffd34a85b1e02 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 05:11:51 +0000 Subject: [PATCH 08/54] [autofix.ci] apply automated fixes --- packages/server/src/utils/docker/utils.ts | 46 ++++++++++++----------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index a58ad441c..565e15033 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -262,35 +262,37 @@ export const cleanupSystem = async (serverId?: string) => { * * https://github.com/Dokploy/dokploy/pull/3266 */ -const excludedCleanupAllCommands: (keyof typeof cleanupCommands)[] = ['volumes']; +const excludedCleanupAllCommands: (keyof typeof cleanupCommands)[] = [ + "volumes", +]; export const cleanupAll = async (serverId?: string) => { - for (const [key, command] of Object.entries(cleanupCommands)) { - if (excludedCleanupAllCommands.includes(key)) continue; + for (const [key, command] of Object.entries(cleanupCommands)) { + if (excludedCleanupAllCommands.includes(key)) continue; - try { - if (serverId) { - await execAsyncRemote(serverId, dockerSafeExec(command)); - } else { - await execAsync(dockerSafeExec(command)); - } - } catch {} - } + try { + if (serverId) { + await execAsyncRemote(serverId, dockerSafeExec(command)); + } else { + await execAsync(dockerSafeExec(command)); + } + } catch {} + } }; export const cleanupAllBackground = async (serverId?: string) => { Promise.allSettled( - Object.entries(cleanupCommands) - .filter(([key]) => !excludedCleanupAllCommands.includes(key)) - .map(async ([, command]) => { - try { - if (serverId) { - await execAsyncRemote(serverId, dockerSafeExec(command)); - } else { - await execAsync(dockerSafeExec(command)); - } - } catch {} - }) + Object.entries(cleanupCommands) + .filter(([key]) => !excludedCleanupAllCommands.includes(key)) + .map(async ([, command]) => { + try { + if (serverId) { + await execAsyncRemote(serverId, dockerSafeExec(command)); + } else { + await execAsync(dockerSafeExec(command)); + } + } catch {} + }), ) .then((results) => { const failed = results.filter((r) => r.status === "rejected"); From 51abf494585aa8c6be34e1619010a6b6ebcd995e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=84=D1=8B=D1=80=D0=B0=D1=82=20=D1=91=D0=B7=D0=B4=D1=8D?= =?UTF-8?q?=D0=BD?= <31664778+fir4tozden@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:13:02 +0300 Subject: [PATCH 09/54] chore: update pr id --- packages/server/src/utils/docker/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 565e15033..2a1674151 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -260,7 +260,7 @@ export const cleanupSystem = async (serverId?: string) => { /** * Volume cleanup should always be performed manually by the user. The reason is that during automatic cleanup, a volume may be deleted due to a stopped container, which is a dangerous situation. * - * https://github.com/Dokploy/dokploy/pull/3266 + * https://github.com/Dokploy/dokploy/pull/3267 */ const excludedCleanupAllCommands: (keyof typeof cleanupCommands)[] = [ "volumes", From 371cf83e52979621dfa226e084b98786a99ba0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=84=D1=8B=D1=80=D0=B0=D1=82=20=D1=91=D0=B7=D0=B4=D1=8D?= =?UTF-8?q?=D0=BD?= <31664778+fir4tozden@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:16:09 +0300 Subject: [PATCH 10/54] fix: typing --- packages/server/src/utils/docker/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 2a1674151..abd1f678c 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -267,7 +267,7 @@ const excludedCleanupAllCommands: (keyof typeof cleanupCommands)[] = [ ]; export const cleanupAll = async (serverId?: string) => { - for (const [key, command] of Object.entries(cleanupCommands)) { + for (const [key, command] of Object.entries(cleanupCommands) as [keyof typeof cleanupCommands, string][]) { if (excludedCleanupAllCommands.includes(key)) continue; try { From 669de0f95f79915e8d0faa7e61cdcc4628de5166 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 05:16:30 +0000 Subject: [PATCH 11/54] [autofix.ci] apply automated fixes --- packages/server/src/utils/docker/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index abd1f678c..86e8ccb2f 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -267,7 +267,10 @@ const excludedCleanupAllCommands: (keyof typeof cleanupCommands)[] = [ ]; export const cleanupAll = async (serverId?: string) => { - for (const [key, command] of Object.entries(cleanupCommands) as [keyof typeof cleanupCommands, string][]) { + for (const [key, command] of Object.entries(cleanupCommands) as [ + keyof typeof cleanupCommands, + string, + ][]) { if (excludedCleanupAllCommands.includes(key)) continue; try { From b66156956ac4ff24f6fa7e6d2fa3a69aee3ecfdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=84=D1=8B=D1=80=D0=B0=D1=82=20=D1=91=D0=B7=D0=B4=D1=8D?= =?UTF-8?q?=D0=BD?= <31664778+fir4tozden@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:20:00 +0300 Subject: [PATCH 12/54] fix: typing --- packages/server/src/utils/docker/utils.ts | 53 ++++++++++++----------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 86e8ccb2f..ab3dac173 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -284,33 +284,34 @@ export const cleanupAll = async (serverId?: string) => { }; export const cleanupAllBackground = async (serverId?: string) => { - Promise.allSettled( - Object.entries(cleanupCommands) - .filter(([key]) => !excludedCleanupAllCommands.includes(key)) - .map(async ([, command]) => { - try { - if (serverId) { - await execAsyncRemote(serverId, dockerSafeExec(command)); - } else { - await execAsync(dockerSafeExec(command)); - } - } catch {} - }), - ) - .then((results) => { - const failed = results.filter((r) => r.status === "rejected"); - if (failed.length > 0) { - console.error(`Docker cleanup: ${failed.length} operations failed`); - } else { - console.log("Docker cleanup completed successfully"); - } - }) - .catch((error) => console.error("Error in cleanup:", error)); + Promise.allSettled( + (Object.entries(cleanupCommands) as [ + keyof typeof cleanupCommands, + string + ][]) + .filter(([key]) => !excludedCleanupAllCommands.includes(key)) + .map(async ([, command]) => { + if (serverId) { + await execAsyncRemote(serverId, dockerSafeExec(command)); + } else { + await execAsync(dockerSafeExec(command)); + } + }) + ) + .then((results) => { + const failed = results.filter((r) => r.status === "rejected"); + if (failed.length > 0) { + console.error(`Docker cleanup: ${failed.length} operations failed`); + } else { + console.log("Docker cleanup completed successfully"); + } + }) + .catch((error) => console.error("Error in cleanup:", error)); - return { - status: "scheduled", - message: "Docker cleanup has been initiated in the background", - }; + return { + status: "scheduled", + message: "Docker cleanup has been initiated in the background", + }; }; export const startService = async (appName: string) => { From 2b1a3db7b89a94fed54031e581407200f802aaa8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 05:20:20 +0000 Subject: [PATCH 13/54] [autofix.ci] apply automated fixes --- packages/server/src/utils/docker/utils.ts | 56 ++++++++++++----------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index ab3dac173..140960426 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -284,34 +284,36 @@ export const cleanupAll = async (serverId?: string) => { }; export const cleanupAllBackground = async (serverId?: string) => { - Promise.allSettled( - (Object.entries(cleanupCommands) as [ - keyof typeof cleanupCommands, - string - ][]) - .filter(([key]) => !excludedCleanupAllCommands.includes(key)) - .map(async ([, command]) => { - if (serverId) { - await execAsyncRemote(serverId, dockerSafeExec(command)); - } else { - await execAsync(dockerSafeExec(command)); - } - }) - ) - .then((results) => { - const failed = results.filter((r) => r.status === "rejected"); - if (failed.length > 0) { - console.error(`Docker cleanup: ${failed.length} operations failed`); - } else { - console.log("Docker cleanup completed successfully"); - } - }) - .catch((error) => console.error("Error in cleanup:", error)); + Promise.allSettled( + ( + Object.entries(cleanupCommands) as [ + keyof typeof cleanupCommands, + string, + ][] + ) + .filter(([key]) => !excludedCleanupAllCommands.includes(key)) + .map(async ([, command]) => { + if (serverId) { + await execAsyncRemote(serverId, dockerSafeExec(command)); + } else { + await execAsync(dockerSafeExec(command)); + } + }), + ) + .then((results) => { + const failed = results.filter((r) => r.status === "rejected"); + if (failed.length > 0) { + console.error(`Docker cleanup: ${failed.length} operations failed`); + } else { + console.log("Docker cleanup completed successfully"); + } + }) + .catch((error) => console.error("Error in cleanup:", error)); - return { - status: "scheduled", - message: "Docker cleanup has been initiated in the background", - }; + return { + status: "scheduled", + message: "Docker cleanup has been initiated in the background", + }; }; export const startService = async (appName: string) => { From b65f53d141ef587c5d1e0c8c1d44b5a70968d889 Mon Sep 17 00:00:00 2001 From: gosangam Date: Sun, 14 Dec 2025 20:31:05 +0530 Subject: [PATCH 14/54] fix: return database instance as response on db creation (mongo, mysql, mariadb & postgres) --- apps/dokploy/server/api/routers/mariadb.ts | 2 +- apps/dokploy/server/api/routers/mongo.ts | 2 +- apps/dokploy/server/api/routers/mysql.ts | 2 +- apps/dokploy/server/api/routers/postgres.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index 18950b7a3..7d4bd2e50 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -87,7 +87,7 @@ export const mariadbRouter = createTRPCRouter({ type: "volume", }); - return true; + return newMariadb; } catch (error) { if (error instanceof TRPCError) { throw error; diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index 51b830fc8..ae0fa4741 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -87,7 +87,7 @@ export const mongoRouter = createTRPCRouter({ type: "volume", }); - return true; + return newMongo; } catch (error) { if (error instanceof TRPCError) { throw error; diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index 5edb27da4..5204fedc8 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -89,7 +89,7 @@ export const mysqlRouter = createTRPCRouter({ type: "volume", }); - return true; + return newMysql; } catch (error) { if (error instanceof TRPCError) { throw error; diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 3112beb66..e1718bff1 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -91,7 +91,7 @@ export const postgresRouter = createTRPCRouter({ type: "volume", }); - return true; + return newPostgres; } catch (error) { if (error instanceof TRPCError) { throw error; From 3e356e6890e43c1e6c96df2dc2a9bf5894a90795 Mon Sep 17 00:00:00 2001 From: Bima42 Date: Sun, 14 Dec 2025 17:01:44 +0100 Subject: [PATCH 15/54] feat: being able to switch environments in sidebar --- .../components/shared/breadcrumb-sidebar.tsx | 50 +++++++++++++++---- .../environment/[environmentId].tsx | 11 ++++ .../services/compose/[composeId].tsx | 10 +++- .../services/mariadb/[mariadbId].tsx | 11 +++- .../services/mongo/[mongoId].tsx | 10 +++- .../services/mysql/[mysqlId].tsx | 10 +++- .../services/postgres/[postgresId].tsx | 10 +++- .../services/redis/[redisId].tsx | 10 +++- 8 files changed, 106 insertions(+), 16 deletions(-) diff --git a/apps/dokploy/components/shared/breadcrumb-sidebar.tsx b/apps/dokploy/components/shared/breadcrumb-sidebar.tsx index 7bde4761e..c3428e301 100644 --- a/apps/dokploy/components/shared/breadcrumb-sidebar.tsx +++ b/apps/dokploy/components/shared/breadcrumb-sidebar.tsx @@ -1,22 +1,36 @@ import Link from "next/link"; import { Fragment } from "react"; +import { ChevronDown } from "lucide-react"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, + BreadcrumbPage, } from "@/components/ui/breadcrumb"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Separator } from "@/components/ui/separator"; import { SidebarTrigger } from "@/components/ui/sidebar"; -interface Props { - list: { +interface BreadcrumbEntry { + name: string; + href?: string; + dropdownItems?: { name: string; - href?: string; + href: string; }[]; } +interface Props { + list: BreadcrumbEntry[]; +} + export const BreadcrumbSidebar = ({ list }: Props) => { return (
@@ -29,13 +43,29 @@ export const BreadcrumbSidebar = ({ list }: Props) => { {list.map((item, index) => ( - - {item.href ? ( - {item?.name} - ) : ( - item?.name - )} - + {item.dropdownItems && item.dropdownItems.length > 0 ? ( + + + {item.name} + + + + {item.dropdownItems.map((subItem) => ( + + {subItem.name} + + ))} + + + ) : ( + + {item.href ? ( + {item?.name} + ) : ( + {item?.name} + )} + + )} {index + 1 < list.length && ( diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index a2e54ad51..dcc34cec2 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -279,6 +279,16 @@ const EnvironmentPage = ( const [isBulkActionLoading, setIsBulkActionLoading] = useState(false); const { projectId, environmentId } = props; const { data: auth } = api.user.get.useQuery(); + + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: projectId, + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; + const [sortBy, setSortBy] = useState(() => { if (typeof window !== "undefined") { return localStorage.getItem("servicesSort") || "lastDeploy-desc"; @@ -863,6 +873,7 @@ const EnvironmentPage = ( }, { name: currentEnvironment.name, + dropdownItems: environmentDropdownItems, }, ]} /> diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx index df7cb9a9c..56b4b5d0c 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx @@ -80,6 +80,14 @@ const Service = ( const { data: auth } = api.user.get.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: data?.environment?.projectId || "", + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; return (
@@ -92,7 +100,7 @@ const Service = ( }, { name: data?.environment?.name || "", - href: `/dashboard/project/${projectId}/environment/${environmentId}`, + dropdownItems: environmentDropdownItems, }, { name: data?.name || "", diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx index e5133a9bb..d47fbd14d 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx @@ -62,6 +62,15 @@ const Mariadb = ( const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: data?.environment?.projectId || "", + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; + return (
@@ -73,7 +82,7 @@ const Mariadb = ( }, { name: data?.environment?.name || "", - href: `/dashboard/project/${projectId}/environment/${environmentId}`, + dropdownItems: environmentDropdownItems, }, { name: data?.name || "", diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx index 2de7350b7..660315d5a 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx @@ -61,6 +61,14 @@ const Mongo = ( const { data: auth } = api.user.get.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: data?.environment?.projectId || "", + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; return (
@@ -73,7 +81,7 @@ const Mongo = ( }, { name: data?.environment?.name || "", - href: `/dashboard/project/${projectId}/environment/${environmentId}`, + dropdownItems: environmentDropdownItems, }, { name: data?.name || "", diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx index 23227f385..7f4cc791c 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx @@ -60,6 +60,14 @@ const MySql = ( const { data: auth } = api.user.get.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: data?.environment?.projectId || "", + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; return (
@@ -72,7 +80,7 @@ const MySql = ( }, { name: data?.environment?.name || "", - href: `/dashboard/project/${projectId}/environment/${environmentId}`, + dropdownItems: environmentDropdownItems, }, { name: data?.name || "", diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx index 8fe7742e3..a34f7b7ee 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx @@ -60,6 +60,14 @@ const Postgresql = ( const { data: auth } = api.user.get.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: data?.environment?.projectId || "", + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; return (
@@ -72,7 +80,7 @@ const Postgresql = ( }, { name: data?.environment?.name || "", - href: `/dashboard/project/${projectId}/environment/${environmentId}`, + dropdownItems: environmentDropdownItems, }, { name: data?.name || "", diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx index 14c873094..72a513fba 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx @@ -60,6 +60,14 @@ const Redis = ( const { data: auth } = api.user.get.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: data?.environment?.projectId || "", + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; return (
@@ -72,7 +80,7 @@ const Redis = ( }, { name: data?.environment?.name || "", - href: `/dashboard/project/${projectId}/environment/${environmentId}`, + dropdownItems: environmentDropdownItems, }, { name: data?.name || "", From 6bb5404f87c70317047816ab5f7af8651d86edae Mon Sep 17 00:00:00 2001 From: ayham291 Date: Sun, 14 Dec 2025 23:58:08 +0100 Subject: [PATCH 16/54] fix(mongo): use appName instead of localhost for replica set localhost doesn't work properly in containers --- packages/server/src/utils/databases/mongo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/databases/mongo.ts b/packages/server/src/utils/databases/mongo.ts index 9cb8a69f4..556878fe2 100644 --- a/packages/server/src/utils/databases/mongo.ts +++ b/packages/server/src/utils/databases/mongo.ts @@ -54,7 +54,7 @@ if [ "$REPLICA_STATUS" != "1" ]; then mongosh --eval ' rs.initiate({ _id: "rs0", - members: [{ _id: 0, host: "localhost:27017", priority: 1 }] + members: [{ _id: 0, host: "${appName}:27017", priority: 1 }] }); // Wait for the replica set to initialize From 8eaf2ab5c757170464553d91e06174690e0c86b0 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Mon, 15 Dec 2025 11:56:39 +0530 Subject: [PATCH 17/54] fix(api): return database object from create endpoints Database creation APIs (mysql, mariadb, postgres, mongo) now return the created database object with databaseID instead of boolean true. This enables automation workflows to deploy databases immediately after creation. Fixes #3268 --- apps/dokploy/server/api/routers/mariadb.ts | 2 +- apps/dokploy/server/api/routers/mongo.ts | 2 +- apps/dokploy/server/api/routers/mysql.ts | 2 +- apps/dokploy/server/api/routers/postgres.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index 18950b7a3..7d4bd2e50 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -87,7 +87,7 @@ export const mariadbRouter = createTRPCRouter({ type: "volume", }); - return true; + return newMariadb; } catch (error) { if (error instanceof TRPCError) { throw error; diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index 51b830fc8..ae0fa4741 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -87,7 +87,7 @@ export const mongoRouter = createTRPCRouter({ type: "volume", }); - return true; + return newMongo; } catch (error) { if (error instanceof TRPCError) { throw error; diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index 5edb27da4..5204fedc8 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -89,7 +89,7 @@ export const mysqlRouter = createTRPCRouter({ type: "volume", }); - return true; + return newMysql; } catch (error) { if (error instanceof TRPCError) { throw error; diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 3112beb66..e1718bff1 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -91,7 +91,7 @@ export const postgresRouter = createTRPCRouter({ type: "volume", }); - return true; + return newPostgres; } catch (error) { if (error instanceof TRPCError) { throw error; From 3aeb52810c718f018b77bfe1f7e613484068bd5f Mon Sep 17 00:00:00 2001 From: Bima42 Date: Mon, 15 Dec 2025 10:10:12 +0100 Subject: [PATCH 18/54] fix: missing switch env for apps --- .../services/application/[applicationId].tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx index a20d307b3..e6fc9ddf0 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx @@ -91,6 +91,15 @@ const Service = ( const { data: isCloud } = api.settings.isCloud.useQuery(); const { data: auth } = api.user.get.useQuery(); + const { data: environments } = api.environment.byProjectId.useQuery({ + projectId: data?.environment?.project?.projectId || "", + }); + const environmentDropdownItems = + environments?.map((env) => ({ + name: env.name, + href: `/dashboard/project/${projectId}/environment/${env.environmentId}`, + })) || []; + return (
@@ -98,11 +107,11 @@ const Service = ( list={[ { name: "Projects", href: "/dashboard/projects" }, { - name: data?.environment.project.name || "", + name: data?.environment?.project?.name || "", }, { name: data?.environment?.name || "", - href: `/dashboard/project/${projectId}/environment/${environmentId}`, + dropdownItems: environmentDropdownItems, }, { name: data?.name || "", From eb4fbff1b20dda42119d0d2c9d636cb7667337a3 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 15 Dec 2025 15:17:56 -0600 Subject: [PATCH 19/54] feat(servers): enhance server management UI with button options - Added `asButton` prop to `HandleServers`, `SetupServer`, `ShowServerActions`, and `TerminalModal` components to allow rendering as buttons for improved UI flexibility. - Updated the server management interface to use buttons for actions like editing and setting up servers, enhancing user experience. - Introduced new icons for better visual representation of actions in the server management dashboard. --- .../servers/actions/show-server-actions.tsx | 20 +- .../settings/servers/handle-servers.tsx | 28 +- .../settings/servers/setup-server.tsx | 20 +- .../settings/servers/show-servers.tsx | 445 +++++++++--------- .../settings/web-server/terminal-modal.tsx | 19 +- 5 files changed, 293 insertions(+), 239 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-server-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-server-actions.tsx index 41156d35b..ea2300cc5 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-server-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-server-actions.tsx @@ -1,3 +1,4 @@ +import { Activity } from "lucide-react"; import { useState } from "react"; import { Dialog, @@ -6,6 +7,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { ShowStorageActions } from "./show-storage-actions"; import { ShowTraefikActions } from "./show-traefik-actions"; @@ -13,20 +15,30 @@ import { ToggleDockerCleanup } from "./toggle-docker-cleanup"; interface Props { serverId: string; + asButton?: boolean; } -export const ShowServerActions = ({ serverId }: Props) => { +export const ShowServerActions = ({ serverId, asButton = false }: Props) => { const [isOpen, setIsOpen] = useState(false); return ( - + {asButton ? ( + + + + ) : ( e.preventDefault()} + onSelect={(e) => { + e.preventDefault(); + setIsOpen(true); + }} > View Actions - + )}
Web server settings diff --git a/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx index b36aec7c4..d9d1977a7 100644 --- a/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx @@ -1,5 +1,5 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; +import { PlusIcon, Pencil } from "lucide-react"; import Link from "next/link"; import { useTranslation } from "next-i18next"; import { useEffect, useState } from "react"; @@ -59,9 +59,10 @@ type Schema = z.infer; interface Props { serverId?: string; + asButton?: boolean; } -export const HandleServers = ({ serverId }: Props) => { +export const HandleServers = ({ serverId, asButton = false }: Props) => { const { t } = useTranslation("settings"); const utils = api.useUtils(); @@ -137,21 +138,32 @@ export const HandleServers = ({ serverId }: Props) => { return ( - - {serverId ? ( + {serverId ? ( + asButton ? ( + + + + ) : ( e.preventDefault()} + onSelect={(e) => { + e.preventDefault(); + setIsOpen(true); + }} > Edit Server - ) : ( + ) + ) : ( + - )} - + + )} {serverId ? "Edit" : "Create"} Server diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx index 95899f20a..d88e9a3e4 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx @@ -1,5 +1,5 @@ import copy from "copy-to-clipboard"; -import { CopyIcon, ExternalLinkIcon, ServerIcon } from "lucide-react"; +import { CopyIcon, ExternalLinkIcon, ServerIcon, Settings } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; @@ -36,9 +36,10 @@ import { ValidateServer } from "./validate-server"; interface Props { serverId: string; + asButton?: boolean; } -export const SetupServer = ({ serverId }: Props) => { +export const SetupServer = ({ serverId, asButton = false }: Props) => { const [isOpen, setIsOpen] = useState(false); const { data: server } = api.server.one.useQuery( { @@ -81,14 +82,23 @@ export const SetupServer = ({ serverId }: Props) => { return ( - + {asButton ? ( + + + + ) : ( e.preventDefault()} + onSelect={(e) => { + e.preventDefault(); + setIsOpen(true); + }} > Setup Server - + )}
diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index 2f8ac24e2..2efc718ee 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -1,5 +1,5 @@ import { format } from "date-fns"; -import { KeyIcon, Loader2, MoreHorizontal, ServerIcon } from "lucide-react"; +import { KeyIcon, Loader2, MoreHorizontal, ServerIcon, Clock, User, Key, Network, Terminal, Settings, Pencil, Trash2 } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; @@ -24,14 +24,11 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { - Table, - TableBody, - TableCaption, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { api } from "@/utils/api"; import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal"; import { TerminalModal } from "../web-server/terminal-modal"; @@ -59,7 +56,7 @@ export const ShowServers = () => { return (
{query?.success && isCloud && } - +
@@ -114,57 +111,70 @@ export const ShowServers = () => {
) : ( -
- - -
- See all servers -
-
- - - Name - {isCloud && ( - - Status - - )} - - Type - - - IP Address - - - Port - - - Username - - - SSH Key - - - Created - - - Actions - - - - - {data?.map((server) => { - const canDelete = server.totalSum === 0; - const isActive = server.serverStatus === "active"; - const isBuildServer = - server.serverType === "build"; - return ( - - - {server.name} - - {isCloud && ( - +
+
+ {data?.map((server) => { + const canDelete = server.totalSum === 0; + const isActive = server.serverStatus === "active"; + const isBuildServer = + server.serverType === "build"; + return ( + + +
+
+ + + {server.name} + +
+ {isActive && server.sshKeyId && !isBuildServer && ( + + + + + + + Advanced + + + + {isCloud && ( + + )} + + + + + + )} +
+
+ {isCloud && ( { > {server.serverStatus} - - )} - + )} { > {server.serverType} - - - {server.ipAddress} - - - {server.port} - - - {server.username} - - - +
+
+ +
+ + IP: + {server.ipAddress} + Port: + {server.port} +
+
+ + User: + {server.username} +
+
+ + SSH Key: + {server.sshKeyId ? "Yes" : "No"} - - - - {format( - new Date(server.createdAt), - "PPpp", - )} +
+
+ + + Created {format(new Date(server.createdAt), "PPp")} - - - - - - - - - - Actions - - - {isActive && ( - <> - {server.sshKeyId && ( - + + {/* Compact Actions */} + {isActive && ( +
+ + {server.sshKeyId && ( + + +
+ + + +
+
+ +

Terminal

+
+
+ )} + + + +
+ - - {t( - "settings.common.enterTerminal", - )} - - - )} - + asButton={true} + /> +
+
+ +

Setup Server

+
+
- + + +
+ +
+
+ +

Edit Server

+
+
- {server.sshKeyId && - !isBuildServer && ( + {server.sshKeyId && !isBuildServer && ( + + +
- )} - - )} - - - You can not delete this server - because it has active services. - - You have active services - associated with this server, - please delete them first. -
- ) - } - onClick={async () => { - await mutateAsync({ - serverId: server.serverId, - }) - .then(() => { - refetch(); - toast.success( - `Server ${server.name} deleted successfully`, - ); - }) - .catch((err) => { - toast.error(err.message); - }); - }} - > - e.preventDefault()} - > - Delete Server - - +
+ +

Web Server Actions

+
+
+ )} + +
+ + + +
+ + You can not delete this server + because it has active services. + + You have active services + associated with this server, + please delete them first. + +
+ ) + } + onClick={async () => { + await mutateAsync({ + serverId: server.serverId, + }) + .then(() => { + refetch(); + toast.success( + `Server ${server.name} deleted successfully`, + ); + }) + .catch((err) => { + toast.error(err.message); + }); + }} + > + + +
+ + +

{canDelete ? 'Delete Server' : 'Cannot delete - has active services'}

+
+ +
+
+ )} + + + ); + })} +
- {isActive && - server.sshKeyId && - !isBuildServer && ( - <> - - - Extra - - - - - {isCloud && ( - - )} - - - - - - - )} - - - - - ); - })} - -
- -
+
{data && data?.length > 0 && (
diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx index 58e4c9d4e..0bea05fc0 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx @@ -24,10 +24,12 @@ const getTerminalKey = () => { interface Props { children?: React.ReactNode; serverId: string; + asButton?: boolean; } -export const TerminalModal = ({ children, serverId }: Props) => { +export const TerminalModal = ({ children, serverId, asButton = false }: Props) => { const [terminalKey, setTerminalKey] = useState(getTerminalKey()); + const [isOpen, setIsOpen] = useState(false); const isLocalServer = serverId === "local"; const { data } = api.server.one.useQuery( @@ -43,15 +45,22 @@ export const TerminalModal = ({ children, serverId }: Props) => { }; return ( - - + + {asButton ? ( + + {children} + + ) : ( e.preventDefault()} + onSelect={(e) => { + e.preventDefault(); + setIsOpen(true); + }} > {children} - + )} event.preventDefault()} From 0ddf6b851f0d9919d73430ce384297c5c2a5887a Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 16 Dec 2025 21:05:52 -0600 Subject: [PATCH 20/54] feat(servers): add tooltip for deactivated server status in dashboard - Wrapped server status display in a TooltipProvider to provide additional context for deactivated servers. - Implemented a tooltip that informs users about the reason for deactivation and instructions for reactivation, enhancing user experience and clarity in server management. --- .../settings/servers/show-servers.tsx | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index 2efc718ee..e0aa3c54e 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -173,26 +173,41 @@ export const ShowServers = () => { )}
-
- {isCloud && ( + +
+ {isCloud && ( + <> + {server.serverStatus === "active" ? ( + + {server.serverStatus} + + ) : ( + + + + + {server.serverStatus} + + + + +

+ This server is deactivated due to lack of payment. Please pay your invoice to reactivate it. If you think this is an error, please contact support. +

+
+
+ )} + + )} - {server.serverStatus} + {server.serverType} - )} - - {server.serverType} - -
+
+
From 3a5ac9d31f91c0d3cdb74a24be5356ef83dfbd5c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:09:23 +0000 Subject: [PATCH 21/54] [autofix.ci] apply automated fixes --- .../settings/servers/show-servers.tsx | 195 +++++++++++------- .../settings/web-server/terminal-modal.tsx | 10 +- 2 files changed, 130 insertions(+), 75 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index e0aa3c54e..111f10e28 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -1,5 +1,18 @@ import { format } from "date-fns"; -import { KeyIcon, Loader2, MoreHorizontal, ServerIcon, Clock, User, Key, Network, Terminal, Settings, Pencil, Trash2 } from "lucide-react"; +import { + KeyIcon, + Loader2, + MoreHorizontal, + ServerIcon, + Clock, + User, + Key, + Network, + Terminal, + Settings, + Pencil, + Trash2, +} from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; @@ -116,10 +129,12 @@ export const ShowServers = () => { {data?.map((server) => { const canDelete = server.totalSum === 0; const isActive = server.serverStatus === "active"; - const isBuildServer = - server.serverType === "build"; + const isBuildServer = server.serverType === "build"; return ( - +
@@ -128,50 +143,52 @@ export const ShowServers = () => { {server.name}
- {isActive && server.sshKeyId && !isBuildServer && ( - - - - - - - Advanced - - - - {isCloud && ( - + + + + + + Advanced + + - )} - - - - - - )} + + {isCloud && ( + + )} + + + + + + )}
@@ -185,14 +202,24 @@ export const ShowServers = () => { - + {server.serverStatus} - +

- This server is deactivated due to lack of payment. Please pay your invoice to reactivate it. If you think this is an error, please contact support. + This server is deactivated due + to lack of payment. Please pay + your invoice to reactivate it. + If you think this is an error, + please contact support.

@@ -201,7 +228,9 @@ export const ShowServers = () => { )} {server.serverType} @@ -212,19 +241,33 @@ export const ShowServers = () => {
- IP: - {server.ipAddress} - Port: - {server.port} + + IP: + + + {server.ipAddress} + + + Port: + + + {server.port} +
- User: - {server.username} + + User: + + + {server.username} +
- SSH Key: + + SSH Key: + {server.sshKeyId ? "Yes" : "No"} @@ -232,10 +275,14 @@ export const ShowServers = () => {
- Created {format(new Date(server.createdAt), "PPp")} + Created{" "} + {format( + new Date(server.createdAt), + "PPp", + )}
- + {/* Compact Actions */} {isActive && (
@@ -248,8 +295,8 @@ export const ShowServers = () => { serverId={server.serverId} asButton={true} > - @@ -361,7 +410,11 @@ export const ShowServers = () => {
-

{canDelete ? 'Delete Server' : 'Cannot delete - has active services'}

+

+ {canDelete + ? "Delete Server" + : "Cannot delete - has active services"} +

diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx index 0bea05fc0..2647e1dc0 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx @@ -27,7 +27,11 @@ interface Props { asButton?: boolean; } -export const TerminalModal = ({ children, serverId, asButton = false }: Props) => { +export const TerminalModal = ({ + children, + serverId, + asButton = false, +}: Props) => { const [terminalKey, setTerminalKey] = useState(getTerminalKey()); const [isOpen, setIsOpen] = useState(false); const isLocalServer = serverId === "local"; @@ -47,9 +51,7 @@ export const TerminalModal = ({ children, serverId, asButton = false }: Props) = return ( {asButton ? ( - - {children} - + {children} ) : ( Date: Tue, 16 Dec 2025 22:07:52 -0600 Subject: [PATCH 22/54] feat(registry): enhance registry handling with optional password and new test functionality - Updated the AddRegistrySchema to make the password field optional when editing an existing registry. - Introduced a new mutation, testRegistryById, to validate registry credentials using existing data. - Improved form handling to conditionally require the password based on the editing state. - Enhanced user feedback for registry testing with clearer error messages and instructions. --- .../cluster/registry/handle-registry.tsx | 97 ++++++++++++++++--- apps/dokploy/server/api/routers/registry.ts | 62 ++++++++++++ packages/server/src/db/schema/registry.ts | 8 ++ 3 files changed, 155 insertions(+), 12 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/cluster/registry/handle-registry.tsx b/apps/dokploy/components/dashboard/settings/cluster/registry/handle-registry.tsx index f751e262e..d68e23b92 100644 --- a/apps/dokploy/components/dashboard/settings/cluster/registry/handle-registry.tsx +++ b/apps/dokploy/components/dashboard/settings/cluster/registry/handle-registry.tsx @@ -42,9 +42,7 @@ const AddRegistrySchema = z.object({ username: z.string().min(1, { message: "Username is required", }), - password: z.string().min(1, { - message: "Password is required", - }), + password: z.string(), registryUrl: z .string() .optional() @@ -75,6 +73,7 @@ const AddRegistrySchema = z.object({ ), imagePrefix: z.string(), serverId: z.string().optional(), + isEditing: z.boolean().optional(), }); type AddRegistry = z.infer; @@ -108,6 +107,12 @@ export const HandleRegistry = ({ registryId }: Props) => { error: testRegistryError, isError: testRegistryIsError, } = api.registry.testRegistry.useMutation(); + const { + mutateAsync: testRegistryById, + isLoading: isLoadingById, + error: testRegistryByIdError, + isError: testRegistryByIdIsError, + } = api.registry.testRegistryById.useMutation(); const form = useForm({ defaultValues: { username: "", @@ -116,8 +121,23 @@ export const HandleRegistry = ({ registryId }: Props) => { imagePrefix: "", registryName: "", serverId: "", + isEditing: !!registryId, }, - resolver: zodResolver(AddRegistrySchema), + resolver: zodResolver( + AddRegistrySchema.refine( + (data) => { + // When creating a new registry, password is required + if (!data.isEditing && (!data.password || data.password.length === 0)) { + return false; + } + return true; + }, + { + message: "Password is required", + path: ["password"], + }, + ), + ), }); const password = form.watch("password"); @@ -138,6 +158,7 @@ export const HandleRegistry = ({ registryId }: Props) => { registryUrl: registry.registryUrl, imagePrefix: registry.imagePrefix || "", registryName: registry.registryName, + isEditing: true, }); } else { form.reset({ @@ -146,13 +167,13 @@ export const HandleRegistry = ({ registryId }: Props) => { registryUrl: "", imagePrefix: "", serverId: "", + isEditing: false, }); } }, [form, form.reset, form.formState.isSubmitSuccessful, registry]); const onSubmit = async (data: AddRegistry) => { - await mutateAsync({ - password: data.password, + const payload: any = { registryName: data.registryName, username: data.username, registryUrl: data.registryUrl || "", @@ -160,7 +181,15 @@ export const HandleRegistry = ({ registryId }: Props) => { imagePrefix: data.imagePrefix, serverId: data.serverId, registryId: registryId || "", - }) + }; + + // Only include password if it's been provided (not empty) + // When editing, empty password means "keep the existing password" + if (data.password && data.password.length > 0) { + payload.password = data.password; + } + + await mutateAsync(payload) .then(async (_data) => { await utils.registry.all.invalidate(); toast.success(registryId ? "Registry updated" : "Registry added"); @@ -198,11 +227,14 @@ export const HandleRegistry = ({ registryId }: Props) => { Fill the next fields to add a external registry. - {(isError || testRegistryIsError) && ( + {(isError || testRegistryIsError || testRegistryByIdIsError) && (
- {testRegistryError?.message || error?.message || ""} + {testRegistryError?.message || + testRegistryByIdError?.message || + error?.message || + ""}
)} @@ -253,10 +285,21 @@ export const HandleRegistry = ({ registryId }: Props) => { name="password" render={({ field }) => ( - Password + + Password{registryId && " (Optional)"} + + {registryId && ( + + Leave blank to keep existing password. Enter new password to test or update it. + + )} {