Files
balikci/frontend/src/pages/Settings.jsx

495 lines
16 KiB
React
Raw Normal View History

import { useState, useEffect } from 'react';
import {
Box,
Paper,
Typography,
TextField,
Button,
Alert,
CircularProgress,
Divider,
FormControlLabel,
Checkbox,
Grid,
} from '@mui/material';
import { Save, Send } from '@mui/icons-material';
import axios from 'axios';
const API_URL = import.meta.env.VITE_API_URL;
function Settings() {
const [settings, setSettings] = useState({
2025-11-10 20:01:41 +03:00
base_url: '',
frontend_url: '',
cors_enabled: false,
gmail_user: '',
gmail_app_password: '',
2025-11-10 20:01:41 +03:00
gmail_from_name: '',
telegram_bot_token: '',
telegram_chat_id: '',
ollama_server_url: '',
ollama_model: '',
});
const [loading, setLoading] = useState(true);
const [testLoading, setTestLoading] = useState({ mail: false, telegram: false, ollama: false });
const [alerts, setAlerts] = useState({ mail: null, telegram: null, ollama: null });
const [ollamaModels, setOllamaModels] = useState([]);
useEffect(() => {
loadSettings();
}, []);
const loadSettings = async () => {
try {
const response = await axios.get(`${API_URL}/api/settings`, {
withCredentials: true,
});
const data = response.data.data || {};
2025-11-10 20:01:41 +03:00
// Convert array to object
const settingsObj = {};
if (Array.isArray(data)) {
data.forEach(item => {
settingsObj[item.key] = item.value === '********' ? '' : item.value;
});
}
setSettings({
2025-11-10 20:01:41 +03:00
base_url: settingsObj.base_url || '',
frontend_url: settingsObj.frontend_url || '',
cors_enabled: settingsObj.cors_enabled === 'true',
gmail_user: settingsObj.gmail_user || '',
gmail_app_password: settingsObj.gmail_password || '',
gmail_from_name: settingsObj.gmail_from_name || '',
telegram_bot_token: settingsObj.telegram_bot_token || '',
telegram_chat_id: settingsObj.telegram_chat_id || '',
ollama_server_url: settingsObj.ollama_server_url || '',
ollama_model: settingsObj.ollama_model || '',
});
} catch (error) {
console.error('Failed to load settings:', error);
} finally {
setLoading(false);
}
};
const handleSave = async () => {
try {
await Promise.all([
2025-11-10 20:01:41 +03:00
axios.put(`${API_URL}/api/settings/system`, {
base_url: settings.base_url,
frontend_url: settings.frontend_url,
cors_enabled: settings.cors_enabled,
}, { withCredentials: true }),
axios.put(`${API_URL}/api/settings/gmail`, {
gmail_user: settings.gmail_user,
gmail_app_password: settings.gmail_app_password,
2025-11-10 20:01:41 +03:00
gmail_from_name: settings.gmail_from_name,
}, { withCredentials: true }),
axios.put(`${API_URL}/api/settings/telegram`, {
telegram_bot_token: settings.telegram_bot_token,
telegram_chat_id: settings.telegram_chat_id,
}, { withCredentials: true }),
axios.put(`${API_URL}/api/ollama/settings`, {
ollama_server_url: settings.ollama_server_url,
ollama_model: settings.ollama_model,
}, { withCredentials: true }),
]);
alert('Ayarlar kaydedildi!');
} catch (error) {
console.error('Failed to save settings:', error);
2025-11-10 20:01:41 +03:00
alert('Ayarlar kaydedilemedi: ' + (error.response?.data?.error || error.message));
}
};
const handleTestMail = async () => {
setTestLoading({ ...testLoading, mail: true });
try {
const response = await axios.post(
`${API_URL}/api/settings/test-gmail`,
{},
{ withCredentials: true }
);
setAlerts({ ...alerts, mail: { severity: 'success', message: response.data.message } });
} catch (error) {
setAlerts({
...alerts,
mail: { severity: 'error', message: error.response?.data?.error || 'Test başarısız' },
});
} finally {
setTestLoading({ ...testLoading, mail: false });
}
};
const handleTestTelegram = async () => {
setTestLoading({ ...testLoading, telegram: true });
try {
const response = await axios.post(
`${API_URL}/api/settings/test-telegram`,
{},
{ withCredentials: true }
);
setAlerts({ ...alerts, telegram: { severity: 'success', message: response.data.message } });
} catch (error) {
setAlerts({
...alerts,
telegram: {
severity: 'error',
message: error.response?.data?.error || 'Test başarısız',
},
});
} finally {
setTestLoading({ ...testLoading, telegram: false });
}
};
const handleTestOllama = async () => {
setTestLoading({ ...testLoading, ollama: true });
setAlerts({ ...alerts, ollama: null });
try {
// First save the settings if they are filled
if (settings.ollama_server_url && settings.ollama_model) {
await axios.put(`${API_URL}/api/ollama/settings`, {
ollama_server_url: settings.ollama_server_url,
ollama_model: settings.ollama_model,
}, { withCredentials: true });
}
const response = await axios.get(
`${API_URL}/api/ollama/test`,
{ withCredentials: true }
);
if (response.data.success) {
setOllamaModels(response.data.data.models || []);
setAlerts({
...alerts,
ollama: {
severity: 'success',
message: `${response.data.message} - ${response.data.data.models.length} model bulundu`
},
});
} else {
setAlerts({
...alerts,
ollama: { severity: 'error', message: response.data.message },
});
}
} catch (error) {
setAlerts({
...alerts,
ollama: {
severity: 'error',
message: error.response?.data?.error || 'Ollama bağlantısı başarısız',
},
});
} finally {
setTestLoading({ ...testLoading, ollama: false });
}
};
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
<CircularProgress />
</Box>
);
}
return (
<Box>
<Typography variant="h4" gutterBottom>
Sistem Ayarları
</Typography>
<Grid container spacing={3}>
2025-11-10 20:01:41 +03:00
{/* System Settings */}
<Grid size={12}>
2025-11-10 20:01:41 +03:00
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
🌐 Genel Ayarlar
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
Domain ve genel sistem ayarları
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
<strong>Tek Domain (Önerilen):</strong> Frontend ve backend aynı domainde, path ile ayrılır<br/>
<strong>İki Domain:</strong> Frontend ve backend farklı domainlerde (CORS gerekir)
</Typography>
<FormControlLabel
control={
<Checkbox
checked={settings.cors_enabled}
onChange={(e) =>
setSettings({ ...settings, cors_enabled: e.target.checked })
}
/>
}
label="İki Ayrı Domain Kullan (CORS Aktif Et)"
/>
<TextField
fullWidth
margin="normal"
label={settings.cors_enabled ? "Backend Domain (Base URL)" : "Domain (Base URL)"}
type="url"
placeholder="https://yourdomain.com"
value={settings.base_url}
onChange={(e) =>
setSettings({ ...settings, base_url: e.target.value })
}
helperText={
settings.cors_enabled
? "Backend API domain'i. Örnek: https://api.yourdomain.com"
: "Hem frontend hem backend için kullanılacak domain. Örnek: https://yourdomain.com"
}
/>
{settings.cors_enabled && (
<TextField
fullWidth
margin="normal"
label="Frontend Domain"
type="url"
placeholder="https://panel.yourdomain.com"
value={settings.frontend_url}
onChange={(e) =>
setSettings({ ...settings, frontend_url: e.target.value })
}
helperText="Frontend panel domain'i. CORS için gerekli."
/>
)}
</Paper>
</Grid>
{/* Gmail Settings */}
<Grid size={{ xs: 12, md: 6 }}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
2025-11-10 20:01:41 +03:00
📧 Gmail Ayarları
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
Gmail App Password kullanın (2FA aktif olmalı)
</Typography>
<TextField
fullWidth
margin="normal"
label="Gmail Adresi"
type="email"
value={settings.gmail_user}
onChange={(e) =>
setSettings({ ...settings, gmail_user: e.target.value })
}
/>
<TextField
fullWidth
margin="normal"
label="App Password"
type="password"
value={settings.gmail_app_password}
onChange={(e) =>
setSettings({ ...settings, gmail_app_password: e.target.value })
}
2025-11-10 20:01:41 +03:00
helperText="Google Hesap → Güvenlik → 2FA → Uygulama Şifreleri"
/>
<TextField
fullWidth
margin="normal"
label="Gönderen Adı (Varsayılan)"
value={settings.gmail_from_name}
onChange={(e) =>
setSettings({ ...settings, gmail_from_name: e.target.value })
}
placeholder="Güvenlik Ekibi"
helperText="Mail gönderirken görünecek varsayılan isim"
/>
{alerts.mail && (
<Alert severity={alerts.mail.severity} sx={{ mt: 2 }}>
{alerts.mail.message}
</Alert>
)}
<Box mt={2} display="flex" gap={2}>
<Button
variant="contained"
startIcon={<Save />}
onClick={handleSave}
>
Kaydet
</Button>
<Button
variant="outlined"
startIcon={<Send />}
onClick={handleTestMail}
disabled={testLoading.mail}
>
Test Mail Gönder
</Button>
</Box>
</Paper>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
Telegram Ayarları
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
@BotFather'dan bot token alın, @userinfobot'dan chat ID öğrenin
</Typography>
<TextField
fullWidth
margin="normal"
label="Bot Token"
type="password"
value={settings.telegram_bot_token}
onChange={(e) =>
setSettings({ ...settings, telegram_bot_token: e.target.value })
}
/>
<TextField
fullWidth
margin="normal"
label="Chat ID"
value={settings.telegram_chat_id}
onChange={(e) =>
setSettings({ ...settings, telegram_chat_id: e.target.value })
}
/>
{alerts.telegram && (
<Alert severity={alerts.telegram.severity} sx={{ mt: 2 }}>
{alerts.telegram.message}
</Alert>
)}
<Box mt={2} display="flex" gap={2}>
<Button
variant="contained"
startIcon={<Save />}
onClick={handleSave}
>
Kaydet
</Button>
<Button
variant="outlined"
startIcon={<Send />}
onClick={handleTestTelegram}
disabled={testLoading.telegram}
>
Test Bildirimi
</Button>
</Box>
</Paper>
</Grid>
{/* Ollama Settings */}
<Grid size={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
🤖 Ollama AI Ayarları
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
AI ile mail şablonu oluşturmak için Ollama yapılandırması
</Typography>
<TextField
fullWidth
margin="normal"
label="Ollama Server URL"
type="url"
placeholder="http://localhost:11434"
value={settings.ollama_server_url}
onChange={(e) =>
setSettings({ ...settings, ollama_server_url: e.target.value })
}
helperText="Ollama sunucu adresi (varsayılan: http://localhost:11434)"
/>
<TextField
fullWidth
margin="normal"
label="Model"
placeholder="llama3.2"
value={settings.ollama_model}
onChange={(e) =>
setSettings({ ...settings, ollama_model: e.target.value })
}
helperText="Kullanılacak Ollama model adı (örn: llama3.2, mistral, gemma)"
/>
{alerts.ollama && (
<Alert severity={alerts.ollama.severity} sx={{ mt: 2 }}>
{alerts.ollama.message}
</Alert>
)}
{ollamaModels.length > 0 && (
<Alert severity="info" sx={{ mt: 2 }}>
<Typography variant="body2" fontWeight="bold">
Mevcut Modeller (tıklayarak seçin):
</Typography>
{ollamaModels.map((model, idx) => (
<Typography
key={idx}
variant="body2"
sx={{
cursor: 'pointer',
'&:hover': { backgroundColor: 'action.hover' },
p: 0.5,
borderRadius: 1,
}}
onClick={() => setSettings({ ...settings, ollama_model: model.name })}
>
{model.name} ({(model.size / 1024 / 1024 / 1024).toFixed(1)} GB)
</Typography>
))}
</Alert>
)}
<Box mt={2} display="flex" gap={2}>
<Button
variant="contained"
startIcon={<Save />}
onClick={handleSave}
>
Kaydet
</Button>
<Button
variant="outlined"
startIcon={<Send />}
onClick={handleTestOllama}
disabled={testLoading.ollama}
>
{testLoading.ollama ? <CircularProgress size={20} /> : 'Bağlantıyı Test Et'}
</Button>
</Box>
</Paper>
</Grid>
</Grid>
<Paper sx={{ p: 3, mt: 3 }}>
<Typography variant="h6" gutterBottom>
Tracking URL Bilgisi
</Typography>
<Divider sx={{ my: 2 }} />
<Typography variant="body2" color="text.secondary">
Tracking URL formatı: <strong>http://your-domain.com/t/TOKEN</strong>
</Typography>
<Typography variant="body2" color="text.secondary" mt={1}>
Bu URL'ler mail şablonlarında otomatik olarak oluşturulur ve gönderilir.
</Typography>
</Paper>
</Box>
);
}
export default Settings;