Refactor scheduler and settings UI

This commit is contained in:
Mikhail Chusavitin
2026-03-07 21:10:20 +03:00
parent 27e33db446
commit b2b2f4774c
23 changed files with 1601 additions and 551 deletions

View File

@@ -18,11 +18,11 @@
<div class="flex justify-between h-14">
<div class="flex items-center space-x-8">
<a href="/" class="text-xl font-bold text-orange-600">PriceForge</a>
<div class="hidden md:flex space-x-4">
<a href="/lot" class="text-gray-600 hover:text-gray-900 px-3 py-2 text-sm">LOT</a>
<a href="/vendor-mappings" class="text-gray-600 hover:text-gray-900 px-3 py-2 text-sm">Vendor mappings</a>
<a href="/admin/pricing" class="text-gray-600 hover:text-gray-900 px-3 py-2 text-sm">Администратор цен</a>
<button type="button" onclick="openConnectionSettingsModal()" class="text-gray-600 hover:text-gray-900 px-3 py-2 text-sm">Настройки</button>
<div class="hidden md:flex space-x-2">
<a href="/lot" class="{{if eq .ActivePage `lot`}}bg-orange-50 text-orange-700{{else}}text-gray-600 hover:text-gray-900 hover:bg-gray-50{{end}} px-3 py-2 text-sm rounded-md transition">LOT</a>
<a href="/vendor-mappings" class="{{if eq .ActivePage `vendor_mappings`}}bg-orange-50 text-orange-700{{else}}text-gray-600 hover:text-gray-900 hover:bg-gray-50{{end}} px-3 py-2 text-sm rounded-md transition">Vendor mappings</a>
<a href="/admin/pricing" class="{{if eq .ActivePage `admin`}}bg-orange-50 text-orange-700{{else}}text-gray-600 hover:text-gray-900 hover:bg-gray-50{{end}} px-3 py-2 text-sm rounded-md transition">Администратор цен</a>
<a href="/setup" class="{{if eq .ActivePage `setup`}}bg-orange-50 text-orange-700{{else}}text-gray-600 hover:text-gray-900 hover:bg-gray-50{{end}} px-3 py-2 text-sm rounded-md transition">Настройки</a>
</div>
</div>
<div class="flex items-center space-x-4">
@@ -41,43 +41,6 @@
{{template "content" .}}
</main>
<div id="connection-settings-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 px-4">
<div class="bg-white rounded-lg shadow-lg w-full max-w-md">
<div class="flex items-center justify-between px-5 py-4 border-b">
<h2 class="text-lg font-semibold text-gray-900">Параметры подключения</h2>
<button type="button" onclick="closeConnectionSettingsModal()" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
</div>
<form id="connection-settings-form" class="p-5 space-y-3">
<div>
<label class="block text-sm text-gray-700 mb-1">Хост</label>
<input id="connection-host" name="host" type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500">
</div>
<div>
<label class="block text-sm text-gray-700 mb-1">Порт</label>
<input id="connection-port" name="port" type="number" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500">
</div>
<div>
<label class="block text-sm text-gray-700 mb-1">База данных</label>
<input id="connection-database" name="database" type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500">
</div>
<div>
<label class="block text-sm text-gray-700 mb-1">Пользователь</label>
<input id="connection-user" name="user" type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500">
</div>
<div>
<label class="block text-sm text-gray-700 mb-1">Пароль</label>
<input id="connection-password" name="password" type="password" autocomplete="new-password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500">
<p class="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы сохранить текущий пароль.</p>
</div>
<div id="connection-settings-status" class="hidden text-sm rounded-md px-3 py-2"></div>
<div class="flex items-center justify-end gap-2 pt-2">
<button type="button" onclick="testConnectionSettings()" class="px-3 py-2 border border-gray-300 rounded-md text-sm hover:bg-gray-50">Проверить</button>
<button type="submit" class="px-3 py-2 bg-orange-600 text-white rounded-md text-sm hover:bg-orange-700">Сохранить</button>
</div>
</form>
</div>
</div>
<div id="toast" class="fixed bottom-4 right-4 z-50"></div>
<script>
@@ -197,105 +160,7 @@
}
}
function closeConnectionSettingsModal() {
const modal = document.getElementById('connection-settings-modal');
if (!modal) return;
modal.classList.add('hidden');
modal.classList.remove('flex');
}
function showConnectionSettingsStatus(message, type) {
const el = document.getElementById('connection-settings-status');
if (!el) return;
el.classList.remove('hidden', 'bg-green-100', 'text-green-800', 'bg-red-100', 'text-red-800', 'bg-orange-100', 'text-orange-800');
if (type === 'success') {
el.classList.add('bg-green-100', 'text-green-800');
} else if (type === 'error') {
el.classList.add('bg-red-100', 'text-red-800');
} else {
el.classList.add('bg-orange-100', 'text-orange-800');
}
el.textContent = message;
}
async function openConnectionSettingsModal() {
const modal = document.getElementById('connection-settings-modal');
if (!modal) return;
modal.classList.remove('hidden');
modal.classList.add('flex');
showConnectionSettingsStatus('Загрузка текущих параметров...', 'info');
try {
const resp = await fetch('/api/connection-settings');
const data = await resp.json();
document.getElementById('connection-host').value = data.host || '';
document.getElementById('connection-port').value = data.port || 3306;
document.getElementById('connection-database').value = data.database || '';
document.getElementById('connection-user').value = data.user || '';
document.getElementById('connection-password').value = '';
document.getElementById('connection-settings-status').classList.add('hidden');
} catch (e) {
showConnectionSettingsStatus('Не удалось загрузить параметры: ' + e.message, 'error');
}
}
async function testConnectionSettings() {
const form = document.getElementById('connection-settings-form');
if (!form) return;
showConnectionSettingsStatus('Проверка подключения...', 'info');
try {
const resp = await fetch('/api/connection-settings/test', {
method: 'POST',
body: new FormData(form),
});
const data = await resp.json();
if (data.success) {
showConnectionSettingsStatus('Подключение успешно.', 'success');
} else {
showConnectionSettingsStatus(data.error || 'Ошибка проверки подключения.', 'error');
}
} catch (e) {
showConnectionSettingsStatus('Ошибка сети: ' + e.message, 'error');
}
}
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('connection-settings-form');
if (form) {
form.addEventListener('submit', async function (e) {
e.preventDefault();
showConnectionSettingsStatus('Сохранение параметров...', 'info');
try {
const resp = await fetch('/api/connection-settings', {
method: 'POST',
body: new FormData(form),
});
const data = await resp.json();
if (!resp.ok || !data.success) {
showConnectionSettingsStatus(data.error || 'Ошибка сохранения параметров.', 'error');
return;
}
showConnectionSettingsStatus('Параметры сохранены. Выполняется перезапуск...', 'success');
setTimeout(async function () {
try {
await fetch('/api/restart', { method: 'POST' });
} catch (_) {
}
}, 300);
} catch (e) {
showConnectionSettingsStatus('Ошибка сети: ' + e.message, 'error');
}
});
}
const modal = document.getElementById('connection-settings-modal');
if (modal) {
modal.addEventListener('click', function (e) {
if (e.target === modal) closeConnectionSettingsModal();
});
}
refreshSystemStatus();
setInterval(refreshSystemStatus, 5000); // 5 second polling
});