Files
balikci/frontend/src/pages/Settings.jsx
salvacybersec af0510e486 feat: Add Ollama AI integration for automatic mail template generation
 New Features:
- 🤖 AI-powered mail template generation with Ollama
- 📧 Test mail sending with preview
- 🔧 Ollama server and model management
- 🎨 Beautiful AI generation dialog in Templates page
- ⚙️ Ollama settings panel with connection test

Backend:
- Add ollama.service.js - Ollama API integration
- Add ollama.controller.js - Template generation endpoint
- Add ollama.routes.js - /api/ollama/* routes
- Support for multiple Ollama models (llama3.2, mistral, gemma)
- JSON-formatted AI responses with subject + HTML body
- Configurable server URL and model selection

Frontend:
- Settings: Ollama configuration panel
  - Server URL input
  - Model selection
  - Connection test with model listing
- Templates: AI generation dialog
  - Company name, scenario, employee info inputs
  - Custom prompt for AI instructions
  - Auto-save to database
  - Test mail sending functionality

Documentation:
- OLLAMA_SETUP.md - Comprehensive setup guide
- Installation instructions
- Model recommendations
- Usage examples
- Troubleshooting

Tech Stack:
- Ollama API integration (REST)
- Axios HTTP client
- React dialogs with MUI
- Self-hosted AI (privacy-friendly)
- Zero external API dependencies

Example Usage:
  Company: Garanti Bankası
  Scenario: Account security warning
  → AI generates professional phishing test mail in seconds!
2025-11-10 21:13:58 +03:00

477 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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({
base_url: '',
frontend_url: '',
cors_enabled: false,
gmail_user: '',
gmail_app_password: '',
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 || {};
// Convert array to object
const settingsObj = {};
if (Array.isArray(data)) {
data.forEach(item => {
settingsObj[item.key] = item.value === '********' ? '' : item.value;
});
}
setSettings({
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([
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,
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);
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 {
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}>
{/* System Settings */}
<Grid size={12}>
<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>
📧 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 })
}
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:
</Typography>
{ollamaModels.map((model, idx) => (
<Typography key={idx} variant="body2">
{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;