PHP Classes

File: templates/admin/chat_rooms.twig

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   templates/admin/chat_rooms.twig   Download  
File: templates/admin/chat_rooms.twig
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 1 month ago
Size: 26,995 bytes
 

Contents

Class file image Download
{% extends "base.twig" %} {% block title %}{{ t('ui.admin.chat_rooms.page_title', {}, locale, ['common']) }}{% endblock %} {% block content %} <div class="container-fluid"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="h2">{{ t('ui.admin.chat_rooms.heading', {}, locale, ['common']) }}</h1> <button type="button" class="btn btn-primary" onclick="showCreateRoomModal()"> <i class="fas fa-plus me-2"></i>{{ t('ui.admin.chat_rooms.add_room', {}, locale, ['common']) }} </button> </div> <div class="card shadow mb-3"> <div class="card-header"> <i class="fas fa-bridge-water me-2"></i>{{ t('ui.admin.matterbridge_settings.heading', {}, locale, ['common']) }} </div> <div class="card-body"> <div id="matterbridgeAlert"></div> <form id="matterbridgeSettingsForm"> <div class="form-check form-switch mb-3"> <input class="form-check-input" type="checkbox" id="matterbridgeEnabled"> <label class="form-check-label" for="matterbridgeEnabled">{{ t('ui.admin.matterbridge_settings.enabled', {}, locale, ['common']) }}</label> </div> <div class="row g-3"> <div class="col-md-6"> <label for="matterbridgeBaseUrl" class="form-label">{{ t('ui.admin.matterbridge_settings.base_url', {}, locale, ['common']) }}</label> <input type="url" class="form-control" id="matterbridgeBaseUrl" placeholder="http://127.0.0.1:4240"> </div> <div class="col-md-3"> <label for="matterbridgeTimeout" class="form-label">{{ t('ui.admin.matterbridge_settings.timeout_seconds', {}, locale, ['common']) }}</label> <input type="number" min="1" max="120" class="form-control" id="matterbridgeTimeout" value="10"> </div> <div class="col-md-3"> <label for="matterbridgeUsernameSuffix" class="form-label">{{ t('ui.admin.matterbridge_settings.username_suffix', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="matterbridgeUsernameSuffix" placeholder=" @ BinktermPHP"> </div> <div class="col-md-6"> <label for="matterbridgeToken" class="form-label">{{ t('ui.admin.matterbridge_settings.token', {}, locale, ['common']) }}</label> <div class="input-group"> <input type="password" class="form-control" id="matterbridgeToken" autocomplete="off"> <button class="btn btn-outline-secondary" type="button" onclick="toggleMatterbridgeTokenVisibility(this)" title="{{ t('ui.common.show_password', {}, locale, ['common']) }}" aria-label="{{ t('ui.common.show_password', {}, locale, ['common']) }}"> <i class="fas fa-eye"></i> </button> </div> </div> <div class="col-md-3"> <label for="matterbridgeBridgeUserSearch" class="form-label">{{ t('ui.admin.matterbridge_settings.bridge_user_id', {}, locale, ['common']) }}</label> <input type="hidden" id="matterbridgeBridgeUserId"> <div class="position-relative"> <input type="text" class="form-control" id="matterbridgeBridgeUserSearch" autocomplete="off" placeholder="{{ t('ui.admin.matterbridge_settings.bridge_user_search_placeholder', {}, locale, ['common']) }}"> <div id="matterbridgeBridgeUserResults" class="list-group position-absolute w-100 shadow-sm d-none" style="z-index: 1060;"></div> </div> <div class="form-text">{{ t('ui.admin.matterbridge_settings.bridge_user_id_help', {}, locale, ['common']) }}</div> </div> <div class="col-md-3 d-flex align-items-start pt-4"> <div class="form-check mt-1"> <input class="form-check-input" type="checkbox" id="matterbridgeSkipTlsVerify"> <label class="form-check-label" for="matterbridgeSkipTlsVerify">{{ t('ui.admin.matterbridge_settings.skip_tls_verify', {}, locale, ['common']) }}</label> </div> </div> </div> <div class="form-text mt-2">{{ t('ui.admin.matterbridge_settings.help', {}, locale, ['common']) }}</div> <div class="mt-3"> <button type="button" class="btn btn-primary" onclick="saveMatterbridgeSettings()"> {{ t('ui.admin.matterbridge_settings.save', {}, locale, ['common']) }} </button> </div> </form> </div> </div> <div class="card shadow"> <div class="card-body"> <div class="table-responsive"> <table class="table table-hover" id="chatRoomsTable"> <thead class="table-dark"> <tr> <th>{{ t('ui.common.id', {}, locale, ['common']) }}</th> <th>{{ t('ui.common.name', {}, locale, ['common']) }}</th> <th>{{ t('ui.common.description', {}, locale, ['common']) }}</th> <th>{{ t('ui.admin.chat_rooms.bridge', {}, locale, ['common']) }}</th> <th>{{ t('ui.admin.chat_rooms.status', {}, locale, ['common']) }}</th> <th>{{ t('ui.admin.chat_rooms.created', {}, locale, ['common']) }}</th> <th>{{ t('ui.admin.chat_rooms.actions', {}, locale, ['common']) }}</th> </tr> </thead> <tbody id="chatRoomsTableBody"> <!-- Populated by JavaScript --> </tbody> </table> </div> </div> </div> </div> <div class="modal fade" id="chatRoomModal" tabindex="-1" aria-labelledby="chatRoomModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="chatRoomModalLabel">{{ t('ui.admin.chat_rooms.add_chat_room', {}, locale, ['common']) }}</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ t('ui.common.close', {}, locale, ['common']) }}"></button> </div> <div class="modal-body"> <form id="chatRoomForm"> <input type="hidden" id="chatRoomId" name="chatRoomId"> <div class="mb-3"> <label for="chatRoomName" class="form-label">{{ t('ui.common.name', {}, locale, ['common']) }} <span class="text-danger">*</span></label> <input type="text" class="form-control" id="chatRoomName" name="name" required> </div> <div class="mb-3"> <label for="chatRoomDescription" class="form-label">{{ t('ui.common.description', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="chatRoomDescription" name="description"> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="chatRoomActive" name="is_active" checked> <label class="form-check-label" for="chatRoomActive"> {{ t('ui.admin.chat_rooms.active_room', {}, locale, ['common']) }} </label> </div> <hr> <div class="form-check form-switch mb-3"> <input class="form-check-input" type="checkbox" id="chatRoomMatterbridgeEnabled" name="matterbridge_enabled"> <label class="form-check-label" for="chatRoomMatterbridgeEnabled"> {{ t('ui.admin.chat_rooms.matterbridge_enabled', {}, locale, ['common']) }} </label> </div> <div class="mb-3"> <label for="chatRoomMatterbridgeGateway" class="form-label">{{ t('ui.admin.chat_rooms.matterbridge_gateway', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="chatRoomMatterbridgeGateway" name="matterbridge_gateway"> <div class="form-text">{{ t('ui.admin.chat_rooms.matterbridge_gateway_help', {}, locale, ['common']) }}</div> </div> <div class="mb-3"> <label for="chatRoomMatterbridgeUsernameTemplate" class="form-label">{{ t('ui.admin.chat_rooms.matterbridge_username_template', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="chatRoomMatterbridgeUsernameTemplate" name="matterbridge_username_template" placeholder="{username} @ BinktermPHP"> <div class="form-text">{{ t('ui.admin.chat_rooms.matterbridge_username_template_help', {}, locale, ['common']) }}</div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('ui.common.cancel', {}, locale, ['common']) }}</button> <button type="button" class="btn btn-primary" onclick="saveChatRoom()">{{ t('ui.admin.chat_rooms.save_room', {}, locale, ['common']) }}</button> </div> </div> </div> </div> <script> function apiError(payload, fallback) { if (window.getApiErrorMessage) { return window.getApiErrorMessage(payload, fallback); } if (payload && payload.error) { return String(payload.error); } return fallback; } function uiT(key, fallback, params = {}) { if (window.t) { return window.t(key, params, fallback); } return fallback; } function escapeHtml(text) { return String(text).replace(/[&<>"']/g, function(m) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[m]; }); } // ---- Bridge user search ---- let selectedBridgeUser = null; let bridgeUserSearchTimer = null; function initBridgeUserSearch() { const searchEl = document.getElementById('matterbridgeBridgeUserSearch'); if (!searchEl) return; searchEl.addEventListener('input', function() { selectedBridgeUser = null; document.getElementById('matterbridgeBridgeUserId').value = ''; clearTimeout(bridgeUserSearchTimer); bridgeUserSearchTimer = setTimeout(function() { searchBridgeUsers(searchEl.value); }, 200); }); searchEl.addEventListener('focus', function() { clearTimeout(bridgeUserSearchTimer); bridgeUserSearchTimer = setTimeout(function() { searchBridgeUsers(searchEl.value); }, 200); }); searchEl.addEventListener('blur', function() { setTimeout(function() { document.getElementById('matterbridgeBridgeUserResults').classList.add('d-none'); }, 150); }); } function searchBridgeUsers(query) { const search = (query || '').trim(); const results = document.getElementById('matterbridgeBridgeUserResults'); if (search.length < 2) { results.classList.add('d-none'); results.innerHTML = ''; return; } results.classList.remove('d-none'); results.innerHTML = `<div class="list-group-item text-muted">${uiT('ui.admin.auto_feed.searching_users', 'Searching users...')}</div>`; fetch(`/admin/api/users?search=${encodeURIComponent(search)}&limit=10`) .then(r => r.json()) .then(data => displayBridgeUserResults(data.users || [])) .catch(() => { results.classList.remove('d-none'); results.innerHTML = `<div class="list-group-item text-danger">${uiT('ui.admin.auto_feed.user_search_failed', 'User search failed')}</div>`; }); } function displayBridgeUserResults(users) { const results = document.getElementById('matterbridgeBridgeUserResults'); results.innerHTML = ''; if (!users.length) { results.classList.remove('d-none'); results.innerHTML = `<div class="list-group-item text-muted">${uiT('ui.admin.auto_feed.no_users_found', 'No users found')}</div>`; return; } users.forEach(function(user) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'list-group-item list-group-item-action'; btn.innerHTML = `<div class="fw-semibold">${escapeHtml(user.username || '')}</div><small class="text-muted">${escapeHtml(user.real_name || '')}</small>`; btn.addEventListener('mousedown', function(e) { e.preventDefault(); setSelectedBridgeUser(user); results.classList.add('d-none'); }); results.appendChild(btn); }); results.classList.remove('d-none'); } function setSelectedBridgeUser(user) { selectedBridgeUser = (user && user.id) ? user : null; const hiddenEl = document.getElementById('matterbridgeBridgeUserId'); const searchEl = document.getElementById('matterbridgeBridgeUserSearch'); if (!hiddenEl || !searchEl) return; if (selectedBridgeUser) { hiddenEl.value = selectedBridgeUser.id; const label = selectedBridgeUser.real_name ? `${selectedBridgeUser.username} (${selectedBridgeUser.real_name})` : selectedBridgeUser.username; searchEl.value = label; } else { hiddenEl.value = ''; searchEl.value = ''; } } // ---- End bridge user search ---- document.addEventListener('DOMContentLoaded', function() { loadI18nNamespaces(['common', 'errors']).then(function() { initBridgeUserSearch(); loadMatterbridgeSettings(); loadChatRooms(); }); }); function toggleMatterbridgeTokenVisibility(btn) { const input = document.getElementById('matterbridgeToken'); const icon = btn.querySelector('i'); const isHidden = input.type === 'password'; input.type = isHidden ? 'text' : 'password'; icon.classList.toggle('fa-eye', !isHidden); icon.classList.toggle('fa-eye-slash', isHidden); } function loadMatterbridgeSettings() { fetch('/admin/api/matterbridge-settings') .then(response => response.json()) .then(data => { const config = data.config || {}; document.getElementById('matterbridgeEnabled').checked = !!config.enabled; document.getElementById('matterbridgeBaseUrl').value = config.api?.base_url || 'http://127.0.0.1:4240'; document.getElementById('matterbridgeToken').value = config.api?.token || ''; document.getElementById('matterbridgeTimeout').value = config.api?.timeout_seconds || 10; document.getElementById('matterbridgeSkipTlsVerify').checked = !!config.api?.skip_tls_verify; document.getElementById('matterbridgeUsernameSuffix').value = config.defaults?.username_suffix || ' @ BinktermPHP'; const bridgeId = parseInt(config.bridge_user_id || 0, 10); if (bridgeId > 0) { fetch(`/admin/api/users/${bridgeId}`) .then(r => r.json()) .then(u => { if (u && u.user && u.user.id) setSelectedBridgeUser(u.user); }) .catch(() => {}); } else { setSelectedBridgeUser(null); } }) .catch(error => { console.error(uiT('ui.admin.matterbridge_settings.load_failed', 'Error loading Matterbridge settings') + ':', error); showInlineAlert('matterbridgeAlert', uiT('ui.admin.matterbridge_settings.load_failed', 'Error loading Matterbridge settings'), 'danger'); }); } function saveMatterbridgeSettings() { const payload = { config: { enabled: document.getElementById('matterbridgeEnabled').checked, bridge_user_id: parseInt(document.getElementById('matterbridgeBridgeUserId').value || '0', 10), api: { base_url: document.getElementById('matterbridgeBaseUrl').value.trim(), token: document.getElementById('matterbridgeToken').value, timeout_seconds: parseInt(document.getElementById('matterbridgeTimeout').value || '10', 10), skip_tls_verify: document.getElementById('matterbridgeSkipTlsVerify').checked }, defaults: { username_suffix: document.getElementById('matterbridgeUsernameSuffix').value } } }; fetch('/admin/api/matterbridge-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(response => response.json()) .then(data => { if (data.success) { const message = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.matterbridge_settings.saved_success', 'Matterbridge settings saved successfully')) : uiT('ui.admin.matterbridge_settings.saved_success', 'Matterbridge settings saved successfully'); showInlineAlert('matterbridgeAlert', message, 'success'); } else { showInlineAlert('matterbridgeAlert', apiError(data, uiT('ui.admin.matterbridge_settings.save_failed', 'Error saving Matterbridge settings')), 'danger'); } }) .catch(error => { console.error(uiT('ui.admin.matterbridge_settings.save_failed', 'Error saving Matterbridge settings') + ':', error); showInlineAlert('matterbridgeAlert', uiT('ui.admin.matterbridge_settings.save_failed', 'Error saving Matterbridge settings'), 'danger'); }); } function loadChatRooms() { fetch('/admin/api/chat-rooms') .then(response => response.json()) .then(data => { displayChatRooms(data.rooms || []); }) .catch(error => { console.error(uiT('ui.admin.chat_rooms.load_failed', 'Error loading chat rooms') + ':', error); showAlert(uiT('ui.admin.chat_rooms.load_failed', 'Error loading chat rooms'), 'danger'); }); } function displayChatRooms(rooms) { const tbody = document.getElementById('chatRoomsTableBody'); tbody.innerHTML = ''; rooms.forEach(room => { const row = document.createElement('tr'); const isLobby = room.name === 'Lobby'; row.innerHTML = ` <td>${room.id}</td> <td>${room.name}</td> <td>${room.description || uiT('ui.admin.chat_rooms.no_description', '-')}</td> <td> ${room.matterbridge_enabled ? `<span class="badge bg-info text-dark">${escapeHtml(room.matterbridge_gateway || uiT('ui.admin.chat_rooms.bridge_enabled', 'Enabled'))}</span>` : `<span class="badge bg-secondary">${uiT('ui.admin.chat_rooms.bridge_disabled', 'Disabled')}</span>`} </td> <td> <span class="badge bg-${room.is_active ? 'success' : 'secondary'}"> ${room.is_active ? uiT('ui.admin.chat_rooms.active', 'Active') : uiT('ui.admin.chat_rooms.inactive', 'Inactive')} </span> </td> <td>${new Date(room.created_at).toLocaleDateString()}</td> <td> <div class="btn-group btn-group-sm" role="group"> <button type="button" class="btn btn-outline-primary" onclick="editChatRoom(${room.id})" title="${uiT('ui.common.edit', 'Edit')}"> <i class="fas fa-edit"></i> </button> <button type="button" class="btn btn-outline-danger" onclick="deleteChatRoom(${room.id})" title="${uiT('ui.common.delete', 'Delete')}" ${isLobby ? 'disabled' : ''}> <i class="fas fa-trash"></i> </button> </div> </td> `; tbody.appendChild(row); }); } function showCreateRoomModal() { document.getElementById('chatRoomModalLabel').textContent = uiT('ui.admin.chat_rooms.add_chat_room', 'Add Chat Room'); document.getElementById('chatRoomForm').reset(); document.getElementById('chatRoomId').value = ''; document.getElementById('chatRoomName').disabled = false; document.getElementById('chatRoomActive').checked = true; document.getElementById('chatRoomMatterbridgeEnabled').checked = false; document.getElementById('chatRoomMatterbridgeGateway').value = ''; document.getElementById('chatRoomMatterbridgeUsernameTemplate').value = ''; new bootstrap.Modal(document.getElementById('chatRoomModal')).show(); } function editChatRoom(roomId) { fetch('/admin/api/chat-rooms') .then(response => response.json()) .then(data => { const room = (data.rooms || []).find(item => item.id === roomId); if (!room) { showAlert(uiT('ui.admin.chat_rooms.not_found', 'Chat room not found'), 'danger'); return; } document.getElementById('chatRoomModalLabel').textContent = uiT('ui.admin.chat_rooms.edit_chat_room', 'Edit Chat Room'); document.getElementById('chatRoomId').value = room.id; document.getElementById('chatRoomName').value = room.name; document.getElementById('chatRoomName').disabled = room.name === 'Lobby'; document.getElementById('chatRoomDescription').value = room.description || ''; document.getElementById('chatRoomActive').checked = !!room.is_active; document.getElementById('chatRoomMatterbridgeEnabled').checked = !!room.matterbridge_enabled; document.getElementById('chatRoomMatterbridgeGateway').value = room.matterbridge_gateway || ''; document.getElementById('chatRoomMatterbridgeUsernameTemplate').value = room.matterbridge_options?.username_template || ''; new bootstrap.Modal(document.getElementById('chatRoomModal')).show(); }) .catch(error => { console.error(uiT('ui.admin.chat_rooms.load_single_failed', 'Error loading chat room') + ':', error); showAlert(uiT('ui.admin.chat_rooms.load_single_failed', 'Error loading chat room'), 'danger'); }); } function saveChatRoom() { const form = document.getElementById('chatRoomForm'); const formData = new FormData(form); const roomId = formData.get('chatRoomId'); const payload = { name: formData.get('name'), description: formData.get('description'), is_active: formData.has('is_active'), matterbridge_enabled: formData.has('matterbridge_enabled'), matterbridge_gateway: formData.get('matterbridge_gateway'), matterbridge_options: { username_template: formData.get('matterbridge_username_template') } }; const url = roomId ? `/admin/api/chat-rooms/${roomId}` : '/admin/api/chat-rooms'; const method = roomId ? 'PUT' : 'POST'; fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(response => response.json()) .then(data => { if (data.success || data.id) { const successMessage = window.getApiMessage ? window.getApiMessage( data, roomId ? uiT('ui.admin.chat_rooms.updated_success', 'Room updated successfully') : uiT('ui.admin.chat_rooms.created_success', 'Room created successfully') ) : (roomId ? uiT('ui.admin.chat_rooms.updated_success', 'Room updated successfully') : uiT('ui.admin.chat_rooms.created_success', 'Room created successfully')); showAlert(successMessage, 'success'); bootstrap.Modal.getInstance(document.getElementById('chatRoomModal')).hide(); loadChatRooms(); } else { showAlert(apiError(data, uiT('ui.admin.chat_rooms.save_failed', 'Error saving room')), 'danger'); } }) .catch(error => { console.error(uiT('ui.admin.chat_rooms.save_failed', 'Error saving room') + ':', error); showAlert(uiT('ui.admin.chat_rooms.save_failed', 'Error saving room'), 'danger'); }); } function deleteChatRoom(roomId) { if (!confirm(uiT('ui.admin.chat_rooms.delete_confirm', 'Delete this chat room? This cannot be undone.'))) { return; } fetch(`/admin/api/chat-rooms/${roomId}`, { method: 'DELETE' }) .then(response => response.json()) .then(data => { if (data.success) { const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.chat_rooms.deleted_success', 'Room deleted successfully')) : uiT('ui.admin.chat_rooms.deleted_success', 'Room deleted successfully'); showAlert(successMessage, 'success'); loadChatRooms(); } else { showAlert(apiError(data, uiT('ui.admin.chat_rooms.delete_failed', 'Error deleting room')), 'danger'); } }) .catch(error => { console.error(uiT('ui.admin.chat_rooms.delete_failed', 'Error deleting room') + ':', error); showAlert(uiT('ui.admin.chat_rooms.delete_failed', 'Error deleting room'), 'danger'); }); } function showAlert(message, type) { const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type} alert-dismissible fade show`; alertDiv.innerHTML = ` ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> `; const container = document.querySelector('.container-fluid'); container.insertBefore(alertDiv, container.firstChild); setTimeout(() => { alertDiv.remove(); }, 5000); } function showInlineAlert(elementId, message, type) { const container = document.getElementById(elementId); if (!container) { return; } container.innerHTML = ` <div class="alert alert-${type} alert-dismissible fade show" role="alert"> ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> `; } </script> {% endblock %}