رفع الملفات إلى "/"
هذا الالتزام موجود في:
180
PostgreSQLS3Migrator.jsx
Normal file
180
PostgreSQLS3Migrator.jsx
Normal file
@@ -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 (
|
||||
<div className="space-y-8">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
||||
PostgreSQL → S3 Migration
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Export PostgreSQL database to a compressed file in S3.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{/* PostgreSQL Database */}
|
||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-gray-100">
|
||||
PostgreSQL Database
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Host</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g., localhost"
|
||||
value={srcHost}
|
||||
onChange={e => 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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g., postgres"
|
||||
value={srcUser}
|
||||
onChange={e => 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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Enter password"
|
||||
value={srcPass}
|
||||
onChange={e => 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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Database Name</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g., mydatabase"
|
||||
value={dbName}
|
||||
onChange={e => 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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* S3 Target */}
|
||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-gray-100">
|
||||
S3 Target
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">S3 Bucket</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g., my-bucket"
|
||||
value={destBucket}
|
||||
onChange={e => 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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">AWS Access Key</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter AWS Access Key"
|
||||
value={awsAccess}
|
||||
onChange={e => 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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">AWS Secret Key</label>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Enter AWS Secret Key"
|
||||
value={awsSecret}
|
||||
onChange={e => 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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Migration Button */}
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={handleMigration}
|
||||
disabled={!srcHost || !srcUser || !dbName || !destBucket || !awsAccess || !awsSecret}
|
||||
className="bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white font-medium py-3 px-8 rounded-md transition-colors text-lg"
|
||||
>
|
||||
Start Migration
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Progress */}
|
||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
|
||||
<h4 className="text-lg font-semibold mb-4 text-gray-900 dark:text-gray-100">Progress</h4>
|
||||
<div className="bg-gray-200 dark:bg-gray-600 rounded-full h-4 mb-4">
|
||||
<div
|
||||
className="bg-blue-600 h-4 rounded-full transition-all duration-300"
|
||||
style={{ width: `${progress.percent}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<pre className="bg-gray-800 text-green-400 p-4 rounded-md overflow-auto text-sm">
|
||||
{JSON.stringify(progress, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
المرجع في مشكلة جديدة
حظر مستخدم