Files
ovh-dns/components/Settings.js
2025-10-24 16:28:53 +02:00

426 líneas
22 KiB
JavaScript
Original Blame Histórico

Este archivo contiene caracteres Unicode ambiguos
Este archivo contiene caracteres Unicode que pueden confundirse con otros caracteres. Si crees que esto es intencional, puedes ignorar esta advertencia. Usa el botón de Escape para revelarlos.
'use client';
import { useState, useEffect } from 'react';
import { Settings as SettingsIcon, Plus, Trash2, Save, RefreshCw, Wifi, Clock } from 'lucide-react';
const Settings = () => {
const [config, setConfig] = useState(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [currentIPs, setCurrentIPs] = useState({ ipv4: '', ipv6: '' });
const [checkingIP, setCheckingIP] = useState(false);
const fetchConfig = async () => {
try {
const response = await fetch('/api/config');
const data = await response.json();
if (data.success) {
setConfig(data.config);
}
} catch (error) {
console.error('Error fetching config:', error);
}
setLoading(false);
};
const fetchCurrentIPs = async () => {
try {
const response = await fetch('/api/ip/current');
const data = await response.json();
if (data.success && data.ips) {
setCurrentIPs(data.ips);
}
} catch (error) {
console.error('Error fetching current IPs:', error);
}
};
const checkIPs = async () => {
setCheckingIP(true);
try {
const response = await fetch('/api/ip/current', { method: 'POST' });
const data = await response.json();
if (data.success) {
setCurrentIPs(data.newIPs);
}
} catch (error) {
console.error('Error checking IPs:', error);
}
setCheckingIP(false);
};
useEffect(() => {
// Initial data loading
// eslint-disable-next-line react-hooks/set-state-in-effect
fetchConfig();
fetchCurrentIPs();
}, []);
const saveConfig = async () => {
setSaving(true);
try {
const response = await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
if (response.ok) {
alert('Configuration saved successfully');
}
} catch (error) {
console.error('Error saving config:', error);
alert('Error saving configuration');
}
setSaving(false);
};
const addAccount = () => {
const newAccount = {
id: `account${Date.now()}`,
name: 'New Account',
appKey: '',
appSecret: '',
consumerKey: '',
endpoint: 'ovh-eu',
domains: []
};
setConfig({
...config,
ovhAccounts: [...config.ovhAccounts, newAccount]
});
};
const removeAccount = (accountId) => {
if (!confirm('Are you sure you want to delete this account?')) return;
setConfig({
...config,
ovhAccounts: config.ovhAccounts.filter(acc => acc.id !== accountId)
});
};
const updateAccount = (accountId, field, value) => {
setConfig({
...config,
ovhAccounts: config.ovhAccounts.map(acc =>
acc.id === accountId ? { ...acc, [field]: value } : acc
)
});
};
const addDomain = (accountId) => {
const domain = prompt('Enter the domain name:');
if (!domain) return;
setConfig({
...config,
ovhAccounts: config.ovhAccounts.map(acc =>
acc.id === accountId
? { ...acc, domains: [...(acc.domains || []), domain] }
: acc
)
});
};
const removeDomainFromAccount = (accountId, domain) => {
setConfig({
...config,
ovhAccounts: config.ovhAccounts.map(acc =>
acc.id === accountId
? { ...acc, domains: acc.domains.filter(d => d !== domain) }
: acc
)
});
};
const updateIPProvider = (providerId, field, value) => {
setConfig({
...config,
ipProviders: config.ipProviders.map(provider =>
provider.id === providerId ? { ...provider, [field]: value } : provider
)
});
};
const updateAutoUpdate = (field, value) => {
setConfig({
...config,
autoUpdate: { ...config.autoUpdate, [field]: value }
});
};
if (loading || !config) {
return (
<div className="bg-white rounded-2xl shadow-xl p-8 text-center">
<RefreshCw className="h-12 w-12 animate-spin mx-auto text-blue-500" />
<p className="mt-4 text-lg text-gray-500">Cargando configuración...</p>
</div>
);
}
return (
<div className="bg-white rounded-2xl shadow-xl p-8">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div className="flex items-center">
<div className="bg-gradient-to-br from-purple-500 to-pink-600 p-3 rounded-xl mr-4">
<SettingsIcon className="h-8 w-8 text-white" />
</div>
<div>
<h2 className="text-3xl font-bold text-gray-900">Settings</h2>
<p className="text-gray-500 mt-1">Manage your OVH accounts and IP providers</p>
</div>
</div>
<button
onClick={saveConfig}
disabled={saving}
className="inline-flex items-center px-6 py-3 border border-transparent text-sm font-medium rounded-lg text-white bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 transition-all"
>
<Save className={`h-5 w-5 mr-2 ${saving ? 'animate-spin' : ''}`} />
{saving ? 'Guardando...' : 'Guardar Configuración'}
</button>
</div>
{/* Current IPs Section */}
<div className="mb-8 p-6 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl border-2 border-blue-200">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<Wifi className="h-6 w-6 text-blue-600 mr-2" />
<h3 className="text-xl font-semibold text-gray-900">IPs Actuales</h3>
</div>
<button
onClick={checkIPs}
disabled={checkingIP}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 transition-all"
>
<RefreshCw className={`h-4 w-4 mr-2 ${checkingIP ? 'animate-spin' : ''}`} />
Actualizar IPs
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white p-4 rounded-lg">
<p className="text-sm font-medium text-gray-500 mb-1">IPv4</p>
<p className="text-lg font-mono text-gray-900">{currentIPs.ipv4 || 'No disponible'}</p>
</div>
<div className="bg-white p-4 rounded-lg">
<p className="text-sm font-medium text-gray-500 mb-1">IPv6</p>
<p className="text-lg font-mono text-gray-900">{currentIPs.ipv6 || 'No disponible'}</p>
</div>
</div>
</div>
{/* OVH Accounts Section */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-semibold text-gray-900">Cuentas OVH</h3>
<button
onClick={addAccount}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all"
>
<Plus className="w-5 h-5 mr-2" />
Add Account
</button>
</div>
<div className="space-y-4">
{config.ovhAccounts.map((account) => (
<div key={account.id} className="border border-gray-200 rounded-xl p-6 bg-gray-50">
<div className="flex items-center justify-between mb-4">
<input
type="text"
value={account.name}
onChange={(e) => updateAccount(account.id, 'name', e.target.value)}
className="text-lg font-semibold text-gray-900 bg-transparent border-none focus:outline-none focus:ring-2 focus:ring-blue-500 rounded px-2"
/>
<button
onClick={() => removeAccount(account.id)}
className="text-red-600 hover:text-red-900 transition-colors"
>
<Trash2 className="h-5 w-5" />
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">App Key</label>
<input
type="text"
value={account.appKey}
onChange={(e) => updateAccount(account.id, 'appKey', e.target.value)}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 py-2 px-3"
placeholder="Your OVH App Key"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">App Secret</label>
<input
type="password"
value={account.appSecret}
onChange={(e) => updateAccount(account.id, 'appSecret', e.target.value)}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 py-2 px-3"
placeholder="Your OVH App Secret"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Consumer Key</label>
<input
type="password"
value={account.consumerKey}
onChange={(e) => updateAccount(account.id, 'consumerKey', e.target.value)}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 py-2 px-3"
placeholder="Your OVH Consumer Key"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Endpoint</label>
<select
value={account.endpoint}
onChange={(e) => updateAccount(account.id, 'endpoint', e.target.value)}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 py-2 px-3"
>
<option value="ovh-eu">OVH Europe</option>
<option value="ovh-ca">OVH Canada</option>
<option value="ovh-us">OVH US</option>
</select>
</div>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700">Dominios</label>
<button
onClick={() => addDomain(account.id)}
className="text-sm text-blue-600 hover:text-blue-700 font-medium"
>
+ Add domain
</button>
</div>
<div className="flex flex-wrap gap-2">
{account.domains?.map((domain) => (
<span
key={domain}
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800"
>
{domain}
<button
onClick={() => removeDomainFromAccount(account.id, domain)}
className="ml-2 text-blue-600 hover:text-blue-900"
>
×
</button>
</span>
))}
{(!account.domains || account.domains.length === 0) && (
<span className="text-sm text-gray-500 italic">No hay dominios configurados</span>
)}
</div>
</div>
</div>
))}
</div>
</div>
{/* IP Providers Section */}
<div className="mb-8">
<h3 className="text-xl font-semibold text-gray-900 mb-4">IP Providers</h3>
<div className="space-y-4">
{config.ipProviders.map((provider) => (
<div key={provider.id} className="border border-gray-200 rounded-xl p-6 bg-gray-50">
<div className="flex items-center justify-between mb-4">
<h4 className="text-lg font-semibold text-gray-900">{provider.name}</h4>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.enabled}
onChange={(e) => updateIPProvider(provider.id, 'enabled', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-700">
{provider.enabled ? 'Enabled' : 'Disabled'}
</span>
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">URL IPv4</label>
<input
type="text"
value={provider.ipv4Url}
onChange={(e) => updateIPProvider(provider.id, 'ipv4Url', e.target.value)}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 py-2 px-3"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">URL IPv6</label>
<input
type="text"
value={provider.ipv6Url}
onChange={(e) => updateIPProvider(provider.id, 'ipv6Url', e.target.value)}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 py-2 px-3"
/>
</div>
</div>
</div>
))}
</div>
</div>
{/* Auto Update Section */}
<div className="border border-gray-200 rounded-xl p-6 bg-gradient-to-br from-purple-50 to-pink-50">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<Clock className="h-6 w-6 text-purple-600 mr-2" />
<h3 className="text-xl font-semibold text-gray-900">Actualización Automática</h3>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={config.autoUpdate.enabled}
onChange={(e) => updateAutoUpdate('enabled', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-purple-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-purple-600"></div>
<span className="ml-3 text-sm font-medium text-gray-700">
{config.autoUpdate.enabled ? 'Enabled' : 'Disabled'}
</span>
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Verification Interval (seconds)
</label>
<input
type="number"
value={config.autoUpdate.checkInterval}
onChange={(e) => updateAutoUpdate('checkInterval', parseInt(e.target.value))}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500 py-2 px-3"
min="60"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Target Domains
</label>
<input
type="text"
value={config.autoUpdate.targetDomains?.join(', ') || ''}
onChange={(e) => updateAutoUpdate('targetDomains', e.target.value.split(',').map(d => d.trim()).filter(Boolean))}
className="block w-full border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500 py-2 px-3"
placeholder="domain1.com, domain2.com"
/>
</div>
</div>
<p className="mt-3 text-sm text-gray-600">
Automatic updates will periodically check public IPs and update DNS records for the specified domains.
</p>
</div>
</div>
);
};
export default Settings;