Files
2026-05-16 13:44:12 -04:00

275 lines
11 KiB
PHP
Executable File

<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
<?php elseif (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<div class="container-fluid"
data-config-endpoint="<?= esc($configEndpoint ?? site_url('api/configuration')) ?>"
data-csrf-name="<?= esc(csrf_token()) ?>"
data-csrf-value="<?= esc(csrf_hash()) ?>">
<div class="wrapper">
<!-- Centered Title with proper spacing -->
<div class="text-center mb-4">
<h2 class="text-center mt-4 mb-3">Configuration Management</h2>
</div>
<!-- Spacer after the button -->
<div class="mb-4"></div>
<!-- Add New Configuration Form -->
<div id="config-form" style="display: none;" class="mb-4">
<form action="/configuration/addConfig" method="post" class="mt-3">
<?= csrf_field() ?>
<div class="row">
<div class="col-md-4 mb-3">
<label for="config_key">Config Key:</label>
<input type="text" name="config_key" class="form-control" required>
</div>
<div class="col-md-4 mb-3">
<label for="config_value">Config Value:</label>
<input type="text" name="config_value" class="form-control" required>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Save</button>
<a href="/configuration/configuration_view" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
<!-- Display existing configurations -->
<div class="table-responsive">
<table id="configTable" class="display table table-striped align-middle">
<thead>
<tr>
<th>ID</th>
<th>Config Key</th>
<th>Config Value</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4" class="text-center text-muted">Loading configurations...</td>
</tr>
</tbody>
</table>
<div class="mb-4">
<button id="addNewBtn" class="btn btn-success">Add New Config</button>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editConfigModal" tabindex="-1" aria-labelledby="editConfigModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="editConfigForm" method="post" class="modal-content">
<?= csrf_field() ?>
<div class="modal-header">
<h5 class="modal-title" id="editConfigModalLabel">Edit Configuration</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="config_id" id="config_id">
<div class="mb-3">
<label for="modal_config_key" class="form-label">Config Key:</label>
<input type="text" name="config_key" id="modal_config_key" class="form-control" required>
</div>
<div class="mb-3">
<label for="modal_config_value" class="form-label">Config Value:</label>
<input type="text" name="config_value" id="modal_config_value" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-config-endpoint]');
if (!container) return;
const endpoint = container.dataset.configEndpoint;
const csrfName = container.dataset.csrfName;
const csrfValue = container.dataset.csrfValue;
const CSRF_COOKIE_NAME = <?= json_encode(config('Security')->csrfCookieName ?? (config('Security')->cookieName ?? 'csrf_cookie_name')) ?>;
function getCookie(n){
return document.cookie.split(';').map(v=>v.trim()).find(v=>v.startsWith(n+'='))?.split('=')[1] || '';
}
const addNewBtn = document.getElementById('addNewBtn');
const configForm = document.getElementById('config-form');
const tableBody = document.querySelector('#configTable tbody');
const editModalElement = document.getElementById('editConfigModal');
const editForm = document.getElementById('editConfigForm');
const editModal = (editModalElement && window.bootstrap && window.bootstrap.Modal)
? new window.bootstrap.Modal(editModalElement)
: null;
let configs = [];
const safe = (value, fallback = '') =>
value === undefined || value === null ? fallback : value;
if (addNewBtn && configForm) {
addNewBtn.addEventListener('click', () => {
const isVisible = configForm.style.display === 'block';
configForm.style.display = isVisible ? 'none' : 'block';
});
}
const hasDataTables = () =>
window.jQuery && window.jQuery.fn && window.jQuery.fn.DataTable;
const destroyDataTable = () => {
if (!hasDataTables()) return;
if (window.jQuery.fn.DataTable.isDataTable('#configTable')) {
window.jQuery('#configTable').DataTable().clear().destroy();
}
};
const initDataTable = () => {
if (!hasDataTables()) return;
window.jQuery('#configTable').DataTable({
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
});
};
const buildDeleteForm = (id) => {
const form = document.createElement('form');
form.method = 'post';
form.action = '/configuration/deleteConfig/' + safe(id);
form.className = 'd-inline';
form.addEventListener('submit', (event) => {
if (!confirm('Are you sure you want to delete this config?')) {
event.preventDefault();
}
// Refresh CSRF token just before submit
try {
const fresh = decodeURIComponent(getCookie(CSRF_COOKIE_NAME) || '');
if (fresh) {
const hidden = form.querySelector('input[name="' + csrfName + '"]');
if (hidden) hidden.value = fresh;
}
} catch(_) {}
});
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = csrfName;
csrfInput.value = csrfValue;
form.appendChild(csrfInput);
const deleteButton = document.createElement('button');
deleteButton.type = 'submit';
deleteButton.className = 'btn btn-sm btn-danger';
deleteButton.textContent = 'Delete';
form.appendChild(deleteButton);
return form;
};
const openEditModal = (config) => {
if (!editModal || !editForm) return;
editForm.action = '/configuration/editConfig/' + safe(config.id);
editForm.querySelector('#config_id').value = safe(config.id);
editForm.querySelector('#modal_config_key').value = safe(config.config_key);
editForm.querySelector('#modal_config_value').value = safe(config.config_value);
editModal.show();
};
const renderConfigs = function() {
destroyDataTable();
tableBody.innerHTML = '';
if (!Array.isArray(configs) || configs.length === 0) {
const emptyRow = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 4;
cell.classList.add('text-center', 'text-muted');
cell.textContent = 'No configurations found.';
emptyRow.appendChild(cell);
tableBody.appendChild(emptyRow);
return;
}
configs.forEach(function(config) {
const row = document.createElement('tr');
const idCell = document.createElement('td');
idCell.textContent = safe(config.id);
row.appendChild(idCell);
const keyCell = document.createElement('td');
keyCell.textContent = safe(config.config_key);
row.appendChild(keyCell);
const valueCell = document.createElement('td');
valueCell.textContent = safe(config.config_value);
row.appendChild(valueCell);
const actionsCell = document.createElement('td');
actionsCell.className = 'd-flex gap-1';
const editButton = document.createElement('button');
editButton.type = 'button';
editButton.className = 'btn btn-sm btn-warning';
editButton.textContent = 'Edit';
editButton.addEventListener('click', function() {
openEditModal(config);
});
actionsCell.appendChild(editButton);
actionsCell.appendChild(buildDeleteForm(config.id));
row.appendChild(actionsCell);
tableBody.appendChild(row);
});
initDataTable();
};
const fetchConfigs = function() {
tableBody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">Loading configurations...</td></tr>';
fetch(endpoint, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin',
})
.then(function(response) {
if (!response.ok) {
throw new Error('Request failed with status ' + response.status);
}
return response.json();
})
.then(function(payload) {
const list = payload && Array.isArray(payload.configs) ? payload.configs : [];
configs = list;
renderConfigs();
})
.catch(function(error) {
console.error(error);
destroyDataTable();
tableBody.innerHTML = '<tr><td colspan="4" class="text-center text-danger">Failed to load configurations.</td></tr>';
});
};
fetchConfigs();
});
</script>
<?= $this->endSection() ?>