251 lines
14 KiB
HTML
251 lines
14 KiB
HTML
{{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>
|
||
<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);
|
||
}
|
||
};
|
||
|
||
loadServices();
|
||
setInterval(loadServices, 5000);
|
||
</script>
|
||
{{end}} |