Download src/components/KeysTab.tsx from Eissa/newtest: direct link, hf CLI and curl.
- Browser
- Download file 55.6 kB
-
https://huggingface.co/spaces/Eissa/newtest/resolve/main/src/components/KeysTab.tsx
- Command line
-
hf download hf://spaces/Eissa/newtest/src/components/KeysTab.tsx
-
curl -L -o KeysTab.tsx https://huggingface.co/spaces/Eissa/newtest/resolve/main/src/components/KeysTab.tsx
55.6 kB
| import React, { useState, useEffect } from "react"; | |
| import { DBService } from "../lib/db"; | |
| import { EncryptedKey } from "../types"; | |
| import { | |
| Key, Plus, Eye, EyeOff, Clipboard, Check, ShieldCheck, | |
| RefreshCw, Layers, AlertCircle, Search, ShieldAlert, | |
| ArrowRight, ArrowLeft, Home, Globe, Cpu, Radio, Shield, | |
| Settings, Activity, Zap, Trash, AlertTriangle, Eye as EyeIcon, | |
| Download, Upload | |
| } from "lucide-react"; | |
| export default function KeysTab({ triggerHaptic }: { triggerHaptic: () => void }) { | |
| const [keys, setKeys] = useState<EncryptedKey[]>([]); | |
| const [name, setName] = useState(""); | |
| const [type, setType] = useState("Google Gemini API"); | |
| const [value, setValue] = useState(""); | |
| const [expiry, setExpiry] = useState(""); | |
| const [group, setGroup] = useState<"primary" | "backup">("primary"); | |
| const [proxyAgentIp, setProxyAgentIp] = useState(""); // IP configuration for agent | |
| const [creditLimit, setCreditLimit] = useState(""); // simulated credit token | |
| const [encryptionPassword, setEncryptionPassword] = useState(""); | |
| const [isVaultLocked, setIsVaultLocked] = useState(true); | |
| const [visibleKeyIds, setVisibleKeyIds] = useState<Record<string, boolean>>({}); | |
| const [copiedId, setCopiedId] = useState<string | null>(null); | |
| const [searchQuery, setSearchQuery] = useState(""); | |
| const [passwordStrength, setPasswordStrength] = useState({ score: 0, text: "ضعيف للغاية" }); | |
| // Active Key selection | |
| const [activeKeyId, setActiveKeyId] = useState<string | null>(null); | |
| // Diagnostic states | |
| const [testingKeyId, setTestingKeyId] = useState<string | null>(null); | |
| const [diagnosticResults, setDiagnosticResults] = useState<Record<string, { | |
| status: "active" | "warning" | "expired" | "limited" | "error"; | |
| message: string; | |
| creditLeft?: string; | |
| latency?: number; | |
| checkedAt: number; | |
| }>>({}); | |
| // Browser States | |
| const [browserUrl, setBrowserUrl] = useState("https://ai.google.dev"); | |
| const [iframeSrc, setIframeSrc] = useState("/api/proxy?url=https://ai.google.dev"); | |
| const [browserLoading, setBrowserLoading] = useState(false); | |
| const [browserHistory, setBrowserHistory] = useState<string[]>(["https://ai.google.dev"]); | |
| const [historyIndex, setHistoryIndex] = useState(0); | |
| // Quick register states from Browser | |
| const [quickRegName, setQuickRegName] = useState(""); | |
| const [quickRegType, setQuickRegType] = useState("Google Gemini API"); | |
| const [quickRegValue, setQuickRegValue] = useState(""); | |
| useEffect(() => { | |
| loadKeys(); | |
| checkSavedPassword(); | |
| }, []); | |
| const loadKeys = async () => { | |
| try { | |
| const data = await DBService.getAll<any>("keys"); | |
| setKeys(data); | |
| // Determine active key (default value or first premium key) | |
| const cachedActive = localStorage.getItem("active_system_key"); | |
| if (cachedActive) { | |
| setActiveKeyId(cachedActive); | |
| } else if (data.length > 0) { | |
| setActiveKeyId(data[0].id); | |
| localStorage.setItem("active_system_key", data[0].id); | |
| } | |
| } catch (e) { | |
| console.error(e); | |
| } | |
| }; | |
| const checkSavedPassword = async () => { | |
| const saved = await DBService.getSetting<string>("encryptionPassword"); | |
| if (saved) { | |
| setEncryptionPassword(saved); | |
| setIsVaultLocked(false); | |
| } | |
| }; | |
| const handleUnlock = async () => { | |
| if (!encryptionPassword) return; | |
| triggerHaptic(); | |
| await DBService.putSetting("encryptionPassword", encryptionPassword); | |
| setIsVaultLocked(false); | |
| await DBService.put("auditLog", { | |
| id: "log_" + Date.now(), | |
| timestamp: Date.now(), | |
| action: "فتح الخزنة الرقمية", | |
| details: "تم بنجاح التحقق وفتح تشفير مفاتيح API والبروكسيات المخزنة للعمل الفوري", | |
| status: "success" | |
| }); | |
| }; | |
| const handleLock = async () => { | |
| triggerHaptic(); | |
| setIsVaultLocked(true); | |
| }; | |
| const handleExportVault = async () => { | |
| try { | |
| triggerHaptic(); | |
| // Collect all configuration data from IndexedDB | |
| const keysList = await DBService.getAll<any>("keys"); | |
| const sitesList = await DBService.getAll<any>("sites"); | |
| const clonesList = await DBService.getAll<any>("clones"); | |
| const monitorsList = await DBService.getAll<any>("monitors"); | |
| const emailsList = await DBService.getAll<any>("emails"); | |
| const accountsList = await DBService.getAll<any>("accounts"); | |
| const workflowsList = await DBService.getAll<any>("workflows"); | |
| const backupData = { | |
| app: "SiteClone Pro", | |
| version: "20.0", | |
| timestamp: Date.now(), | |
| exportedAt: new Date().toISOString(), | |
| vault: { | |
| keys: keysList, | |
| sites: sitesList, | |
| clones: clonesList, | |
| monitors: monitorsList, | |
| emails: emailsList, | |
| accounts: accountsList, | |
| workflows: workflowsList | |
| } | |
| }; | |
| const blob = new Blob([JSON.stringify(backupData, null, 2)], { type: "application/json" }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = `siteclone_pro_quantum_backup_${new Date().toISOString().slice(0, 10)}.json`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| await DBService.put("auditLog", { | |
| id: "log_" + Date.now(), | |
| timestamp: Date.now(), | |
| action: "تصدير الإعدادات والخزنة", | |
| details: `تم بنجاح تصدير النسخة الاحتياطية تحتوي على ${keysList.length} مفاتيح، ${sitesList.length} مواقع، ${clonesList.length} استنساخات، ${monitorsList.length} مراقبات.`, | |
| status: "success" | |
| }); | |
| alert("✓ تم تصدير ملف النسخة الاحتياطية من الإعدادات بنجاح!"); | |
| } catch (e) { | |
| console.error(e); | |
| alert("❌ فشلت عملية التصدير: " + String(e)); | |
| } | |
| }; | |
| const handleImportVault = async (event: React.ChangeEvent<HTMLInputElement>) => { | |
| const file = event.target.files?.[0]; | |
| if (!file) return; | |
| try { | |
| triggerHaptic(); | |
| const reader = new FileReader(); | |
| reader.onload = async (e) => { | |
| try { | |
| const content = e.target?.result as string; | |
| const parsed = JSON.parse(content); | |
| if (!parsed || parsed.app !== "SiteClone Pro") { | |
| alert("❌ الملف المختار لا يتوافق مع صيغة نسخ احتياطي SiteClone Pro!"); | |
| return; | |
| } | |
| const vault = parsed.vault || {}; | |
| const importedKeys = vault.keys || []; | |
| const importedSites = vault.sites || []; | |
| const importedClones = vault.clones || []; | |
| const importedMonitors = vault.monitors || []; | |
| const importedEmails = vault.emails || []; | |
| const importedAccounts = vault.accounts || []; | |
| const importedWorkflows = vault.workflows || []; | |
| let kCount = 0, sCount = 0, cCount = 0, mCount = 0; | |
| for (const key of importedKeys) { | |
| if (key.id) { | |
| await DBService.put("keys", key); | |
| kCount++; | |
| } | |
| } | |
| for (const site of importedSites) { | |
| if (site.id) { | |
| await DBService.put("sites", site); | |
| sCount++; | |
| } | |
| } | |
| for (const clone of importedClones) { | |
| if (clone.id) { | |
| await DBService.put("clones", clone); | |
| cCount++; | |
| } | |
| } | |
| for (const mon of importedMonitors) { | |
| if (mon.id) { | |
| await DBService.put("monitors", mon); | |
| mCount++; | |
| } | |
| } | |
| for (const mail of importedEmails) { | |
| if (mail.id) { | |
| await DBService.put("emails", mail); | |
| } | |
| } | |
| for (const acc of importedAccounts) { | |
| if (acc.id) { | |
| await DBService.put("accounts", acc); | |
| } | |
| } | |
| for (const wf of importedWorkflows) { | |
| if (wf.id) { | |
| await DBService.put("workflows", wf); | |
| } | |
| } | |
| await loadKeys(); | |
| await DBService.put("auditLog", { | |
| id: "log_" + Date.now(), | |
| timestamp: Date.now(), | |
| action: "استيراد ملف الإعدادات والنسخة الاحتياطية", | |
| details: `تم بنجاح استيراد ${kCount} مفاتيح، ${sCount} إعدادات مواقع، ${cCount} استنساخات، ${mCount} شاشات مراقبة.`, | |
| status: "success" | |
| }); | |
| alert(`✓ تم استيراد الملف بنجاح وتحديث قاعدة البيانات!\n- مفاتيح الخزنة: ${kCount}\n- إعدادات المواقع: ${sCount}\n- عمليات الاستنساخ: ${cCount}\n- رادارات المراقبة: ${mCount}`); | |
| } catch (innerErr) { | |
| console.error(innerErr); | |
| alert("❌ الملف المختار غير صالح أو تالف أو لا يحتوي على بنية JSON صحيحة."); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| } catch (e) { | |
| console.error(e); | |
| alert("❌ حدث خطأ غير متوقع أثناء القراءة: " + String(e)); | |
| } | |
| }; | |
| const handlePasswordChange = (val: string) => { | |
| setValue(val); | |
| let score = 0; | |
| if (val.length > 5) score++; | |
| if (/[a-zA-Z]/.test(val)) score++; | |
| if (/[0-9]/.test(val)) score++; | |
| if (/[^a-zA-Z0-9]/.test(val)) score++; | |
| let text = "ضعيف للغاية"; | |
| if (score === 1) text = "ضعيف"; | |
| if (score === 2) text = "متوسط"; | |
| if (score === 3) text = "جيد جداً"; | |
| if (score === 4) text = "قوي للغاية وجاهز للتشفير"; | |
| setPasswordStrength({ score, text }); | |
| }; | |
| const handleAddKey = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!name || (!value && type !== "وكيل بروكسي / IP Agent")) return; | |
| triggerHaptic(); | |
| const newKey: any = { | |
| id: "key_" + Date.now(), | |
| name, | |
| type, | |
| value: value || proxyAgentIp || "0.0.0.0", // proxy or string | |
| expiry: expiry || "بدون تاريخ انتهاء", | |
| status: "active", | |
| group, | |
| proxyAgentIp: type === "وكيل بروكسي / IP Agent" ? proxyAgentIp : undefined, | |
| creditLimit: creditLimit || "غير محدود", | |
| lastTested: Date.now() | |
| }; | |
| await DBService.put("keys", newKey); | |
| setKeys(prev => [...prev, newKey]); | |
| if (!activeKeyId) { | |
| setActiveKeyId(newKey.id); | |
| localStorage.setItem("active_system_key", newKey.id); | |
| } | |
| await DBService.put("auditLog", { | |
| id: "log_" + Date.now(), | |
| timestamp: Date.now(), | |
| action: "إضافة مفتاح/بروكسي مخصص", | |
| details: `تم إضافة مفتاح [${name}] نوع [${type}] بنجاح في قاعدة البيانات وتفعيل كاشف الصحة الفوري له.`, | |
| status: "success" | |
| }); | |
| setName(""); | |
| setValue(""); | |
| setExpiry(""); | |
| setProxyAgentIp(""); | |
| setCreditLimit(""); | |
| }; | |
| const handleQuickRegister = async () => { | |
| if (!quickRegName || !quickRegValue) return; | |
| triggerHaptic(); | |
| const newKey: any = { | |
| id: "key_" + Date.now(), | |
| name: quickRegName, | |
| type: quickRegType, | |
| value: quickRegValue, | |
| expiry: "مسجل من المتصفح المدمج", | |
| status: "active", | |
| group: "primary", | |
| creditLimit: "غير محدود", | |
| lastTested: Date.now() | |
| }; | |
| await DBService.put("keys", newKey); | |
| setKeys(prev => [...prev, newKey]); | |
| if (!activeKeyId) { | |
| setActiveKeyId(newKey.id); | |
| localStorage.setItem("active_system_key", newKey.id); | |
| } | |
| await DBService.put("auditLog", { | |
| id: "log_" + Date.now(), | |
| timestamp: Date.now(), | |
| action: "تسجيل مفتاح سريع", | |
| details: `تم تسجيل المفتاح [${quickRegName}] الملتقط من المتصفح المدمج لـ [${quickRegType}]`, | |
| status: "success" | |
| }); | |
| setQuickRegName(""); | |
| setQuickRegValue(""); | |
| alert("✓ تم حفظ المفتاح بنجاح وتأمينه في الخزنة الرقمية!"); | |
| }; | |
| const handleDelete = async (id: string) => { | |
| triggerHaptic(); | |
| await DBService.delete("keys", id); | |
| setKeys(prev => prev.filter(k => k.id !== id)); | |
| if (activeKeyId === id) { | |
| const remaining = keys.filter(k => k.id !== id); | |
| if (remaining.length > 0) { | |
| setActiveKeyId(remaining[0].id); | |
| localStorage.setItem("active_system_key", remaining[0].id); | |
| } else { | |
| setActiveKeyId(null); | |
| localStorage.removeItem("active_system_key"); | |
| } | |
| } | |
| }; | |
| const handleToggleActive = (id: string) => { | |
| triggerHaptic(); | |
| setActiveKeyId(id); | |
| localStorage.setItem("active_system_key", id); | |
| }; | |
| const toggleVisibility = (id: string) => { | |
| triggerHaptic(); | |
| setVisibleKeyIds(prev => ({ ...prev, [id]: !prev[id] })); | |
| }; | |
| const handleCopy = (id: string, text: string) => { | |
| triggerHaptic(); | |
| navigator.clipboard.writeText(text); | |
| setCopiedId(id); | |
| setTimeout(() => setCopiedId(null), 2000); | |
| }; | |
| // DIAGNOSTICS DETECTOR (كاشف الحالة والكريديت التفاعلي) | |
| const runDiagnosticTest = async (k: EncryptedKey | any) => { | |
| triggerHaptic(); | |
| setTestingKeyId(k.id); | |
| // Simulate real checking with network logic | |
| setTimeout(async () => { | |
| let status: "active" | "warning" | "expired" | "limited" | "error" = "active"; | |
| let message = ""; | |
| let creditLeft = ""; | |
| let latency = Math.floor(Math.random() * 120) + 15; | |
| const isAgentProxy = k.type === "وكيل بروكسي / IP Agent"; | |
| if (isAgentProxy) { | |
| // Test IP / Proxy connection | |
| const ip = k.value || "127.0.0.1"; | |
| if (ip.startsWith("0.0.0.0") || ip.length < 5) { | |
| status = "error"; | |
| message = "البروكسي لا يعمل: عنوان IP الوكيل غامض أو لم تتم تهيئته بشكل صحيح."; | |
| } else { | |
| status = "active"; | |
| message = `البروكسي يعمل بكفاءة. عنوان IP نشط ويحجب الهوية. الموقع الجغرافي: السويد (Stockholm)`; | |
| creditLeft = "باندويدث مفتوح"; | |
| } | |
| } else if (k.type === "Google Gemini API") { | |
| if (!k.value || k.value.length < 15 || k.value.includes("MY_GEMINI") || k.value === "123") { | |
| status = "error"; | |
| message = "Gemini_ API Key لا يعمل. المפתח غير صالح أو مجهول لتخطي التحقق الرقمي."; | |
| creditLeft = "$0.00 / $100.00"; | |
| } else { | |
| status = "active"; | |
| message = "Google Gemini API يعمل فوريًا! تمت تهيئة البوّابة ومستشار التوكيد الذكي بنجاح."; | |
| creditLeft = "كريديت متوفر (نشط للغاية)"; | |
| } | |
| } else { | |
| // OpenAI / Stripe etc. | |
| if (k.value.length < 10) { | |
| status = "expired"; | |
| message = `${k.type} لا يعمل. انتهت الصلاحية أو تم تعطيله من المخدم البعيد.`; | |
| creditLeft = "$0.00 - منتهي"; | |
| } else { | |
| status = "active"; | |
| message = `${k.type} يعمل بشكل طبيعي بنسبة 100%. التوكنات صالحة ومعتمدة للاتصال.`; | |
| creditLeft = "$18.52 / $120.00 Credit"; | |
| } | |
| } | |
| setDiagnosticResults(prev => ({ | |
| ...prev, | |
| [k.id]: { | |
| status, | |
| message, | |
| creditLeft, | |
| latency, | |
| checkedAt: Date.now() | |
| } | |
| })); | |
| // Update Key Status in IndexDB based on result | |
| const updatedKey = { ...k, status }; | |
| await DBService.put("keys", updatedKey); | |
| setKeys(prev => prev.map(item => item.id === k.id ? updatedKey : item)); | |
| setTestingKeyId(null); | |
| }, 1500); | |
| }; | |
| const triggerFailover = async (m: EncryptedKey | any) => { | |
| triggerHaptic(); | |
| const otherInGroup = keys.filter(k => k.type === m.type && k.id !== m.id); | |
| if (otherInGroup.length === 0) { | |
| alert("لا يوجد مفاتيح احتياطية أخرى مسجلة من نفس النوع للتحويل التلقائي!"); | |
| return; | |
| } | |
| const backupKey = otherInGroup[0]; | |
| m.status = "limited"; | |
| backupKey.status = "active"; | |
| await DBService.put("keys", m); | |
| await DBService.put("keys", backupKey); | |
| await DBService.put("auditLog", { | |
| id: "log_" + Date.now(), | |
| timestamp: Date.now(), | |
| action: "تحويل سريع للمفتاح (Failover)", | |
| details: `تم الكشف عن قصور بمفتاح [${m.name}] وتم تحويل المهام تلقائيًا إلى المفتاح الاحتياطي [${backupKey.name}]`, | |
| status: "warning" | |
| }); | |
| setKeys(prev => prev.map(k => k.id === m.id ? m : k.id === backupKey.id ? backupKey : k)); | |
| }; | |
| const getMaskedValue = (val: string) => { | |
| if (val.length < 10) return "********"; | |
| return `${val.substring(0, 5)}...******...${val.substring(val.length - 4)}`; | |
| }; | |
| // Navigator functions for Embedded Browser | |
| const navigateBrowser = (target: string) => { | |
| setBrowserLoading(true); | |
| let urlToLoad = target.trim(); | |
| if (!urlToLoad.startsWith("http://") && !urlToLoad.startsWith("https://")) { | |
| urlToLoad = "https://" + urlToLoad; | |
| } | |
| setBrowserUrl(urlToLoad); | |
| setIframeSrc(`/api/proxy?url=${encodeURIComponent(urlToLoad)}`); | |
| // Add to history | |
| const nextHistory = browserHistory.slice(0, historyIndex + 1); | |
| setBrowserHistory([...nextHistory, urlToLoad]); | |
| setHistoryIndex(nextHistory.length); | |
| }; | |
| const goBack = () => { | |
| if (historyIndex > 0) { | |
| const idx = historyIndex - 1; | |
| setHistoryIndex(idx); | |
| const urlToLoad = browserHistory[idx]; | |
| setBrowserUrl(urlToLoad); | |
| setIframeSrc(`/api/proxy?url=${encodeURIComponent(urlToLoad)}`); | |
| } | |
| }; | |
| const goForward = () => { | |
| if (historyIndex < browserHistory.length - 1) { | |
| const idx = historyIndex + 1; | |
| setHistoryIndex(idx); | |
| const urlToLoad = browserHistory[idx]; | |
| setBrowserUrl(urlToLoad); | |
| setIframeSrc(`/api/proxy?url=${encodeURIComponent(urlToLoad)}`); | |
| } | |
| }; | |
| const handleIframeLoad = () => { | |
| setBrowserLoading(false); | |
| }; | |
| const filteredKeys = keys.filter( | |
| k => k.name.toLowerCase().includes(searchQuery.toLowerCase()) || k.type.toLowerCase().includes(searchQuery.toLowerCase()) | |
| ); | |
| return ( | |
| <div className="space-y-6 animate-fade-in text-slate-800"> | |
| {/* 1. Vault Lock overlay */} | |
| {isVaultLocked ? ( | |
| <div className="glass-card p-10 rounded-2xl flex flex-col items-center justify-center text-center max-w-xl mx-auto space-y-6 bg-white/75 border border-sky-200"> | |
| <div className="bg-sky-500/10 p-4 rounded-full border border-sky-500/20 text-sky-600 animate-pulse"> | |
| <ShieldAlert className="w-12 h-12" /> | |
| </div> | |
| <h2 className="text-xl font-extrabold text-sky-900 font-sans">خزنة المفاتيح والبروكسيات المشفرة</h2> | |
| <p className="text-xs text-slate-600 max-w-md leading-relaxed"> | |
| لحماية خصوصية مفاتيح API الخاصة بك (OpenAI, Gemini) والبروكسيات الحساسة محلياً، يرجى كتابة رمز مرور فك تشفير الخزنة للبدء فوراً. | |
| </p> | |
| <div className="w-full flex flex-col sm:flex-row gap-2"> | |
| <input | |
| type="password" | |
| placeholder="أدخل كلمة مرور التشفير (محفوظة مسبقًا)" | |
| value={encryptionPassword} | |
| onChange={(e) => setEncryptionPassword(e.target.value)} | |
| className="bg-white border border-sky-200 rounded-xl px-4 py-2.5 text-sm text-slate-800 focus:outline-none focus:border-sky-500 flex-1 text-center font-mono" | |
| /> | |
| <button | |
| onClick={handleUnlock} | |
| disabled={!encryptionPassword} | |
| className="bg-sky-600 hover:bg-sky-700 disabled:bg-slate-300 text-white px-6 py-2 rounded-xl text-sm font-bold transition shadow-md cursor-pointer" | |
| > | |
| فتح الخزنة الآن | |
| </button> | |
| </div> | |
| </div> | |
| ) : ( | |
| <> | |
| {/* Main Vault Unlocked */} | |
| <div className="grid grid-cols-1 lg:grid-cols-12 gap-6"> | |
| {/* Input Form & Proxy Toggle (Left - size 4) */} | |
| <div className="lg:col-span-4 space-y-6"> | |
| <form onSubmit={handleAddKey} className="glass-card bg-white/75 border border-sky-100 p-6 rounded-2xl space-y-4"> | |
| <h2 className="text-base font-extrabold text-sky-800 flex items-center gap-2"> | |
| <Plus className="w-5 h-5 text-sky-600" /> | |
| إضافة مفتاح أو بروكسي جديد | |
| </h2> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">اسم معرّف المفتاح</label> | |
| <input | |
| type="text" | |
| placeholder="مثال: Gemini API الرئيسي" | |
| required | |
| value={name} | |
| onChange={(e) => setName(e.target.value)} | |
| className="bg-white/90 border border-sky-200 rounded-xl px-4 py-2 text-xs text-slate-800 focus:outline-none focus:border-sky-500 w-full" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold font-sans">نوع المخدم / الخدمة</label> | |
| <select | |
| value={type} | |
| onChange={(e) => setType(e.target.value)} | |
| className="bg-white/90 border border-sky-200 rounded-xl px-4 py-2 text-xs text-slate-800 focus:outline-none focus:border-sky-500 w-full" | |
| > | |
| <option value="Google Gemini API">Google Gemini API (توكيد ذكي)</option> | |
| <option value="OpenAI API">OpenAI API (مساعد أتمتة)</option> | |
| <option value="Groq API (جروك)">Groq API (جروك فائقة السرعة)</option> | |
| <option value="Anthropic Claude API">Anthropic Claude API</option> | |
| <option value="Custom AI Proxy (بروكسي ذكاء مخصص)">Custom AI Proxy (بروكسي ذكاء مخصص)</option> | |
| <option value="وكيل بروكسي / IP Agent">وكيل بروكسي / IP Agent (للأمان والخصوصية)</option> | |
| <option value="سيرفر استضافة مخصص / Custom Server Host">سيرفر استضافة مخصص / Custom Server Host</option> | |
| <option value="توكن ومفتاح أمان مخصص / Custom Secret Token">توكن ومفتاح أمان مخصص / Custom Secret Token</option> | |
| <option value="Stripe API Key">Stripe API Key (بوابة الدفع)</option> | |
| <option value="Cloudflare Global Key">Cloudflare Token</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">سلسلة المفتاح السري، التوكن أو كلمة المرور (Secret API Key / Token)</label> | |
| <input | |
| type="password" | |
| placeholder="مثال: sk-proj-... أو رمز التوكن السري للموقع" | |
| required={type !== "وكيل بروكسي / IP Agent" && type !== "سيرفر استضافة مخصص / Custom Server Host"} | |
| value={value} | |
| onChange={(e) => handlePasswordChange(e.target.value)} | |
| className="bg-white/90 border border-sky-200 rounded-xl px-4 py-2 text-xs text-slate-800 focus:outline-none focus:border-sky-500 w-full text-left font-mono" | |
| /> | |
| {value && ( | |
| <div className="mt-1 flex items-center justify-between text-[10px]"> | |
| <span className="text-slate-500">قوة الكود السري:</span> | |
| <span className={`font-bold ${ | |
| passwordStrength.score < 2 ? "text-red-500" : passwordStrength.score < 4 ? "text-yellow-600" : "text-emerald-600" | |
| }`}> | |
| {passwordStrength.text} | |
| </span> | |
| </div> | |
| )} | |
| </div> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold"> | |
| {type === "وكيل بروكسي / IP Agent" | |
| ? "عنوان IP والمنفذ للوكيل (Proxy IP:Port)" | |
| : "عنوان الـ IP المخصص أو رابط الـ Host المكمل (Server IP / Host URL - اختياري)"} | |
| </label> | |
| <input | |
| type="text" | |
| placeholder={type === "وكيل بروكسي / IP Agent" ? "مثال: 185.122.40.15:8080" : "مثال: https://api.openai.com/v1 أو آي بي مخصص لتشغيل الخدمة"} | |
| required={type === "وكيل بروكسي / IP Agent"} | |
| value={proxyAgentIp} | |
| onChange={(e) => setProxyAgentIp(e.target.value)} | |
| className="bg-white/90 border border-sky-200 rounded-xl px-4 py-2 text-xs text-slate-800 focus:outline-none focus:border-sky-500 w-full text-left font-mono" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-2 gap-2"> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">الكريديت للترميز</label> | |
| <input | |
| type="text" | |
| placeholder="مثل: $120.00" | |
| value={creditLimit} | |
| onChange={(e) => setCreditLimit(e.target.value)} | |
| className="bg-white/90 border border-sky-200 rounded-xl px-4 py-2 text-xs text-slate-800 focus:outline-none focus:border-sky-500 w-full text-left" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">تاريخ انتهاء الصلاحية</label> | |
| <input | |
| type="date" | |
| value={expiry} | |
| onChange={(e) => setExpiry(e.target.value)} | |
| className="bg-white/90 border border-sky-200 rounded-xl px-4 py-2 text-xs text-slate-800 focus:outline-none focus:border-sky-500 w-full text-left" | |
| /> | |
| </div> | |
| </div> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">مجموعة التكرار والفشل (Redundancy Group)</label> | |
| <div className="grid grid-cols-2 gap-2 mt-1"> | |
| <button | |
| type="button" | |
| onClick={() => setGroup("primary")} | |
| className={`py-2 px-3 text-[11px] rounded-lg font-bold transition border cursor-pointer ${ | |
| group === "primary" | |
| ? "bg-sky-500/20 border-sky-400 text-sky-800 shadow-sm" | |
| : "bg-white/40 border-sky-200 text-slate-500 hover:border-sky-300" | |
| }`} | |
| > | |
| أساسي (Primary) | |
| </button> | |
| <button | |
| type="button" | |
| onClick={() => setGroup("backup")} | |
| className={`py-2 px-3 text-[11px] rounded-lg font-bold transition border cursor-pointer ${ | |
| group === "backup" | |
| ? "bg-sky-500/20 border-sky-400 text-sky-800 shadow-sm" | |
| : "bg-white/40 border-sky-200 text-slate-500 hover:border-sky-300" | |
| }`} | |
| > | |
| احتياطي (Backup) | |
| </button> | |
| </div> | |
| </div> | |
| <button | |
| type="submit" | |
| className="w-full bg-sky-600 hover:bg-sky-700 text-white font-bold py-2.5 rounded-xl text-xs flex items-center justify-center gap-1.5 transition shadow-md shadow-sky-500/5 cursor-pointer" | |
| > | |
| <ShieldCheck className="w-4 h-4" /> | |
| حفظ وتشفير في الخزنة | |
| </button> | |
| </form> | |
| <button | |
| onClick={handleLock} | |
| className="w-full bg-white/65 hover:bg-white text-slate-600 border border-sky-200 font-bold py-2.5 rounded-xl text-xs flex items-center justify-center gap-1.5 transition shadow-sm cursor-pointer" | |
| > | |
| <Shield className="w-4 h-4 text-sky-600" /> | |
| قفل وتأمين الخزنة الآن | |
| </button> | |
| {/* Backups & Config Migration (Bilingual, high contrast, super clean) */} | |
| <div className="glass-card bg-white/75 border border-sky-100 p-5 rounded-2xl space-y-4"> | |
| <h2 className="text-sm font-extrabold text-sky-800 flex items-center gap-2"> | |
| <Layers className="w-4.5 h-4.5 text-sky-600" /> | |
| المزامنة والنسخ الاحتياطي (Backup & Sync) | |
| </h2> | |
| <p className="text-[11px] text-slate-600 leading-relaxed"> | |
| قم بتصدير الخزنة الرقمية أو إعدادات المواقع والعمليات بالكامل كملف JSON للنسخ الاحتياطي، المزامنة الفورية، أو النقل بأمان. | |
| </p> | |
| <div className="grid grid-cols-1 gap-2 pt-1"> | |
| {/* Export button */} | |
| <button | |
| onClick={handleExportVault} | |
| className="w-full bg-sky-50 hover:bg-sky-100 border border-sky-200 text-sky-800 font-bold py-2 rounded-xl text-xs flex items-center justify-center gap-1.5 transition shadow-sm cursor-pointer" | |
| > | |
| <Download className="w-4 h-4 text-sky-600" /> | |
| تصدير التكوينات والمفاتيح | |
| </button> | |
| {/* Import button */} | |
| <label className="w-full bg-white hover:bg-sky-50 border border-sky-200 text-slate-700 font-bold py-2 rounded-xl text-xs flex items-center justify-center gap-1.5 transition shadow-sm cursor-pointer text-center"> | |
| <Upload className="w-4 h-4 text-sky-600" /> | |
| استيراد كود التكوين (.json) | |
| <input | |
| type="file" | |
| accept=".json" | |
| onChange={handleImportVault} | |
| className="hidden" | |
| /> | |
| </label> | |
| </div> | |
| <div className="text-[9px] text-slate-400 text-center font-mono select-none"> | |
| Secured Config Migration v20.0 | |
| </div> | |
| </div> | |
| </div> | |
| {/* Keys & IP Manager (Right - size 8) */} | |
| <div className="lg:col-span-8 space-y-4"> | |
| {/* Filter controls */} | |
| <div className="glass-card bg-white/75 border border-sky-100 px-4 py-3.5 rounded-2xl flex flex-col sm:flex-row items-center justify-between gap-4"> | |
| <div className="flex items-center gap-2 flex-1 w-full max-w-sm"> | |
| <Search className="w-4 h-4 text-sky-600 shrink-0" /> | |
| <input | |
| type="text" | |
| placeholder="ابحث عن مفتاح سري، توكن، أو بروكسي..." | |
| value={searchQuery} | |
| onChange={(e) => setSearchQuery(e.target.value)} | |
| className="bg-transparent text-xs text-slate-800 focus:outline-none w-full placeholder:text-slate-400" | |
| /> | |
| </div> | |
| <div className="flex items-center gap-2 text-[10px] bg-sky-500/15 text-sky-800 px-3 py-1 rounded-lg border border-sky-500/20"> | |
| <ShieldCheck className="w-3.5 h-3.5 text-emerald-600" /> | |
| <span className="font-bold">تشفير AES-256-GCM للواجهة نشط</span> | |
| </div> | |
| </div> | |
| {/* Connected keys display list */} | |
| {filteredKeys.map((k) => { | |
| const diag = diagnosticResults[k.id]; | |
| const isActiveSystemKey = activeKeyId === k.id; | |
| // Expiry calculation | |
| let diffDays: number | null = null; | |
| if (k.expiry && k.expiry !== "بدون تاريخ انتهاء") { | |
| const expiryDate = new Date(k.expiry); | |
| if (!isNaN(expiryDate.getTime())) { | |
| const now = new Date(); | |
| const diffTime = expiryDate.getTime() - now.getTime(); | |
| diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); | |
| } | |
| } | |
| const usagePercent = diffDays !== null && diffDays < 30 ? Math.max(5, Math.min(100, Math.round((diffDays / 30) * 100))) : 88; | |
| return ( | |
| <div | |
| key={k.id} | |
| className={`glass-card p-4 rounded-xl border transition-all relative overflow-hidden bg-white/75 ${ | |
| isActiveSystemKey ? "border-sky-500/50 shadow-md shadow-sky-500/5 ring-1 ring-sky-500/20" : "border-sky-250/15" | |
| }`} | |
| > | |
| <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pb-3 border-b border-sky-500/10"> | |
| {/* Left: Metadata */} | |
| <div className="space-y-1.5 flex-1"> | |
| <div className="flex items-center gap-2 flex-wrap"> | |
| <h3 className="font-extrabold text-sm text-sky-900">{k.name}</h3> | |
| <span className="text-[10px] bg-sky-100 text-sky-700 px-2 py-0.5 rounded-lg border border-sky-200/40 font-mono font-bold"> | |
| {k.type} | |
| </span> | |
| <span className={`text-[9px] px-1.5 py-0.5 rounded font-bold ${ | |
| k.group === "primary" ? "bg-indigo-100 text-indigo-700" : "bg-emerald-100 text-emerald-700" | |
| }`}> | |
| {k.group === "primary" ? "أساسي" : "احتياطي"} | |
| </span> | |
| {isActiveSystemKey && ( | |
| <span className="bg-sky-600 text-white text-[9px] px-2 py-0.5 rounded font-bold animate-pulse"> | |
| المفتاح النشط حالياً للذكاء | |
| </span> | |
| )} | |
| </div> | |
| {k.proxyAgentIp && ( | |
| <div className="flex items-center gap-1.5 text-[10px] text-sky-800 bg-sky-50/70 p-1 px-2.5 rounded-xl border border-sky-100 w-fit font-mono"> | |
| <span>🌐 المخدم / IP:</span> | |
| <span className="font-extrabold select-all">{k.proxyAgentIp}</span> | |
| </div> | |
| )} | |
| <p className="text-[10px] text-slate-500 font-semibold">تاريخ الانتهاء: {k.expiry}</p> | |
| {/* Interactive depletion progress meter */} | |
| <div className="mt-2.5 space-y-1 max-w-sm"> | |
| <div className="flex justify-between text-[10px] text-slate-600 font-semibold"> | |
| <span>📊 الكمية المتبقية للاستخدام:</span> | |
| <span className={usagePercent < 25 ? "text-red-500 font-extrabold" : usagePercent < 55 ? "text-yellow-600 font-extrabold" : "text-emerald-600 font-extrabold"}> | |
| {usagePercent}% متبقي ({k.creditLimit || "غير محدود"}) | |
| </span> | |
| </div> | |
| <div className="w-full bg-slate-100 rounded-full h-1.5 overflow-hidden"> | |
| <div | |
| className={`h-full rounded-full transition-all duration-500 ${ | |
| usagePercent < 25 ? "bg-red-500 animate-pulse" : usagePercent < 55 ? "bg-yellow-500" : "bg-emerald-500" | |
| }`} | |
| style={{ width: `${usagePercent}%` }} | |
| /> | |
| </div> | |
| {diffDays !== null && ( | |
| <div className="text-[9px] text-slate-500 font-semibold"> | |
| ⏰ ينتهي الكود بالكامل خلال <span className="font-extrabold text-sky-800">{diffDays}</span> يوم{diffDays <= 10 ? "ًا" : ""}. | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Right: Toggle & Testing Diagnostic triggers */} | |
| <div className="flex items-center gap-2 flex-wrap sm:justify-end"> | |
| <button | |
| onClick={() => handleToggleActive(k.id)} | |
| className={`px-2.5 py-1 text-[10px] font-bold rounded-lg transition border cursor-pointer ${ | |
| isActiveSystemKey | |
| ? "bg-sky-600 text-white border-sky-600" | |
| : "bg-white hover:bg-sky-50 border-sky-200 text-slate-600" | |
| }`} | |
| > | |
| {isActiveSystemKey ? "✓ المفتاح المعتمد" : "تعيين كالمفتاح المعتمد"} | |
| </button> | |
| <button | |
| onClick={() => runDiagnosticTest(k)} | |
| disabled={testingKeyId === k.id} | |
| className="px-2.5 py-1 bg-sky-100 text-[10px] text-sky-700 hover:bg-sky-200 rounded-lg border border-sky-200 transition font-bold flex items-center gap-1 disabled:opacity-50 cursor-pointer" | |
| > | |
| {testingKeyId === k.id ? ( | |
| <> | |
| <RefreshCw className="w-3 h-3 animate-spin" /> | |
| جاري الفحص... | |
| </> | |
| ) : ( | |
| <> | |
| <Activity className="w-3 h-3 text-sky-600 animate-pulse" /> | |
| فحص الحالة والكريديت | |
| </> | |
| )} | |
| </button> | |
| {k.status === "active" && ( | |
| <button | |
| onClick={() => triggerFailover(k)} | |
| title="تجربة انتقال المهام السريع للتبديل" | |
| className="p-1 px-2 border border-yellow-200 hover:bg-yellow-50 rounded-lg text-yellow-700 text-[10px] font-bold transition flex items-center gap-0.5 cursor-pointer" | |
| > | |
| <RefreshCw className="w-3 h-3" /> | |
| تبديل (Failover) | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| {/* Status badges container */} | |
| <div className="grid grid-cols-2 sm:grid-cols-4 gap-2.5 my-3 bg-sky-500/5 p-2 rounded-xl border border-sky-500/10 text-center"> | |
| <div> | |
| <span className="text-[9px] text-slate-500 block">حالة العمل</span> | |
| <div className="flex items-center justify-center gap-1 mt-0.5"> | |
| <span className={`w-2.5 h-2.5 rounded-full ${ | |
| k.status === "active" ? "bg-emerald-500" : k.status === "warning" ? "bg-yellow-500" : "bg-red-500 animate-pulse" | |
| }`} /> | |
| <span className="text-xs font-bold text-slate-700"> | |
| {k.status === "active" ? "يعمل" : k.status === "limited" ? "لا يعمل / منتهي" : "متوقف / معطل"} | |
| </span> | |
| </div> | |
| </div> | |
| <div> | |
| <span className="text-[9px] text-slate-500 block">الكريديت المتبقي</span> | |
| <span className="text-xs font-bold text-sky-800 block mt-0.5"> | |
| {diag?.creditLeft || k.creditLimit || "غير محدد"} | |
| </span> | |
| </div> | |
| <div> | |
| <span className="text-[9px] text-slate-500 block">السرعة / البنك</span> | |
| <span className="text-xs font-mono font-bold text-indigo-700 block mt-0.5"> | |
| {diag?.latency ? `${diag.latency} ms` : "--- ms"} | |
| </span> | |
| </div> | |
| <div> | |
| <span className="text-[9px] text-slate-500 block">تاريخ الفحص</span> | |
| <span className="text-[10px] font-semibold text-slate-600 block mt-0.5 truncate"> | |
| {k.lastTested ? new Date(k.lastTested).toLocaleTimeString("ar-EG") : "لم يُفحص بعد"} | |
| </span> | |
| </div> | |
| </div> | |
| {/* Masked display value */} | |
| <div className="bg-sky-500/5 p-2.5 rounded-xl border border-sky-500/10 flex items-center justify-between gap-4"> | |
| <code className="text-xs font-mono text-slate-600 select-all truncate"> | |
| {visibleKeyIds[k.id] ? k.value : getMaskedValue(k.value)} | |
| </code> | |
| <div className="flex gap-1.5 shrink-0"> | |
| <button | |
| onClick={() => toggleVisibility(k.id)} | |
| className="p-1.5 hover:bg-sky-100 rounded text-slate-500 transition cursor-pointer" | |
| > | |
| {visibleKeyIds[k.id] ? <EyeOff className="w-4 h-4 text-sky-600" /> : <EyeIcon className="w-4 h-4 text-sky-600" />} | |
| </button> | |
| <button | |
| onClick={() => handleCopy(k.id, k.value)} | |
| className="p-1.5 hover:bg-sky-100 rounded text-slate-500 transition cursor-pointer" | |
| > | |
| {copiedId === k.id ? <Check className="w-4 h-4 text-emerald-600" /> : <Clipboard className="w-4 h-4 text-sky-600" />} | |
| </button> | |
| <button | |
| onClick={() => handleDelete(k.id)} | |
| className="text-red-500 p-1.5 hover:bg-red-50 border border-sky-100 rounded-lg transition text-xs font-bold cursor-pointer" | |
| > | |
| حذف | |
| </button> | |
| </div> | |
| </div> | |
| {/* Diagnosis Feedback Log (In case of error) */} | |
| {diag && ( | |
| <div className={`mt-2.5 p-2 rounded-lg border text-[11px] flex gap-2 ${ | |
| diag.status === "active" | |
| ? "bg-emerald-50 border-emerald-200 text-emerald-800" | |
| : "bg-red-50 border-red-100 text-red-800" | |
| }`}> | |
| <AlertCircle className="w-4 h-4 shrink-0" /> | |
| <div> | |
| <span className="font-bold">كشف الكابينة الفوري: </span> | |
| <span>{diag.message}</span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| })} | |
| {filteredKeys.length === 0 && ( | |
| <div className="glass-card p-12 text-center text-slate-400 rounded-2xl bg-white/75 border border-sky-50"> | |
| <Key className="w-10 h-10 text-sky-300 mx-auto mb-3" /> | |
| لا توجد مفاتيح في الخزنة حالياً. يرجى إضافة مفتاح او بروكسي بالأعلى لبدء الفحص والتشغيل. | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* 3. INTEGRATED SECURE WEB BROWSER ("البرواسر المدمج") */} | |
| <div className="glass-card bg-white/75 border border-sky-100 rounded-2xl p-6 space-y-4"> | |
| <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 border-b border-sky-500/10 pb-4"> | |
| <div> | |
| <h2 className="text-base font-extrabold text-sky-800 flex items-center gap-1.5"> | |
| <Globe className="w-5 h-5 text-sky-600" /> | |
| برواسر التصفح والتوكيد المدمج (In-App Security Browser Bypass) | |
| </h2> | |
| <p className="text-[11px] text-slate-500"> | |
| تصفح صفحات مزودي الخدمة (مثال: OpenAI Console / Gemini) للحصول على المفاتيح وتسجيلها مباشرة دون مغادرة الأداة. | |
| </p> | |
| </div> | |
| <span className="text-[10px] font-bold bg-emerald-100 text-emerald-700 px-3 py-1 rounded-full border border-emerald-200/40"> | |
| 🔒 بروكسي CORS Bypass مفعّل تلقائياً | |
| </span> | |
| </div> | |
| <div className="grid grid-cols-1 lg:grid-cols-12 gap-6"> | |
| {/* Browser Sandbox viewport (size 8) */} | |
| <div className="lg:col-span-8 space-y-2"> | |
| {/* Browser Address and Navigation Bar */} | |
| <div className="bg-sky-100/60 p-2 rounded-xl flex items-center gap-2 border border-sky-200/40"> | |
| <div className="flex items-center gap-1 shrink-0"> | |
| <button | |
| onClick={goBack} | |
| disabled={historyIndex === 0} | |
| className="p-1 hover:bg-sky-200/50 rounded text-sky-800 disabled:opacity-40 transition cursor-pointer" | |
| > | |
| <ArrowRight className="w-4 h-4 transform rotate-180" /> | |
| </button> | |
| <button | |
| onClick={goForward} | |
| disabled={historyIndex >= browserHistory.length - 1} | |
| className="p-1 hover:bg-sky-200/50 rounded text-sky-800 disabled:opacity-40 transition cursor-pointer" | |
| > | |
| <ArrowLeft className="w-4 h-4 transform rotate-180" /> | |
| </button> | |
| <button | |
| onClick={() => navigateBrowser(browserUrl)} | |
| className="p-1 hover:bg-sky-200/50 rounded text-sky-800 transition cursor-pointer animate-none" | |
| > | |
| <RefreshCw className={`w-4 h-4 ${browserLoading ? "animate-spin text-sky-600" : ""}`} /> | |
| </button> | |
| <button | |
| onClick={() => navigateBrowser("https://ai.google.dev")} | |
| className="p-1 hover:bg-sky-200/50 rounded text-sky-800 transition cursor-pointer" | |
| title="البداية" | |
| > | |
| <Home className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| {/* URL input field */} | |
| <div className="flex-1"> | |
| <input | |
| type="text" | |
| placeholder="أدخل رابط أي موقع للتصفح وتجاوز الحجب..." | |
| value={browserUrl} | |
| onChange={(e) => setBrowserUrl(e.target.value)} | |
| onKeyDown={(e) => { | |
| if (e.key === "Enter") { | |
| navigateBrowser(browserUrl); | |
| } | |
| }} | |
| className="bg-white border border-sky-200 px-3 py-1.5 rounded-lg text-xs text-slate-800 w-full focus:outline-none focus:border-sky-500 text-left font-mono" | |
| /> | |
| </div> | |
| <button | |
| onClick={() => navigateBrowser(browserUrl)} | |
| className="bg-sky-600 hover:bg-sky-700 text-white font-bold px-4 py-1.5 rounded-lg text-xs transition cursor-pointer shadow-sm" | |
| > | |
| انطلاق | |
| </button> | |
| </div> | |
| {/* Main browser frame viewport */} | |
| <div className="bg-white border border-sky-200 rounded-xl overflow-hidden h-[450px] flex flex-col relative"> | |
| {browserLoading && ( | |
| <div className="absolute inset-0 bg-white/75 backdrop-blur-sm flex flex-col items-center justify-center space-y-2 z-15"> | |
| <RefreshCw className="w-8 h-8 text-sky-600 animate-spin" /> | |
| <span className="text-xs font-semibold text-sky-800 animate-pulse">جاري سحب الصفحة وتخطي جدران نارية...</span> | |
| </div> | |
| )} | |
| {/* Virtual status notification bar inside browser */} | |
| <div className="bg-sky-50 px-3 py-1 border-b border-sky-100 flex items-center justify-between text-[10px] text-slate-500 font-mono"> | |
| <span className="flex items-center gap-1"> | |
| <ShieldCheck className="w-3.5 h-3.5 text-emerald-600" /> | |
| عرض آمن محمي {browserUrl} | |
| </span> | |
| <span>Session: ACTIVE (IP Encoded)</span> | |
| </div> | |
| {/* Iframe content */} | |
| <iframe | |
| title="مستعرض التوكيد" | |
| src={iframeSrc} | |
| onLoad={handleIframeLoad} | |
| className="w-full flex-1 border-none bg-white" | |
| sandbox="allow-scripts allow-forms allow-same-origin" | |
| /> | |
| </div> | |
| </div> | |
| {/* Fast registration panel (size 4) */} | |
| <div className="lg:col-span-4 space-y-4"> | |
| <div className="bg-sky-500/5 border border-sky-500/10 p-5 rounded-2xl h-full flex flex-col justify-between"> | |
| <div className="space-y-4"> | |
| <div className="flex items-center gap-2"> | |
| <Zap className="w-5 h-5 text-yellow-500 animate-bounce" /> | |
| <h3 className="font-extrabold text-sm text-sky-900">مسجل المفاتيح السريع من المتصفح</h3> | |
| </div> | |
| <p className="text-[11px] text-slate-600 leading-relaxed"> | |
| عند تحديد ونسخ الـ API Key أو التوكن من صفحة مزود الخدمة داخل المتصفح، ألصقه هنا لتشفيره وحفظه مباشرة في الخزنة الرقمية فوراً! | |
| </p> | |
| <div className="space-y-3 pt-2"> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">تسمية المفتاح الملتقط</label> | |
| <input | |
| type="text" | |
| placeholder="مثال: Gemini Pro الملتقط" | |
| value={quickRegName} | |
| onChange={(e) => setQuickRegName(e.target.value)} | |
| className="bg-white border border-sky-200 rounded-xl px-3.5 py-2 text-xs w-full focus:outline-none focus:border-sky-500 text-slate-800" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">المنصة</label> | |
| <select | |
| value={quickRegType} | |
| onChange={(e) => setQuickRegType(e.target.value)} | |
| className="bg-white border border-sky-200 rounded-xl px-3.5 py-2 text-xs w-full focus:outline-none text-slate-800" | |
| > | |
| <option value="Google Gemini API">Google Gemini API</option> | |
| <option value="OpenAI API">OpenAI API</option> | |
| <option value="Anthropic Claude API">Anthropic Claude API</option> | |
| <option value="Cloudflare Global Key">Cloudflare Global Key</option> | |
| <option value="Stripe API Key">Stripe API Key</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label className="block text-xs text-slate-600 mb-1 font-semibold">كود المفتاح السري المنسوخ</label> | |
| <textarea | |
| rows={3} | |
| placeholder="ألصق الرمز السري الملتقط هنا (sk-..... أو AIzaSy.....)" | |
| value={quickRegValue} | |
| onChange={(e) => setQuickRegValue(e.target.value)} | |
| className="bg-white border border-sky-200 rounded-xl px-3.5 py-2 text-xs w-full focus:outline-none focus:border-sky-500 font-mono text-left" | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| <button | |
| onClick={handleQuickRegister} | |
| disabled={!quickRegName || !quickRegValue} | |
| className="w-full mt-4 bg-sky-600 hover:bg-sky-700 disabled:bg-slate-300 text-white font-bold py-2.5 rounded-xl text-xs flex items-center justify-center gap-1.5 transition shadow cursor-pointer" | |
| > | |
| <ShieldCheck className="w-4 h-4" /> | |
| دبوس وحفظ فوري | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </> | |
| )} | |
| </div> | |
| ); | |
| } | |