залил

This commit is contained in:
2026-04-15 08:00:15 +03:00
commit 5549b3545e
51 changed files with 8073 additions and 0 deletions
+296
View File
@@ -0,0 +1,296 @@
{{define "content"}}
<h2>Веб-сервисы для OBS</h2>
<p>Создавайте несколько независимых оверлеев чата и оповещений. Каждый сервис работает на своём порту.</p>
<div class="card">
<button id="add-chat-btn" style="margin-right:10px;">➕ Добавить чат-оверлей</button>
<button id="add-alert-btn">🔔 Добавить оверлей оповещений</button>
</div>
<div class="card">
<h3>Тестирование оповещений</h3>
<label>Выберите alert-сервис:</label>
<select id="test-alert-service"></select>
<label>Тип события:</label>
<select id="test-alert-event">
<option value="follow">Фолловер</option>
<option value="subscribe">Подписка</option>
<option value="gift_sub">Подарочная подписка</option>
<option value="raid">Рейд</option>
<option value="reward_redemption">Награда</option>
</select>
<button id="test-alert-btn">🔔 Тестировать оповещение</button>
</div>
<div class="card">
<h3>Список сервисов</h3>
<table id="services-table" style="width:100%; border-collapse: collapse;">
<thead>
<tr><th>ID</th><th>Тип</th><th>Порт</th><th>Статус</th><th>Действия</th></tr>
</thead>
<tbody>
<tr><td colspan="5">Загрузка...</td></tr>
</tbody>
</table>
</div>
<!-- Модальное окно для создания/редактирования -->
<div id="service-modal" class="modal">
<div class="modal-content">
<h3 id="modal-title">Создание сервиса</h3>
<form id="service-form">
<input type="hidden" id="service-id" value="0">
<label>Порт: <input type="number" id="service-port" required style="width:100%;"></label>
<label><input type="checkbox" id="service-enabled" checked> Сервис активен</label>
<!-- Настройки чата (показываются только для типа chat) -->
<div id="chat-config-fields" style="display:none;">
<h4>Настройки чата</h4>
<label>Фон (цвет): <input type="color" id="chat-bg-color" value="#000000"></label>
<label>Цвет текста: <input type="color" id="chat-text-color" value="#ffffff"></label>
<label>Размер шрифта (px): <input type="number" id="chat-font-size" min="10" max="50" value="14"></label>
<label>Шрифт: <input type="text" id="chat-font-family" value="Arial, sans-serif"></label>
<label>Прозрачность (%): <input type="range" id="chat-opacity" min="0" max="100" value="80"> <span id="opacity-val">80</span></label>
<label>Таймаут сообщения (сек): <input type="number" id="chat-timeout" min="0" max="60" value="10"></label>
<label>Макс. сообщений: <input type="number" id="chat-max-msgs" min="1" max="100" value="20"></label>
<label><input type="checkbox" id="chat-show-badges" checked> Показывать значки</label>
<label><input type="checkbox" id="chat-show-timestamps"> Показывать время</label>
<div class="preview-box">
<strong>Превью:</strong>
<div id="chat-preview" class="chat-preview" style="background:#000; color:#fff; padding:8px; border-radius:4px; margin-top:6px;">
<span style="font-weight:bold;">TestUser:</span> Привет, мир!
</div>
</div>
</div>
<!-- Для alert-сервиса ничего лишнего не показываем -->
<div id="alert-config-fields" style="display:none;">
<p class="info">Оповещения настраиваются через HTTP-запросы к эндпоинту <code>/notify</code> этого сервиса. Все уведомления будут отображаться автоматически.</p>
</div>
<div style="margin-top:15px;">
<button type="submit">Сохранить</button>
<button type="button" id="close-modal">Отмена</button>
</div>
</form>
</div>
</div>
<script>
let currentType = 'chat';
let services = [];
async function loadServices() {
try {
const res = await fetch('/api/webservices');
if (!res.ok) throw new Error('HTTP ' + res.status);
services = await res.json();
if (!Array.isArray(services)) services = [];
const tbody = document.querySelector('#services-table tbody');
if (!services.length) {
tbody.innerHTML = '<tr><td colspan="5">Нет сервисов. Создайте первый.</td></tr>';
return;
}
tbody.innerHTML = services.map(s => `
<tr>
<td>${s.id}</td>
<td>${s.service_type === 'chat' ? 'Чат' : 'Оповещения'}</td>
<td>${s.port}</td>
<td class="status-${s.id}">${s.running ? '✅ Запущен' : (s.enabled ? '⏹ Остановлен' : '❌ Отключен')}</td>
<td>
<button onclick="startService(${s.id})">▶ Запустить</button>
<button onclick="stopService(${s.id})">⏹ Остановить</button>
<button onclick="editService(${s.id})">✏️ Редактировать</button>
<button onclick="deleteService(${s.id})">🗑 Удалить</button>
</td>
</tr>
`).join('');
} catch (err) {
console.error('loadServices error:', err);
document.querySelector('#services-table tbody').innerHTML = '<tr><td colspan="5">Ошибка загрузки</td></tr>';
}
}
async function startService(id) {
const res = await fetch(`/api/webservices/start?id=${id}`, { method: 'POST' });
if (res.ok) loadServices(); else alert('Ошибка запуска');
}
async function stopService(id) {
const res = await fetch(`/api/webservices/stop?id=${id}`, { method: 'POST' });
if (res.ok) loadServices(); else alert('Ошибка остановки');
}
async function deleteService(id) {
if (!confirm('Удалить сервис?')) return;
const res = await fetch(`/api/webservices/delete?id=${id}`, { method: 'DELETE' });
if (res.ok) loadServices(); else alert('Ошибка удаления');
}
async function editService(id) {
const service = services.find(s => s.id === id);
if (!service) return;
currentType = service.service_type;
document.getElementById('service-id').value = service.id;
document.getElementById('service-port').value = service.port;
document.getElementById('service-enabled').checked = service.enabled;
if (currentType === 'chat') {
const cfg = service.config_json ? JSON.parse(service.config_json) : {};
document.getElementById('chat-bg-color').value = cfg.bg_color || '#000000';
document.getElementById('chat-text-color').value = cfg.text_color || '#ffffff';
document.getElementById('chat-font-size').value = cfg.font_size || 14;
document.getElementById('chat-font-family').value = cfg.font_family || 'Arial, sans-serif';
document.getElementById('chat-opacity').value = cfg.opacity || 80;
document.getElementById('opacity-val').innerText = cfg.opacity || 80;
document.getElementById('chat-timeout').value = cfg.message_timeout_sec || 10;
document.getElementById('chat-max-msgs').value = cfg.max_messages || 20;
document.getElementById('chat-show-badges').checked = cfg.show_badges !== undefined ? cfg.show_badges : true;
document.getElementById('chat-show-timestamps').checked = cfg.show_timestamps || false;
updateChatPreview();
}
showModal();
}
function updateChatPreview() {
const bg = document.getElementById('chat-bg-color').value;
const color = document.getElementById('chat-text-color').value;
const fontSize = document.getElementById('chat-font-size').value;
const fontFamily = document.getElementById('chat-font-family').value;
const opacity = document.getElementById('chat-opacity').value;
const showBadges = document.getElementById('chat-show-badges').checked;
const showTimestamps = document.getElementById('chat-show-timestamps').checked;
const previewDiv = document.getElementById('chat-preview');
previewDiv.style.backgroundColor = bg;
previewDiv.style.color = color;
previewDiv.style.fontSize = fontSize + 'px';
previewDiv.style.fontFamily = fontFamily;
previewDiv.style.opacity = opacity/100;
let badgesHtml = '';
if (showBadges) badgesHtml = '<span style="margin-right:4px;">🎭</span><span style="margin-right:4px;">👑</span><span>✔️</span> ';
let timeHtml = '';
if (showTimestamps) timeHtml = '<span style="color:#aaa; margin-right:8px;">12:34</span> ';
previewDiv.innerHTML = timeHtml + badgesHtml + '<span style="font-weight:bold;">TestUser:</span> Привет, мир!';
}
function showModal() {
document.getElementById('service-modal').style.display = 'flex';
document.getElementById('chat-config-fields').style.display = currentType === 'chat' ? 'block' : 'none';
document.getElementById('alert-config-fields').style.display = currentType === 'alert' ? 'block' : 'none';
document.getElementById('modal-title').innerText = (document.getElementById('service-id').value == 0 ? 'Создание' : 'Редактирование') + (currentType === 'chat' ? ' чат-оверлея' : ' оверлея оповещений');
}
async function openCreateDialog(type) {
currentType = type;
document.getElementById('service-id').value = 0;
document.getElementById('service-enabled').checked = true;
// Определяем свободный порт
const res = await fetch('/api/webservices');
let servicesList = [];
if (res.ok) {
servicesList = await res.json();
if (!Array.isArray(servicesList)) servicesList = [];
}
const usedPorts = servicesList.map(s => s.port);
let basePort = 9000;
while (usedPorts.includes(basePort)) basePort++;
document.getElementById('service-port').value = basePort;
if (type === 'chat') {
// Настройки чата по умолчанию
document.getElementById('chat-bg-color').value = '#000000';
document.getElementById('chat-text-color').value = '#ffffff';
document.getElementById('chat-font-size').value = 14;
document.getElementById('chat-font-family').value = 'Arial, sans-serif';
document.getElementById('chat-opacity').value = 80;
document.getElementById('opacity-val').innerText = '80';
document.getElementById('chat-timeout').value = 10;
document.getElementById('chat-max-msgs').value = 20;
document.getElementById('chat-show-badges').checked = true;
document.getElementById('chat-show-timestamps').checked = false;
updateChatPreview();
}
showModal();
}
document.getElementById('add-chat-btn').onclick = () => openCreateDialog('chat');
document.getElementById('add-alert-btn').onclick = () => openCreateDialog('alert');
document.getElementById('close-modal').onclick = () => document.getElementById('service-modal').style.display = 'none';
// Обработчики для превью чата
document.getElementById('chat-bg-color').addEventListener('input', updateChatPreview);
document.getElementById('chat-text-color').addEventListener('input', updateChatPreview);
document.getElementById('chat-font-size').addEventListener('input', updateChatPreview);
document.getElementById('chat-font-family').addEventListener('input', updateChatPreview);
document.getElementById('chat-opacity').addEventListener('input', (e) => { document.getElementById('opacity-val').innerText = e.target.value; updateChatPreview(); });
document.getElementById('chat-show-badges').addEventListener('change', updateChatPreview);
document.getElementById('chat-show-timestamps').addEventListener('change', updateChatPreview);
document.getElementById('service-form').onsubmit = async (e) => {
e.preventDefault();
const id = parseInt(document.getElementById('service-id').value);
const port = parseInt(document.getElementById('service-port').value);
const enabled = document.getElementById('service-enabled').checked;
let config = {};
if (currentType === 'chat') {
config = {
bg_color: document.getElementById('chat-bg-color').value,
text_color: document.getElementById('chat-text-color').value,
font_size: parseInt(document.getElementById('chat-font-size').value),
font_family: document.getElementById('chat-font-family').value,
opacity: parseInt(document.getElementById('chat-opacity').value),
message_timeout_sec: parseInt(document.getElementById('chat-timeout').value),
max_messages: parseInt(document.getElementById('chat-max-msgs').value),
show_badges: document.getElementById('chat-show-badges').checked,
show_timestamps: document.getElementById('chat-show-timestamps').checked
};
} else {
// Для alert-сервиса отправляем пустой конфиг
config = {};
}
const url = id === 0 ? '/api/webservices/create' : `/api/webservices/update?id=${id}`;
const method = id === 0 ? 'POST' : 'PUT';
const body = JSON.stringify({ type: currentType, port, config, enabled });
const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body });
if (res.ok) {
document.getElementById('service-modal').style.display = 'none';
loadServices();
} else {
const err = await res.text();
alert('Ошибка: ' + err);
}
};
async function loadAlertServices() {
const res = await fetch('/api/webservices/alert/list');
const services = await res.json();
const select = document.getElementById('test-alert-service');
select.innerHTML = '<option value="">-- выберите сервис --</option>';
services.forEach(s => {
const option = document.createElement('option');
option.value = s.id;
option.textContent = s.name;
select.appendChild(option);
});
}
document.getElementById('test-alert-btn').onclick = async () => {
const serviceId = document.getElementById('test-alert-service').value;
if (!serviceId) {
alert('Выберите alert-сервис');
return;
}
const eventType = document.getElementById('test-alert-event').value;
const res = await fetch('/api/webservices/test/alert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ serviceId: parseInt(serviceId), eventType: eventType })
});
if (res.ok) alert('Тестовое оповещение отправлено');
else alert('Ошибка');
};
// Вызываем при загрузке страницы
loadAlertServices();
loadServices();
setInterval(loadServices, 5000);
</script>
{{end}}