This implements a function to determine if Pakfire is already running. It tests the PID and lockfile and can be expanded easily later. 'pidof' checks the full path to avoid confusion.
Removes the unreachable function "refreshpage".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de --- html/cgi-bin/pakfire.cgi | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 4d6eee284..7957bc154 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -44,8 +44,6 @@ $cgiparams{'VALID'} = ''; $cgiparams{'INSPAKS'} = ''; $cgiparams{'DELPAKS'} = '';
-sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='1;'>" );print "<center><img src='/images/clock.gif' alt='' /><br/><font color='red'>$Lang::tr{'pagerefresh'}</font></center>";&Header::closebox();} - &Header::getcgihash(%cgiparams);
&General::readhash("${General::swroot}/main/settings", %mainsettings); @@ -54,7 +52,7 @@ sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' cont &Header::openpage($Lang::tr{'pakfire configuration'}, 1); &Header::openbigbox('100%', 'left', '', $errormessage);
-if (($cgiparams{'ACTION'} eq 'install') && (! -e $Pakfire::lockfile)) { +if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); @@ -92,7 +90,7 @@ END &Header::closepage(); exit; } -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'DELPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); @@ -131,10 +129,10 @@ END exit; }
-} elsif (($cgiparams{'ACTION'} eq 'update') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'update') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); sleep(1); -} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (!-e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); sleep(1); } elsif ($cgiparams{'ACTION'} eq "$Lang::tr{'save'}") { @@ -173,11 +171,7 @@ if ($errormessage) { }
# Check if pakfire is already running. -# -# The system backpipe command is safe, because no user input is computed. -my $pid = `pidof pakfire`; - -if ($pid) { +if (&_is_pakfire_busy()) { &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" ); print <<END; <table> @@ -203,7 +197,6 @@ END &Header::closebigbox(); &Header::closepage(); exit; - refreshpage(); }
my $core_release = `cat /opt/pakfire/db/core/mine 2>/dev/null`; @@ -314,3 +307,16 @@ END &Header::closebox(); &Header::closebigbox(); &Header::closepage(); + +###--- Internal functions ---### + +# Check if pakfire is already running (extend test here if necessary) +sub _is_pakfire_busy { + # Get PID of a running pakfire instance + # (The system backpipe command is safe, because no user input is computed.) + my $pakfire_pid = `pidof -s /usr/local/bin/pakfire`; + chomp($pakfire_pid); + + # Test presence of PID or lockfile + return (($pakfire_pid) || (-e "$Pakfire::lockfile")); +}
Currently the page becomes unresponsive while Pakfire is busy. This patch implements a AJAX/JSON driven log output, to provide continuous information to the user while Pakfire is running.
The output is updated 1x per second, if the load should be too high, the interval can be change by writing to "pakfire.refreshInterval".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de --- html/cgi-bin/pakfire.cgi | 153 ++++++++++++++++++---- html/html/include/pakfire.js | 241 +++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 26 deletions(-) create mode 100644 html/html/include/pakfire.js
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 7957bc154..e5f5f7d6a 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -36,8 +36,11 @@ my %color = (); my %pakfiresettings = (); my %mainsettings = ();
-&Header::showhttpheaders(); +# Load general settings +&General::readhash("${General::swroot}/main/settings", %mainsettings); +&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color);
+# Get CGI request data $cgiparams{'ACTION'} = ''; $cgiparams{'VALID'} = '';
@@ -46,12 +49,102 @@ $cgiparams{'DELPAKS'} = '';
&Header::getcgihash(%cgiparams);
-&General::readhash("${General::swroot}/main/settings", %mainsettings); -&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color); +### Process AJAX/JSON request ### +if($cgiparams{'ACTION'} eq 'json-getstatus') { + # Send HTTP headers + _start_json_output(); + + # Collect Pakfire status and log messages + my %status = ( + 'running' => &_is_pakfire_busy() || "0", + 'running_since' => &General::age("$Pakfire::lockfile") || "0s", + 'reboot' => (-e "/var/run/need_reboot") || "0" + ); + my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`; + + # Start JSON file + print "{\n"; + + foreach my $key (keys %status) { + my $value = $status{$key}; + print qq{\t"$key": "$value",\n}; + } + + # Print sanitized messages in reverse order to undo previous "tac" + print qq{\t"messages": [\n}; + for my $index (reverse (0 .. $#messages)) { + my $line = $messages[$index]; + $line =~ s/[[:cntrl:]<>&\]+//g; + + print qq{\t\t"$line"}; + print ",\n" unless $index < 1; + } + print "\n\t]\n"; + + # Finalize JSON file & stop + print "}"; + exit; +} + +### Start pakfire page ### +&Header::showhttpheaders(); + +###--- HTML HEAD ---### +my $extraHead = <<END +<style> + /* Pakfire log viewer */ + section#pflog-header { + width: 100%; + display: flex; + text-align: left; + align-items: center; + column-gap: 20px; + } + #pflog-header > div:last-child { + margin-left: auto; + margin-right: 20px; + } + #pflog-header span { + line-height: 1.3em; + } + #pflog-header span:empty::before { + content: "\200b"; /* zero width space */ + } + + pre#pflog-messages { + margin-top: 0.7em; + padding-top: 0.7em; + border-top: 0.5px solid $Header::bordercolour;
-&Header::openpage($Lang::tr{'pakfire configuration'}, 1); + text-align: left; + min-height: 15em; + overflow-x: auto; + } +</style> + +<script src="/include/pakfire.js"></script> +<script> + // Translations + pakfire.i18n.load({ + 'working': '$Lang::tr{'pakfire working'}', + 'finished': 'Pakfire is finished! Please check the log output.', + 'since': '$Lang::tr{'since'} ', //(space is intentional) + + 'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>', + 'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' + }); + + // AJAX auto refresh interval + pakfire.refreshInterval = 1000; +</script> +END +; +###--- END HTML HEAD ---### + +&Header::openpage($Lang::tr{'pakfire configuration'}, 1, $extraHead); &Header::openbigbox('100%', 'left', '', $errormessage);
+# Process Pakfire commands if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { @@ -170,29 +263,30 @@ if ($errormessage) { &Header::closebox(); }
-# Check if pakfire is already running. -if (&_is_pakfire_busy()) { - &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" ); - print <<END; - <table> - <tr><td> - <img src='/images/indicator.gif' alt='$Lang::tr{'active'}' title='$Lang::tr{'active'}' /> - <td> - $Lang::tr{'pakfire working'} - <tr><td colspan='2' align='center'> - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <input type='image' alt='$Lang::tr{'reload'}' title='$Lang::tr{'reload'}' src='/images/view-refresh.png' /> - </form> - <tr><td colspan='2' align='left'><code> -END - my @output = `grep pakfire /var/log/messages | tail -20`; - foreach (@output) { - print "$_<br>"; - } - print <<END; - </code> - </table> +# Show log output while Pakfire is running +if(&_is_pakfire_busy()) { + &Header::openbox("100%", "center", "Pakfire"); + + print <<END +<section id="pflog-header"> + <div><img src="/images/indicator.gif" alt="$Lang::tr{'active'}" title="$Lang::tr{'pagerefresh'}"></div> + <div> + <span id="pflog-status">$Lang::tr{'pakfire working'}</span><br> + <span id="pflog-time"></span><br> + <span id="pflog-action"></span> + </div> + <div><a href="$ENV{'SCRIPT_NAME'}"><img src="/images/view-refresh.png" alt="$Lang::tr{'refresh'}" title="$Lang::tr{'refresh'}"></a></div> +</section> + +<!-- Pakfire log messages --> +<pre id="pflog-messages"></pre> +<script> + pakfire.running = true; +</script> + END +; + &Header::closebox(); &Header::closebigbox(); &Header::closepage(); @@ -320,3 +414,10 @@ sub _is_pakfire_busy { # Test presence of PID or lockfile return (($pakfire_pid) || (-e "$Pakfire::lockfile")); } + +# Send HTTP headers +sub _start_json_output { + print "Cache-Control: no-cache, no-store\n"; + print "Content-Type: application/json\n"; + print "\n"; # End of HTTP headers +} diff --git a/html/html/include/pakfire.js b/html/html/include/pakfire.js new file mode 100644 index 000000000..0950870e0 --- /dev/null +++ b/html/html/include/pakfire.js @@ -0,0 +1,241 @@ +/*############################################################################# +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2021 IPFire Team info@ipfire.org # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +#############################################################################*/ + +"use strict"; + +// Pakfire Javascript functions (requires jQuery) +class PakfireJS { + constructor() { + //--- Public properties --- + // Translation strings + this.i18n = new PakfireI18N(); + + //--- Private properties --- + // Status flags (access outside constructor only with setter/getter) + this._states = Object.create(null); + this._states.running = false; + this._states.reboot = false; + + // Status refresh helper + this._autoRefresh = { + delay: 1000, //Delay between requests (default: 1s) + jsonAction: 'getstatus', //CGI POST action parameter + timeout: 5000, //XHR timeout (5s) + + delayTimer: null, //setTimeout reference + jqXHR: undefined, //jQuery.ajax promise reference + get runningDelay() { //Waiting for end of delay + return (this.delayTimer !== null); + }, + get runningXHR() { //Waiting for CGI response + return (this.jqXHR && (this.jqXHR.state() === 'pending')); + }, + get isRunning() { + return (this.runningDelay || this.runningXHR); + } + }; + } + + //### Public properties ### + + // Pakfire is running (true/false) + set running(state) { + if(this._states.running !== state) { + this._states.running = state; + this._states_onChange('running'); + } + } + get running() { + return this._states.running; + } + + // Reboot needed (true/false) + set reboot(state) { + if(this._states.reboot !== state) { + this._states.reboot = state; + this._states_onChange('reboot'); + } + } + get reboot() { + return this._states.reboot; + } + + // Status refresh interval in ms + set refreshInterval(delay) { + if(delay < 500) { + delay = 500; //enforce reasonable minimum + } + this._autoRefresh.delay = delay; + } + get refreshInterval() { + return this._autoRefresh.delay; + } + + // Document loaded (call once from jQuery.ready) + documentReady() { + // Status refresh late start + if(this.running && (! this._autoRefresh.isRunning)) { + this._autoRefresh_runNow(); + } + } + + //### Private properties ### + + // Pakfire status change handler + // property: Affected status (running, reboot, ...) + _states_onChange(property) { + // Always update UI + if(this.running) { + $('#pflog-status').text(this.i18n.get('working')); + $('#pflog-action').empty(); + } else { + $('#pflog-status').text(this.i18n.get('finished')); + if(this.reboot) { //Enable return or reboot links in UI + $('#pflog-action').html(this.i18n.get('link_reboot')); + } else { + $('#pflog-action').html(this.i18n.get('link_return')); + } + } + + // Start/stop status refresh if Pakfire started/stopped + if(property === 'running') { + if(this.running) { + this._autoRefresh_runNow(); + } else { + this._autoRefresh_clearSchedule(); + } + } + } + + //--- Status refresh scheduling functions --- + + // Immediately perform AJAX status refresh request + _autoRefresh_runNow() { + if(this._autoRefresh.runningXHR) { + return; // Don't send multiple requests + } + this._autoRefresh_clearSchedule(); // Stop scheduled refresh, will send immediately + + // Send AJAX request, attach listeners + this._autoRefresh.jqXHR = this._JSON_get(this._autoRefresh.jsonAction, this._autoRefresh.timeout); + this._autoRefresh.jqXHR.done(function() { // Request succeeded + if(this.running) { // Keep refreshing while Pakfire is running + this._autoRefresh_scheduleRun(); + } + }); + this._autoRefresh.jqXHR.fail(function() { // Request failed + this._autoRefresh_scheduleRun(); // Try refreshing until valid status is received + }); + } + + // Schedule next refresh + _autoRefresh_scheduleRun() { + if(this._autoRefresh.runningDelay || this._autoRefresh.runningXHR) { + return; // Refresh already scheduled or in progress + } + this._autoRefresh.delayTimer = window.setTimeout(function() { + this._autoRefresh.delayTimer = null; + this._autoRefresh_runNow(); + }.bind(this), this._autoRefresh.delay); + } + + // Stop scheduled refresh (can still be refreshed up to 1x if XHR is already sent) + _autoRefresh_clearSchedule() { + if(this._autoRefresh.runningDelay) { + window.clearTimeout(this._autoRefresh.delayTimer); + this._autoRefresh.delayTimer = null; + } + } + + //--- JSON request & data handling --- + + // Load JSON data from Pakfire CGI, using a POST request + // action: POST paramter "json-[action]" + // maxTime: XHR timeout, 0 = no timeout + _JSON_get(action, maxTime = 0) { + return $.ajax({ + url: '/cgi-bin/pakfire.cgi', + method: 'POST', + timeout: maxTime, + context: this, + data: {'ACTION': `json-${action}`}, + dataType: 'json' //automatically check and convert result + }) + .done(function(response) { + this._JSON_process(action, response); + }); + } + + // Process successful response from Pakfire CGI + // action: POST paramter "json-[action]" used to send request + // data: JSON data object + _JSON_process(action, data) { + // Pakfire status refresh + if(action === this._autoRefresh.jsonAction) { + // Update status flags + this.running = (data['running'] != '0'); + this.reboot = (data['reboot'] != '0'); + + // Update timer display + if(this.running && data['running_since']) { + $('#pflog-time').text(this.i18n.get('since') + data['running_since']); + } else { + $('#pflog-time').empty(); + } + + // Print log messages + let messages = ""; + data['messages'].forEach(function(line) { + messages += `${line}\n`; + }); + $('#pflog-messages').text(messages); + } + } +} + +// Simple translation strings helper +// Format: {key: "translation"} +class PakfireI18N { + constructor() { + this._strings = Object.create(null); //Object without prototypes + } + + // Get translation + get(key) { + if(Object.prototype.hasOwnProperty.call(this._strings, key)) { + return this._strings[key]; + } + return `(undefined string '${key}')`; + } + + // Load key/translation object + load(translations) { + if(translations instanceof Object) { + Object.assign(this._strings, translations); + } + } +} + +//### Initialize Pakfire ### +const pakfire = new PakfireJS(); + +$(function() { + pakfire.documentReady(); +});
Hello,
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
Currently the page becomes unresponsive while Pakfire is busy. This patch implements a AJAX/JSON driven log output, to provide continuous information to the user while Pakfire is running.
The output is updated 1x per second, if the load should be too high, the interval can be change by writing to "pakfire.refreshInterval".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 153 ++++++++++++++++++---- html/html/include/pakfire.js | 241 +++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 26 deletions(-) create mode 100644 html/html/include/pakfire.js
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 7957bc154..e5f5f7d6a 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -36,8 +36,11 @@ my %color = (); my %pakfiresettings = (); my %mainsettings = ();
-&Header::showhttpheaders(); +# Load general settings +&General::readhash("${General::swroot}/main/settings", %mainsettings); +&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color);
+# Get CGI request data $cgiparams{'ACTION'} = ''; $cgiparams{'VALID'} = '';
@@ -46,12 +49,102 @@ $cgiparams{'DELPAKS'} = '';
&Header::getcgihash(%cgiparams);
-&General::readhash("${General::swroot}/main/settings", %mainsettings); -&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color); +### Process AJAX/JSON request ### +if($cgiparams{'ACTION'} eq 'json-getstatus') {
- # Send HTTP headers
- _start_json_output();
- # Collect Pakfire status and log messages
- my %status = (
'running' => &_is_pakfire_busy() || "0",
'running_since' => &General::age("$Pakfire::lockfile") || "0s",
'reboot' => (-e "/var/run/need_reboot") || "0"
- );
- my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`;
- # Start JSON file
- print "{\n";
- foreach my $key (keys %status) {
my $value = $status{$key};
print qq{\t"$key": "$value",\n};
- }
- # Print sanitized messages in reverse order to undo previous "tac"
- print qq{\t"messages": [\n};
- for my $index (reverse (0 .. $#messages)) {
my $line = $messages[$index];
$line =~ s/[[:cntrl:]<>&\\]+//g;
print qq{\t\t"$line"};
print ",\n" unless $index < 1;
- }
- print "\n\t]\n";
What is the reason to “tac” the log file first and then reverse the order again?
Is it just to limit the length of the JSON array?
It might be faster to read the entire file, grep out what we need and then throw away most of the array. Or push a line to the end of the array and remove one from the beginning if it is longer than a certain threshold.
- # Finalize JSON file & stop
- print "}";
- exit;
+}
+### Start pakfire page ### +&Header::showhttpheaders();
+###--- HTML HEAD ---### +my $extraHead = <<END +<style>
- /* Pakfire log viewer */
- section#pflog-header {
width: 100%;
display: flex;
text-align: left;
align-items: center;
column-gap: 20px;
- }
- #pflog-header > div:last-child {
margin-left: auto;
margin-right: 20px;
- }
- #pflog-header span {
line-height: 1.3em;
- }
- #pflog-header span:empty::before {
content: "\\200b"; /* zero width space */
- }
- pre#pflog-messages {
margin-top: 0.7em;
padding-top: 0.7em;
border-top: 0.5px solid $Header::bordercolour;
-&Header::openpage($Lang::tr{'pakfire configuration'}, 1);
text-align: left;
min-height: 15em;
overflow-x: auto;
- }
+</style>
+<script src="/include/pakfire.js"></script> +<script>
- // Translations
- pakfire.i18n.load({
'working': '$Lang::tr{'pakfire working'}',
'finished': 'Pakfire is finished! Please check the log output.',
'since': '$Lang::tr{'since'} ', //(space is intentional)
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>',
'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>'
- });
- // AJAX auto refresh interval
- pakfire.refreshInterval = 1000;
+</script> +END +; +###--- END HTML HEAD ---###
+&Header::openpage($Lang::tr{'pakfire configuration'}, 1, $extraHead); &Header::openbigbox('100%', 'left', '', $errormessage);
+# Process Pakfire commands if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { @@ -170,29 +263,30 @@ if ($errormessage) { &Header::closebox(); }
-# Check if pakfire is already running. -if (&_is_pakfire_busy()) {
- &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" );
- print <<END;
<table>
<tr><td>
<img src='/images/indicator.gif' alt='$Lang::tr{'active'}' title='$Lang::tr{'active'}' />
<td>
$Lang::tr{'pakfire working'}
<tr><td colspan='2' align='center'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='image' alt='$Lang::tr{'reload'}' title='$Lang::tr{'reload'}' src='/images/view-refresh.png' />
</form>
<tr><td colspan='2' align='left'><code>
-END
- my @output = `grep pakfire /var/log/messages | tail -20`;
- foreach (@output) {
print "$_<br>";
- }
- print <<END;
</code>
</table>
+# Show log output while Pakfire is running +if(&_is_pakfire_busy()) {
- &Header::openbox("100%", "center", "Pakfire");
- print <<END
+<section id="pflog-header">
<div><img src="/images/indicator.gif" alt="$Lang::tr{'active'}" title="$Lang::tr{'pagerefresh'}"></div>
<div>
<span id="pflog-status">$Lang::tr{'pakfire working'}</span><br>
<span id="pflog-time"></span><br>
<span id="pflog-action"></span>
</div>
<div><a href="$ENV{'SCRIPT_NAME'}"><img src="/images/view-refresh.png" alt="$Lang::tr{'refresh'}" title="$Lang::tr{'refresh'}"></a></div>
+</section>
+<!-- Pakfire log messages --> +<pre id="pflog-messages"></pre> +<script>
- pakfire.running = true;
+</script>
END +;
- &Header::closebox(); &Header::closebigbox(); &Header::closepage();
@@ -320,3 +414,10 @@ sub _is_pakfire_busy { # Test presence of PID or lockfile return (($pakfire_pid) || (-e "$Pakfire::lockfile")); }
+# Send HTTP headers +sub _start_json_output {
- print "Cache-Control: no-cache, no-store\n";
- print "Content-Type: application/json\n";
- print "\n"; # End of HTTP headers
+} diff --git a/html/html/include/pakfire.js b/html/html/include/pakfire.js new file mode 100644 index 000000000..0950870e0 --- /dev/null +++ b/html/html/include/pakfire.js @@ -0,0 +1,241 @@ +/*############################################################################# +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2021 IPFire Team info@ipfire.org # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +#############################################################################*/
+"use strict";
+// Pakfire Javascript functions (requires jQuery) +class PakfireJS {
- constructor() {
//--- Public properties ---
// Translation strings
this.i18n = new PakfireI18N();
//--- Private properties ---
// Status flags (access outside constructor only with setter/getter)
this._states = Object.create(null);
this._states.running = false;
this._states.reboot = false;
// Status refresh helper
this._autoRefresh = {
delay: 1000, //Delay between requests (default: 1s)
jsonAction: 'getstatus', //CGI POST action parameter
timeout: 5000, //XHR timeout (5s)
delayTimer: null, //setTimeout reference
jqXHR: undefined, //jQuery.ajax promise reference
get runningDelay() { //Waiting for end of delay
return (this.delayTimer !== null);
},
get runningXHR() { //Waiting for CGI response
return (this.jqXHR && (this.jqXHR.state() === 'pending'));
},
get isRunning() {
return (this.runningDelay || this.runningXHR);
}
};
- }
- //### Public properties ###
- // Pakfire is running (true/false)
- set running(state) {
if(this._states.running !== state) {
this._states.running = state;
this._states_onChange('running');
}
- }
- get running() {
return this._states.running;
- }
- // Reboot needed (true/false)
- set reboot(state) {
if(this._states.reboot !== state) {
this._states.reboot = state;
this._states_onChange('reboot');
}
- }
- get reboot() {
return this._states.reboot;
- }
- // Status refresh interval in ms
- set refreshInterval(delay) {
if(delay < 500) {
delay = 500; //enforce reasonable minimum
}
this._autoRefresh.delay = delay;
- }
- get refreshInterval() {
return this._autoRefresh.delay;
- }
- // Document loaded (call once from jQuery.ready)
- documentReady() {
// Status refresh late start
if(this.running && (! this._autoRefresh.isRunning)) {
this._autoRefresh_runNow();
}
- }
- //### Private properties ###
- // Pakfire status change handler
- // property: Affected status (running, reboot, ...)
- _states_onChange(property) {
// Always update UI
if(this.running) {
$('#pflog-status').text(this.i18n.get('working'));
$('#pflog-action').empty();
} else {
$('#pflog-status').text(this.i18n.get('finished'));
if(this.reboot) { //Enable return or reboot links in UI
$('#pflog-action').html(this.i18n.get('link_reboot'));
} else {
$('#pflog-action').html(this.i18n.get('link_return'));
}
}
// Start/stop status refresh if Pakfire started/stopped
if(property === 'running') {
if(this.running) {
this._autoRefresh_runNow();
} else {
this._autoRefresh_clearSchedule();
}
}
- }
- //--- Status refresh scheduling functions ---
- // Immediately perform AJAX status refresh request
- _autoRefresh_runNow() {
if(this._autoRefresh.runningXHR) {
return; // Don't send multiple requests
}
this._autoRefresh_clearSchedule(); // Stop scheduled refresh, will send immediately
// Send AJAX request, attach listeners
this._autoRefresh.jqXHR = this._JSON_get(this._autoRefresh.jsonAction, this._autoRefresh.timeout);
this._autoRefresh.jqXHR.done(function() { // Request succeeded
if(this.running) { // Keep refreshing while Pakfire is running
this._autoRefresh_scheduleRun();
}
});
this._autoRefresh.jqXHR.fail(function() { // Request failed
this._autoRefresh_scheduleRun(); // Try refreshing until valid status is received
});
- }
- // Schedule next refresh
- _autoRefresh_scheduleRun() {
if(this._autoRefresh.runningDelay || this._autoRefresh.runningXHR) {
return; // Refresh already scheduled or in progress
}
this._autoRefresh.delayTimer = window.setTimeout(function() {
this._autoRefresh.delayTimer = null;
this._autoRefresh_runNow();
}.bind(this), this._autoRefresh.delay);
- }
- // Stop scheduled refresh (can still be refreshed up to 1x if XHR is already sent)
- _autoRefresh_clearSchedule() {
if(this._autoRefresh.runningDelay) {
window.clearTimeout(this._autoRefresh.delayTimer);
this._autoRefresh.delayTimer = null;
}
- }
- //--- JSON request & data handling ---
- // Load JSON data from Pakfire CGI, using a POST request
- // action: POST paramter "json-[action]"
- // maxTime: XHR timeout, 0 = no timeout
- _JSON_get(action, maxTime = 0) {
return $.ajax({
url: '/cgi-bin/pakfire.cgi',
method: 'POST',
timeout: maxTime,
context: this,
data: {'ACTION': `json-${action}`},
dataType: 'json' //automatically check and convert result
})
.done(function(response) {
this._JSON_process(action, response);
});
- }
- // Process successful response from Pakfire CGI
- // action: POST paramter "json-[action]" used to send request
- // data: JSON data object
- _JSON_process(action, data) {
// Pakfire status refresh
if(action === this._autoRefresh.jsonAction) {
// Update status flags
this.running = (data['running'] != '0');
this.reboot = (data['reboot'] != '0');
// Update timer display
if(this.running && data['running_since']) {
$('#pflog-time').text(this.i18n.get('since') + data['running_since']);
} else {
$('#pflog-time').empty();
}
// Print log messages
let messages = "";
data['messages'].forEach(function(line) {
messages += `${line}\n`;
});
$('#pflog-messages').text(messages);
}
- }
+}
+// Simple translation strings helper +// Format: {key: "translation"} +class PakfireI18N {
- constructor() {
this._strings = Object.create(null); //Object without prototypes
- }
- // Get translation
- get(key) {
if(Object.prototype.hasOwnProperty.call(this._strings, key)) {
return this._strings[key];
}
return `(undefined string '${key}')`;
- }
- // Load key/translation object
- load(translations) {
if(translations instanceof Object) {
Object.assign(this._strings, translations);
}
- }
+}
+//### Initialize Pakfire ### +const pakfire = new PakfireJS();
+$(function() {
- pakfire.documentReady();
+});
2.27.0.windows.1
Hi,
Am 02.12.2021 um 16:59 schrieb Michael Tremer:
Hello,
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
Currently the page becomes unresponsive while Pakfire is busy. This patch implements a AJAX/JSON driven log output, to provide continuous information to the user while Pakfire is running.
The output is updated 1x per second, if the load should be too high, the interval can be change by writing to "pakfire.refreshInterval".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 153 ++++++++++++++++++---- html/html/include/pakfire.js | 241 +++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 26 deletions(-) create mode 100644 html/html/include/pakfire.js
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 7957bc154..e5f5f7d6a 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -36,8 +36,11 @@ my %color = (); my %pakfiresettings = (); my %mainsettings = ();
-&Header::showhttpheaders(); +# Load general settings +&General::readhash("${General::swroot}/main/settings", %mainsettings); +&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color);
+# Get CGI request data $cgiparams{'ACTION'} = ''; $cgiparams{'VALID'} = '';
@@ -46,12 +49,102 @@ $cgiparams{'DELPAKS'} = '';
&Header::getcgihash(%cgiparams);
-&General::readhash("${General::swroot}/main/settings", %mainsettings); -&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color); +### Process AJAX/JSON request ### +if($cgiparams{'ACTION'} eq 'json-getstatus') {
- # Send HTTP headers
- _start_json_output();
- # Collect Pakfire status and log messages
- my %status = (
'running' => &_is_pakfire_busy() || "0",
'running_since' => &General::age("$Pakfire::lockfile") || "0s",
'reboot' => (-e "/var/run/need_reboot") || "0"
- );
- my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`;
- # Start JSON file
- print "{\n";
- foreach my $key (keys %status) {
my $value = $status{$key};
print qq{\t"$key": "$value",\n};
- }
- # Print sanitized messages in reverse order to undo previous "tac"
- print qq{\t"messages": [\n};
- for my $index (reverse (0 .. $#messages)) {
my $line = $messages[$index];
$line =~ s/[[:cntrl:]<>&\\]+//g;
print qq{\t\t"$line"};
print ",\n" unless $index < 1;
- }
- print "\n\t]\n";
What is the reason to “tac” the log file first and then reverse the order again?
Is it just to limit the length of the JSON array?
It might be faster to read the entire file, grep out what we need and then throw away most of the array. Or push a line to the end of the array and remove one from the beginning if it is longer than a certain threshold.
I wanted to make sure that only the output of the current Pakfire run is shown. Therefore, I use tac and sed to read the logfile backwards until the last "Pakfire ... started!" header is reached. This works very well, but then of course the messages array is also in reverse order.
All the ideas I had required some form of "reverse", or I had to load the entire file in Perl and check every line. I assumed that tac & sed would be more efficient than any Perl solution I could come up with. I'll try to time this and report back!
Leo
- # Finalize JSON file & stop
- print "}";
- exit;
+}
+### Start pakfire page ### +&Header::showhttpheaders();
+###--- HTML HEAD ---### +my $extraHead = <<END +<style>
- /* Pakfire log viewer */
- section#pflog-header {
width: 100%;
display: flex;
text-align: left;
align-items: center;
column-gap: 20px;
- }
- #pflog-header > div:last-child {
margin-left: auto;
margin-right: 20px;
- }
- #pflog-header span {
line-height: 1.3em;
- }
- #pflog-header span:empty::before {
content: "\\200b"; /* zero width space */
- }
- pre#pflog-messages {
margin-top: 0.7em;
padding-top: 0.7em;
border-top: 0.5px solid $Header::bordercolour;
-&Header::openpage($Lang::tr{'pakfire configuration'}, 1);
text-align: left;
min-height: 15em;
overflow-x: auto;
- }
+</style>
+<script src="/include/pakfire.js"></script> +<script>
- // Translations
- pakfire.i18n.load({
'working': '$Lang::tr{'pakfire working'}',
'finished': 'Pakfire is finished! Please check the log output.',
'since': '$Lang::tr{'since'} ', //(space is intentional)
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>',
'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>'
- });
- // AJAX auto refresh interval
- pakfire.refreshInterval = 1000;
+</script> +END +; +###--- END HTML HEAD ---###
+&Header::openpage($Lang::tr{'pakfire configuration'}, 1, $extraHead); &Header::openbigbox('100%', 'left', '', $errormessage);
+# Process Pakfire commands if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { @@ -170,29 +263,30 @@ if ($errormessage) { &Header::closebox(); }
-# Check if pakfire is already running. -if (&_is_pakfire_busy()) {
- &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" );
- print <<END;
<table>
<tr><td>
<img src='/images/indicator.gif' alt='$Lang::tr{'active'}' title='$Lang::tr{'active'}' />
<td>
$Lang::tr{'pakfire working'}
<tr><td colspan='2' align='center'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='image' alt='$Lang::tr{'reload'}' title='$Lang::tr{'reload'}' src='/images/view-refresh.png' />
</form>
<tr><td colspan='2' align='left'><code>
-END
- my @output = `grep pakfire /var/log/messages | tail -20`;
- foreach (@output) {
print "$_<br>";
- }
- print <<END;
</code>
</table>
+# Show log output while Pakfire is running +if(&_is_pakfire_busy()) {
- &Header::openbox("100%", "center", "Pakfire");
- print <<END
+<section id="pflog-header">
<div><img src="/images/indicator.gif" alt="$Lang::tr{'active'}" title="$Lang::tr{'pagerefresh'}"></div>
<div>
<span id="pflog-status">$Lang::tr{'pakfire working'}</span><br>
<span id="pflog-time"></span><br>
<span id="pflog-action"></span>
</div>
<div><a href="$ENV{'SCRIPT_NAME'}"><img src="/images/view-refresh.png" alt="$Lang::tr{'refresh'}" title="$Lang::tr{'refresh'}"></a></div>
+</section>
+<!-- Pakfire log messages --> +<pre id="pflog-messages"></pre> +<script>
- pakfire.running = true;
+</script>
END +;
- &Header::closebox(); &Header::closebigbox(); &Header::closepage();
@@ -320,3 +414,10 @@ sub _is_pakfire_busy { # Test presence of PID or lockfile return (($pakfire_pid) || (-e "$Pakfire::lockfile")); }
+# Send HTTP headers +sub _start_json_output {
- print "Cache-Control: no-cache, no-store\n";
- print "Content-Type: application/json\n";
- print "\n"; # End of HTTP headers
+} diff --git a/html/html/include/pakfire.js b/html/html/include/pakfire.js new file mode 100644 index 000000000..0950870e0 --- /dev/null +++ b/html/html/include/pakfire.js @@ -0,0 +1,241 @@ +/*############################################################################# +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2021 IPFire Team info@ipfire.org # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +#############################################################################*/
+"use strict";
+// Pakfire Javascript functions (requires jQuery) +class PakfireJS {
- constructor() {
//--- Public properties ---
// Translation strings
this.i18n = new PakfireI18N();
//--- Private properties ---
// Status flags (access outside constructor only with setter/getter)
this._states = Object.create(null);
this._states.running = false;
this._states.reboot = false;
// Status refresh helper
this._autoRefresh = {
delay: 1000, //Delay between requests (default: 1s)
jsonAction: 'getstatus', //CGI POST action parameter
timeout: 5000, //XHR timeout (5s)
delayTimer: null, //setTimeout reference
jqXHR: undefined, //jQuery.ajax promise reference
get runningDelay() { //Waiting for end of delay
return (this.delayTimer !== null);
},
get runningXHR() { //Waiting for CGI response
return (this.jqXHR && (this.jqXHR.state() === 'pending'));
},
get isRunning() {
return (this.runningDelay || this.runningXHR);
}
};
- }
- //### Public properties ###
- // Pakfire is running (true/false)
- set running(state) {
if(this._states.running !== state) {
this._states.running = state;
this._states_onChange('running');
}
- }
- get running() {
return this._states.running;
- }
- // Reboot needed (true/false)
- set reboot(state) {
if(this._states.reboot !== state) {
this._states.reboot = state;
this._states_onChange('reboot');
}
- }
- get reboot() {
return this._states.reboot;
- }
- // Status refresh interval in ms
- set refreshInterval(delay) {
if(delay < 500) {
delay = 500; //enforce reasonable minimum
}
this._autoRefresh.delay = delay;
- }
- get refreshInterval() {
return this._autoRefresh.delay;
- }
- // Document loaded (call once from jQuery.ready)
- documentReady() {
// Status refresh late start
if(this.running && (! this._autoRefresh.isRunning)) {
this._autoRefresh_runNow();
}
- }
- //### Private properties ###
- // Pakfire status change handler
- // property: Affected status (running, reboot, ...)
- _states_onChange(property) {
// Always update UI
if(this.running) {
$('#pflog-status').text(this.i18n.get('working'));
$('#pflog-action').empty();
} else {
$('#pflog-status').text(this.i18n.get('finished'));
if(this.reboot) { //Enable return or reboot links in UI
$('#pflog-action').html(this.i18n.get('link_reboot'));
} else {
$('#pflog-action').html(this.i18n.get('link_return'));
}
}
// Start/stop status refresh if Pakfire started/stopped
if(property === 'running') {
if(this.running) {
this._autoRefresh_runNow();
} else {
this._autoRefresh_clearSchedule();
}
}
- }
- //--- Status refresh scheduling functions ---
- // Immediately perform AJAX status refresh request
- _autoRefresh_runNow() {
if(this._autoRefresh.runningXHR) {
return; // Don't send multiple requests
}
this._autoRefresh_clearSchedule(); // Stop scheduled refresh, will send immediately
// Send AJAX request, attach listeners
this._autoRefresh.jqXHR = this._JSON_get(this._autoRefresh.jsonAction, this._autoRefresh.timeout);
this._autoRefresh.jqXHR.done(function() { // Request succeeded
if(this.running) { // Keep refreshing while Pakfire is running
this._autoRefresh_scheduleRun();
}
});
this._autoRefresh.jqXHR.fail(function() { // Request failed
this._autoRefresh_scheduleRun(); // Try refreshing until valid status is received
});
- }
- // Schedule next refresh
- _autoRefresh_scheduleRun() {
if(this._autoRefresh.runningDelay || this._autoRefresh.runningXHR) {
return; // Refresh already scheduled or in progress
}
this._autoRefresh.delayTimer = window.setTimeout(function() {
this._autoRefresh.delayTimer = null;
this._autoRefresh_runNow();
}.bind(this), this._autoRefresh.delay);
- }
- // Stop scheduled refresh (can still be refreshed up to 1x if XHR is already sent)
- _autoRefresh_clearSchedule() {
if(this._autoRefresh.runningDelay) {
window.clearTimeout(this._autoRefresh.delayTimer);
this._autoRefresh.delayTimer = null;
}
- }
- //--- JSON request & data handling ---
- // Load JSON data from Pakfire CGI, using a POST request
- // action: POST paramter "json-[action]"
- // maxTime: XHR timeout, 0 = no timeout
- _JSON_get(action, maxTime = 0) {
return $.ajax({
url: '/cgi-bin/pakfire.cgi',
method: 'POST',
timeout: maxTime,
context: this,
data: {'ACTION': `json-${action}`},
dataType: 'json' //automatically check and convert result
})
.done(function(response) {
this._JSON_process(action, response);
});
- }
- // Process successful response from Pakfire CGI
- // action: POST paramter "json-[action]" used to send request
- // data: JSON data object
- _JSON_process(action, data) {
// Pakfire status refresh
if(action === this._autoRefresh.jsonAction) {
// Update status flags
this.running = (data['running'] != '0');
this.reboot = (data['reboot'] != '0');
// Update timer display
if(this.running && data['running_since']) {
$('#pflog-time').text(this.i18n.get('since') + data['running_since']);
} else {
$('#pflog-time').empty();
}
// Print log messages
let messages = "";
data['messages'].forEach(function(line) {
messages += `${line}\n`;
});
$('#pflog-messages').text(messages);
}
- }
+}
+// Simple translation strings helper +// Format: {key: "translation"} +class PakfireI18N {
- constructor() {
this._strings = Object.create(null); //Object without prototypes
- }
- // Get translation
- get(key) {
if(Object.prototype.hasOwnProperty.call(this._strings, key)) {
return this._strings[key];
}
return `(undefined string '${key}')`;
- }
- // Load key/translation object
- load(translations) {
if(translations instanceof Object) {
Object.assign(this._strings, translations);
}
- }
+}
+//### Initialize Pakfire ### +const pakfire = new PakfireJS();
+$(function() {
- pakfire.documentReady();
+});
2.27.0.windows.1
Hello,
On 2 Dec 2021, at 16:30, Leo Hofmann hofmann@leo-andres.de wrote:
Hi,
Am 02.12.2021 um 16:59 schrieb Michael Tremer:
Hello,
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
Currently the page becomes unresponsive while Pakfire is busy. This patch implements a AJAX/JSON driven log output, to provide continuous information to the user while Pakfire is running.
The output is updated 1x per second, if the load should be too high, the interval can be change by writing to "pakfire.refreshInterval".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 153 ++++++++++++++++++---- html/html/include/pakfire.js | 241 +++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 26 deletions(-) create mode 100644 html/html/include/pakfire.js
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 7957bc154..e5f5f7d6a 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -36,8 +36,11 @@ my %color = (); my %pakfiresettings = (); my %mainsettings = ();
-&Header::showhttpheaders(); +# Load general settings +&General::readhash("${General::swroot}/main/settings", %mainsettings); +&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color);
+# Get CGI request data $cgiparams{'ACTION'} = ''; $cgiparams{'VALID'} = '';
@@ -46,12 +49,102 @@ $cgiparams{'DELPAKS'} = '';
&Header::getcgihash(%cgiparams);
-&General::readhash("${General::swroot}/main/settings", %mainsettings); -&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color); +### Process AJAX/JSON request ### +if($cgiparams{'ACTION'} eq 'json-getstatus') {
- # Send HTTP headers
- _start_json_output();
- # Collect Pakfire status and log messages
- my %status = (
'running' => &_is_pakfire_busy() || "0",
'running_since' => &General::age("$Pakfire::lockfile") || "0s",
'reboot' => (-e "/var/run/need_reboot") || "0"
- );
- my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`;
- # Start JSON file
- print "{\n";
- foreach my $key (keys %status) {
my $value = $status{$key};
print qq{\t"$key": "$value",\n};
- }
- # Print sanitized messages in reverse order to undo previous "tac"
- print qq{\t"messages": [\n};
- for my $index (reverse (0 .. $#messages)) {
my $line = $messages[$index];
$line =~ s/[[:cntrl:]<>&\\]+//g;
print qq{\t\t"$line"};
print ",\n" unless $index < 1;
- }
- print "\n\t]\n";
What is the reason to “tac” the log file first and then reverse the order again?
Is it just to limit the length of the JSON array?
It might be faster to read the entire file, grep out what we need and then throw away most of the array. Or push a line to the end of the array and remove one from the beginning if it is longer than a certain threshold.
I wanted to make sure that only the output of the current Pakfire run is shown. Therefore, I use tac and sed to read the logfile backwards until the last "Pakfire ... started!" header is reached. This works very well, but then of course the messages array is also in reverse order.
Okay, that makes sense. Maybe we should start logging things into a separate file to make things easier?
/var/log/messages can become really large.
A C program is probably the fastest that we could ever have, so might be good enough as a solution for me.
-Michael
All the ideas I had required some form of "reverse", or I had to load the entire file in Perl and check every line. I assumed that tac & sed would be more efficient than any Perl solution I could come up with. I'll try to time this and report back!
Leo
- # Finalize JSON file & stop
- print "}";
- exit;
+}
+### Start pakfire page ### +&Header::showhttpheaders();
+###--- HTML HEAD ---### +my $extraHead = <<END +<style>
- /* Pakfire log viewer */
- section#pflog-header {
width: 100%;
display: flex;
text-align: left;
align-items: center;
column-gap: 20px;
- }
- #pflog-header > div:last-child {
margin-left: auto;
margin-right: 20px;
- }
- #pflog-header span {
line-height: 1.3em;
- }
- #pflog-header span:empty::before {
content: "\\200b"; /* zero width space */
- }
- pre#pflog-messages {
margin-top: 0.7em;
padding-top: 0.7em;
border-top: 0.5px solid $Header::bordercolour;
-&Header::openpage($Lang::tr{'pakfire configuration'}, 1);
text-align: left;
min-height: 15em;
overflow-x: auto;
- }
+</style>
+<script src="/include/pakfire.js"></script> +<script>
- // Translations
- pakfire.i18n.load({
'working': '$Lang::tr{'pakfire working'}',
'finished': 'Pakfire is finished! Please check the log output.',
'since': '$Lang::tr{'since'} ', //(space is intentional)
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>',
'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>'
- });
- // AJAX auto refresh interval
- pakfire.refreshInterval = 1000;
+</script> +END +; +###--- END HTML HEAD ---###
+&Header::openpage($Lang::tr{'pakfire configuration'}, 1, $extraHead); &Header::openbigbox('100%', 'left', '', $errormessage);
+# Process Pakfire commands if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { @@ -170,29 +263,30 @@ if ($errormessage) { &Header::closebox(); }
-# Check if pakfire is already running. -if (&_is_pakfire_busy()) {
- &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" );
- print <<END;
<table>
<tr><td>
<img src='/images/indicator.gif' alt='$Lang::tr{'active'}' title='$Lang::tr{'active'}' />
<td>
$Lang::tr{'pakfire working'}
<tr><td colspan='2' align='center'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='image' alt='$Lang::tr{'reload'}' title='$Lang::tr{'reload'}' src='/images/view-refresh.png' />
</form>
<tr><td colspan='2' align='left'><code>
-END
- my @output = `grep pakfire /var/log/messages | tail -20`;
- foreach (@output) {
print "$_<br>";
- }
- print <<END;
</code>
</table>
+# Show log output while Pakfire is running +if(&_is_pakfire_busy()) {
- &Header::openbox("100%", "center", "Pakfire");
- print <<END
+<section id="pflog-header">
<div><img src="/images/indicator.gif" alt="$Lang::tr{'active'}" title="$Lang::tr{'pagerefresh'}"></div>
<div>
<span id="pflog-status">$Lang::tr{'pakfire working'}</span><br>
<span id="pflog-time"></span><br>
<span id="pflog-action"></span>
</div>
<div><a href="$ENV{'SCRIPT_NAME'}"><img src="/images/view-refresh.png" alt="$Lang::tr{'refresh'}" title="$Lang::tr{'refresh'}"></a></div>
+</section>
+<!-- Pakfire log messages --> +<pre id="pflog-messages"></pre> +<script>
- pakfire.running = true;
+</script>
END +;
- &Header::closebox(); &Header::closebigbox(); &Header::closepage();
@@ -320,3 +414,10 @@ sub _is_pakfire_busy { # Test presence of PID or lockfile return (($pakfire_pid) || (-e "$Pakfire::lockfile")); }
+# Send HTTP headers +sub _start_json_output {
- print "Cache-Control: no-cache, no-store\n";
- print "Content-Type: application/json\n";
- print "\n"; # End of HTTP headers
+} diff --git a/html/html/include/pakfire.js b/html/html/include/pakfire.js new file mode 100644 index 000000000..0950870e0 --- /dev/null +++ b/html/html/include/pakfire.js @@ -0,0 +1,241 @@ +/*############################################################################# +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2021 IPFire Team info@ipfire.org # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +#############################################################################*/
+"use strict";
+// Pakfire Javascript functions (requires jQuery) +class PakfireJS {
- constructor() {
//--- Public properties ---
// Translation strings
this.i18n = new PakfireI18N();
//--- Private properties ---
// Status flags (access outside constructor only with setter/getter)
this._states = Object.create(null);
this._states.running = false;
this._states.reboot = false;
// Status refresh helper
this._autoRefresh = {
delay: 1000, //Delay between requests (default: 1s)
jsonAction: 'getstatus', //CGI POST action parameter
timeout: 5000, //XHR timeout (5s)
delayTimer: null, //setTimeout reference
jqXHR: undefined, //jQuery.ajax promise reference
get runningDelay() { //Waiting for end of delay
return (this.delayTimer !== null);
},
get runningXHR() { //Waiting for CGI response
return (this.jqXHR && (this.jqXHR.state() === 'pending'));
},
get isRunning() {
return (this.runningDelay || this.runningXHR);
}
};
- }
- //### Public properties ###
- // Pakfire is running (true/false)
- set running(state) {
if(this._states.running !== state) {
this._states.running = state;
this._states_onChange('running');
}
- }
- get running() {
return this._states.running;
- }
- // Reboot needed (true/false)
- set reboot(state) {
if(this._states.reboot !== state) {
this._states.reboot = state;
this._states_onChange('reboot');
}
- }
- get reboot() {
return this._states.reboot;
- }
- // Status refresh interval in ms
- set refreshInterval(delay) {
if(delay < 500) {
delay = 500; //enforce reasonable minimum
}
this._autoRefresh.delay = delay;
- }
- get refreshInterval() {
return this._autoRefresh.delay;
- }
- // Document loaded (call once from jQuery.ready)
- documentReady() {
// Status refresh late start
if(this.running && (! this._autoRefresh.isRunning)) {
this._autoRefresh_runNow();
}
- }
- //### Private properties ###
- // Pakfire status change handler
- // property: Affected status (running, reboot, ...)
- _states_onChange(property) {
// Always update UI
if(this.running) {
$('#pflog-status').text(this.i18n.get('working'));
$('#pflog-action').empty();
} else {
$('#pflog-status').text(this.i18n.get('finished'));
if(this.reboot) { //Enable return or reboot links in UI
$('#pflog-action').html(this.i18n.get('link_reboot'));
} else {
$('#pflog-action').html(this.i18n.get('link_return'));
}
}
// Start/stop status refresh if Pakfire started/stopped
if(property === 'running') {
if(this.running) {
this._autoRefresh_runNow();
} else {
this._autoRefresh_clearSchedule();
}
}
- }
- //--- Status refresh scheduling functions ---
- // Immediately perform AJAX status refresh request
- _autoRefresh_runNow() {
if(this._autoRefresh.runningXHR) {
return; // Don't send multiple requests
}
this._autoRefresh_clearSchedule(); // Stop scheduled refresh, will send immediately
// Send AJAX request, attach listeners
this._autoRefresh.jqXHR = this._JSON_get(this._autoRefresh.jsonAction, this._autoRefresh.timeout);
this._autoRefresh.jqXHR.done(function() { // Request succeeded
if(this.running) { // Keep refreshing while Pakfire is running
this._autoRefresh_scheduleRun();
}
});
this._autoRefresh.jqXHR.fail(function() { // Request failed
this._autoRefresh_scheduleRun(); // Try refreshing until valid status is received
});
- }
- // Schedule next refresh
- _autoRefresh_scheduleRun() {
if(this._autoRefresh.runningDelay || this._autoRefresh.runningXHR) {
return; // Refresh already scheduled or in progress
}
this._autoRefresh.delayTimer = window.setTimeout(function() {
this._autoRefresh.delayTimer = null;
this._autoRefresh_runNow();
}.bind(this), this._autoRefresh.delay);
- }
- // Stop scheduled refresh (can still be refreshed up to 1x if XHR is already sent)
- _autoRefresh_clearSchedule() {
if(this._autoRefresh.runningDelay) {
window.clearTimeout(this._autoRefresh.delayTimer);
this._autoRefresh.delayTimer = null;
}
- }
- //--- JSON request & data handling ---
- // Load JSON data from Pakfire CGI, using a POST request
- // action: POST paramter "json-[action]"
- // maxTime: XHR timeout, 0 = no timeout
- _JSON_get(action, maxTime = 0) {
return $.ajax({
url: '/cgi-bin/pakfire.cgi',
method: 'POST',
timeout: maxTime,
context: this,
data: {'ACTION': `json-${action}`},
dataType: 'json' //automatically check and convert result
})
.done(function(response) {
this._JSON_process(action, response);
});
- }
- // Process successful response from Pakfire CGI
- // action: POST paramter "json-[action]" used to send request
- // data: JSON data object
- _JSON_process(action, data) {
// Pakfire status refresh
if(action === this._autoRefresh.jsonAction) {
// Update status flags
this.running = (data['running'] != '0');
this.reboot = (data['reboot'] != '0');
// Update timer display
if(this.running && data['running_since']) {
$('#pflog-time').text(this.i18n.get('since') + data['running_since']);
} else {
$('#pflog-time').empty();
}
// Print log messages
let messages = "";
data['messages'].forEach(function(line) {
messages += `${line}\n`;
});
$('#pflog-messages').text(messages);
}
- }
+}
+// Simple translation strings helper +// Format: {key: "translation"} +class PakfireI18N {
- constructor() {
this._strings = Object.create(null); //Object without prototypes
- }
- // Get translation
- get(key) {
if(Object.prototype.hasOwnProperty.call(this._strings, key)) {
return this._strings[key];
}
return `(undefined string '${key}')`;
- }
- // Load key/translation object
- load(translations) {
if(translations instanceof Object) {
Object.assign(this._strings, translations);
}
- }
+}
+//### Initialize Pakfire ### +const pakfire = new PakfireJS();
+$(function() {
- pakfire.documentReady();
+});
2.27.0.windows.1
Hi,
I made up a simple bash script (attached below) to compare 10,000 runs of tac|sed, grep|tail, plain grep:
tac + sed method (tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}' > /dev/null)
real 0m28.365s user 0m31.261s sys 0m17.783s
grep + tail method (grep pakfire /var/log/messages | tail -20 > /dev/null)
real 1m7.391s user 1m0.450s sys 1m2.278s
plain grep method (grep pakfire /var/log/messages > /dev/null)
real 0m24.822s user 0m16.607s sys 0m9.018s
Although a simple "grep pakfire" seems to be the fastest solution at first glance, you would have to process a lot of data in Perl afterwards, loosing any benefit. The tac+sed method already returns only the desired lines and is still faster than the previously used grep+tail technique. Therefore, I would want to argue that this method is suitable!
What do you think? Is this test acceptable?
Regards Leo
#!/bin/bash tacsed () { for run in {1..10000}; do tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}' > /dev/null done } greptail () { for run in {1..10000}; do grep pakfire /var/log/messages | tail -20 > /dev/null done } plaingrep () { for run in {1..10000}; do grep pakfire /var/log/messages > /dev/null done } echo -e "\ntac + sed method" time tacsed echo -e "\ngrep + tail method" time greptail echo -e "\nplain grep method" time plaingrep
Am 02.12.2021 um 17:30 schrieb Leo Hofmann:
Hi,
Am 02.12.2021 um 16:59 schrieb Michael Tremer:
Hello,
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
Currently the page becomes unresponsive while Pakfire is busy. This patch implements a AJAX/JSON driven log output, to provide continuous information to the user while Pakfire is running.
The output is updated 1x per second, if the load should be too high, the interval can be change by writing to "pakfire.refreshInterval".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 153 ++++++++++++++++++---- html/html/include/pakfire.js | 241 +++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 26 deletions(-) create mode 100644 html/html/include/pakfire.js
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 7957bc154..e5f5f7d6a 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -36,8 +36,11 @@ my %color = (); my %pakfiresettings = (); my %mainsettings = ();
-&Header::showhttpheaders(); +# Load general settings +&General::readhash("${General::swroot}/main/settings", %mainsettings); +&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color);
+# Get CGI request data $cgiparams{'ACTION'} = ''; $cgiparams{'VALID'} = '';
@@ -46,12 +49,102 @@ $cgiparams{'DELPAKS'} = '';
&Header::getcgihash(%cgiparams);
-&General::readhash("${General::swroot}/main/settings", %mainsettings); -&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", %color); +### Process AJAX/JSON request ### +if($cgiparams{'ACTION'} eq 'json-getstatus') { + # Send HTTP headers + _start_json_output();
+ # Collect Pakfire status and log messages + my %status = ( + 'running' => &_is_pakfire_busy() || "0", + 'running_since' => &General::age("$Pakfire::lockfile") || "0s", + 'reboot' => (-e "/var/run/need_reboot") || "0" + ); + my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`;
+ # Start JSON file + print "{\n";
+ foreach my $key (keys %status) { + my $value = $status{$key}; + print qq{\t"$key": "$value",\n}; + }
+ # Print sanitized messages in reverse order to undo previous "tac" + print qq{\t"messages": [\n}; + for my $index (reverse (0 .. $#messages)) { + my $line = $messages[$index]; + $line =~ s/[[:cntrl:]<>&\]+//g;
+ print qq{\t\t"$line"}; + print ",\n" unless $index < 1; + } + print "\n\t]\n";
What is the reason to “tac” the log file first and then reverse the order again?
Is it just to limit the length of the JSON array?
It might be faster to read the entire file, grep out what we need and then throw away most of the array. Or push a line to the end of the array and remove one from the beginning if it is longer than a certain threshold.
I wanted to make sure that only the output of the current Pakfire run is shown. Therefore, I use tac and sed to read the logfile backwards until the last "Pakfire ... started!" header is reached. This works very well, but then of course the messages array is also in reverse order.
All the ideas I had required some form of "reverse", or I had to load the entire file in Perl and check every line. I assumed that tac & sed would be more efficient than any Perl solution I could come up with. I'll try to time this and report back!
Leo
+ # Finalize JSON file & stop + print "}"; + exit; +}
+### Start pakfire page ### +&Header::showhttpheaders();
+###--- HTML HEAD ---### +my $extraHead = <<END +<style> + /* Pakfire log viewer */ + section#pflog-header { + width: 100%; + display: flex; + text-align: left; + align-items: center; + column-gap: 20px; + } + #pflog-header > div:last-child { + margin-left: auto; + margin-right: 20px; + } + #pflog-header span { + line-height: 1.3em; + } + #pflog-header span:empty::before { + content: "\200b"; /* zero width space */ + }
+ pre#pflog-messages { + margin-top: 0.7em; + padding-top: 0.7em; + border-top: 0.5px solid $Header::bordercolour;
-&Header::openpage($Lang::tr{'pakfire configuration'}, 1); + text-align: left; + min-height: 15em; + overflow-x: auto; + } +</style>
+<script src="/include/pakfire.js"></script> +<script> + // Translations + pakfire.i18n.load({ + 'working': '$Lang::tr{'pakfire working'}', + 'finished': 'Pakfire is finished! Please check the log output.', + 'since': '$Lang::tr{'since'} ', //(space is intentional)
+ 'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>', + 'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' + });
+ // AJAX auto refresh interval + pakfire.refreshInterval = 1000; +</script> +END +; +###--- END HTML HEAD ---###
+&Header::openpage($Lang::tr{'pakfire configuration'}, 1, $extraHead); &Header::openbigbox('100%', 'left', '', $errormessage);
+# Process Pakfire commands if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { @@ -170,29 +263,30 @@ if ($errormessage) { &Header::closebox(); }
-# Check if pakfire is already running. -if (&_is_pakfire_busy()) { - &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" ); - print <<END; - <table> - <tr><td> - <img src='/images/indicator.gif' alt='$Lang::tr{'active'}' title='$Lang::tr{'active'}' /> - <td> - $Lang::tr{'pakfire working'} - <tr><td colspan='2' align='center'> - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <input type='image' alt='$Lang::tr{'reload'}' title='$Lang::tr{'reload'}' src='/images/view-refresh.png' /> - </form> - <tr><td colspan='2' align='left'><code> -END - my @output = `grep pakfire /var/log/messages | tail -20`; - foreach (@output) { - print "$_<br>"; - } - print <<END; - </code> - </table> +# Show log output while Pakfire is running +if(&_is_pakfire_busy()) { + &Header::openbox("100%", "center", "Pakfire");
+ print <<END +<section id="pflog-header"> + <div><img src="/images/indicator.gif" alt="$Lang::tr{'active'}" title="$Lang::tr{'pagerefresh'}"></div> + <div> + <span id="pflog-status">$Lang::tr{'pakfire working'}</span><br> + <span id="pflog-time"></span><br> + <span id="pflog-action"></span> + </div> + <div><a href="$ENV{'SCRIPT_NAME'}"><img src="/images/view-refresh.png" alt="$Lang::tr{'refresh'}" title="$Lang::tr{'refresh'}"></a></div> +</section>
+<!-- Pakfire log messages --> +<pre id="pflog-messages"></pre> +<script> + pakfire.running = true; +</script>
END +;
&Header::closebox(); &Header::closebigbox(); &Header::closepage(); @@ -320,3 +414,10 @@ sub _is_pakfire_busy { # Test presence of PID or lockfile return (($pakfire_pid) || (-e "$Pakfire::lockfile")); }
+# Send HTTP headers +sub _start_json_output { + print "Cache-Control: no-cache, no-store\n"; + print "Content-Type: application/json\n"; + print "\n"; # End of HTTP headers +} diff --git a/html/html/include/pakfire.js b/html/html/include/pakfire.js new file mode 100644 index 000000000..0950870e0 --- /dev/null +++ b/html/html/include/pakfire.js @@ -0,0 +1,241 @@ +/*############################################################################# +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2021 IPFire Team info@ipfire.org # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +#############################################################################*/
+"use strict";
+// Pakfire Javascript functions (requires jQuery) +class PakfireJS { + constructor() { + //--- Public properties --- + // Translation strings + this.i18n = new PakfireI18N();
+ //--- Private properties --- + // Status flags (access outside constructor only with setter/getter) + this._states = Object.create(null); + this._states.running = false; + this._states.reboot = false;
+ // Status refresh helper + this._autoRefresh = { + delay: 1000, //Delay between requests (default: 1s) + jsonAction: 'getstatus', //CGI POST action parameter + timeout: 5000, //XHR timeout (5s)
+ delayTimer: null, //setTimeout reference + jqXHR: undefined, //jQuery.ajax promise reference + get runningDelay() { //Waiting for end of delay + return (this.delayTimer !== null); + }, + get runningXHR() { //Waiting for CGI response + return (this.jqXHR && (this.jqXHR.state() === 'pending')); + }, + get isRunning() { + return (this.runningDelay || this.runningXHR); + } + }; + }
+ //### Public properties ###
+ // Pakfire is running (true/false) + set running(state) { + if(this._states.running !== state) { + this._states.running = state; + this._states_onChange('running'); + } + } + get running() { + return this._states.running; + }
+ // Reboot needed (true/false) + set reboot(state) { + if(this._states.reboot !== state) { + this._states.reboot = state; + this._states_onChange('reboot'); + } + } + get reboot() { + return this._states.reboot; + }
+ // Status refresh interval in ms + set refreshInterval(delay) { + if(delay < 500) { + delay = 500; //enforce reasonable minimum + } + this._autoRefresh.delay = delay; + } + get refreshInterval() { + return this._autoRefresh.delay; + }
+ // Document loaded (call once from jQuery.ready) + documentReady() { + // Status refresh late start + if(this.running && (! this._autoRefresh.isRunning)) { + this._autoRefresh_runNow(); + } + }
+ //### Private properties ###
+ // Pakfire status change handler + // property: Affected status (running, reboot, ...) + _states_onChange(property) { + // Always update UI + if(this.running) {
- $('#pflog-status').text(this.i18n.get('working'));
+ $('#pflog-action').empty(); + } else {
- $('#pflog-status').text(this.i18n.get('finished'));
+ if(this.reboot) { //Enable return or reboot links in UI
- $('#pflog-action').html(this.i18n.get('link_reboot'));
+ } else {
- $('#pflog-action').html(this.i18n.get('link_return'));
+ } + }
+ // Start/stop status refresh if Pakfire started/stopped + if(property === 'running') { + if(this.running) { + this._autoRefresh_runNow(); + } else { + this._autoRefresh_clearSchedule(); + } + } + }
+ //--- Status refresh scheduling functions ---
+ // Immediately perform AJAX status refresh request + _autoRefresh_runNow() { + if(this._autoRefresh.runningXHR) { + return; // Don't send multiple requests + } + this._autoRefresh_clearSchedule(); // Stop scheduled refresh, will send immediately
+ // Send AJAX request, attach listeners + this._autoRefresh.jqXHR = this._JSON_get(this._autoRefresh.jsonAction, this._autoRefresh.timeout); + this._autoRefresh.jqXHR.done(function() { // Request succeeded + if(this.running) { // Keep refreshing while Pakfire is running + this._autoRefresh_scheduleRun(); + } + }); + this._autoRefresh.jqXHR.fail(function() { // Request failed + this._autoRefresh_scheduleRun(); // Try refreshing until valid status is received + }); + }
+ // Schedule next refresh + _autoRefresh_scheduleRun() { + if(this._autoRefresh.runningDelay || this._autoRefresh.runningXHR) { + return; // Refresh already scheduled or in progress + } + this._autoRefresh.delayTimer = window.setTimeout(function() { + this._autoRefresh.delayTimer = null; + this._autoRefresh_runNow(); + }.bind(this), this._autoRefresh.delay); + }
+ // Stop scheduled refresh (can still be refreshed up to 1x if XHR is already sent) + _autoRefresh_clearSchedule() { + if(this._autoRefresh.runningDelay) {
- window.clearTimeout(this._autoRefresh.delayTimer);
+ this._autoRefresh.delayTimer = null; + } + }
+ //--- JSON request & data handling ---
+ // Load JSON data from Pakfire CGI, using a POST request + // action: POST paramter "json-[action]" + // maxTime: XHR timeout, 0 = no timeout + _JSON_get(action, maxTime = 0) { + return $.ajax({ + url: '/cgi-bin/pakfire.cgi', + method: 'POST', + timeout: maxTime, + context: this, + data: {'ACTION': `json-${action}`}, + dataType: 'json' //automatically check and convert result + }) + .done(function(response) { + this._JSON_process(action, response); + }); + }
+ // Process successful response from Pakfire CGI + // action: POST paramter "json-[action]" used to send request + // data: JSON data object + _JSON_process(action, data) { + // Pakfire status refresh + if(action === this._autoRefresh.jsonAction) { + // Update status flags + this.running = (data['running'] != '0'); + this.reboot = (data['reboot'] != '0');
+ // Update timer display + if(this.running && data['running_since']) { + $('#pflog-time').text(this.i18n.get('since') + data['running_since']); + } else { + $('#pflog-time').empty(); + }
+ // Print log messages + let messages = ""; + data['messages'].forEach(function(line) { + messages += `${line}\n`; + }); + $('#pflog-messages').text(messages); + } + } +}
+// Simple translation strings helper +// Format: {key: "translation"} +class PakfireI18N { + constructor() { + this._strings = Object.create(null); //Object without prototypes + }
+ // Get translation + get(key) {
- if(Object.prototype.hasOwnProperty.call(this._strings, key)) {
+ return this._strings[key]; + } + return `(undefined string '${key}')`; + }
+ // Load key/translation object + load(translations) { + if(translations instanceof Object) { + Object.assign(this._strings, translations); + } + } +}
+//### Initialize Pakfire ### +const pakfire = new PakfireJS();
+$(function() { + pakfire.documentReady();
+});
2.27.0.windows.1
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de --- html/cgi-bin/pakfire.cgi | 4 ++-- langs/de/cgi-bin/de.pl | 2 ++ langs/en/cgi-bin/en.pl | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index e5f5f7d6a..72518a6fe 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -127,10 +127,10 @@ my $extraHead = <<END // Translations pakfire.i18n.load({ 'working': '$Lang::tr{'pakfire working'}', - 'finished': 'Pakfire is finished! Please check the log output.', + 'finished': '$Lang::tr{'pakfire finished'}', 'since': '$Lang::tr{'since'} ', //(space is intentional)
- 'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>', + 'link_return': '<a href="$ENV{'SCRIPT_NAME'}">$Lang::tr{'pakfire return'}</a>', 'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' }); diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index c81b28fea..490879c90 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1974,6 +1974,7 @@ 'pakfire configuration' => 'Pakfire Konfiguration', 'pakfire core update auto' => 'Core- und Addon-Updates automatisch installieren:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire ist fertig!. Bitte überprüfen Sie die Log Ausgabe.', 'pakfire health check' => 'Mirrors auf Erreichbarkeit prüfen (Ping):', 'pakfire install description' => 'Wählen Sie ein oder mehrere Pakete zur Installation aus und drücken Sie auf das plus-Symbol.', 'pakfire install package' => 'Sie möchten folgende Pakete installieren: ', @@ -1985,6 +1986,7 @@ 'pakfire last update' => 'Letztes Update ist', 'pakfire possible dependency' => ' Möglicherweise haben diese Pakete Abhängigkeiten, d.h. andere Pakete müssen zusätzlich installiert werden. Dazu sehen Sie unten eine Liste.', 'pakfire register' => 'Registrierung am Pakfire-Server:', +'pakfire return' => 'Zurück zu Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Zu verwendendes Pakfire-Repository:', 'pakfire tree stable' => 'Veröffentlichte Versionen (stable)', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index a92bb07f8..4442ea772 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -2009,6 +2009,7 @@ 'pakfire configuration' => 'Pakfire Configuration', 'pakfire core update auto' => 'Install core and addon updates automatically:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire is finished! Please check the log output.', 'pakfire health check' => 'Check if mirror is reachable (ping):', 'pakfire install description' => 'Please choose one or more items from the list below and click the plus to install.', 'pakfire install package' => 'You want to install the following packages: ', @@ -2020,6 +2021,7 @@ 'pakfire last update' => 'Last update made', 'pakfire possible dependency' => ' There may be depending packages, here is a list of packages that need to be installed.', 'pakfire register' => 'Register at pakfire-server:', +'pakfire return' => 'Return to Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Repository', 'pakfire tree stable' => 'Stable',
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 4 ++-- langs/de/cgi-bin/de.pl | 2 ++ langs/en/cgi-bin/en.pl | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index e5f5f7d6a..72518a6fe 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -127,10 +127,10 @@ my $extraHead = <<END // Translations pakfire.i18n.load({ 'working': '$Lang::tr{'pakfire working'}',
'finished': 'Pakfire is finished! Please check the log output.',
'finished': '$Lang::tr{'pakfire finished'}',
Do we generally want to do this? Does the log stay around in the end?
It is mostly for debugging and I would vote for it going away once pakfire is done.
We could show it if there was an error.
'since': '$Lang::tr{'since'} ', //(space is intentional)
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>',
'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' });'link_return': '<a href="$ENV{'SCRIPT_NAME'}">$Lang::tr{'pakfire return'}</a>',
diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index c81b28fea..490879c90 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1974,6 +1974,7 @@ 'pakfire configuration' => 'Pakfire Konfiguration', 'pakfire core update auto' => 'Core- und Addon-Updates automatisch installieren:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire ist fertig!. Bitte überprüfen Sie die Log Ausgabe.', 'pakfire health check' => 'Mirrors auf Erreichbarkeit prüfen (Ping):', 'pakfire install description' => 'Wählen Sie ein oder mehrere Pakete zur Installation aus und drücken Sie auf das plus-Symbol.', 'pakfire install package' => 'Sie möchten folgende Pakete installieren: ', @@ -1985,6 +1986,7 @@ 'pakfire last update' => 'Letztes Update ist', 'pakfire possible dependency' => ' Möglicherweise haben diese Pakete Abhängigkeiten, d.h. andere Pakete müssen zusätzlich installiert werden. Dazu sehen Sie unten eine Liste.', 'pakfire register' => 'Registrierung am Pakfire-Server:', +'pakfire return' => 'Zurück zu Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Zu verwendendes Pakfire-Repository:', 'pakfire tree stable' => 'Veröffentlichte Versionen (stable)', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index a92bb07f8..4442ea772 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -2009,6 +2009,7 @@ 'pakfire configuration' => 'Pakfire Configuration', 'pakfire core update auto' => 'Install core and addon updates automatically:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire is finished! Please check the log output.', 'pakfire health check' => 'Check if mirror is reachable (ping):', 'pakfire install description' => 'Please choose one or more items from the list below and click the plus to install.', 'pakfire install package' => 'You want to install the following packages: ', @@ -2020,6 +2021,7 @@ 'pakfire last update' => 'Last update made', 'pakfire possible dependency' => ' There may be depending packages, here is a list of packages that need to be installed.', 'pakfire register' => 'Register at pakfire-server:', +'pakfire return' => 'Return to Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Repository', 'pakfire tree stable' => 'Stable', -- 2.27.0.windows.1
Am 02.12.2021 um 17:00 schrieb Michael Tremer:
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 4 ++-- langs/de/cgi-bin/de.pl | 2 ++ langs/en/cgi-bin/en.pl | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index e5f5f7d6a..72518a6fe 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -127,10 +127,10 @@ my $extraHead = <<END // Translations pakfire.i18n.load({ 'working': '$Lang::tr{'pakfire working'}',
'finished': 'Pakfire is finished! Please check the log output.',
'finished': '$Lang::tr{'pakfire finished'}',
Do we generally want to do this? Does the log stay around in the end?
Yes, the way I did it, you have to manually go back to Pakfire.
I liked the idea, because it gives you a clear confirmation that the installation was completed.
It is mostly for debugging and I would vote for it going away once pakfire is done.
We could show it if there was an error.
Do you know if there is an easily accessible return code somewhere?
'since': '$Lang::tr{'since'} ', //(space is intentional)
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>',
'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' });'link_return': '<a href="$ENV{'SCRIPT_NAME'}">$Lang::tr{'pakfire return'}</a>',
diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index c81b28fea..490879c90 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1974,6 +1974,7 @@ 'pakfire configuration' => 'Pakfire Konfiguration', 'pakfire core update auto' => 'Core- und Addon-Updates automatisch installieren:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire ist fertig!. Bitte überprüfen Sie die Log Ausgabe.', 'pakfire health check' => 'Mirrors auf Erreichbarkeit prüfen (Ping):', 'pakfire install description' => 'Wählen Sie ein oder mehrere Pakete zur Installation aus und drücken Sie auf das plus-Symbol.', 'pakfire install package' => 'Sie möchten folgende Pakete installieren: ', @@ -1985,6 +1986,7 @@ 'pakfire last update' => 'Letztes Update ist', 'pakfire possible dependency' => ' Möglicherweise haben diese Pakete Abhängigkeiten, d.h. andere Pakete müssen zusätzlich installiert werden. Dazu sehen Sie unten eine Liste.', 'pakfire register' => 'Registrierung am Pakfire-Server:', +'pakfire return' => 'Zurück zu Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Zu verwendendes Pakfire-Repository:', 'pakfire tree stable' => 'Veröffentlichte Versionen (stable)', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index a92bb07f8..4442ea772 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -2009,6 +2009,7 @@ 'pakfire configuration' => 'Pakfire Configuration', 'pakfire core update auto' => 'Install core and addon updates automatically:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire is finished! Please check the log output.', 'pakfire health check' => 'Check if mirror is reachable (ping):', 'pakfire install description' => 'Please choose one or more items from the list below and click the plus to install.', 'pakfire install package' => 'You want to install the following packages: ', @@ -2020,6 +2021,7 @@ 'pakfire last update' => 'Last update made', 'pakfire possible dependency' => ' There may be depending packages, here is a list of packages that need to be installed.', 'pakfire register' => 'Register at pakfire-server:', +'pakfire return' => 'Return to Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Repository', 'pakfire tree stable' => 'Stable', -- 2.27.0.windows.1
Hello,
On 2 Dec 2021, at 16:40, Leo Hofmann hofmann@leo-andres.de wrote:
Am 02.12.2021 um 17:00 schrieb Michael Tremer:
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 4 ++-- langs/de/cgi-bin/de.pl | 2 ++ langs/en/cgi-bin/en.pl | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index e5f5f7d6a..72518a6fe 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -127,10 +127,10 @@ my $extraHead = <<END // Translations pakfire.i18n.load({ 'working': '$Lang::tr{'pakfire working'}',
'finished': 'Pakfire is finished! Please check the log output.',
'finished': '$Lang::tr{'pakfire finished'}',
Do we generally want to do this? Does the log stay around in the end?
Yes, the way I did it, you have to manually go back to Pakfire.
I liked the idea, because it gives you a clear confirmation that the installation was completed.
Okay. Let’s keep it that way then…
It is mostly for debugging and I would vote for it going away once pakfire is done.
We could show it if there was an error.
Do you know if there is an easily accessible return code somewhere?
Not if we send the process into the background. But hopefully this should be clear from the last log message?!
-Michael
'since': '$Lang::tr{'since'} ', //(space is intentional)
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">Return to Pakfire</a>',
'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' });'link_return': '<a href="$ENV{'SCRIPT_NAME'}">$Lang::tr{'pakfire return'}</a>',
diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index c81b28fea..490879c90 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1974,6 +1974,7 @@ 'pakfire configuration' => 'Pakfire Konfiguration', 'pakfire core update auto' => 'Core- und Addon-Updates automatisch installieren:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire ist fertig!. Bitte überprüfen Sie die Log Ausgabe.', 'pakfire health check' => 'Mirrors auf Erreichbarkeit prüfen (Ping):', 'pakfire install description' => 'Wählen Sie ein oder mehrere Pakete zur Installation aus und drücken Sie auf das plus-Symbol.', 'pakfire install package' => 'Sie möchten folgende Pakete installieren: ', @@ -1985,6 +1986,7 @@ 'pakfire last update' => 'Letztes Update ist', 'pakfire possible dependency' => ' Möglicherweise haben diese Pakete Abhängigkeiten, d.h. andere Pakete müssen zusätzlich installiert werden. Dazu sehen Sie unten eine Liste.', 'pakfire register' => 'Registrierung am Pakfire-Server:', +'pakfire return' => 'Zurück zu Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Zu verwendendes Pakfire-Repository:', 'pakfire tree stable' => 'Veröffentlichte Versionen (stable)', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index a92bb07f8..4442ea772 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -2009,6 +2009,7 @@ 'pakfire configuration' => 'Pakfire Configuration', 'pakfire core update auto' => 'Install core and addon updates automatically:', 'pakfire core update level' => 'Core-Update-Level', +'pakfire finished' => 'Pakfire is finished! Please check the log output.', 'pakfire health check' => 'Check if mirror is reachable (ping):', 'pakfire install description' => 'Please choose one or more items from the list below and click the plus to install.', 'pakfire install package' => 'You want to install the following packages: ', @@ -2020,6 +2021,7 @@ 'pakfire last update' => 'Last update made', 'pakfire possible dependency' => ' There may be depending packages, here is a list of packages that need to be installed.', 'pakfire register' => 'Register at pakfire-server:', +'pakfire return' => 'Return to Pakfire', 'pakfire system state' => 'System Status', 'pakfire tree' => 'Repository', 'pakfire tree stable' => 'Stable', -- 2.27.0.windows.1
The extended lockfile test seems to be sufficient to detect a running Pakfire process and display the logs. "Sleep" even proved to be counterproductive, as fast processes can finish in under a second and are then again not detected.
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de --- html/cgi-bin/pakfire.cgi | 5 ----- 1 file changed, 5 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 72518a6fe..e14658ffb 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -149,7 +149,6 @@ if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); - sleep(1); } else { &Header::openbox("100%", "center", $Lang::tr{'request'}); my @output = &General::system_output("/usr/local/bin/pakfire", "resolvedeps", "--no-colors", @pkgs); @@ -187,7 +186,6 @@ END my @pkgs = split(/|/, $cgiparams{'DELPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); - sleep(1); } else { &Header::openbox("100%", "center", $Lang::tr{'request'}); my @output = &General::system_output("/usr/local/bin/pakfire", "resolvedeps", "--no-colors", @pkgs); @@ -224,10 +222,8 @@ END
} elsif (($cgiparams{'ACTION'} eq 'update') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); - sleep(1); } elsif (($cgiparams{'ACTION'} eq 'upgrade') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); - sleep(1); } elsif ($cgiparams{'ACTION'} eq "$Lang::tr{'save'}") { $pakfiresettings{"TREE"} = $cgiparams{"TREE"};
@@ -241,7 +237,6 @@ END
# Update lists &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); - sleep(1); } }
Hi all,
this is my attempt at an AJAX log viewer for Pakfire, as I promised here: https://lists.ipfire.org/pipermail/development/2021-November/011585.html
Stefan's patch must be applied beforehand for this to work! https://patchwork.ipfire.org/patch/4842
In the last patch of this series, I remove the "sleep" calls. If someone has a very slow system, please test both variants, to see if "sleep" is still necessary there.
Happy testing!
Best regards Leo
Am 02.12.2021 um 16:39 schrieb Leo-Andres Hofmann:
This implements a function to determine if Pakfire is already running. It tests the PID and lockfile and can be expanded easily later. 'pidof' checks the full path to avoid confusion.
Removes the unreachable function "refreshpage".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 4d6eee284..7957bc154 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -44,8 +44,6 @@ $cgiparams{'VALID'} = ''; $cgiparams{'INSPAKS'} = ''; $cgiparams{'DELPAKS'} = '';
-sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='1;'>" );print "<center><img src='/images/clock.gif' alt='' /><br/><font color='red'>$Lang::tr{'pagerefresh'}</font></center>";&Header::closebox();}
&Header::getcgihash(%cgiparams);
&General::readhash("${General::swroot}/main/settings", %mainsettings);
@@ -54,7 +52,7 @@ sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' cont &Header::openpage($Lang::tr{'pakfire configuration'}, 1); &Header::openbigbox('100%', 'left', '', $errormessage);
-if (($cgiparams{'ACTION'} eq 'install') && (! -e $Pakfire::lockfile)) { +if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); @@ -92,7 +90,7 @@ END &Header::closepage(); exit; } -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'DELPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); @@ -131,10 +129,10 @@ END exit; }
-} elsif (($cgiparams{'ACTION'} eq 'update') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'update') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); sleep(1); -} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (!-e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); sleep(1); } elsif ($cgiparams{'ACTION'} eq "$Lang::tr{'save'}") { @@ -173,11 +171,7 @@ if ($errormessage) { }
# Check if pakfire is already running. -# -# The system backpipe command is safe, because no user input is computed. -my $pid = `pidof pakfire`;
-if ($pid) { +if (&_is_pakfire_busy()) { &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" ); print <<END;
<table> @@ -203,7 +197,6 @@ END &Header::closebigbox(); &Header::closepage(); exit; - refreshpage(); }
my $core_release = `cat /opt/pakfire/db/core/mine 2>/dev/null`; @@ -314,3 +307,16 @@ END &Header::closebox(); &Header::closebigbox(); &Header::closepage();
+###--- Internal functions ---###
+# Check if pakfire is already running (extend test here if necessary) +sub _is_pakfire_busy {
- # Get PID of a running pakfire instance
- # (The system backpipe command is safe, because no user input is computed.)
- my $pakfire_pid = `pidof -s /usr/local/bin/pakfire`;
- chomp($pakfire_pid);
- # Test presence of PID or lockfile
- return (($pakfire_pid) || (-e "$Pakfire::lockfile"));
+}
Hello,
This patch looks good to me. I only have a small thought...
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
This implements a function to determine if Pakfire is already running. It tests the PID and lockfile and can be expanded easily later. 'pidof' checks the full path to avoid confusion.
Removes the unreachable function "refreshpage".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 4d6eee284..7957bc154 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -44,8 +44,6 @@ $cgiparams{'VALID'} = ''; $cgiparams{'INSPAKS'} = ''; $cgiparams{'DELPAKS'} = '';
-sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='1;'>" );print "<center><img src='/images/clock.gif' alt='' /><br/><font color='red'>$Lang::tr{'pagerefresh'}</font></center>";&Header::closebox();}
&Header::getcgihash(%cgiparams);
&General::readhash("${General::swroot}/main/settings", %mainsettings); @@ -54,7 +52,7 @@ sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' cont &Header::openpage($Lang::tr{'pakfire configuration'}, 1); &Header::openbigbox('100%', 'left', '', $errormessage);
-if (($cgiparams{'ACTION'} eq 'install') && (! -e $Pakfire::lockfile)) { +if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); @@ -92,7 +90,7 @@ END &Header::closepage(); exit; } -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'DELPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); @@ -131,10 +129,10 @@ END exit; }
-} elsif (($cgiparams{'ACTION'} eq 'update') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'update') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); sleep(1); -} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (!-e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); sleep(1); } elsif ($cgiparams{'ACTION'} eq "$Lang::tr{'save'}") { @@ -173,11 +171,7 @@ if ($errormessage) { }
# Check if pakfire is already running. -# -# The system backpipe command is safe, because no user input is computed. -my $pid = `pidof pakfire`;
-if ($pid) { +if (&_is_pakfire_busy()) { &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" ); print <<END;
<table> @@ -203,7 +197,6 @@ END &Header::closebigbox(); &Header::closepage(); exit; - refreshpage(); }
my $core_release = `cat /opt/pakfire/db/core/mine 2>/dev/null`; @@ -314,3 +307,16 @@ END &Header::closebox(); &Header::closebigbox(); &Header::closepage();
+###--- Internal functions ---###
+# Check if pakfire is already running (extend test here if necessary) +sub _is_pakfire_busy {
- # Get PID of a running pakfire instance
- # (The system backpipe command is safe, because no user input is computed.)
- my $pakfire_pid = `pidof -s /usr/local/bin/pakfire`;
- chomp($pakfire_pid);
- # Test presence of PID or lockfile
- return (($pakfire_pid) || (-e "$Pakfire::lockfile"));
As we would normally expect the lock file, it might make sense to move this first as it will be a lot more efficient to check for a file than launching a sub-process.
-Michael
+}
2.27.0.windows.1
Hi,
Thank you for checking the patches!
Am 02.12.2021 um 16:52 schrieb Michael Tremer:
Hello,
This patch looks good to me. I only have a small thought...
On 2 Dec 2021, at 15:39, Leo-Andres Hofmann hofmann@leo-andres.de wrote:
This implements a function to determine if Pakfire is already running. It tests the PID and lockfile and can be expanded easily later. 'pidof' checks the full path to avoid confusion.
Removes the unreachable function "refreshpage".
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 4d6eee284..7957bc154 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -44,8 +44,6 @@ $cgiparams{'VALID'} = ''; $cgiparams{'INSPAKS'} = ''; $cgiparams{'DELPAKS'} = '';
-sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='1;'>" );print "<center><img src='/images/clock.gif' alt='' /><br/><font color='red'>$Lang::tr{'pagerefresh'}</font></center>";&Header::closebox();}
&Header::getcgihash(%cgiparams);
&General::readhash("${General::swroot}/main/settings", %mainsettings); @@ -54,7 +52,7 @@ sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' cont &Header::openpage($Lang::tr{'pakfire configuration'}, 1); &Header::openbigbox('100%', 'left', '', $errormessage);
-if (($cgiparams{'ACTION'} eq 'install') && (! -e $Pakfire::lockfile)) { +if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'INSPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); @@ -92,7 +90,7 @@ END &Header::closepage(); exit; } -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { my @pkgs = split(/|/, $cgiparams{'DELPAKS'}); if ("$cgiparams{'FORCE'}" eq "on") { &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); @@ -131,10 +129,10 @@ END exit; }
-} elsif (($cgiparams{'ACTION'} eq 'update') && (! -e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'update') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); sleep(1); -} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (!-e $Pakfire::lockfile)) { +} elsif (($cgiparams{'ACTION'} eq 'upgrade') && (! &_is_pakfire_busy())) { &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); sleep(1); } elsif ($cgiparams{'ACTION'} eq "$Lang::tr{'save'}") { @@ -173,11 +171,7 @@ if ($errormessage) { }
# Check if pakfire is already running. -# -# The system backpipe command is safe, because no user input is computed. -my $pid = `pidof pakfire`;
-if ($pid) { +if (&_is_pakfire_busy()) { &Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='10;'>" ); print <<END;
<table> @@ -203,7 +197,6 @@ END &Header::closebigbox(); &Header::closepage(); exit; - refreshpage(); }
my $core_release = `cat /opt/pakfire/db/core/mine 2>/dev/null`; @@ -314,3 +307,16 @@ END &Header::closebox(); &Header::closebigbox(); &Header::closepage();
+###--- Internal functions ---###
+# Check if pakfire is already running (extend test here if necessary) +sub _is_pakfire_busy {
- # Get PID of a running pakfire instance
- # (The system backpipe command is safe, because no user input is computed.)
- my $pakfire_pid = `pidof -s /usr/local/bin/pakfire`;
- chomp($pakfire_pid);
- # Test presence of PID or lockfile
- return (($pakfire_pid) || (-e "$Pakfire::lockfile"));
As we would normally expect the lock file, it might make sense to move this first as it will be a lot more efficient to check for a file than launching a sub-process.
That makes sense, I'll split it into two "return" statements.
Regards Leo
-Michael
+}
2.27.0.windows.1
- Improve lockfile test: Return immediately if lockfile is present, to prevent unnecessary and expensive "pidof" calls
- Add better explanation to the log file reading command and JS
- Change user interface: If no errors occurred, the page returns to the main screen (after a short delay). If an error occurred, the log output remains and a message is shown.
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de --- html/cgi-bin/pakfire.cgi | 42 ++++++++++++---- html/html/include/pakfire.js | 96 ++++++++++++++++++++++++++++++++++-- langs/de/cgi-bin/de.pl | 3 +- langs/en/cgi-bin/en.pl | 3 +- 4 files changed, 127 insertions(+), 17 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index e14658ffb..8516b07b1 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -20,6 +20,7 @@ ###############################################################################
use strict; +use List::Util qw(any);
# enable only the following on debugging purpose #use warnings; @@ -54,13 +55,20 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { # Send HTTP headers _start_json_output();
- # Collect Pakfire status and log messages + # Read /var/log/messages backwards until a "Pakfire started" header is found, + # to capture all messages of the last (i.e. current) Pakfire run + my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`; + + # Test if the log contains an error message (fastest implementation, stops at first match) + my $failure = any{ index($_, 'ERROR') != -1 } @messages; + + # Collect Pakfire status my %status = ( 'running' => &_is_pakfire_busy() || "0", 'running_since' => &General::age("$Pakfire::lockfile") || "0s", - 'reboot' => (-e "/var/run/need_reboot") || "0" + 'reboot' => (-e "/var/run/need_reboot") || "0", + 'failure' => $failure || "0" ); - my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`;
# Start JSON file print "{\n"; @@ -128,14 +136,18 @@ my $extraHead = <<END pakfire.i18n.load({ 'working': '$Lang::tr{'pakfire working'}', 'finished': '$Lang::tr{'pakfire finished'}', - 'since': '$Lang::tr{'since'} ', //(space is intentional) + 'finished error': '$Lang::tr{'pakfire finished error'}', + 'since': '$Lang::tr{'since'}',
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">$Lang::tr{'pakfire return'}</a>', 'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' }); - - // AJAX auto refresh interval - pakfire.refreshInterval = 1000; + + // AJAX auto refresh interval (in ms, default: 1000) + //pakfire.refreshInterval = 1000; + + // Enable returning to main screen (delay in ms) + pakfire.setupPageReload(true, 3000); </script> END ; @@ -276,6 +288,7 @@ if(&_is_pakfire_busy()) { <!-- Pakfire log messages --> <pre id="pflog-messages"></pre> <script> + // Start automatic log refresh pakfire.running = true; </script>
@@ -401,13 +414,22 @@ END
# Check if pakfire is already running (extend test here if necessary) sub _is_pakfire_busy { - # Get PID of a running pakfire instance + # Return immediately if lockfile is present + if(-e "$Pakfire::lockfile") { + return 1; + } + + # Check if a PID of a running pakfire instance is found # (The system backpipe command is safe, because no user input is computed.) my $pakfire_pid = `pidof -s /usr/local/bin/pakfire`; chomp($pakfire_pid);
- # Test presence of PID or lockfile - return (($pakfire_pid) || (-e "$Pakfire::lockfile")); + if($pakfire_pid) { + return 1; + } + + # Pakfire isn't running + return 0; }
# Send HTTP headers diff --git a/html/html/include/pakfire.js b/html/html/include/pakfire.js index 0950870e0..44a40c75f 100644 --- a/html/html/include/pakfire.js +++ b/html/html/include/pakfire.js @@ -32,12 +32,13 @@ class PakfireJS { this._states = Object.create(null); this._states.running = false; this._states.reboot = false; + this._states.failure = false;
// Status refresh helper this._autoRefresh = { - delay: 1000, //Delay between requests (default: 1s) + delay: 1000, //Delay between requests (minimum: 500, default: 1s) jsonAction: 'getstatus', //CGI POST action parameter - timeout: 5000, //XHR timeout (5s) + timeout: 5000, //XHR timeout (0 to disable, default: 5s)
delayTimer: null, //setTimeout reference jqXHR: undefined, //jQuery.ajax promise reference @@ -51,10 +52,32 @@ class PakfireJS { return (this.runningDelay || this.runningXHR); } }; + + // Return to main screen helper + this._pageReload = { + delay: 1000, //Delay before page reload (default: 1s) + enabled: false, //Reload disabled by default + + delayTimer: null, //setTimeout reference + get isTriggered() { //Reload timer started + return (this.delayTimer !== null); + } + }; }
//### Public properties ###
+ // Note on using the status flags + // running: Pakfire is performing a task. + // Writing "true" activates the periodic AJAX/JSON status polling, writing "false" stops polling. + // When the task has been completed, status polling stops and this returns to "false". + // The page can then be reloaded to go back to the main screen. Writing "false" does not trigger a reload. + // "refreshInterval" and "setupPageReload" can be used to adjust the respective behaviour. + // reboot: An update requires a reboot. + // If set to "true", a link to the reboot menu is shown after the task is completed. + // failure: An error has occured. + // To display the error log, the page does not return to the main screen. + // Pakfire is running (true/false) set running(state) { if(this._states.running !== state) { @@ -77,6 +100,17 @@ class PakfireJS { return this._states.reboot; }
+ // Error encountered (true/false) + set failure(state) { + if(this._states.failure !== state) { + this._states.failure = state; + this._states_onChange('failure'); + } + } + get failure() { + return this._states.failure; + } + // Status refresh interval in ms set refreshInterval(delay) { if(delay < 500) { @@ -88,6 +122,16 @@ class PakfireJS { return this._autoRefresh.delay; }
+ // Configure page reload after successful task (returns to main screen) + // delay: In ms + setupPageReload(enabled, delay) { + if(delay < 0) { + delay = 0; + } + this._pageReload.delay = delay; + this._pageReload.enabled = enabled; + } + // Document loaded (call once from jQuery.ready) documentReady() { // Status refresh late start @@ -96,6 +140,12 @@ class PakfireJS { } }
+ // Reload entire CGI page (clears POST/GET data from history) + documentReload() { + let url = window.location.origin + window.location.pathname; + window.location.replace(url); + } + //### Private properties ###
// Pakfire status change handler @@ -106,9 +156,13 @@ class PakfireJS { $('#pflog-status').text(this.i18n.get('working')); $('#pflog-action').empty(); } else { - $('#pflog-status').text(this.i18n.get('finished')); + if(this.failure) { + $('#pflog-status').text(this.i18n.get('finished error')); + } else { + $('#pflog-status').text(this.i18n.get('finished')); + } if(this.reboot) { //Enable return or reboot links in UI - $('#pflog-action').html(this.i18n.get('link_reboot')); + $('#pflog-action').html(this.i18n.get('link_return') + " • " + this.i18n.get('link_reboot')); } else { $('#pflog-action').html(this.i18n.get('link_return')); } @@ -122,6 +176,13 @@ class PakfireJS { this._autoRefresh_clearSchedule(); } } + + // Always stay in the log viewer if Pakfire failed + if(property === 'failure') { + if(this.failure) { + this._pageReload_cancel(); + } + } }
//--- Status refresh scheduling functions --- @@ -164,6 +225,25 @@ class PakfireJS { } }
+ // Start delayed page reload to return to main screen + _pageReload_trigger() { + if((! this._pageReload.enabled) || this._pageReload.isTriggered) { + return; // Disabled or already started + } + this._pageReload.delayTimer = window.setTimeout(function() { + this._pageReload.delayTimer = null; + this.documentReload(); + }.bind(this), this._pageReload.delay); + } + + // Stop scheduled reload + _pageReload_cancel() { + if(this._pageReload.isTriggered) { + window.clearTimeout(this._pageReload.delayTimer); + this._pageReload.delayTimer = null; + } + } + //--- JSON request & data handling ---
// Load JSON data from Pakfire CGI, using a POST request @@ -192,10 +272,11 @@ class PakfireJS { // Update status flags this.running = (data['running'] != '0'); this.reboot = (data['reboot'] != '0'); + this.failure = (data['failure'] != '0');
// Update timer display if(this.running && data['running_since']) { - $('#pflog-time').text(this.i18n.get('since') + data['running_since']); + $('#pflog-time').text(this.i18n.get('since') + " " + data['running_since']); } else { $('#pflog-time').empty(); } @@ -206,6 +287,11 @@ class PakfireJS { messages += `${line}\n`; }); $('#pflog-messages').text(messages); + + // Pakfire finished without errors, return to main screen + if((! this.running) && (! this.failure)) { + this._pageReload_trigger(); + } } } } diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index 490879c90..dd946081d 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1974,7 +1974,8 @@ 'pakfire configuration' => 'Pakfire Konfiguration', 'pakfire core update auto' => 'Core- und Addon-Updates automatisch installieren:', 'pakfire core update level' => 'Core-Update-Level', -'pakfire finished' => 'Pakfire ist fertig!. Bitte überprüfen Sie die Log Ausgabe.', +'pakfire finished' => 'Pakfire ist fertig! Kehre zurück...', +'pakfire finished error' => 'Pakfire ist fertig! Fehler sind aufgetreten, bitte überprüfen Sie die Log-Ausgabe, bevor Sie fortfahren.', 'pakfire health check' => 'Mirrors auf Erreichbarkeit prüfen (Ping):', 'pakfire install description' => 'Wählen Sie ein oder mehrere Pakete zur Installation aus und drücken Sie auf das plus-Symbol.', 'pakfire install package' => 'Sie möchten folgende Pakete installieren: ', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index 4442ea772..fc5a57cb0 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -2009,7 +2009,8 @@ 'pakfire configuration' => 'Pakfire Configuration', 'pakfire core update auto' => 'Install core and addon updates automatically:', 'pakfire core update level' => 'Core-Update-Level', -'pakfire finished' => 'Pakfire is finished! Please check the log output.', +'pakfire finished' => 'Pakfire has finished! Returning...', +'pakfire finished error' => 'Pakfire has finished! Errors occurred, please check the log output before proceeding.', 'pakfire health check' => 'Check if mirror is reachable (ping):', 'pakfire install description' => 'Please choose one or more items from the list below and click the plus to install.', 'pakfire install package' => 'You want to install the following packages: ',
Add missing closing tags, indentation and CSS styling. Add link to reboot notice, left-align info list and resize packages lists for better readability.
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de ---
Hi all,
this patch series implements everything that came up in the discussion so far and fixes a few HTML issues I found along the way. Because more changes are probably needed, I have not yet created a new patchset. Instead, this patch builds on the first proposal.
To test it, the following patches must therefore also be applied: https://patchwork.ipfire.org/patch/4842 https://patchwork.ipfire.org/project/ipfire/list/?series=2452
I hope this doesn't cause confusion again, if it does, I can of course build a new patchset :)
Happy holidays! Leo
html/cgi-bin/pakfire.cgi | 137 ++++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 53 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 8516b07b1..51f586aa2 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -100,6 +100,32 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { ###--- HTML HEAD ---### my $extraHead = <<END <style> + /* Main screen */ + table#pfmain { + width: 100%; + border-style: hidden; + table-layout: fixed; + } + + #pfmain td { + padding: 5px 20px 0; + text-align: center; + } + #pfmain tr:not(:last-child) > td { + padding-bottom: 1.5em; + } + #pfmain tr > td.heading { + padding: 0; + font-weight: bold; + background-color: $color{'color20'}; + } + + .pflist { + width: 100%; + text-align: left; + margin-bottom: 0.8em; + } + /* Pakfire log viewer */ section#pflog-header { width: 100%; @@ -173,20 +199,23 @@ END print "$_\n"; } print <<END; - </pre> - <tr><td colspan='2'>$Lang::tr{'pakfire accept all'} - <tr><td colspan='2'> + </pre></td></tr> + <tr><td colspan='2'>$Lang::tr{'pakfire accept all'}</td></tr> + <tr><td colspan='2'> </td></tr> <tr><td align='right'><form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='INSPAKS' value='$cgiparams{'INSPAKS'}' /> <input type='hidden' name='FORCE' value='on' /> <input type='hidden' name='ACTION' value='install' /> <input type='image' alt='$Lang::tr{'install'}' title='$Lang::tr{'install'}' src='/images/go-next.png' /> </form> + </td> <td align='left'> <form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='ACTION' value='' /> <input type='image' alt='$Lang::tr{'abort'}' title='$Lang::tr{'abort'}' src='/images/dialog-error.png' /> </form> + </td> + </tr> </table> END &Header::closebox(); @@ -210,20 +239,23 @@ END print "$_\n"; } print <<END; - </pre> - <tr><td colspan='2'>$Lang::tr{'pakfire uninstall all'} - <tr><td colspan='2'> + </pre></td></tr> + <tr><td colspan='2'>$Lang::tr{'pakfire uninstall all'}</td></tr> + <tr><td colspan='2'> </td></tr> <tr><td align='right'><form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='DELPAKS' value='$cgiparams{'DELPAKS'}' /> <input type='hidden' name='FORCE' value='on' /> <input type='hidden' name='ACTION' value='remove' /> <input type='image' alt='$Lang::tr{'uninstall'}' title='$Lang::tr{'uninstall'}' src='/images/go-next.png' /> </form> + </td> <td align='left'> <form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='ACTION' value='' /> <input type='image' alt='$Lang::tr{'abort'}' title='$Lang::tr{'abort'}' src='/images/dialog-error.png' /> </form> + </td> + </tr> </table> END &Header::closebox(); @@ -311,70 +343,69 @@ my $packages_update_age = &General::age("/opt/pakfire/db/lists/packages_list.db" &Header::openbox("100%", "center", "Pakfire");
print <<END; - <table width='95%' cellpadding='5'> + <table id="pfmain"> END if ( -e "/var/run/need_reboot") { - print "<tr><td align='center' colspan='2'><font color='red'>$Lang::tr{'needreboot'}!</font></td></tr>"; - print "<tr><td colspan='2'> </font></td></tr>" + print "\t\t<tr><td colspan='2'><a href='/cgi-bin/shutdown.cgi'>$Lang::tr{'needreboot'}!</a></td></tr>\n"; } print <<END; - <tr><td width="50%" bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'pakfire system state'}:</b> - - <td width="50%" bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'available updates'}:</b></tr> - - <tr><td align="center">$Lang::tr{'pakfire core update level'}: $core_release<hr /> - $Lang::tr{'pakfire last update'} $core_update_age $Lang::tr{'pakfire ago'}<br /> - $Lang::tr{'pakfire last serverlist update'} $server_update_age $Lang::tr{'pakfire ago'}<br /> - $Lang::tr{'pakfire last core list update'} $corelist_update_age $Lang::tr{'pakfire ago'}<br /> + <tr><td class="heading">$Lang::tr{'pakfire system state'}:</td> + <td class="heading">$Lang::tr{'available updates'}:</td></tr> + + <tr><td><strong>$Lang::tr{'pakfire core update level'}: $core_release</strong> + <hr> + <div class="pflist"> + $Lang::tr{'pakfire last update'} $core_update_age $Lang::tr{'pakfire ago'}<br> + $Lang::tr{'pakfire last serverlist update'} $server_update_age $Lang::tr{'pakfire ago'}<br> + $Lang::tr{'pakfire last core list update'} $corelist_update_age $Lang::tr{'pakfire ago'}<br> $Lang::tr{'pakfire last package update'} $packages_update_age $Lang::tr{'pakfire ago'} - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <input type='hidden' name='ACTION' value='update' /><br /> - <input type='submit' value='$Lang::tr{'calamaris refresh list'}' /><br /> - </form> -<br /> - <td align="center"> + </div> <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <select name="UPDPAKS" size="5" disabled> + <input type='hidden' name='ACTION' value='update' /> + <input type='submit' value='$Lang::tr{'calamaris refresh list'}' /> + </form> + </td> + <td> + <form method='post' action='$ENV{'SCRIPT_NAME'}'> + <select name="UPDPAKS" class="pflist" size="5" disabled> END - &Pakfire::dblist("upgrade", "forweb"); + + &Pakfire::dblist("upgrade", "forweb"); print <<END; </select> - <br /> <input type='hidden' name='ACTION' value='upgrade' /> <input type='image' alt='$Lang::tr{'upgrade'}' title='$Lang::tr{'upgrade'}' src='/images/document-save.png' /> </form> + </td> + </tr> + <tr><td class="heading">$Lang::tr{'pakfire available addons'}</td> + <td class="heading">$Lang::tr{'pakfire installed addons'}</td></tr>
- <tr><td colspan="2"><!-- Just an empty line --> - <tr><td bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'pakfire available addons'}</b> - <td bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'pakfire installed addons'}</b> - <tr><td style="padding:5px 10px 20px 20px" align="center"> - <p>$Lang::tr{'pakfire install description'}</p> - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <select name="INSPAKS" size="10" multiple> + <tr><td><p>$Lang::tr{'pakfire install description'}</p> + <form method='post' action='$ENV{'SCRIPT_NAME'}'> + <select name="INSPAKS" class="pflist" size="10" multiple> END - &Pakfire::dblist("notinstalled", "forweb");
-print <<END; - </select> - <br /> - <input type='hidden' name='ACTION' value='install' /> - <input type='image' alt='$Lang::tr{'install'}' title='$Lang::tr{'install'}' src='/images/list-add.png' /> - </form> - - <td style="padding:5px 10px 20px 20px" align="center"> - <p>$Lang::tr{'pakfire uninstall description'}</p> - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <select name="DELPAKS" size="10" multiple> + &Pakfire::dblist("notinstalled", "forweb"); + print <<END; + </select> + <input type='hidden' name='ACTION' value='install' /> + <input type='image' alt='$Lang::tr{'install'}' title='$Lang::tr{'install'}' src='/images/list-add.png' /> + </form> + </td> + <td><p>$Lang::tr{'pakfire uninstall description'}</p> + <form method='post' action='$ENV{'SCRIPT_NAME'}'> + <select name="DELPAKS" class="pflist" size="10" multiple> END
- &Pakfire::dblist("installed", "forweb"); - -print <<END; - </select> - <br /> - <input type='hidden' name='ACTION' value='remove' /> - <input type='image' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' src='/images/list-remove.png' /> - </form> + &Pakfire::dblist("installed", "forweb"); + print <<END; + </select> + <input type='hidden' name='ACTION' value='remove' /> + <input type='image' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' src='/images/list-remove.png' /> + </form> + </td> + </tr> </table> END
Acked-by: Peter Müller peter.mueller@ipfire.org
Add missing closing tags, indentation and CSS styling. Add link to reboot notice, left-align info list and resize packages lists for better readability.
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
Hi all,
this patch series implements everything that came up in the discussion so far and fixes a few HTML issues I found along the way. Because more changes are probably needed, I have not yet created a new patchset. Instead, this patch builds on the first proposal.
To test it, the following patches must therefore also be applied: https://patchwork.ipfire.org/patch/4842 https://patchwork.ipfire.org/project/ipfire/list/?series=2452
I hope this doesn't cause confusion again, if it does, I can of course build a new patchset :)
Happy holidays! Leo
html/cgi-bin/pakfire.cgi | 137 ++++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 53 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index 8516b07b1..51f586aa2 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -100,6 +100,32 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { ###--- HTML HEAD ---### my $extraHead = <<END
<style> + /* Main screen */ + table#pfmain { + width: 100%; + border-style: hidden; + table-layout: fixed; + } + + #pfmain td { + padding: 5px 20px 0; + text-align: center; + } + #pfmain tr:not(:last-child) > td { + padding-bottom: 1.5em; + } + #pfmain tr > td.heading { + padding: 0; + font-weight: bold; + background-color: $color{'color20'}; + } + + .pflist { + width: 100%; + text-align: left; + margin-bottom: 0.8em; + } + /* Pakfire log viewer */ section#pflog-header { width: 100%; @@ -173,20 +199,23 @@ END print "$_\n"; } print <<END; - </pre> - <tr><td colspan='2'>$Lang::tr{'pakfire accept all'} - <tr><td colspan='2'> + </pre></td></tr> + <tr><td colspan='2'>$Lang::tr{'pakfire accept all'}</td></tr> + <tr><td colspan='2'> </td></tr> <tr><td align='right'><form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='INSPAKS' value='$cgiparams{'INSPAKS'}' /> <input type='hidden' name='FORCE' value='on' /> <input type='hidden' name='ACTION' value='install' /> <input type='image' alt='$Lang::tr{'install'}' title='$Lang::tr{'install'}' src='/images/go-next.png' /> </form> + </td> <td align='left'> <form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='ACTION' value='' /> <input type='image' alt='$Lang::tr{'abort'}' title='$Lang::tr{'abort'}' src='/images/dialog-error.png' /> </form> + </td> + </tr> </table> END &Header::closebox(); @@ -210,20 +239,23 @@ END print "$_\n"; } print <<END; - </pre> - <tr><td colspan='2'>$Lang::tr{'pakfire uninstall all'} - <tr><td colspan='2'> + </pre></td></tr> + <tr><td colspan='2'>$Lang::tr{'pakfire uninstall all'}</td></tr> + <tr><td colspan='2'> </td></tr> <tr><td align='right'><form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='DELPAKS' value='$cgiparams{'DELPAKS'}' /> <input type='hidden' name='FORCE' value='on' /> <input type='hidden' name='ACTION' value='remove' /> <input type='image' alt='$Lang::tr{'uninstall'}' title='$Lang::tr{'uninstall'}' src='/images/go-next.png' /> </form> + </td> <td align='left'> <form method='post' action='$ENV{'SCRIPT_NAME'}'> <input type='hidden' name='ACTION' value='' /> <input type='image' alt='$Lang::tr{'abort'}' title='$Lang::tr{'abort'}' src='/images/dialog-error.png' /> </form> + </td> + </tr> </table> END &Header::closebox(); @@ -311,70 +343,69 @@ my $packages_update_age = &General::age("/opt/pakfire/db/lists/packages_list.db" &Header::openbox("100%", "center", "Pakfire"); print <<END; - <table width='95%' cellpadding='5'> + <table id="pfmain"> END if ( -e "/var/run/need_reboot") { - print "<tr><td align='center' colspan='2'><font color='red'>$Lang::tr{'needreboot'}!</font></td></tr>"; - print "<tr><td colspan='2'> </font></td></tr>" + print "\t\t<tr><td colspan='2'><a href='/cgi-bin/shutdown.cgi'>$Lang::tr{'needreboot'}!</a></td></tr>\n"; } print <<END; - <tr><td width="50%" bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'pakfire system state'}:</b> - - <td width="50%" bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'available updates'}:</b></tr> - - <tr><td align="center">$Lang::tr{'pakfire core update level'}: $core_release<hr /> - $Lang::tr{'pakfire last update'} $core_update_age $Lang::tr{'pakfire ago'}<br /> - $Lang::tr{'pakfire last serverlist update'} $server_update_age $Lang::tr{'pakfire ago'}<br /> - $Lang::tr{'pakfire last core list update'} $corelist_update_age $Lang::tr{'pakfire ago'}<br /> + <tr><td class="heading">$Lang::tr{'pakfire system state'}:</td> + <td class="heading">$Lang::tr{'available updates'}:</td></tr> + + <tr><td><strong>$Lang::tr{'pakfire core update level'}: $core_release</strong> + <hr> + <div class="pflist"> + $Lang::tr{'pakfire last update'} $core_update_age $Lang::tr{'pakfire ago'}<br> + $Lang::tr{'pakfire last serverlist update'} $server_update_age $Lang::tr{'pakfire ago'}<br> + $Lang::tr{'pakfire last core list update'} $corelist_update_age $Lang::tr{'pakfire ago'}<br> $Lang::tr{'pakfire last package update'} $packages_update_age $Lang::tr{'pakfire ago'} - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <input type='hidden' name='ACTION' value='update' /><br /> - <input type='submit' value='$Lang::tr{'calamaris refresh list'}' /><br /> - </form> -<br /> - <td align="center"> + </div> <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <select name="UPDPAKS" size="5" disabled> + <input type='hidden' name='ACTION' value='update' /> + <input type='submit' value='$Lang::tr{'calamaris refresh list'}' /> + </form> + </td> + <td> + <form method='post' action='$ENV{'SCRIPT_NAME'}'> + <select name="UPDPAKS" class="pflist" size="5" disabled> END - &Pakfire::dblist("upgrade", "forweb"); + + &Pakfire::dblist("upgrade", "forweb"); print <<END; </select> - <br /> <input type='hidden' name='ACTION' value='upgrade' /> <input type='image' alt='$Lang::tr{'upgrade'}' title='$Lang::tr{'upgrade'}' src='/images/document-save.png' /> </form> + </td> + </tr> + <tr><td class="heading">$Lang::tr{'pakfire available addons'}</td> + <td class="heading">$Lang::tr{'pakfire installed addons'}</td></tr> - <tr><td colspan="2"><!-- Just an empty line --> - <tr><td bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'pakfire available addons'}</b> - <td bgcolor='$color{'color20'}' align="center"><b>$Lang::tr{'pakfire installed addons'}</b> - <tr><td style="padding:5px 10px 20px 20px" align="center"> - <p>$Lang::tr{'pakfire install description'}</p> - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <select name="INSPAKS" size="10" multiple> + <tr><td><p>$Lang::tr{'pakfire install description'}</p> + <form method='post' action='$ENV{'SCRIPT_NAME'}'> + <select name="INSPAKS" class="pflist" size="10" multiple> END - &Pakfire::dblist("notinstalled", "forweb"); -print <<END; - </select> - <br /> - <input type='hidden' name='ACTION' value='install' /> - <input type='image' alt='$Lang::tr{'install'}' title='$Lang::tr{'install'}' src='/images/list-add.png' /> - </form> - - <td style="padding:5px 10px 20px 20px" align="center"> - <p>$Lang::tr{'pakfire uninstall description'}</p> - <form method='post' action='$ENV{'SCRIPT_NAME'}'> - <select name="DELPAKS" size="10" multiple> + &Pakfire::dblist("notinstalled", "forweb"); + print <<END; + </select> + <input type='hidden' name='ACTION' value='install' /> + <input type='image' alt='$Lang::tr{'install'}' title='$Lang::tr{'install'}' src='/images/list-add.png' /> + </form> + </td> + <td><p>$Lang::tr{'pakfire uninstall description'}</p> + <form method='post' action='$ENV{'SCRIPT_NAME'}'> + <select name="DELPAKS" class="pflist" size="10" multiple> END - &Pakfire::dblist("installed", "forweb"); - -print <<END; - </select> - <br /> - <input type='hidden' name='ACTION' value='remove' /> - <input type='image' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' src='/images/list-remove.png' /> - </form> + &Pakfire::dblist("installed", "forweb"); + print <<END; + </select> + <input type='hidden' name='ACTION' value='remove' /> + <input type='image' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' src='/images/list-remove.png' /> + </form> + </td> + </tr> </table> END
Acked-by: Peter Müller peter.mueller@ipfire.org
- Improve lockfile test: Return immediately if lockfile is present,
to prevent unnecessary and expensive "pidof" calls
Add better explanation to the log file reading command and JS
Change user interface: If no errors occurred, the page returns to
the main screen (after a short delay). If an error occurred, the log output remains and a message is shown.
Signed-off-by: Leo-Andres Hofmann hofmann@leo-andres.de
html/cgi-bin/pakfire.cgi | 42 ++++++++++++---- html/html/include/pakfire.js | 96 ++++++++++++++++++++++++++++++++++-- langs/de/cgi-bin/de.pl | 3 +- langs/en/cgi-bin/en.pl | 3 +- 4 files changed, 127 insertions(+), 17 deletions(-)
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index e14658ffb..8516b07b1 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -20,6 +20,7 @@ ###############################################################################
use strict; +use List::Util qw(any);
# enable only the following on debugging purpose #use warnings; @@ -54,13 +55,20 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { # Send HTTP headers _start_json_output();
- # Collect Pakfire status and log messages
- # Read /var/log/messages backwards until a "Pakfire started" header is found,
- # to capture all messages of the last (i.e. current) Pakfire run
- my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`;
- # Test if the log contains an error message (fastest implementation, stops at first match)
- my $failure = any{ index($_, 'ERROR') != -1 } @messages;
- # Collect Pakfire status my %status = ( 'running' => &_is_pakfire_busy() || "0", 'running_since' => &General::age("$Pakfire::lockfile") || "0s",
'reboot' => (-e "/var/run/need_reboot") || "0"
'reboot' => (-e "/var/run/need_reboot") || "0",
);'failure' => $failure || "0"
my @messages = `tac /var/log/messages | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`;
# Start JSON file print "{\n";
@@ -128,14 +136,18 @@ my $extraHead = <<END pakfire.i18n.load({ 'working': '$Lang::tr{'pakfire working'}', 'finished': '$Lang::tr{'pakfire finished'}',
'since': '$Lang::tr{'since'} ', //(space is intentional)
'finished error': '$Lang::tr{'pakfire finished error'}',
'since': '$Lang::tr{'since'}',
'link_return': '<a href="$ENV{'SCRIPT_NAME'}">$Lang::tr{'pakfire return'}</a>', 'link_reboot': '<a href="/cgi-bin/shutdown.cgi">$Lang::tr{'needreboot'}</a>' });
- // AJAX auto refresh interval
- pakfire.refreshInterval = 1000;
- // AJAX auto refresh interval (in ms, default: 1000)
- //pakfire.refreshInterval = 1000;
- // Enable returning to main screen (delay in ms)
- pakfire.setupPageReload(true, 3000);
</script> END ; @@ -276,6 +288,7 @@ if(&_is_pakfire_busy()) {
<!-- Pakfire log messages -->
<pre id="pflog-messages"></pre>
<script> + // Start automatic log refresh pakfire.running = true; </script>
@@ -401,13 +414,22 @@ END
# Check if pakfire is already running (extend test here if necessary) sub _is_pakfire_busy {
- # Get PID of a running pakfire instance
- # Return immediately if lockfile is present
- if(-e "$Pakfire::lockfile") {
return 1;
- }
- # Check if a PID of a running pakfire instance is found # (The system backpipe command is safe, because no user input is computed.) my $pakfire_pid = `pidof -s /usr/local/bin/pakfire`; chomp($pakfire_pid);
- # Test presence of PID or lockfile
- return (($pakfire_pid) || (-e "$Pakfire::lockfile"));
- if($pakfire_pid) {
return 1;
- }
- # Pakfire isn't running
- return 0;
}
# Send HTTP headers diff --git a/html/html/include/pakfire.js b/html/html/include/pakfire.js index 0950870e0..44a40c75f 100644 --- a/html/html/include/pakfire.js +++ b/html/html/include/pakfire.js @@ -32,12 +32,13 @@ class PakfireJS { this._states = Object.create(null); this._states.running = false; this._states.reboot = false;
this._states.failure = false;
// Status refresh helper this._autoRefresh = {
delay: 1000, //Delay between requests (default: 1s)
delay: 1000, //Delay between requests (minimum: 500, default: 1s) jsonAction: 'getstatus', //CGI POST action parameter
timeout: 5000, //XHR timeout (5s)
timeout: 5000, //XHR timeout (0 to disable, default: 5s) delayTimer: null, //setTimeout reference jqXHR: undefined, //jQuery.ajax promise reference
@@ -51,10 +52,32 @@ class PakfireJS { return (this.runningDelay || this.runningXHR); } };
// Return to main screen helper
this._pageReload = {
delay: 1000, //Delay before page reload (default: 1s)
enabled: false, //Reload disabled by default
delayTimer: null, //setTimeout reference
get isTriggered() { //Reload timer started
return (this.delayTimer !== null);
}
};
}
//### Public properties ###
// Note on using the status flags
// running: Pakfire is performing a task.
// Writing "true" activates the periodic AJAX/JSON status polling, writing "false" stops polling.
// When the task has been completed, status polling stops and this returns to "false".
// The page can then be reloaded to go back to the main screen. Writing "false" does not trigger a reload.
// "refreshInterval" and "setupPageReload" can be used to adjust the respective behaviour.
// reboot: An update requires a reboot.
// If set to "true", a link to the reboot menu is shown after the task is completed.
// failure: An error has occured.
// To display the error log, the page does not return to the main screen.
// Pakfire is running (true/false) set running(state) { if(this._states.running !== state) {
@@ -77,6 +100,17 @@ class PakfireJS { return this._states.reboot; }
- // Error encountered (true/false)
- set failure(state) {
if(this._states.failure !== state) {
this._states.failure = state;
this._states_onChange('failure');
}
- }
- get failure() {
return this._states.failure;
- }
- // Status refresh interval in ms set refreshInterval(delay) { if(delay < 500) {
@@ -88,6 +122,16 @@ class PakfireJS { return this._autoRefresh.delay; }
- // Configure page reload after successful task (returns to main screen)
- // delay: In ms
- setupPageReload(enabled, delay) {
if(delay < 0) {
delay = 0;
}
this._pageReload.delay = delay;
this._pageReload.enabled = enabled;
- }
- // Document loaded (call once from jQuery.ready) documentReady() { // Status refresh late start
@@ -96,6 +140,12 @@ class PakfireJS { } }
// Reload entire CGI page (clears POST/GET data from history)
documentReload() {
let url = window.location.origin + window.location.pathname;
window.location.replace(url);
}
//### Private properties ###
// Pakfire status change handler
@@ -106,9 +156,13 @@ class PakfireJS { $('#pflog-status').text(this.i18n.get('working')); $('#pflog-action').empty(); } else {
$('#pflog-status').text(this.i18n.get('finished'));
if(this.failure) {
$('#pflog-status').text(this.i18n.get('finished error'));
} else {
$('#pflog-status').text(this.i18n.get('finished'));
} if(this.reboot) { //Enable return or reboot links in UI
$('#pflog-action').html(this.i18n.get('link_reboot'));
$('#pflog-action').html(this.i18n.get('link_return') + " • " + this.i18n.get('link_reboot')); } else { $('#pflog-action').html(this.i18n.get('link_return')); }
@@ -122,6 +176,13 @@ class PakfireJS { this._autoRefresh_clearSchedule(); } }
// Always stay in the log viewer if Pakfire failed
if(property === 'failure') {
if(this.failure) {
this._pageReload_cancel();
}
}
}
//--- Status refresh scheduling functions ---
@@ -164,6 +225,25 @@ class PakfireJS { } }
// Start delayed page reload to return to main screen
_pageReload_trigger() {
if((! this._pageReload.enabled) || this._pageReload.isTriggered) {
return; // Disabled or already started
}
this._pageReload.delayTimer = window.setTimeout(function() {
this._pageReload.delayTimer = null;
this.documentReload();
}.bind(this), this._pageReload.delay);
}
// Stop scheduled reload
_pageReload_cancel() {
if(this._pageReload.isTriggered) {
window.clearTimeout(this._pageReload.delayTimer);
this._pageReload.delayTimer = null;
}
}
//--- JSON request & data handling ---
// Load JSON data from Pakfire CGI, using a POST request
@@ -192,10 +272,11 @@ class PakfireJS { // Update status flags this.running = (data['running'] != '0'); this.reboot = (data['reboot'] != '0');
this.failure = (data['failure'] != '0'); // Update timer display if(this.running && data['running_since']) {
$('#pflog-time').text(this.i18n.get('since') + data['running_since']);
$('#pflog-time').text(this.i18n.get('since') + " " + data['running_since']); } else { $('#pflog-time').empty(); }
@@ -206,6 +287,11 @@ class PakfireJS { messages += `${line}\n`; }); $('#pflog-messages').text(messages);
// Pakfire finished without errors, return to main screen
if((! this.running) && (! this.failure)) {
this._pageReload_trigger();
} }}
} diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index 490879c90..dd946081d 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1974,7 +1974,8 @@ 'pakfire configuration' => 'Pakfire Konfiguration', 'pakfire core update auto' => 'Core- und Addon-Updates automatisch installieren:', 'pakfire core update level' => 'Core-Update-Level', -'pakfire finished' => 'Pakfire ist fertig!. Bitte überprüfen Sie die Log Ausgabe.', +'pakfire finished' => 'Pakfire ist fertig! Kehre zurück...', +'pakfire finished error' => 'Pakfire ist fertig! Fehler sind aufgetreten, bitte überprüfen Sie die Log-Ausgabe, bevor Sie fortfahren.', 'pakfire health check' => 'Mirrors auf Erreichbarkeit prüfen (Ping):', 'pakfire install description' => 'Wählen Sie ein oder mehrere Pakete zur Installation aus und drücken Sie auf das plus-Symbol.', 'pakfire install package' => 'Sie möchten folgende Pakete installieren: ', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index 4442ea772..fc5a57cb0 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -2009,7 +2009,8 @@ 'pakfire configuration' => 'Pakfire Configuration', 'pakfire core update auto' => 'Install core and addon updates automatically:', 'pakfire core update level' => 'Core-Update-Level', -'pakfire finished' => 'Pakfire is finished! Please check the log output.', +'pakfire finished' => 'Pakfire has finished! Returning...', +'pakfire finished error' => 'Pakfire has finished! Errors occurred, please check the log output before proceeding.', 'pakfire health check' => 'Check if mirror is reachable (ping):', 'pakfire install description' => 'Please choose one or more items from the list below and click the plus to install.', 'pakfire install package' => 'You want to install the following packages: ',