275 lines
11 KiB
PHP
275 lines
11 KiB
PHP
<?= $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() ?>
|