حذف PostgreSQLPostgreSQLMigrator.jsx
هذا الالتزام موجود في:
@@ -1,208 +0,0 @@
|
|||||||
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 (
|
|
||||||
<div className="space-y-8">
|
|
||||||
<div className="text-center">
|
|
||||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
|
||||||
PostgreSQL → PostgreSQL Migration
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
|
||||||
Migrate PostgreSQL schemas and tables.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-8">
|
|
||||||
{/* Source Database */}
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg border border-gray-200 dark:border-red-600">
|
|
||||||
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
|
|
||||||
Source Database
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-white">Host</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="e.g., localhost"
|
|
||||||
value={srcHost}
|
|
||||||
onChange={e => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-white">Username</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="e.g., postgres"
|
|
||||||
value={srcUser}
|
|
||||||
onChange={e => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-white">Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="Enter password"
|
|
||||||
value={srcPass}
|
|
||||||
onChange={e => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={loadSchemas}
|
|
||||||
disabled={loading}
|
|
||||||
className="w-full 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"
|
|
||||||
>
|
|
||||||
{loading ? "Loading..." : "Load Schemas"}
|
|
||||||
</button>
|
|
||||||
{schemas.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-white">Select Schemas</label>
|
|
||||||
<select
|
|
||||||
multiple
|
|
||||||
value={selectedSchemas}
|
|
||||||
onChange={e => setSelectedSchemas([...e.target.selectedOptions].map(o => o.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 h-32"
|
|
||||||
>
|
|
||||||
{schemas.map(s => <option key={s} value={s}>{s}</option>)}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Target Database */}
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg border border-gray-200 dark:border-red-600">
|
|
||||||
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
|
|
||||||
Target Database
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-white">Host</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="e.g., localhost"
|
|
||||||
value={destHost}
|
|
||||||
onChange={e => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-white">Username</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="e.g., postgres"
|
|
||||||
value={destUser}
|
|
||||||
onChange={e => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-white">Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="Enter password"
|
|
||||||
value={destPass}
|
|
||||||
onChange={e => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Migration Button */}
|
|
||||||
<div className="text-center">
|
|
||||||
<button
|
|
||||||
onClick={handleMigration}
|
|
||||||
disabled={selectedSchemas.length === 0}
|
|
||||||
className="bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white font-medium py-3 px-8 rounded-lg transition-all duration-200 shadow-lg hover:shadow-xl disabled:cursor-not-allowed text-lg"
|
|
||||||
>
|
|
||||||
Start Migration
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Progress */}
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg border border-gray-200 dark:border-red-600">
|
|
||||||
<h4 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Progress</h4>
|
|
||||||
<div className="bg-gray-200 dark:bg-gray-600 rounded-full h-4 mb-4">
|
|
||||||
<div
|
|
||||||
className="bg-red-600 h-4 rounded-full transition-all duration-300"
|
|
||||||
style={{ width: `${progress.percent}%` }}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<pre className="bg-gray-800 text-red-400 p-4 rounded-lg overflow-auto text-sm border border-gray-600">
|
|
||||||
{JSON.stringify(progress, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم