Files
QuoteForge/web/templates/setup.html
Mikhail Chusavitin 8d84484412 fix: fix online mode after offline-first architecture changes
- 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>
2026-02-03 10:50:07 +03:00

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}}