- Fix nil pointer dereference in PricingHandler alert methods - Add automatic MariaDB connection on startup if settings exist - Update setupRouter to accept mariaDB as parameter - Fix offline mode checks: use h.db instead of h.alertService - Update setup handler to show restart required message - Add warning status support in setup.html UI This ensures that after saving connection settings, the application works correctly in online mode after restart. All repositories are properly initialized with MariaDB connection on startup. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
202 lines
9.1 KiB
HTML
202 lines
9.1 KiB
HTML
{{define "setup.html"}}
|
|
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>QuoteForge - Настройка подключения</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
</head>
|
|
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
|
|
<div class="max-w-md w-full mx-4">
|
|
<div class="bg-white rounded-lg shadow-lg p-8">
|
|
<div class="text-center mb-8">
|
|
<h1 class="text-2xl font-bold text-blue-600">QuoteForge</h1>
|
|
<p class="text-gray-600 mt-2">Настройка подключения к базе данных</p>
|
|
</div>
|
|
|
|
<form id="setup-form" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Хост сервера</label>
|
|
<input type="text" name="host" id="host"
|
|
value="{{if .Settings}}{{.Settings.Host}}{{else}}localhost{{end}}"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
placeholder="localhost или IP-адрес">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Порт</label>
|
|
<input type="number" name="port" id="port"
|
|
value="{{if .Settings}}{{.Settings.Port}}{{else}}3306{{end}}"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
placeholder="3306">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">База данных</label>
|
|
<input type="text" name="database" id="database"
|
|
value="{{if .Settings}}{{.Settings.Database}}{{else}}RFQ_LOG{{end}}"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
placeholder="RFQ_LOG">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Пользователь</label>
|
|
<input type="text" name="user" id="user"
|
|
value="{{if .Settings}}{{.Settings.User}}{{end}}"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
placeholder="username">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Пароль</label>
|
|
<input type="password" name="password" id="password"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
placeholder="{{if .Settings}}********{{else}}password{{end}}">
|
|
{{if .Settings}}
|
|
<p class="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы сохранить текущий пароль</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div id="status" class="hidden p-3 rounded-md text-sm"></div>
|
|
|
|
<div class="flex space-x-3 pt-4">
|
|
{{if .Settings}}
|
|
<a href="/"
|
|
class="flex-1 px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition text-center">
|
|
Назад
|
|
</a>
|
|
{{end}}
|
|
<button type="button" onclick="testConnection()"
|
|
class="flex-1 px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition">
|
|
Проверить
|
|
</button>
|
|
<button type="submit"
|
|
class="flex-1 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">
|
|
Сохранить
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<p class="text-center text-gray-500 text-sm mt-4">
|
|
QuoteForge v1.0 - Конфигуратор серверов
|
|
</p>
|
|
</div>
|
|
|
|
<script>
|
|
function showStatus(message, type) {
|
|
const status = document.getElementById('status');
|
|
status.classList.remove('hidden', 'bg-green-100', 'text-green-800', 'bg-red-100', 'text-red-800', 'bg-blue-100', 'text-blue-800', 'bg-yellow-100', 'text-yellow-800');
|
|
|
|
if (type === 'success') {
|
|
status.classList.add('bg-green-100', 'text-green-800');
|
|
} else if (type === 'error') {
|
|
status.classList.add('bg-red-100', 'text-red-800');
|
|
} else if (type === 'warning') {
|
|
status.classList.add('bg-yellow-100', 'text-yellow-800');
|
|
} else {
|
|
status.classList.add('bg-blue-100', 'text-blue-800');
|
|
}
|
|
|
|
status.textContent = message;
|
|
}
|
|
|
|
async function testConnection() {
|
|
showStatus('Проверка подключения...', 'info');
|
|
|
|
const formData = new FormData(document.getElementById('setup-form'));
|
|
|
|
try {
|
|
const resp = await fetch('/setup/test', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const data = await resp.json();
|
|
|
|
if (data.success) {
|
|
let msg = data.message;
|
|
if (data.can_write) {
|
|
msg += ' Права на запись: есть.';
|
|
} else {
|
|
msg += ' Права на запись: только чтение.';
|
|
}
|
|
showStatus(msg, 'success');
|
|
} else {
|
|
showStatus(data.error, 'error');
|
|
}
|
|
} catch (e) {
|
|
showStatus('Ошибка сети: ' + e.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function checkServerReady() {
|
|
let attempts = 0;
|
|
const maxAttempts = 30; // 30 seconds max
|
|
|
|
const checkInterval = setInterval(async () => {
|
|
attempts++;
|
|
try {
|
|
const resp = await fetch('/health', { method: 'GET' });
|
|
const data = await resp.json();
|
|
|
|
// Check if we're out of setup mode
|
|
if (data.status === 'ok') {
|
|
clearInterval(checkInterval);
|
|
showStatus('✓ Приложение запущено! Перенаправление...', 'success');
|
|
setTimeout(() => {
|
|
window.location.href = '/';
|
|
}, 1000);
|
|
}
|
|
} catch (e) {
|
|
// Server still restarting, continue polling
|
|
if (attempts >= maxAttempts) {
|
|
clearInterval(checkInterval);
|
|
showStatus('Сервер не отвечает. Обновите страницу вручную.', 'error');
|
|
}
|
|
}
|
|
}, 1000); // Check every second
|
|
}
|
|
|
|
document.getElementById('setup-form').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
showStatus('Сохранение настроек...', 'info');
|
|
|
|
const formData = new FormData(this);
|
|
|
|
try {
|
|
const resp = await fetch('/setup', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const data = await resp.json();
|
|
|
|
if (data.success) {
|
|
showStatus('✓ ' + data.message, 'success');
|
|
|
|
// Check if restart is required
|
|
if (data.restart_required) {
|
|
// In normal mode, restart must be done manually
|
|
setTimeout(() => {
|
|
showStatus('⚠️ Пожалуйста, перезапустите приложение вручную для применения изменений', 'warning');
|
|
}, 2000);
|
|
} else {
|
|
// In setup mode, auto-restart is happening
|
|
setTimeout(() => {
|
|
showStatus('✓ Настройки сохранены. Проверка подключения...', 'success');
|
|
// Poll until server is back
|
|
checkServerReady();
|
|
}, 2000);
|
|
}
|
|
} else {
|
|
showStatus(data.error, 'error');
|
|
}
|
|
} catch (e) {
|
|
showStatus('Ошибка сети: ' + e.message, 'error');
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
{{end}}
|