diff --git a/MySQLMySQLMigrator.jsx b/MySQLMySQLMigrator.jsx
new file mode 100644
index 0000000..3b3c1bb
--- /dev/null
+++ b/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
+
+
+
+
+
+ {/* Migration Button */}
+
+
+
+
+ {/* Progress */}
+
+
Progress
+
+
+ {JSON.stringify(progress, null, 2)}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/PostgreSQLPostgreSQLMigrator.jsx b/PostgreSQLPostgreSQLMigrator.jsx
new file mode 100644
index 0000000..53b5671
--- /dev/null
+++ b/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
+
+
+
+
+
+ {/* Migration Button */}
+
+
+
+
+ {/* Progress */}
+
+
Progress
+
+
+ {JSON.stringify(progress, null, 2)}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/PostgreSQLS3Migrator.jsx b/PostgreSQLS3Migrator.jsx
new file mode 100644
index 0000000..50ee824
--- /dev/null
+++ b/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
+
+
+
+
+ {/* S3 Target */}
+
+
+
+ {/* Migration Button */}
+
+
+
+
+ {/* Progress */}
+
+
Progress
+
+
+ {JSON.stringify(progress, null, 2)}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/S3S3Migrator.jsx b/S3S3Migrator.jsx
new file mode 100644
index 0000000..fdbd6f4
--- /dev/null
+++ b/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 */}
+
+
+
+ {/* Migration Button */}
+
+
+
+
+ {/* Progress */}
+
+
Progress
+
+
+ {JSON.stringify(progress, null, 2)}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..2ac97b6
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,137 @@
+@reference "tailwindcss";
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* ===========================
+ LIGHT MODE STYLES
+ =========================== */
+:root {
+ --bg-color: #f4f6f8;
+ --container-bg: white;
+ --text-color: #333;
+ --input-border: #ccc;
+ --button-bg: #2563eb;
+ --button-color: white;
+ --link-color: #2563eb;
+ --pre-bg: #111;
+ --pre-color: #0f0;
+}
+
+/* ===========================
+ DARK MODE STYLES
+ =========================== */
+.dark {
+ --bg-color: #000000;
+ --container-bg: #0d0d0d;
+ --text-color: #ffffff;
+ --input-border: #ff0000;
+ --button-bg: #b91c1c;
+ --button-color: #ffffff;
+ --link-color: #ef4444;
+ --pre-bg: #000000;
+ --pre-color: #ff0000;
+}
+
+/* ===========================
+ BODY STYLES
+ =========================== */
+body {
+ margin: 0;
+ font-family: Arial, sans-serif;
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ transition: background-color 0.3s, color 0.3s;
+}
+
+.container {
+ background: var(--container-bg);
+ padding: 20px;
+ max-width: 800px;
+ margin: auto;
+ border-radius: 8px;
+ transition: background-color 0.3s;
+}
+
+/* ===========================
+ CUSTOM COMPONENTS
+ =========================== */
+.glow-red {
+ box-shadow: 0 0 10px rgba(239, 68, 68, 0.5), 0 0 20px rgba(239, 68, 68, 0.3);
+}
+
+.custom-input {
+ @apply 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;
+}
+
+.custom-button {
+ @apply bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white font-medium py-3 px-4 rounded-lg transition-all duration-200 shadow-lg hover:shadow-xl disabled:cursor-not-allowed;
+}
+
+.custom-select {
+ @apply 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;
+}
+
+.custom-label {
+ @apply block text-sm font-medium mb-1 text-gray-700 dark:text-white;
+}
+
+body {
+ margin: 0;
+ font-family: Arial, sans-serif;
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ transition: background-color 0.3s, color 0.3s;
+}
+
+.container {
+ background: var(--container-bg);
+ padding: 20px;
+ max-width: 800px;
+ margin: auto;
+ border-radius: 8px;
+ transition: background-color 0.3s;
+}
+
+select {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ border: 1px solid var(--input-border);
+ border-radius: 4px;
+ background-color: var(--container-bg);
+ color: var(--text-color);
+ transition: border-color 0.3s, background-color 0.3s, color 0.3s;
+}
+select.multi-select {
+ height: 120px;
+}
+button {
+ background: var(--button-bg);
+ color: var(--button-color);
+ border: none;
+ width: 100%;
+ padding: 10px;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: background-color 0.3s;
+}
+
+button:hover {
+ opacity: 0.9;
+}
+
+a {
+ color: var(--link-color);
+ text-decoration: none;
+}
+
+pre {
+ background: var(--pre-bg);
+ color: var(--pre-color);
+ padding: 10px;
+ height: 140px;
+ overflow: auto;
+ border-radius: 4px;
+}
\ No newline at end of file