Merge pull request #22 from rucko24/feature/auto-refresh

Feature/auto-refresh
Enhancements
This commit is contained in:
JRGTH
2026-02-16 01:16:46 -04:00
committed by GitHub
4 changed files with 379 additions and 45 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

View File

@@ -39,6 +39,41 @@ require_once 'auth.inc';
require_once 'guiconfig.inc';
require_once 'bastille_manager-lib.inc';
$img_path = [
'add' => 'images/add.png',
'mod' => 'images/edit.png',
'del' => 'images/delete.png',
'loc' => 'images/locked.png',
'unl' => 'images/unlocked.png',
'mai' => 'images/maintain.png',
'inf' => 'images/info.png',
'ena' => 'images/status_enabled.png',
'dis' => 'images/status_disabled.png',
'mup' => 'images/up.png',
'mdn' => 'images/down.png'
];
// --- START AUTO-REFRESH LOGIC ---
if (isset($_GET['action']) && $_GET['action'] === 'refresh_table') {
error_reporting(0);
ini_set('display_errors', 0);
ob_start();
// Fetch fresh data
$jls_list = [];
if (function_exists('get_jail_infos')) {
$jls_list = get_jail_infos();
}
// Return JSON
ob_clean();
header('Content-Type: application/json');
header('Cache-Control: no-cache');
echo json_encode(['success' => true, 'jails' => $jls_list ?: []]);
exit;
}
// --- END AUTO-REFRESH LOGIC ---
function mwexec_parallel($commands) {
$processes = [];
$results = [];
@@ -53,7 +88,6 @@ function mwexec_parallel($commands) {
$process = proc_open($command, $descriptors, $pipes);
if (is_resource($process)) {
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
@@ -71,7 +105,6 @@ function mwexec_parallel($commands) {
foreach ($processes as $key => $proc) {
$elapsed = time() - $start_time;
if ($elapsed < $timeout) {
$stdout = stream_get_contents($proc['pipes'][1]);
$stderr = stream_get_contents($proc['pipes'][2]);
@@ -102,8 +135,8 @@ function mwexec_parallel($commands) {
}
function mwexec_background($command) {
$command = $command . ' > /dev/null 2>&1 &';
exec($command);
$command = $command . ' > /dev/null 2>&1 &';
exec($command);
}
$sphere_scriptname = basename(__FILE__);
@@ -127,19 +160,6 @@ $gt_selection_start_confirm = gtext('Do you really want to start selected jail(s
$gt_selection_stop_confirm = gtext('Do you want to stop the selected jail(s)?');
$gt_selection_restart_confirm = gtext('Do you want to restart the selected jail(s)?');
$gt_selection_autoboot_confirm = gtext('Do you want to set auto-boot on selected jail(s)?');
$img_path = [
'add' => 'images/add.png',
'mod' => 'images/edit.png',
'del' => 'images/delete.png',
'loc' => 'images/locked.png',
'unl' => 'images/unlocked.png',
'mai' => 'images/maintain.png',
'inf' => 'images/info.png',
'ena' => 'images/status_enabled.png',
'dis' => 'images/status_disabled.png',
'mup' => 'images/up.png',
'mdn' => 'images/down.png'
];
$jls_list = get_jail_infos();
$sphere_array = $jls_list;
@@ -184,19 +204,16 @@ if($_POST):
if(isset($_POST['start_selected_jail']) && $_POST['start_selected_jail']):
$checkbox_member_array = isset($_POST[$checkbox_member_name]) ? $_POST[$checkbox_member_name] : [];
$commands = [];
$jail_names = [];
foreach($checkbox_member_array as $checkbox_member_record):
if(false !== ($index = array_search_ex($checkbox_member_record, $sphere_array, 'jailname'))):
if(!isset($sphere_array[$index]['protected'])):
$commands[] = "/usr/local/bin/bastille start {$checkbox_member_record}";
$jail_names[] = $checkbox_member_record;
endif;
endif;
endforeach;
if (!empty($commands)):
$results = mwexec_parallel($commands);
$success_count = 0;
@@ -345,36 +362,116 @@ if($_POST):
endif;
endif;
$pgtitle = [gtext("Extensions"), gtext('Bastille')];
$pgtitle = [gtext("Extensions"), gtext('Bastille'), gtext('Manager')];
include 'fbegin.inc';
?>
<style>
/* Refresh button style */
#refresh-now {
appearance: none;
font-family: inherit;
font-size: inherit;
font-weight: bold;
color: var(--txc-input-rw);
background-color: var(--bgc-area-data);
border: 1px solid var(--boc-button);
border-radius: var(--bor);
padding: 0.125rem 0.375rem;
cursor: pointer;
}
#refresh-now:hover {
filter: brightness(150%);
}
/* --- SIMPLE RESIZE STYLES --- */
table.area_data_selection {
table-layout: fixed;
border-collapse: collapse;
}
table.area_data_selection th {
position: relative;
padding: 5px 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* The visible handle */
.resizer {
position: absolute;
top: 0;
right: 0;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 100;
user-select: none;
touch-action: none;
}
.resizer:hover, .resizing {
background-color: #007bff; /* Azul */
opacity: 1;
}
</style>
<script type="text/javascript">
//<![CDATA[
$(window).on("load", function() {
// Init action buttons
$("#start_selected_jail").click(function () {
stopAutoRefresh(); // Pause for safety
return confirm('<?=$gt_selection_start_confirm;?>');
});
$("#stop_selected_jail").click(function () {
stopAutoRefresh();
return confirm('<?=$gt_selection_stop_confirm;?>');
});
$("#restart_selected_jail").click(function () {
stopAutoRefresh();
return confirm('<?=$gt_selection_restart_confirm;?>');
});
$("#autoboot_selected_jail").click(function () {
return confirm('<?=$gt_selection_restart_confirm;?>');
stopAutoRefresh();
return confirm('<?=$gt_selection_autoboot_confirm;?>');
});
// Disable action buttons.
disableactionbuttons(true);
// Init member checkboxes
$("input[name='<?=$checkbox_member_name;?>[]']").click(function() {
controlactionbuttons(this, '<?=$checkbox_member_name;?>[]');
});
// Init spinner onsubmit()
$("#iform").submit(function() { spinner(); });
$(".spin").click(function() { spinner(); });
// Attempt to load the previously saved interval
var savedInterval = localStorage.getItem('bastille_refresh_interval');
if (savedInterval !== null) {
$("#refresh-interval").val(savedInterval);
autoRefresh.interval = parseInt(savedInterval);
}
// --- REFRESH INIT ---
// Only start if the button is visible (enabled in settings)
if (localStorage.getItem('bastille_show_refresh_button') === 'true') {
$("#refresh-controls").show();
startAutoRefresh();
}
$("#refresh-now").click(function() {
updateJailTable();
});
// save interval value in local storage
$("#refresh-interval").change(function() {
var val = parseInt($(this).val());
localStorage.setItem('bastille_refresh_interval', val);
stopAutoRefresh();
if (val > 0) {
autoRefresh.interval = val;
startAutoRefresh();
}
});
initSimpleResize();
});
function disableactionbuttons(ab_disable) {
$("#start_selected_jail").prop("disabled", ab_disable);
$("#stop_selected_jail").prop("disabled", ab_disable);
@@ -397,6 +494,149 @@ function controlactionbuttons(ego, triggerbyname) {
}
disableactionbuttons(ab_disable);
}
// --- AUTO-REFRESH JS ---
var autoRefresh = {
enabled: true,
interval: 30000,
timerId: null,
lastUpdate: Date.now(),
isUpdating: false,
selectedJails: []
};
function updateJailTable() {
if (autoRefresh.isUpdating) return;
autoRefresh.isUpdating = true;
$("#refresh-status").text('Updating...');
// Backup of checked checkboxes for persistence
autoRefresh.selectedJails = [];
$("input[name='<?=$checkbox_member_name;?>[]']:checked").each(function() {
autoRefresh.selectedJails.push($(this).val());
});
$.ajax({
url: 'bastille_manager_gui.php?action=refresh_table',
dataType: 'json',
success: function(data) {
if (data.success) {
var tbody = $(".area_data_selection tbody");
tbody.empty();
data.jails.forEach(function(jail) {
var row = $('<tr>');
var checkCell = $('<td class="lcelc">');
var cb = $('<input type="checkbox">')
.attr('name', '<?=$checkbox_member_name;?>[]')
.attr('value', jail.jailname)
.attr('id', jail.jailname)
.prop('checked', autoRefresh.selectedJails.includes(jail.jailname))
.click(function() { controlactionbuttons(this, '<?=$checkbox_member_name;?>[]'); });
checkCell.append(cb);
row.append(checkCell);
// 2. Data Columns
row.append($('<td class="lcell">').text(jail.id || '-'));
row.append($('<td class="lcell">').text(jail.name || '-'));
row.append($('<td class="lcell">').text(jail.boot || '-'));
row.append($('<td class="lcell">').text(jail.prio || '-'));
row.append($('<td class="lcell">').text(jail.state || '-'));
row.append($('<td class="lcell">').text(jail.type || '-'));
row.append($('<td class="lcell">').text(jail.ip || '-'));
row.append($('<td class="lcell">').text(jail.ports || '-'));
row.append($('<td class="lcell">').text(jail.rel || '-'));
row.append($('<td class="lcell">').text(jail.tags || '-'));
var statImg = (jail.state === "Up") ? '<?=$img_path['ena'];?>' : '<?=$img_path['dis'];?>';
row.append($('<td class="lcell">').append($('<img>').attr('src', statImg)));
row.append($('<td class="lcell">').append($('<img>').attr('src', jail.logo)));
var tools = $('<td class="lcebld">').html('<table class="area_data_selection_toolbox"><tbody><tr>' +
'<td><a href="<?=$sphere_scriptname_child;?>?jailname=' + encodeURIComponent(jail.jailname) + '"><img src="<?=$img_path['mai'];?>" class="spin oneemhigh"></a></td>' +
'<td><a href="bastille_manager_jconf.php?jailname=' + encodeURIComponent(jail.jailname) + '"><img src="<?=$g_img['mod'];?>"></a></td>' +
'<td><a href="bastille_manager_info.php?uuid=' + encodeURIComponent(jail.jailname) + '"><img src="<?=$g_img['inf'];?>"></a></td>' +
'</tr></tbody></table>');
row.append(tools);
tbody.append(row);
});
autoRefresh.lastUpdate = Date.now();
$("#refresh-status").text('Last update: just now');
controlactionbuttons(null, '<?=$checkbox_member_name;?>[]');
}
},
complete: function() { autoRefresh.isUpdating = false; }
});
}
function startAutoRefresh() {
if (autoRefresh.interval > 0) {
autoRefresh.timerId = setInterval(updateJailTable, autoRefresh.interval);
}
}
function stopAutoRefresh() {
if (autoRefresh.timerId) clearInterval(autoRefresh.timerId);
}
// --- STABLE REDIMENSIONING FUNCTION (without %) ---
function initSimpleResize() {
var $table = $("table.area_data_selection");
var $cols = $table.find('colgroup col');
var $headers = $table.find('thead th');
$headers.each(function(i) {
if (i >= $headers.length - 1) return; // Ignore the last column
var $resizer = $('<div class="resizer"></div>');
$(this).append($resizer);
});
var isResizing = false;
var startX = 0;
var $currentCol = null;
var startWidth = 0;
$table.on('mousedown', '.resizer', function(e) {
e.preventDefault(); e.stopPropagation();
stopAutoRefresh();
// Convert all columns to fixed pixels when starting to drag
$cols.each(function() {
var w = $(this).width();
$(this).css('width', w + 'px');
});
var idx = $(this).parent().index();
$currentCol = $cols.eq(idx);
isResizing = true;
startX = e.pageX;
startWidth = $currentCol.width();
$(this).addClass('resizing');
$(document).on('mousemove.rsz', function(e) {
if (!isResizing) return;
var diff = e.pageX - startX;
var newW = startWidth + diff;
if (newW > 30) {
$currentCol.css('width', newW + 'px');
}
});
$(document).on('mouseup.rsz', function() {
if (!isResizing) {
return;
}
isResizing = false;
$('.resizer').removeClass('resizing');
$(document).off('mousemove.rsz mouseup.rsz');
setTimeout(function() {
startAutoRefresh();
}, 500);
});
});
}
//]]>
</script>
<?php
@@ -438,24 +678,37 @@ $document->render();
<tbody>
<?php
?>
</tbody>
</table>
<table class="area_data_selection">
</tbody>
</table>
<div id="refresh-controls" style="text-align: right; display: none;">
<span id="refresh-status" style="font-style: italic; margin-right: 15px; color: #666;">Last update: just now</span>
<button type="button" id="refresh-now" class="formbtn">Refresh</button>
<select id="refresh-interval" class="formfld">
<option value="5000">5s</option>
<option value="10000">10s</option>
<option value="30000" selected>30s</option>
<option value="60000">60s</option>
<option value="0">Manual</option>
</select>
</div>
<table class="area_data_selection" style="width: 100%; table-layout: fixed; border-collapse: collapse;">
<colgroup>
<col style="width:2%">
<col style="width:2%">
<col style="width:3%">
<col style="width:12%">
<col style="width:4%">
<col style="width:4%">
<col style="width:4%">
<col style="width:4%">
<col style="width:12%">
<col style="width:12%">
<col style="width:7%">
<col style="width:12%">
<col style="width:4%">
<col style="width:4%">
<col style="width:10%">
<col style="width:3%">
<col style="width:3%">
<col style="width:3%">
<col style="width:3%">
<col style="width:10%">
<col style="width:10%">
<col style="width:5%">
<col style="width:10%">
<col style="width:3%">
<col style="width:3%">
<col style="width:5%">
</colgroup>
<thead>
<?php
@@ -533,6 +786,7 @@ $document->render();
endif;
endif;
?>
</td>
<td>
<a href="bastille_manager_jconf.php?jailname=<?=urlencode($sphere_record['jailname']);?>"><img src="<?=$g_img['mod'];?>" title="<?=$gt_record_conf?>" alt="<?=$gt_record_conf?>"/></a>
</td>
@@ -562,8 +816,9 @@ $document->render();
<input name="autoboot_selected_jail" id="autoboot_selected_jail" type="submit" class="formbtn" value="<?=$gt_selection_autoboot;?>"/>
</div>
<?php
include 'formend.inc';
include 'formend.inc';
?>
</td></tr></tbody></table></form>
<?php
include 'fend.inc';
?>

View File

@@ -306,6 +306,23 @@ $(document).ready(function(){
$('#getinfo_bastille').html(data.bastille);
$('#getinfo_ext').html(data.ext);
});
// --- LOGICA DEL CHECKBOX REFRESH (LocalStorage) ---
var $chk = $("#show_refresh_button");
// 1. Leer estado inicial desde LocalStorage
var savedState = localStorage.getItem('bastille_show_refresh_button');
if (savedState === 'true') {
$chk.prop('checked', true);
} else {
$chk.prop('checked', false); // Por defecto deshabilitado
}
// 2. Guardar cambios al hacer click
$chk.change(function() {
var isChecked = $(this).is(':checked');
localStorage.setItem('bastille_show_refresh_button', isChecked);
});
});
//]]>
</script>
@@ -373,6 +390,9 @@ $(document).ready(function(){
<?php html_titleline(gtext("Uninstall"));?>
<?php html_checkbox2('delete_confirm',gtext('Uninstall confirm'),'' ? true : false,gtext('Check to confirm extension uninstall. Note: Jail related content will be preserved by default.'),'',false);?>
<?php html_separator();?>
<?php html_titleline(gtext("Refresh"));?>
<?php html_checkbox2('show_refresh_button',gtext('Show refresh button'),'' ? true : false,gtext('This will display a refresh button in the containers tab.'),'',false);?>
<?php html_separator();?>
</table>
<div id="submit1">
<input name="uninstall" type="submit" class="formbtn" title="<?=gtext("Uninstall Extension");?>" value="<?=gtext("Uninstall");?>" onclick="return confirm('<?=gtext("Bastille Extension and packages will be completely removed, Bastille containers and child directories will not be touched, really to proceed?");?>')" />
@@ -384,4 +404,4 @@ $(document).ready(function(){
<script type="text/javascript">
<!--enable_change(false);-->
</script>
<?php include("fend.inc");?>
<?php include("fend.inc");?>

View File

@@ -0,0 +1,58 @@
/* bastille_manager.css
Estilo NATIVO (Minimalista)
*/
#refresh-controls {
/* Fondo transparente y sin bordes para integrarse con el tema */
background: transparent;
border: none;
padding: 10px 0;
margin-bottom: 5px;
/* Alineación a la derecha */
display: flex;
justify-content: flex-end;
align-items: center;
gap: 15px;
/* Fuente estándar del sistema */
font-size: 13px;
color: inherit;
}
#refresh-status {
/* Color de texto por defecto del tema (negro/gris) */
color: inherit;
margin-right: 5px;
/* Coloca el texto a la izquierda de los botones */
order: -1;
}
/* Pequeño spinner azul discreto solo cuando actualiza */
#refresh-status.updating .refresh-spinner {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #ccc;
border-top-color: #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 5px;
}
/* Animación del spinner */
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Asegurar que los iconos de la tabla estén centrados verticalmente */
.area_data_selection tbody td img {
vertical-align: middle;
}
/* Centrado perfecto para los checkboxes */
.lcelc {
text-align: center;
vertical-align: middle;
}