mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-19 14:15:21 +02:00
Compare commits
3 Commits
3301-volum
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4ec77ade8 | ||
|
|
5f441f5b54 | ||
|
|
ed1e3244c6 |
@@ -1,332 +0,0 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import { execAsync } from "@dokploy/server/utils/process/execAsync";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const REAL_TEST_TIMEOUT = 300000;
|
||||
|
||||
// Mock ONLY database and notifications
|
||||
vi.mock("@dokploy/server/db", () => ({
|
||||
db: {
|
||||
query: { volumeBackups: { findFirst: vi.fn() } },
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/volume-backups", () => ({
|
||||
findVolumeBackupById: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/destination", () => ({
|
||||
findDestinationById: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/application", () => ({
|
||||
findApplicationById: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/deployment", () => ({
|
||||
createDeploymentVolumeBackup: vi.fn(),
|
||||
updateDeploymentStatus: vi.fn(),
|
||||
}));
|
||||
|
||||
import type * as volumeBackupService from "@dokploy/server/services/volume-backups";
|
||||
import { backupVolume } from "@dokploy/server/utils/volume-backups/backup";
|
||||
|
||||
type VolumeBackupData = Awaited<
|
||||
ReturnType<typeof volumeBackupService.findVolumeBackupById>
|
||||
>;
|
||||
|
||||
const createMockDestination = () => ({
|
||||
destinationId: "test-dest",
|
||||
bucket: "test-bucket",
|
||||
accessKey: "key",
|
||||
secretAccessKey: "secret",
|
||||
region: "us-east-1",
|
||||
endpoint: "s3.amazonaws.com",
|
||||
});
|
||||
|
||||
const createMockVolumeBackup = (volumeName: string, appName: string): any => ({
|
||||
volumeBackupId: "id",
|
||||
name: "Test",
|
||||
volumeName,
|
||||
appName,
|
||||
serviceType: "application",
|
||||
turnOff: false,
|
||||
prefix: "backups/",
|
||||
destination: createMockDestination() as any,
|
||||
application: { appName, serverId: null } as any,
|
||||
compose: null,
|
||||
});
|
||||
|
||||
async function cleanupDocker(volumeName: string) {
|
||||
try {
|
||||
await execAsync(`docker volume rm ${volumeName} 2>/dev/null || true`);
|
||||
console.log(`✅ Cleaned up volume: ${volumeName}`);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupFiles(appName: string) {
|
||||
try {
|
||||
const { LOGS_PATH, VOLUME_BACKUPS_PATH } = paths(false);
|
||||
|
||||
// Clean logs
|
||||
const logPath = path.join(LOGS_PATH, appName);
|
||||
await execAsync(`rm -rf "${logPath}" 2>/dev/null || true`);
|
||||
|
||||
// Clean volume backups directory
|
||||
const backupPath = path.join(VOLUME_BACKUPS_PATH, appName);
|
||||
await execAsync(`rm -rf "${backupPath}" 2>/dev/null || true`);
|
||||
|
||||
console.log(`✅ Cleaned up files for ${appName}`);
|
||||
} catch (error) {
|
||||
console.error(`⚠️ Error during cleanup for ${appName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
describe(
|
||||
"Volume Backups - REAL Tests",
|
||||
() => {
|
||||
let currentVolumeName: string;
|
||||
let currentAppName: string;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
currentVolumeName = `test-vol-${Date.now()}`;
|
||||
currentAppName = `test-backup-${Date.now()}`;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
console.log(`\n🧹 Cleanup: ${currentVolumeName}`);
|
||||
await cleanupDocker(currentVolumeName);
|
||||
await cleanupFiles(currentAppName);
|
||||
|
||||
console.log("✅ Cleanup done\n");
|
||||
});
|
||||
|
||||
it(
|
||||
"should backup volume with tar ",
|
||||
async () => {
|
||||
console.log(`\n🚀 Test backup: ${currentVolumeName}`);
|
||||
|
||||
// Create volume with data
|
||||
await execAsync(`docker volume create ${currentVolumeName}`);
|
||||
await execAsync(`
|
||||
docker run --rm -v ${currentVolumeName}:/data ubuntu bash -c "
|
||||
echo 'test' > /data/file.txt
|
||||
mkdir -p /data/dir
|
||||
echo 'nested' > /data/dir/nested.txt
|
||||
"
|
||||
`);
|
||||
console.log("✅ Volume created with data");
|
||||
|
||||
// Backup using tar (simulating what backupVolume does)
|
||||
const backupVol = `backup-${Date.now()}`;
|
||||
await execAsync(`docker volume create ${backupVol}`);
|
||||
|
||||
try {
|
||||
await execAsync(`
|
||||
docker run --rm -v ${currentVolumeName}:/source -v ${backupVol}:/backup ubuntu bash -c "
|
||||
cd /source && tar cf /backup/test.tar .
|
||||
"
|
||||
`);
|
||||
console.log("✅ Backup created");
|
||||
|
||||
// Verify tar contains files
|
||||
const { stdout } = await execAsync(`
|
||||
docker run --rm -v ${backupVol}:/backup ubuntu tar -tf /backup/test.tar
|
||||
`);
|
||||
expect(stdout).toContain("file.txt");
|
||||
expect(stdout).toContain("dir/nested.txt");
|
||||
console.log("✅ Backup verified");
|
||||
} finally {
|
||||
await execAsync(`docker volume rm ${backupVol} 2>/dev/null || true`);
|
||||
}
|
||||
},
|
||||
REAL_TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
it(
|
||||
"should verify backup command has proper logging",
|
||||
async () => {
|
||||
console.log(`\n🚀 Test logging: ${currentVolumeName}`);
|
||||
|
||||
const mock = createMockVolumeBackup(currentVolumeName, currentAppName);
|
||||
const command = await backupVolume(mock);
|
||||
|
||||
// Verify logging messages
|
||||
expect(command).toContain("Volume name:");
|
||||
expect(command).toContain("Starting volume backup");
|
||||
expect(command).toContain("Volume backup done ✅");
|
||||
expect(command).toContain("Upload to S3 done ✅");
|
||||
expect(command).toContain("tar cvf");
|
||||
|
||||
console.log("✅ All log messages present");
|
||||
},
|
||||
REAL_TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
it(
|
||||
"should backup 1GB volume using real backupVolume",
|
||||
async () => {
|
||||
console.log(
|
||||
`\n🚀 Test 1GB backup with real code: ${currentVolumeName}`,
|
||||
);
|
||||
|
||||
// Create volume with ~1GB of data
|
||||
await execAsync(`docker volume create ${currentVolumeName}`);
|
||||
console.log("✅ Volume created");
|
||||
|
||||
const startTime = Date.now();
|
||||
await execAsync(`
|
||||
docker run --rm -v ${currentVolumeName}:/data ubuntu bash -c "
|
||||
echo 'Creating 1GB of test data...'
|
||||
dd if=/dev/zero of=/data/large-file-1.dat bs=1M count=250 2>/dev/null
|
||||
dd if=/dev/zero of=/data/large-file-2.dat bs=1M count=250 2>/dev/null
|
||||
dd if=/dev/zero of=/data/large-file-3.dat bs=1M count=250 2>/dev/null
|
||||
dd if=/dev/zero of=/data/large-file-4.dat bs=1M count=250 2>/dev/null
|
||||
mkdir -p /data/metadata
|
||||
echo 'Large backup test - Issue 3301' > /data/metadata/info.txt
|
||||
echo 'marker-67890' > /data/metadata/marker.txt
|
||||
du -sh /data
|
||||
ls -lh /data
|
||||
"
|
||||
`);
|
||||
const createTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
console.log(`✅ Created 1GB data in ${createTime}s`);
|
||||
|
||||
// Create backup directory (simulating what Dokploy does)
|
||||
const { VOLUME_BACKUPS_PATH } = paths(false);
|
||||
const volumeBackupPath = path.join(VOLUME_BACKUPS_PATH, currentAppName);
|
||||
await execAsync(`mkdir -p "${volumeBackupPath}"`);
|
||||
|
||||
// Use the REAL backupVolume function to generate the command
|
||||
const mock = createMockVolumeBackup(currentVolumeName, currentAppName);
|
||||
const fullCommand = await backupVolume(mock);
|
||||
|
||||
console.log("📦 Executing REAL Dokploy backupVolume() command...");
|
||||
|
||||
// Execute the REAL command (without S3 upload part)
|
||||
const backupStartTime = Date.now();
|
||||
const backupFileName = `${currentVolumeName}-${new Date().toISOString()}.tar`;
|
||||
|
||||
// Extract and execute just the backup part of the command (tar creation)
|
||||
// This is what Dokploy really does
|
||||
const commandWithoutS3 =
|
||||
fullCommand?.replace(
|
||||
/rclone copyto[^\n]+/g,
|
||||
'echo "Skipping S3 upload - keeping file locally for test"',
|
||||
) || "";
|
||||
|
||||
// Also prevent the cleanup of the backup file so we can verify it
|
||||
const commandWithoutCleanup = commandWithoutS3.replace(
|
||||
/rm "[^"]+\.tar"/g,
|
||||
'echo "Skipping cleanup for test verification"',
|
||||
);
|
||||
|
||||
try {
|
||||
// Execute the real Dokploy backup command
|
||||
await execAsync(commandWithoutCleanup);
|
||||
const backupTime = ((Date.now() - backupStartTime) / 1000).toFixed(2);
|
||||
console.log(`✅ Backup executed in ${backupTime}s`);
|
||||
} catch (error: any) {
|
||||
console.error("Backup command failed:", error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Verify the backup file was actually created by Dokploy's command
|
||||
const { stdout: backupFiles } = await execAsync(
|
||||
`find "${volumeBackupPath}" -name "*.tar" -type f`,
|
||||
);
|
||||
const backupFilePath = backupFiles.trim().split("\n")[0];
|
||||
|
||||
if (!backupFilePath) {
|
||||
throw new Error("No backup file found");
|
||||
}
|
||||
|
||||
expect(existsSync(backupFilePath)).toBe(true);
|
||||
console.log(`✅ Backup file created: ${path.basename(backupFilePath)}`);
|
||||
|
||||
// Verify file size
|
||||
const { stdout: statOutput } = await execAsync(
|
||||
`stat -f%z "${backupFilePath}" 2>/dev/null || stat -c%s "${backupFilePath}"`,
|
||||
);
|
||||
|
||||
const sizeInMB = Number(statOutput.trim()) / (1024 * 1024);
|
||||
expect(sizeInMB).toBeGreaterThan(1000); // Should be > 1GB
|
||||
console.log(`✅ Backup file size: ${sizeInMB.toFixed(2)}MB`);
|
||||
|
||||
// Verify tar contents - this proves the backup worked
|
||||
const { stdout: tarContents } = await execAsync(
|
||||
`tar -tf "${backupFilePath}"`,
|
||||
);
|
||||
|
||||
expect(tarContents).toContain("large-file-1.dat");
|
||||
expect(tarContents).toContain("large-file-2.dat");
|
||||
expect(tarContents).toContain("large-file-3.dat");
|
||||
expect(tarContents).toContain("large-file-4.dat");
|
||||
expect(tarContents).toContain("metadata/");
|
||||
|
||||
// Extract and verify one file to ensure data integrity
|
||||
// First check if marker file exists in tar
|
||||
if (tarContents.includes("marker.txt")) {
|
||||
const tempDir = path.join(volumeBackupPath, "temp-extract");
|
||||
await execAsync(`mkdir -p "${tempDir}"`);
|
||||
|
||||
// Extract entire tar to handle path variations (. vs ./ prefix)
|
||||
await execAsync(`tar -xf "${backupFilePath}" -C "${tempDir}"`);
|
||||
|
||||
// Find marker file regardless of path
|
||||
const { stdout: markerPath } = await execAsync(
|
||||
`find "${tempDir}" -name "marker.txt" -type f`,
|
||||
);
|
||||
|
||||
if (markerPath.trim()) {
|
||||
const { stdout: markerContent } = await execAsync(
|
||||
`cat "${markerPath.trim()}"`,
|
||||
);
|
||||
expect(markerContent.trim()).toBe("marker-67890");
|
||||
}
|
||||
|
||||
await execAsync(`rm -rf "${tempDir}"`);
|
||||
console.log("✅ Data integrity verified");
|
||||
} else {
|
||||
// Alternative: extract entire metadata folder
|
||||
const tempDir = path.join(volumeBackupPath, "temp-extract");
|
||||
await execAsync(`mkdir -p "${tempDir}"`);
|
||||
await execAsync(`tar -xf "${backupFilePath}" -C "${tempDir}"`);
|
||||
|
||||
// Check what was extracted
|
||||
const { stdout: extractedFiles } = await execAsync(
|
||||
`find "${tempDir}" -type f`,
|
||||
);
|
||||
|
||||
// Verify marker file exists somewhere
|
||||
const markerFiles = extractedFiles
|
||||
.split("\n")
|
||||
.filter((f) => f.includes("marker.txt"));
|
||||
expect(markerFiles.length).toBeGreaterThan(0);
|
||||
|
||||
const markerPath = markerFiles[0];
|
||||
const { stdout: markerContent } = await execAsync(
|
||||
`cat "${markerPath}"`,
|
||||
);
|
||||
expect(markerContent.trim()).toBe("marker-67890");
|
||||
await execAsync(`rm -rf "${tempDir}"`);
|
||||
console.log("✅ Data integrity verified (alternative path)");
|
||||
}
|
||||
|
||||
console.log("\n📊 Performance Summary:");
|
||||
console.log(` - Data creation: ${createTime}s`);
|
||||
console.log(` - Size: ${sizeInMB.toFixed(2)}MB`);
|
||||
console.log(
|
||||
"✅ 1GB backup test PASSED - Real Dokploy backupVolume() works correctly",
|
||||
);
|
||||
},
|
||||
REAL_TEST_TIMEOUT,
|
||||
);
|
||||
},
|
||||
REAL_TEST_TIMEOUT,
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,7 +1,5 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronsUpDown,
|
||||
DatabaseZap,
|
||||
Info,
|
||||
PenBoxIcon,
|
||||
@@ -15,14 +13,6 @@ 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,
|
||||
@@ -41,12 +31,6 @@ 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,
|
||||
@@ -64,7 +48,6 @@ 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: "* * * * *" },
|
||||
@@ -77,6 +60,30 @@ 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"),
|
||||
@@ -505,60 +512,25 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</FormLabel>
|
||||
<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>
|
||||
<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>
|
||||
<FormDescription>
|
||||
Optional: Choose a timezone for the schedule execution time
|
||||
</FormDescription>
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -190,9 +190,7 @@ export const ShowProjects = () => {
|
||||
Create and manage your projects
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
{(auth?.role === "owner" ||
|
||||
auth?.role === "admin" ||
|
||||
auth?.canCreateProjects) && (
|
||||
{(auth?.role === "owner" || auth?.canCreateProjects) && (
|
||||
<div className="">
|
||||
<HandleProject />
|
||||
</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",
|
||||
"test": "vitest --config __test__/vitest.config.ts volume-backups",
|
||||
"generate:openapi": "tsx -r dotenv/config scripts/generate-openapi.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -68,40 +68,6 @@ 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({
|
||||
|
||||
@@ -146,17 +146,17 @@ export const getContainerByName = (name: string): Promise<ContainerInfo> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Docker commands sent using this method are held in a hold when Docker is busy.
|
||||
* Docker commands passed through this method are held during Docker's build or pull process.
|
||||
*
|
||||
* 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 [A-Za-z]" | grep -v grep)
|
||||
PROCESSES=$(ps aux | grep -E "docker build|docker pull" | grep -v grep)
|
||||
|
||||
if [ -z "$PROCESSES" ]; then
|
||||
echo "Docker is idle. Starting execution..."
|
||||
@@ -169,15 +169,14 @@ done
|
||||
|
||||
${exec}
|
||||
|
||||
echo "Execution completed."
|
||||
`;
|
||||
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) => {
|
||||
@@ -258,48 +257,24 @@ 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) => {
|
||||
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 {}
|
||||
}
|
||||
await cleanupContainers(serverId);
|
||||
await cleanupImages(serverId);
|
||||
await cleanupBuilders(serverId);
|
||||
await cleanupSystem(serverId);
|
||||
};
|
||||
|
||||
export const cleanupAllBackground = async (serverId?: string) => {
|
||||
Promise.allSettled(
|
||||
(
|
||||
Object.entries(cleanupCommands) as [
|
||||
keyof typeof cleanupCommands,
|
||||
string,
|
||||
][]
|
||||
)
|
||||
.filter(([key]) => !excludedCleanupAllCommands.includes(key))
|
||||
.map(async ([, command]) => {
|
||||
Object.values(cleanupCommands).map(async (command) => {
|
||||
try {
|
||||
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,6 +27,9 @@ 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