mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-18 05:35:26 +02:00
Compare commits
35 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c96d8559a | ||
|
|
39cf706053 | ||
|
|
e77f276785 | ||
|
|
78c9a047b0 | ||
|
|
84e0f5856b | ||
|
|
2bfa4643fc | ||
|
|
8c7bc82712 | ||
|
|
44645a6fbe | ||
|
|
771d0dd8ab | ||
|
|
67725759e6 | ||
|
|
2065372d4f | ||
|
|
69d5c6f0cb | ||
|
|
53f67c6eb2 | ||
|
|
7c53a3ef75 | ||
|
|
c065c85ee6 | ||
|
|
db97de2a39 | ||
|
|
dc7af1b840 | ||
|
|
97362da2ae | ||
|
|
b476e50ff1 | ||
|
|
1b22384315 | ||
|
|
e607220bfa | ||
|
|
b65f53d141 | ||
|
|
2b1a3db7b8 | ||
|
|
b66156956a | ||
|
|
669de0f95f | ||
|
|
371cf83e52 | ||
|
|
51abf49458 | ||
|
|
ba5283039c | ||
|
|
19a7a80d43 | ||
|
|
5d42737943 | ||
|
|
4c10056394 | ||
|
|
c045c5328f | ||
|
|
8c889fc71e | ||
|
|
d465fb4da1 | ||
|
|
698104e7b7 |
@@ -206,4 +206,38 @@ describe("getRegistryTag", () => {
|
||||
expect(result).toBe("docker.io/myuser/repo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("special characters in username", () => {
|
||||
it("should handle Harbor robot account username with $ (e.g. robot$library+dokploy)", () => {
|
||||
const registry = createMockRegistry({
|
||||
username: "robot$library+dokploy",
|
||||
});
|
||||
const result = getRegistryTag(registry, "nginx");
|
||||
expect(result).toBe("docker.io/robot$library+dokploy/nginx");
|
||||
});
|
||||
|
||||
it("should handle username with $ and other special characters", () => {
|
||||
const registry = createMockRegistry({
|
||||
username: "robot$test+app",
|
||||
});
|
||||
const result = getRegistryTag(registry, "myapp:latest");
|
||||
expect(result).toBe("docker.io/robot$test+app/myapp:latest");
|
||||
});
|
||||
|
||||
it("should handle username with multiple $ symbols", () => {
|
||||
const registry = createMockRegistry({
|
||||
username: "user$name$test",
|
||||
});
|
||||
const result = getRegistryTag(registry, "app");
|
||||
expect(result).toBe("docker.io/user$name$test/app");
|
||||
});
|
||||
|
||||
it("should handle username with + and - symbols", () => {
|
||||
const registry = createMockRegistry({
|
||||
username: "robot+test-user",
|
||||
});
|
||||
const result = getRegistryTag(registry, "nginx:latest");
|
||||
expect(result).toBe("docker.io/robot+test-user/nginx:latest");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Domain } from "@dokploy/server";
|
||||
import { createDomainLabels } from "@dokploy/server";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parse, stringify } from "yaml";
|
||||
|
||||
/**
|
||||
* Regression tests for Traefik Host rule label format.
|
||||
|
||||
@@ -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<typeof addResourcesSchema>;
|
||||
|
||||
export const ShowResources = ({ id, type }: Props) => {
|
||||
const queryMap = {
|
||||
postgres: () =>
|
||||
@@ -163,16 +184,20 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Memory hard limit in bytes. Example: 1GB =
|
||||
1073741824 bytes
|
||||
1073741824 bytes. Use +/- buttons to adjust by
|
||||
256 MB.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
<NumberInputWithSteps
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="1073741824 (1GB in bytes)"
|
||||
{...field}
|
||||
step={MEMORY_STEP_MB}
|
||||
converter={memoryConverter}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -198,16 +223,20 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Memory soft limit in bytes. Example: 256MB =
|
||||
268435456 bytes
|
||||
268435456 bytes. Use +/- buttons to adjust by 256
|
||||
MB.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
<NumberInputWithSteps
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="268435456 (256MB in bytes)"
|
||||
{...field}
|
||||
step={MEMORY_STEP_MB}
|
||||
converter={memoryConverter}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -234,17 +263,20 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<TooltipContent>
|
||||
<p>
|
||||
CPU quota in units of 10^-9 CPUs. Example: 2
|
||||
CPUs = 2000000000
|
||||
CPUs = 2000000000. Use +/- buttons to adjust by
|
||||
0.25 CPU.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
<NumberInputWithSteps
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="2000000000 (2 CPUs)"
|
||||
{...field}
|
||||
value={field.value?.toString() || ""}
|
||||
step={CPU_STEP}
|
||||
converter={cpuConverter}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -271,14 +303,21 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<TooltipContent>
|
||||
<p>
|
||||
CPU shares (relative weight). Example: 1 CPU =
|
||||
1000000000
|
||||
1000000000. Use +/- buttons to adjust by 0.25
|
||||
CPU.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input placeholder="1000000000 (1 CPU)" {...field} />
|
||||
<NumberInputWithSteps
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="1000000000 (1 CPU)"
|
||||
step={CPU_STEP}
|
||||
converter={cpuConverter}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -123,7 +123,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
previewCertificateType: data.previewCertificateType || "none",
|
||||
previewCustomCertResolver: data.previewCustomCertResolver || "",
|
||||
previewRequireCollaboratorPermissions:
|
||||
data.previewRequireCollaboratorPermissions || true,
|
||||
data.previewRequireCollaboratorPermissions ?? true,
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
@@ -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) => {
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
}}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="UTC (default)" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{commonTimezones.map((tz) => (
|
||||
<SelectItem key={tz.value} value={tz.value}>
|
||||
{tz.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{getTimezoneLabel(field.value)}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[400px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search timezone..."
|
||||
className="h-9"
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No timezone found.</CommandEmpty>
|
||||
<ScrollArea className="h-72">
|
||||
{Object.entries(TIMEZONES).map(
|
||||
([region, zones]) => (
|
||||
<CommandGroup key={region} heading={region}>
|
||||
{zones.map((tz) => (
|
||||
<CommandItem
|
||||
key={tz.value}
|
||||
value={`${region} ${tz.label} ${tz.value}`}
|
||||
onSelect={() => {
|
||||
field.onChange(tz.value);
|
||||
}}
|
||||
>
|
||||
{tz.value}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
field.value === tz.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
),
|
||||
)}
|
||||
</ScrollArea>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormDescription>
|
||||
Optional: Choose a timezone for the schedule execution time
|
||||
</FormDescription>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,9 @@ export const ShowProjects = () => {
|
||||
Create and manage your projects
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
{(auth?.role === "owner" || auth?.canCreateProjects) && (
|
||||
{(auth?.role === "owner" ||
|
||||
auth?.role === "admin" ||
|
||||
auth?.canCreateProjects) && (
|
||||
<div className="">
|
||||
<HandleProject />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Activity } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -7,7 +8,6 @@ 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";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon, Pencil } from "lucide-react";
|
||||
import { Pencil, PlusIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { format } from "date-fns";
|
||||
import {
|
||||
Clock,
|
||||
Key,
|
||||
KeyIcon,
|
||||
Loader2,
|
||||
MoreHorizontal,
|
||||
ServerIcon,
|
||||
Clock,
|
||||
User,
|
||||
Key,
|
||||
Network,
|
||||
Terminal,
|
||||
Settings,
|
||||
Pencil,
|
||||
ServerIcon,
|
||||
Settings,
|
||||
Terminal,
|
||||
Trash2,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Fragment } from "react";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
84
apps/dokploy/components/ui/number-input.tsx
Normal file
84
apps/dokploy/components/ui/number-input.tsx
Normal file
@@ -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 (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-9 w-9 shrink-0"
|
||||
onClick={handleDecrement}
|
||||
disabled={numericValue <= 0}
|
||||
>
|
||||
<MinusIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
value={value || ""}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="text-center"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-9 w-9 shrink-0"
|
||||
onClick={handleIncrement}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{displayValue && (
|
||||
<span className="text-xs text-muted-foreground text-center">
|
||||
{displayValue}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -33,7 +33,7 @@
|
||||
"docker:build:canary": "./docker/build.sh canary",
|
||||
"docker:push:canary": "./docker/push.sh canary",
|
||||
"version": "echo $(node -p \"require('./package.json').version\")",
|
||||
"test": "vitest --config __test__/vitest.config.ts volume-backups",
|
||||
"test": "vitest --config __test__/vitest.config.ts",
|
||||
"generate:openapi": "tsx -r dotenv/config scripts/generate-openapi.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -68,6 +68,40 @@ export const aiRouter = createTRPCRouter({
|
||||
{ headers: {} },
|
||||
);
|
||||
break;
|
||||
case "perplexity":
|
||||
// Perplexity doesn't have a /models endpoint, return hardcoded list
|
||||
return [
|
||||
{
|
||||
id: "sonar-deep-research",
|
||||
object: "model",
|
||||
created: Date.now(),
|
||||
owned_by: "perplexity",
|
||||
},
|
||||
{
|
||||
id: "sonar-reasoning-pro",
|
||||
object: "model",
|
||||
created: Date.now(),
|
||||
owned_by: "perplexity",
|
||||
},
|
||||
{
|
||||
id: "sonar-reasoning",
|
||||
object: "model",
|
||||
created: Date.now(),
|
||||
owned_by: "perplexity",
|
||||
},
|
||||
{
|
||||
id: "sonar-pro",
|
||||
object: "model",
|
||||
created: Date.now(),
|
||||
owned_by: "perplexity",
|
||||
},
|
||||
{
|
||||
id: "sonar",
|
||||
object: "model",
|
||||
created: Date.now(),
|
||||
owned_by: "perplexity",
|
||||
},
|
||||
] as Model[];
|
||||
default:
|
||||
if (!input.apiKey)
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
{
|
||||
"name": "@dokploy/server",
|
||||
"version": "1.0.0",
|
||||
"main": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs.js"
|
||||
},
|
||||
"./db": {
|
||||
"import": "./src/db/index.ts",
|
||||
"import": "./dist/db/index.js",
|
||||
"require": "./dist/db/index.cjs.js"
|
||||
},
|
||||
"./setup/*": {
|
||||
"import": "./src/setup/*.ts",
|
||||
"require": "./dist/setup/index.cjs.js"
|
||||
"./*": {
|
||||
"import": "./dist/*",
|
||||
"require": "./dist/*.cjs"
|
||||
},
|
||||
"./constants": {
|
||||
"import": "./src/constants/index.ts",
|
||||
"require": "./dist/constants.cjs.js"
|
||||
"./dist": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs.js"
|
||||
},
|
||||
"./dist/db": {
|
||||
"import": "./dist/db/index.js",
|
||||
"require": "./dist/db/index.cjs.js"
|
||||
},
|
||||
"./dist/db/schema": {
|
||||
"import": "./dist/db/schema/index.js",
|
||||
"require": "./dist/db/schema/index.cjs.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -117,7 +117,7 @@ const getRegistryCommands = (
|
||||
): string => {
|
||||
return `
|
||||
echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" ;
|
||||
echo "${registry.password}" | docker login ${registry.registryUrl} -u ${registry.username} --password-stdin || {
|
||||
echo "${registry.password}" | docker login ${registry.registryUrl} -u '${registry.username}' --password-stdin || {
|
||||
echo "❌ DockerHub Failed" ;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
@@ -146,17 +146,17 @@ export const getContainerByName = (name: string): Promise<ContainerInfo> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 [A-Za-z]" | grep -v grep)
|
||||
|
||||
if [ -z "$PROCESSES" ]; then
|
||||
echo "Docker is idle. Starting execution..."
|
||||
@@ -167,16 +167,23 @@ while true; do
|
||||
fi
|
||||
done
|
||||
|
||||
# Execute command and capture exit code
|
||||
${exec}
|
||||
EXIT_CODE=$?
|
||||
|
||||
echo "Execution completed."`;
|
||||
# Wait for all background processes to complete to prevent zombie processes
|
||||
wait
|
||||
|
||||
echo "Execution completed with exit code: $EXIT_CODE"
|
||||
exit $EXIT_CODE
|
||||
`;
|
||||
|
||||
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 +264,48 @@ 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/3267
|
||||
*/
|
||||
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) as [
|
||||
keyof typeof cleanupCommands,
|
||||
string,
|
||||
][]) {
|
||||
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 {
|
||||
(
|
||||
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));
|
||||
}
|
||||
} catch (error) {}
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.then((results) => {
|
||||
const failed = results.filter((r) => r.status === "rejected");
|
||||
|
||||
@@ -27,9 +27,6 @@ export const backupVolume = async (
|
||||
echo "Turning off volume backup: ${turnOff ? "Yes" : "No"}"
|
||||
echo "Starting volume backup"
|
||||
echo "Dir: ${volumeBackupPath}"
|
||||
echo "Ensuring ubuntu image is available..."
|
||||
docker image inspect ubuntu:latest > /dev/null 2>&1 || docker pull ubuntu:latest
|
||||
echo "Ubuntu image ready ✅"
|
||||
docker run --rm \
|
||||
-v ${volumeName}:/volume_data \
|
||||
-v ${volumeBackupPath}:/backup \
|
||||
|
||||
Reference in New Issue
Block a user