PHP Classes

File: templates/admin/nativedoors_config.twig

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   templates/admin/nativedoors_config.twig   Download  
File: templates/admin/nativedoors_config.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: 5 days ago
Size: 19,749 bytes
 

Contents

Class file image Download
{% extends "base.twig" %} {% block title %}{{ t('ui.admin.nativedoors_config.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.nativedoors_config.heading', {}, locale, ['common']) }}</h1> </div> <div class="alert alert-info"> <i class="fas fa-info-circle"></i> {{ t('ui.admin.nativedoors_config.info_text_1', {}, locale, ['common']) }} <code>config/nativedoors.json</code>. {{ t('ui.admin.nativedoors_config.info_text_2', {}, locale, ['common']) }} <code>native-doors/doors/</code> {{ t('ui.admin.nativedoors_config.info_text_3', {}, locale, ['common']) }} <code>nativedoor.json</code> {{ t('ui.admin.nativedoors_config.info_text_4', {}, locale, ['common']) }} </div> <div id="statusAlert"></div> <div class="row g-4"> <!-- Available Doors List --> <div class="col-lg-4"> <div class="card shadow"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.nativedoors_config.installed_doors', {}, locale, ['common']) }}</h6> </div> <div class="card-body" id="doorsList"> <div class="text-center text-muted"> <div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">{{ t('ui.common.loading', {}, locale, ['common']) }}</span> </div> {{ t('ui.admin.nativedoors_config.loading_doors', {}, locale, ['common']) }} </div> </div> </div> <div class="card shadow mt-3"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.nativedoors_config.quick_actions', {}, locale, ['common']) }}</h6> </div> <div class="card-body"> <button class="btn btn-sm btn-outline-primary w-100 mb-2" onclick="enableAllDoors()"> <i class="fas fa-check-circle"></i> {{ t('ui.admin.nativedoors_config.enable_all_doors', {}, locale, ['common']) }} </button> <button class="btn btn-sm btn-outline-secondary w-100" onclick="disableAllDoors()"> <i class="fas fa-times-circle"></i> {{ t('ui.admin.nativedoors_config.disable_all_doors', {}, locale, ['common']) }} </button> </div> </div> </div> <!-- Configuration Editor --> <div class="col-lg-8"> <div class="card shadow"> <div class="card-header d-flex justify-content-between align-items-center"> <h6 class="mb-0">{{ t('ui.admin.nativedoors_config.config_filename', {}, locale, ['common']) }}</h6> <div class="btn-group"> <button class="btn btn-sm btn-outline-secondary" onclick="formatJson()"> <i class="fas fa-align-left"></i> {{ t('ui.admin.nativedoors_config.format', {}, locale, ['common']) }} </button> <button class="btn btn-sm btn-outline-primary" onclick="saveConfig()" id="saveBtn"> <i class="fas fa-save"></i> {{ t('ui.common.save', {}, locale, ['common']) }} </button> </div> </div> <div class="card-body"> <div id="editorAlert"></div> <textarea id="configEditor" class="form-control font-monospace" rows="20" spellcheck="false"></textarea> <div class="d-flex justify-content-between align-items-center mt-2"> <small class="text-muted" id="jsonStatus">{{ t('ui.admin.nativedoors_config.loading_config', {}, locale, ['common']) }}</small> <small class="text-muted">{{ t('ui.admin.nativedoors_config.json_validation_before_save', {}, locale, ['common']) }}</small> </div> </div> </div> <!-- Door Configuration Help --> <div class="card shadow mt-3"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.nativedoors_config.configuration_options', {}, locale, ['common']) }}</h6> </div> <div class="card-body"> <table class="table table-sm table-borderless"> <tr> <td class="text-nowrap"><code>enabled</code></td> <td>{{ t('ui.admin.nativedoors_config.option_enabled_help', {}, locale, ['common']) }}</td> </tr> <tr> <td class="text-nowrap"><code>credit_cost</code></td> <td>{{ t('ui.admin.nativedoors_config.option_credit_cost_help', {}, locale, ['common']) }}</td> </tr> <tr> <td class="text-nowrap"><code>max_time_minutes</code></td> <td>{{ t('ui.admin.nativedoors_config.option_max_time_help', {}, locale, ['common']) }}</td> </tr> <tr> <td class="text-nowrap"><code>max_concurrent_sessions</code></td> <td>{{ t('ui.admin.nativedoors_config.option_max_concurrent_help', {}, locale, ['common']) }}</td> </tr> <tr> <td class="text-nowrap"><code>allow_anonymous</code></td> <td>Allow unauthenticated (guest) access via <code>POST /api/door/guest/launch</code>. Requires <code>credit_cost: 0</code>.</td> </tr> <tr> <td class="text-nowrap"><code>guest_max_sessions</code></td> <td>Maximum concurrent guest sessions for this door (default: 2). Only applies when <code>allow_anonymous</code> is true.</td> </tr> <tr> <td class="text-nowrap"><code>terminal_size</code></td> <td> Canvas and initial BBS terminal dimensions for PubTerm. Preset values: <code>80x25</code>, <code>132x24</code>, <code>132x43</code>, <code>132x50</code>. Set to <code>autofit</code> to size the canvas to the user's browser window. Defaults to <code>80x25</code> if not set. Note: with <code>autofit</code> the BBS will start at the correct size but will not adapt if the user resizes their browser mid-session. </td> </tr> </table> </div> </div> </div> </div> </div> {% endblock %} {% block scripts %} <script> let doorsState = { config: {}, availableDoors: [], configExists: false, serverPlatform: '' }; function uiT(key, fallback, params = {}) { if (window.t) { return window.t(key, params, fallback); } return fallback; } function apiError(payload, fallback) { if (window.getApiErrorMessage) { return window.getApiErrorMessage(payload, fallback); } return payload && payload.error ? payload.error : fallback; } document.addEventListener('DOMContentLoaded', function() { loadI18nNamespaces(['common', 'errors']).then(function() { loadConfig(); loadAvailableDoors(); }); document.getElementById('configEditor').addEventListener('input', () => { updateJsonStatus(); renderDoorsList(); }); }); function loadConfig() { fetch('/admin/api/native-doors/config') .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.nativedoors_config.load_config_failed', 'Failed to load config'))); } doorsState.config = data.config || {}; doorsState.configExists = data.exists; const editor = document.getElementById('configEditor'); editor.value = JSON.stringify(doorsState.config, null, 4); updateJsonStatus(); renderDoorsList(); }) .catch(error => { showAlert('danger', uiT('ui.admin.nativedoors_config.load_config_error_prefix', 'Error loading config: ') + error.message); }); } function loadAvailableDoors() { fetch('/admin/api/native-doors') .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.nativedoors_config.load_doors_failed', 'Failed to load doors'))); } doorsState.availableDoors = data.doors || []; doorsState.serverPlatform = data.server_platform || ''; renderDoorsList(); }) .catch(error => { showAlert('danger', uiT('ui.admin.nativedoors_config.load_doors_error_prefix', 'Error loading available doors: ') + error.message); }); } function renderDoorsList() { const container = document.getElementById('doorsList'); if (doorsState.availableDoors.length === 0) { container.innerHTML = '<div class="text-muted">' + uiT('ui.admin.nativedoors_config.no_doors_found_prefix', 'No doors found. Install doors in') + ' <code>native-doors/doors/</code></div>'; return; } // Parse current editor config let currentConfig = {}; try { const editorValue = document.getElementById('configEditor').value.trim(); if (editorValue) { currentConfig = JSON.parse(editorValue); if (Array.isArray(currentConfig)) { currentConfig = {}; } } } catch (e) { currentConfig = doorsState.config; } function escHtml(str) { return String(str) .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } let html = '<div class="list-group list-group-flush">'; doorsState.availableDoors.forEach(door => { const doorConfig = currentConfig[door.id] || {}; const enabled = doorConfig.enabled || false; const cost = doorConfig.credit_cost || 0; const statusClass = enabled ? 'success' : 'secondary'; const statusIcon = enabled ? 'fa-check-circle' : 'fa-circle'; const platform = door.platform || []; const platformMismatch = platform.length > 0 && doorsState.serverPlatform && !platform.includes(doorsState.serverPlatform); const platformBadge = platformMismatch ? `<span class="badge bg-warning text-dark" title="Requires: ${escHtml(platform.join(', '))}. Server is ${escHtml(doorsState.serverPlatform)}."><i class="fas fa-exclamation-triangle"></i> ${escHtml(platform.join(', '))} only</span>` : ''; html += ` <div class="list-group-item"> <div class="d-flex justify-content-between align-items-start"> <div class="flex-grow-1"> <div class="d-flex align-items-center gap-2 mb-1"> <i class="fas ${statusIcon} text-${statusClass}"></i> <strong>${escHtml(door.name)}</strong> </div> <div class="small text-muted">${escHtml(door.description || uiT('ui.admin.nativedoors_config.no_description', 'No description'))}</div> <div class="small mt-1"> <span class="badge bg-secondary">${escHtml(door.id)}</span> ${cost > 0 ? `<span class="badge bg-info">${cost} ${uiT('ui.admin.nativedoors_config.credits', 'credits')}</span>` : '<span class="badge bg-success">' + uiT('ui.admin.nativedoors_config.free', 'Free') + '</span>'} ${platformBadge} </div> </div> <div class="btn-group-vertical btn-group-sm"> <button class="btn btn-outline-${enabled ? 'success' : 'secondary'} toggle-door-btn" data-door-id="${escHtml(door.id)}" data-enable="${!enabled}" title="${enabled ? uiT('ui.admin.nativedoors_config.disable', 'Disable') : uiT('ui.admin.nativedoors_config.enable', 'Enable')}"> <i class="fas fa-power-off"></i> </button> </div> </div> </div> `; }); html += '</div>'; container.innerHTML = html; container.querySelectorAll('.toggle-door-btn').forEach(btn => { btn.addEventListener('click', function() { toggleDoor(this.dataset.doorId, this.dataset.enable === 'true'); }); }); } function toggleDoor(doorId, enable) { try { const editor = document.getElementById('configEditor'); let config = JSON.parse(editor.value || '{}'); if (Array.isArray(config)) { config = {}; } if (!config[doorId]) { config[doorId] = { enabled: enable, credit_cost: 0, max_time_minutes: 30, max_concurrent_sessions: 10, allow_anonymous: false, guest_max_sessions: 2 }; } else { config[doorId].enabled = enable; } editor.value = JSON.stringify(config, null, 4); updateJsonStatus(); renderDoorsList(); } catch (e) { showEditorAlert('danger', uiT('ui.admin.nativedoors_config.invalid_json_toggle', 'Invalid JSON - cannot toggle door')); } } function enableAllDoors() { try { const editor = document.getElementById('configEditor'); let config = JSON.parse(editor.value || '{}'); if (Array.isArray(config)) { config = {}; } doorsState.availableDoors.forEach(door => { if (!config[door.id]) { config[door.id] = { enabled: true, credit_cost: 0, max_time_minutes: 30, max_concurrent_sessions: 10, allow_anonymous: false, guest_max_sessions: 2 }; } else { config[door.id].enabled = true; } }); editor.value = JSON.stringify(config, null, 4); updateJsonStatus(); renderDoorsList(); showEditorAlert('success', uiT('ui.admin.nativedoors_config.enabled_all_editor', 'All doors enabled in editor. Click Save to apply.')); } catch (e) { showEditorAlert('danger', uiT('ui.admin.nativedoors_config.invalid_json_enable_all', 'Invalid JSON - cannot enable all')); } } function disableAllDoors() { try { const editor = document.getElementById('configEditor'); let config = JSON.parse(editor.value || '{}'); Object.keys(config).forEach(doorId => { config[doorId].enabled = false; }); editor.value = JSON.stringify(config, null, 4); updateJsonStatus(); renderDoorsList(); showEditorAlert('warning', uiT('ui.admin.nativedoors_config.disabled_all_editor', 'All doors disabled in editor. Click Save to apply.')); } catch (e) { showEditorAlert('danger', uiT('ui.admin.nativedoors_config.invalid_json_disable_all', 'Invalid JSON - cannot disable all')); } } function formatJson() { try { const editor = document.getElementById('configEditor'); const config = JSON.parse(editor.value); editor.value = JSON.stringify(config, null, 4); updateJsonStatus(); showEditorAlert('success', uiT('ui.admin.nativedoors_config.json_formatted', 'JSON formatted successfully')); } catch (e) { showEditorAlert('danger', uiT('ui.admin.nativedoors_config.cannot_format_prefix', 'Cannot format - invalid JSON: ') + e.message); } } function updateJsonStatus() { const editor = document.getElementById('configEditor'); const status = document.getElementById('jsonStatus'); try { const value = editor.value.trim(); if (!value) { status.textContent = uiT('ui.admin.nativedoors_config.empty_config', 'Empty configuration'); status.className = 'text-warning'; return; } JSON.parse(value); status.textContent = uiT('ui.admin.nativedoors_config.valid_json', 'Valid JSON'); status.className = 'text-success'; } catch (e) { status.textContent = uiT('ui.admin.nativedoors_config.invalid_json_prefix', 'Invalid JSON: ') + e.message; status.className = 'text-danger'; } } function saveConfig() { const editor = document.getElementById('configEditor'); try { const config = JSON.parse(editor.value); document.getElementById('saveBtn').disabled = true; fetch('/admin/api/native-doors/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config: config }) }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.nativedoors_config.save_failed', 'Save failed'))); } doorsState.config = data.config; const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.nativedoors_config.saved_success', 'Configuration saved successfully!')) : uiT('ui.admin.nativedoors_config.saved_success', 'Configuration saved successfully!'); showAlert('success', successMessage); renderDoorsList(); }) .catch(error => { showAlert('danger', uiT('ui.admin.nativedoors_config.save_error_prefix', 'Error saving config: ') + error.message); }) .finally(() => { document.getElementById('saveBtn').disabled = false; }); } catch (e) { showEditorAlert('danger', uiT('ui.admin.nativedoors_config.cannot_save_invalid_json_prefix', 'Cannot save - invalid JSON: ') + e.message); } } function showAlert(type, message) { const alert = document.getElementById('statusAlert'); alert.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> `; } function showEditorAlert(type, message) { const alert = document.getElementById('editorAlert'); alert.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> `; setTimeout(() => { alert.innerHTML = ''; }, 5000); } </script> {% endblock %}