PHP Classes

File: templates/admin/webdoors_config.twig

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   templates/admin/webdoors_config.twig   Download  
File: templates/admin/webdoors_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: 14,755 bytes
 

Contents

Class file image Download
{% extends "base.twig" %} {% block title %}{{ t('ui.admin.webdoors_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.webdoors_config.heading', {}, locale, ['common']) }}</h1> </div> <div class="alert alert-info"> <i class="fas fa-info-circle"></i> {{ t('ui.admin.webdoors_config.info_text_prefix', {}, locale, ['common']) }} <code>config/webdoors.json</code> {{ t('ui.admin.webdoors_config.info_text_suffix', {}, locale, ['common']) }} </div> <div id="webdoorsStatusAlert"></div> <div class="card shadow mb-4"> <div class="card-header d-flex justify-content-between align-items-center"> <h6 class="mb-0">{{ t('ui.admin.webdoors_config.status', {}, locale, ['common']) }}</h6> <div> <button class="btn btn-sm btn-outline-primary" type="button" onclick="activateWebdoors()" id="activateWebdoorsBtn"> {{ t('ui.admin.webdoors_config.activate_webdoors', {}, locale, ['common']) }} </button> </div> </div> <div class="card-body"> <div class="row"> <div class="col-md-6"> <div class="mb-2"><strong>{{ t('ui.admin.webdoors_config.config_file', {}, locale, ['common']) }}</strong> <span id="webdoorsConfigStatus">{{ t('ui.common.loading', {}, locale, ['common']) }}</span></div> <div class="mb-2 text-muted">{{ t('ui.admin.webdoors_config.config_never_deleted', {}, locale, ['common']) }}</div> </div> <div class="col-md-6 text-md-end text-muted"> <small>{{ t('ui.admin.webdoors_config.json_controls_help', {}, locale, ['common']) }}</small> </div> </div> </div> </div> <div class="row g-4" id="webdoorsEditorRow"> <div class="col-lg-4"> <div class="card shadow"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.webdoors_config.doors', {}, locale, ['common']) }}</h6> </div> <div class="card-body" id="webdoorsList"> <div class="text-muted">{{ t('ui.admin.webdoors_config.no_config_loaded', {}, locale, ['common']) }}</div> </div> </div> </div> <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.webdoors_config.config_filename', {}, locale, ['common']) }}</h6> <div class="btn-group"> <button class="btn btn-sm btn-outline-secondary" type="button" onclick="formatWebdoorsJson()">{{ t('ui.admin.webdoors_config.format_json', {}, locale, ['common']) }}</button> <button class="btn btn-sm btn-outline-primary" type="button" onclick="saveWebdoorsConfig()" id="saveWebdoorsBtn">{{ t('ui.common.save', {}, locale, ['common']) }}</button> </div> </div> <div class="card-body"> <div id="webdoorsEditorAlert"></div> <textarea id="webdoorsJsonEditor" class="form-control" rows="20" spellcheck="false" style="font-family: Consolas, 'Courier New', monospace;"></textarea> <div class="d-flex justify-content-between align-items-center mt-2"> <small class="text-muted" id="webdoorsJsonStatus">{{ t('ui.admin.webdoors_config.waiting_for_config', {}, locale, ['common']) }}</small> <small class="text-muted">{{ t('ui.admin.webdoors_config.json_validation_before_save', {}, locale, ['common']) }}</small> </div> </div> </div> </div> </div> </div> {% endblock %} {% block scripts %} <script> let webdoorsConfigState = { active: false, configJson: null, exampleJson: null, availableDoors: [] }; function uiT(key, fallback, params = {}) { if (window.t) { return window.t(key, params, fallback); } return fallback; } document.addEventListener('DOMContentLoaded', function() { loadI18nNamespaces(['common', 'errors']).then(function() { loadWebdoorsConfig(); loadAvailableDoors(); }); document.getElementById('webdoorsJsonEditor').addEventListener('input', () => { updateJsonStatus(); renderDoorList(); }); }); function loadWebdoorsConfig() { fetch('/admin/api/webdoors-config') .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.webdoors_config.load_webdoors_failed', 'Failed to load webdoors config'))); } const cfg = data.config || {}; webdoorsConfigState.active = !!cfg.active; webdoorsConfigState.configJson = cfg.config_json || null; webdoorsConfigState.exampleJson = cfg.example_json || null; const editorValue = webdoorsConfigState.configJson || webdoorsConfigState.exampleJson || '{}'; document.getElementById('webdoorsJsonEditor').value = editorValue; updateStatus(); updateJsonStatus(); renderDoorList(); }) .catch(error => { showStatusAlert(error.message || uiT('ui.admin.webdoors_config.load_failed', 'Failed to load config'), 'danger'); }); } function loadAvailableDoors() { fetch('/admin/api/webdoors-available') .then(response => response.json()) .then(data => { webdoorsConfigState.availableDoors = Array.isArray(data.doors) ? data.doors : []; renderDoorList(); }) .catch(() => {}); } function updateStatus() { const statusText = webdoorsConfigState.active ? uiT('ui.admin.webdoors_config.active_present', 'Active (webdoors.json present)') : uiT('ui.admin.webdoors_config.not_active_using_example', 'Not active (using example)'); document.getElementById('webdoorsConfigStatus').textContent = statusText; document.getElementById('activateWebdoorsBtn').disabled = webdoorsConfigState.active; document.getElementById('saveWebdoorsBtn').disabled = !webdoorsConfigState.active; document.getElementById('webdoorsEditorRow').style.display = webdoorsConfigState.active ? '' : 'none'; } function updateJsonStatus() { const editor = document.getElementById('webdoorsJsonEditor'); const status = document.getElementById('webdoorsJsonStatus'); const { valid } = parseEditorJson(editor.value); status.textContent = valid ? uiT('ui.admin.webdoors_config.json_valid', 'JSON is valid') : uiT('ui.admin.webdoors_config.json_has_errors', 'JSON has errors'); status.className = valid ? 'text-success' : 'text-danger'; } function parseEditorJson(value) { try { return { valid: true, data: JSON.parse(value) }; } catch (err) { return { valid: false, error: err }; } } function renderDoorList() { const list = document.getElementById('webdoorsList'); const editorValue = document.getElementById('webdoorsJsonEditor').value; const parsed = parseEditorJson(editorValue); if (!parsed.valid || !parsed.data || typeof parsed.data !== 'object') { list.innerHTML = `<div class="text-muted">${uiT('ui.admin.webdoors_config.invalid_json', 'Invalid JSON.')}</div>`; return; } const configKeys = Object.keys(parsed.data); const availableKeys = webdoorsConfigState.availableDoors.map(door => door.id); const mergedKeys = Array.from(new Set([...configKeys, ...availableKeys])).sort(); if (mergedKeys.length === 0) { list.innerHTML = `<div class="text-muted">${uiT('ui.admin.webdoors_config.no_doors_defined', 'No doors defined.')}</div>`; return; } const items = mergedKeys.map((key) => { const value = parsed.data[key]; const enabled = value && typeof value === 'object' ? !!value.enabled : false; const isMissing = !(key in parsed.data); const doorInfo = webdoorsConfigState.availableDoors.find(door => door.id === key); const displayName = doorInfo?.name ? `${doorInfo.name}` : key; const encodedKey = encodeURIComponent(key); return ` <div class="d-flex justify-content-between align-items-center border-bottom py-2"> <div> <div class="fw-semibold">${escapeHtml(displayName)}</div> <small class="text-muted">${uiT('ui.admin.webdoors_config.enabled_label', 'enabled')}: ${enabled ? uiT('ui.admin.webdoors_config.true', 'true') : uiT('ui.admin.webdoors_config.false', 'false')}${isMissing ? ' ' + uiT('ui.admin.webdoors_config.not_in_config', '(not in config)') : ''}</small> </div> <div class="form-check form-switch"> <input class="form-check-input" type="checkbox" data-door-key="${encodedKey}" ${enabled ? 'checked' : ''} onchange="toggleDoorEnabled(this)"> </div> </div> `; }); list.innerHTML = items.join(''); } function toggleDoorEnabled(input) { const key = decodeURIComponent(input.dataset.doorKey || ''); const enabled = input.checked; const editor = document.getElementById('webdoorsJsonEditor'); const parsed = parseEditorJson(editor.value); if (!parsed.valid) { return; } if (!parsed.data[key] || typeof parsed.data[key] !== 'object') { parsed.data[key] = {}; } parsed.data[key].enabled = enabled; if (enabled) { const doorInfo = webdoorsConfigState.availableDoors.find(door => door.id === key); const manifestConfig = doorInfo?.config && typeof doorInfo.config === 'object' ? doorInfo.config : null; if (manifestConfig) { Object.keys(manifestConfig).forEach(configKey => { if (!(configKey in parsed.data[key])) { parsed.data[key][configKey] = manifestConfig[configKey]; } }); } } editor.value = JSON.stringify(parsed.data, null, 2); updateJsonStatus(); } function formatWebdoorsJson() { const editor = document.getElementById('webdoorsJsonEditor'); const parsed = parseEditorJson(editor.value); if (!parsed.valid) { showEditorAlert(uiT('ui.admin.webdoors_config.cannot_format_invalid_json', 'Cannot format: invalid JSON'), 'danger'); return; } editor.value = JSON.stringify(parsed.data, null, 2); updateJsonStatus(); } function saveWebdoorsConfig() { const editor = document.getElementById('webdoorsJsonEditor'); const parsed = parseEditorJson(editor.value); if (!parsed.valid) { showEditorAlert(uiT('ui.admin.webdoors_config.fix_json_before_save', 'Please fix JSON errors before saving.'), 'danger'); return; } fetch('/admin/api/webdoors-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ json: editor.value }) }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.webdoors_config.save_failed', 'Failed to save config'))); } const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.webdoors_config.saved_success', 'Webdoors config saved.')) : uiT('ui.admin.webdoors_config.saved_success', 'Webdoors config saved.'); showEditorAlert(successMessage, 'success'); const cfg = data.config || {}; webdoorsConfigState.active = !!cfg.active; webdoorsConfigState.configJson = cfg.config_json || editor.value; updateStatus(); }) .catch(error => { showEditorAlert(error.message || uiT('ui.admin.webdoors_config.save_failed', 'Failed to save config'), 'danger'); }); } function activateWebdoors() { fetch('/admin/api/webdoors-activate', { method: 'POST' }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.webdoors_config.activate_failed', 'Failed to activate webdoors'))); } const cfg = data.config || {}; webdoorsConfigState.active = !!cfg.active; webdoorsConfigState.configJson = cfg.config_json || webdoorsConfigState.configJson; if (cfg.config_json) { document.getElementById('webdoorsJsonEditor').value = cfg.config_json; } updateStatus(); updateJsonStatus(); renderDoorList(); const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.webdoors_config.activated_success', 'Webdoors activated.')) : uiT('ui.admin.webdoors_config.activated_success', 'Webdoors activated.'); showStatusAlert(successMessage, 'success'); }) .catch(error => { showStatusAlert(error.message || uiT('ui.admin.webdoors_config.activate_failed', 'Failed to activate webdoors'), 'danger'); }); } function showStatusAlert(message, type) { const container = document.getElementById('webdoorsStatusAlert'); 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> `; } function showEditorAlert(message, type) { const container = document.getElementById('webdoorsEditorAlert'); 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> `; } function escapeHtml(value) { return String(value) .replaceAll('&', '&amp;') .replaceAll('<', '&lt;') .replaceAll('>', '&gt;') .replaceAll('"', '&quot;') .replaceAll("'", '&#039;'); } function apiError(payload, fallback) { if (window.getApiErrorMessage) { return window.getApiErrorMessage(payload, fallback); } if (payload && payload.error) { return String(payload.error); } return fallback; } </script> {% endblock %}