From ab1ba55fd0b5e76f4e8b733003f0646bfb015834 Mon Sep 17 00:00:00 2001 From: mustafa_98 Date: Fri, 16 Jan 2026 22:58:55 +0000 Subject: [PATCH] =?UTF-8?q?=D8=B1=D9=81=D8=B9=20=D8=A7=D9=84=D9=85=D9=84?= =?UTF-8?q?=D9=81=D8=A7=D8=AA=20=D8=A5=D9=84=D9=89=20"frontend/src/compone?= =?UTF-8?q?nts"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/MySQLMySQLMigrator.jsx | 206 ++++++++++++++++ .../PostgreSQLPostgreSQLMigrator.jsx | 208 ++++++++++++++++ .../src/components/PostgreSQLS3Migrator.jsx | 180 ++++++++++++++ frontend/src/components/S3S3Migrator.jsx | 227 ++++++++++++++++++ 4 files changed, 821 insertions(+) create mode 100644 frontend/src/components/MySQLMySQLMigrator.jsx create mode 100644 frontend/src/components/PostgreSQLPostgreSQLMigrator.jsx create mode 100644 frontend/src/components/PostgreSQLS3Migrator.jsx create mode 100644 frontend/src/components/S3S3Migrator.jsx diff --git a/frontend/src/components/MySQLMySQLMigrator.jsx b/frontend/src/components/MySQLMySQLMigrator.jsx new file mode 100644 index 0000000..3b3c1bb --- /dev/null +++ b/frontend/src/components/MySQLMySQLMigrator.jsx @@ -0,0 +1,206 @@ +import { useState } from "react"; +import { getSchemas, getTables, startMigration, getProgress } from "../api"; + +export default function MySQLMySQLMigrator() { + const [srcHost, setSrcHost] = useState(""); + const [srcUser, setSrcUser] = useState(""); + const [srcPass, setSrcPass] = useState(""); + const [schemas, setSchemas] = useState([]); + const [selectedSchemas, setSelectedSchemas] = useState([]); + const [destHost, setDestHost] = useState(""); + const [destUser, setDestUser] = useState(""); + const [destPass, setDestPass] = useState(""); + const [progress, setProgress] = useState({ percent: 0, message: "Waiting to start..." }); + const [loading, setLoading] = useState(false); + + const loadSchemas = async () => { + setLoading(true); + try { + const data = await getSchemas("mysql_mysql", { host: srcHost, user: srcUser, pass: srcPass }); + setSchemas(data.schemas || []); + if (data.error) { + alert("Error loading databases: " + data.error); + } + } catch { + setSchemas([]); + } finally { + setLoading(false); + } + }; + + const handleMigration = async () => { + const payload = { + SRC_HOST: srcHost, + SRC_USER: srcUser, + SRC_PASS: srcPass, + DEST_HOST: destHost, + DEST_USER: destUser, + DEST_PASS: destPass, + DATABASES: selectedSchemas + }; + try { + const data = await startMigration("mysql_mysql", payload); + if (data.success) { + pollProgress(); + } else { + alert("Migration failed: " + (data.error || "Unknown error")); + } + } catch (error) { + console.error("Error starting migration:", error); + alert("Connection error: " + error.message); + } + }; + + const pollProgress = async () => { + try { + const data = await getProgress("mysql_mysql"); + setProgress(data); + if (data.status === "error") { + alert("Migration error: " + (data.message || "Unknown error")); + } else if (data.percent < 100 && data.status !== "completed") { + setTimeout(pollProgress, 2000); + } + } catch (error) { + console.error("Error polling progress:", error); + setTimeout(pollProgress, 2000); + } + }; + + return ( +
+
+

+ MySQL → MySQL Migration +

+

+ Migrate complete MySQL databases . +

+
+ +
+ {/* Source Database */} +
+

+ Source Database +

+
+
+ + setSrcHost(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setSrcUser(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setSrcPass(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+ + {schemas.length > 0 && ( +
+ + +
+ )} +
+
+ + {/* Target Database */} +
+

+ Target Database +

+
+
+ + setDestHost(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setDestUser(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setDestPass(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+
+
+ + {/* Migration Button */} +
+ +
+ + {/* Progress */} +
+

Progress

+
+
+
+
+          {JSON.stringify(progress, null, 2)}
+        
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/PostgreSQLPostgreSQLMigrator.jsx b/frontend/src/components/PostgreSQLPostgreSQLMigrator.jsx new file mode 100644 index 0000000..53b5671 --- /dev/null +++ b/frontend/src/components/PostgreSQLPostgreSQLMigrator.jsx @@ -0,0 +1,208 @@ +import { useState } from "react"; +import { getSchemas, getTables, startMigration, getProgress } from "../api"; + +export default function PostgreSQLPostgreSQLMigrator() { + const [srcHost, setSrcHost] = useState(""); + const [srcUser, setSrcUser] = useState(""); + const [srcPass, setSrcPass] = useState(""); + const [schemas, setSchemas] = useState([]); + const [selectedSchemas, setSelectedSchemas] = useState([]); + const [destHost, setDestHost] = useState(""); + const [destUser, setDestUser] = useState(""); + const [destPass, setDestPass] = useState(""); + const [progress, setProgress] = useState({ percent: 0, message: "Waiting to start..." }); + const [loading, setLoading] = useState(false); + + const loadSchemas = async () => { + setLoading(true); + try { + const data = await getSchemas("psql_psql", { host: srcHost, user: srcUser, pass: srcPass }); + setSchemas(data.schemas || []); + if (data.error) { + alert("Error loading schemas: " + data.error); + } + } catch { + setSchemas([]); + } finally { + setLoading(false); + } + }; + + const handleMigration = async () => { + const payload = { + DB_HOST: srcHost, + DB_USER: srcUser, + DB_PASS: srcPass, + DB_NAME: selectedSchemas[0] || "", + DEST_HOST: destHost, + DEST_USER: destUser, + DEST_PASS: destPass, + DEST_NAME: selectedSchemas[0] || "", + ONLY_SCHEMAS: selectedSchemas.join(",") + }; + try { + const data = await startMigration("psql_psql", payload); + if (data.success) { + pollProgress(); + } else { + alert("Migration failed: " + (data.error || "Unknown error")); + } + } catch (error) { + console.error("Error starting migration:", error); + alert("Connection error: " + error.message); + } + }; + + const pollProgress = async () => { + try { + const data = await getProgress("psql_psql"); + setProgress(data); + if (data.status === "error") { + alert("Migration error: " + (data.message || "Unknown error")); + } else if (data.percent < 100 && data.status !== "completed") { + setTimeout(pollProgress, 2000); + } + } catch (error) { + console.error("Error polling progress:", error); + setTimeout(pollProgress, 2000); + } + }; + + return ( +
+
+

+ PostgreSQL → PostgreSQL Migration +

+

+ Migrate PostgreSQL schemas and tables. +

+
+ +
+ {/* Source Database */} +
+

+ Source Database +

+
+
+ + setSrcHost(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500" + /> +
+
+ + setSrcUser(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500" + /> +
+
+ + setSrcPass(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500" + /> +
+ + {schemas.length > 0 && ( +
+ + +
+ )} +
+
+ + {/* Target Database */} +
+

+ Target Database +

+
+
+ + setDestHost(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500" + /> +
+
+ + setDestUser(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500" + /> +
+
+ + setDestPass(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500" + /> +
+
+
+
+ + {/* Migration Button */} +
+ +
+ + {/* Progress */} +
+

Progress

+
+
+
+
+          {JSON.stringify(progress, null, 2)}
+        
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/PostgreSQLS3Migrator.jsx b/frontend/src/components/PostgreSQLS3Migrator.jsx new file mode 100644 index 0000000..50ee824 --- /dev/null +++ b/frontend/src/components/PostgreSQLS3Migrator.jsx @@ -0,0 +1,180 @@ +import { useState } from "react"; +import { startMigration, getProgress } from "../api"; + +export default function PostgreSQLS3Migrator() { + const [srcHost, setSrcHost] = useState(""); + const [srcUser, setSrcUser] = useState(""); + const [srcPass, setSrcPass] = useState(""); + const [dbName, setDbName] = useState(""); + const [destBucket, setDestBucket] = useState(""); + const [awsAccess, setAwsAccess] = useState(""); + const [awsSecret, setAwsSecret] = useState(""); + const [progress, setProgress] = useState({ percent: 0, message: "Waiting to start..." }); + + const handleMigration = async () => { + const payload = { + DB_HOST: srcHost, + DB_USER: srcUser, + DB_PASS: srcPass, + DB_NAME: dbName, + DEST_BUCKET: destBucket, + DEST_ENDPOINT: "https://s3.amazonaws.com", + DEST_ACCESS: awsAccess, + DEST_SECRET: awsSecret + }; + try { + const data = await startMigration("psql_s3", payload); + if (data.success) { + pollProgress(); + } else { + alert("Migration failed: " + (data.error || "Unknown error")); + } + } catch (error) { + console.error("Error starting migration:", error); + alert("Connection error: " + error.message); + } + }; + + const pollProgress = async () => { + try { + const data = await getProgress("psql_s3"); + setProgress(data); + if (data.status === "error") { + alert("Migration error: " + (data.message || "Unknown error")); + } else if (data.percent < 100 && data.status !== "completed") { + setTimeout(pollProgress, 2000); + } + } catch (error) { + console.error("Error polling progress:", error); + setTimeout(pollProgress, 2000); + } + }; + + return ( +
+
+

+ PostgreSQL → S3 Migration +

+

+ Export PostgreSQL database to a compressed file in S3. +

+
+ +
+ {/* PostgreSQL Database */} +
+

+ PostgreSQL Database +

+
+
+ + setSrcHost(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setSrcUser(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setSrcPass(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setDbName(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+
+ + {/* S3 Target */} +
+

+ S3 Target +

+
+
+ + setDestBucket(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setAwsAccess(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setAwsSecret(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+
+
+ + {/* Migration Button */} +
+ +
+ + {/* Progress */} +
+

Progress

+
+
+
+
+          {JSON.stringify(progress, null, 2)}
+        
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/S3S3Migrator.jsx b/frontend/src/components/S3S3Migrator.jsx new file mode 100644 index 0000000..fdbd6f4 --- /dev/null +++ b/frontend/src/components/S3S3Migrator.jsx @@ -0,0 +1,227 @@ +import { useState } from "react"; +import { listBuckets, startMigration, getProgress } from "../api"; + +export default function S3S3Migrator() { + const [awsAccess, setAwsAccess] = useState(""); + const [awsSecret, setAwsSecret] = useState(""); + const [awsRegion, setAwsRegion] = useState("us-east-1"); + const [srcBucket, setSrcBucket] = useState(""); + const [destBucket, setDestBucket] = useState(""); + const [destEndpoint, setDestEndpoint] = useState("https://s3.amazonaws.com"); + const [destAccess, setDestAccess] = useState(""); + const [destSecret, setDestSecret] = useState(""); + const [buckets, setBuckets] = useState([]); + const [progress, setProgress] = useState({ percent: 0, message: "Waiting to start..." }); + const [loading, setLoading] = useState(false); + + const loadBuckets = async () => { + if (!awsAccess || !awsSecret) { + alert("Please enter AWS Access Key and Secret Key first"); + return; + } + setLoading(true); + try { + const data = await listBuckets({ AWS_SRC_ACCESS_KEY: awsAccess, AWS_SRC_SECRET_KEY: awsSecret, AWS_SRC_REGION: awsRegion }); + if (data.success) { + setBuckets(data.buckets); + console.log("Buckets loaded:", data.buckets); + } else { + alert("Failed to load buckets: " + (data.error || "Unknown error")); + setBuckets([]); + } + } catch (error) { + console.error("Error loading buckets:", error); + alert("Connection error: " + error.message); + setBuckets([]); + } finally { + setLoading(false); + } + }; + + const handleMigration = async () => { + const payload = { + AWS_SRC_ACCESS_KEY: awsAccess, + AWS_SRC_SECRET_KEY: awsSecret, + AWS_SRC_REGION: awsRegion, + AWS_SRC_BUCKET: srcBucket, + CUMIN_DEST_ACCESS_KEY: destAccess, + CUMIN_DEST_SECRET_KEY: destSecret, + CUMIN_DEST_ENDPOINT: destEndpoint, + CUMIN_DEST_BUCKET: destBucket + }; + try { + const data = await startMigration("s3_s3", payload); + if (data.success) { + pollProgress(); + } else { + alert("Migration failed: " + (data.error || "Unknown error")); + } + } catch (error) { + console.error("Error starting migration:", error); + alert("Connection error: " + error.message); + } + }; + + const pollProgress = async () => { + try { + const data = await getProgress("s3_s3"); + setProgress(data); + if (data.status === "error") { + alert("Migration error: " + (data.message || "Unknown error")); + } else if (data.percent < 100 && data.status !== "completed") { + setTimeout(pollProgress, 2000); + } + } catch (error) { + console.error("Error polling progress:", error); + setTimeout(pollProgress, 2000); + } + }; + + return ( +
+
+

+ S3 → S3 Migration +

+

+ Migrate files between S3 buckets. +

+
+ +
+ {/* Source S3 */} +
+

+ Source S3 +

+
+
+ + setAwsAccess(e.target.value)} + className="custom-input" + /> +
+
+ + setAwsSecret(e.target.value)} + className="custom-input" + /> +
+
+ + setAwsRegion(e.target.value)} + className="custom-input" + /> +
+ + {buckets.length > 0 && ( +
+ + +
+ )} +
+
+ + {/* Target S3 */} +
+

+ Target S3 +

+
+
+ + setDestAccess(e.target.value)} + className="custom-input" + /> +
+
+ + setDestSecret(e.target.value)} + className="custom-input" + /> +
+
+ + setDestEndpoint(e.target.value)} + className="custom-input" + /> +
+
+ + setDestBucket(e.target.value)} + className="custom-input" + /> +
+
+
+
+ + {/* Migration Button */} +
+ +
+ + {/* Progress */} +
+

Progress

+
+
+
+
+          {JSON.stringify(progress, null, 2)}
+        
+
+
+ ); +} \ No newline at end of file