mirror of
https://github.com/webmin/webmin.git
synced 2026-02-03 22:23:28 +00:00
Compare commits
76 Commits
dev/impove
...
dev/embed-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0221a092b9 | ||
|
|
535d4173b3 | ||
|
|
0256ee47f2 | ||
|
|
abeff44b1a | ||
|
|
35298efd8a | ||
|
|
43fc057484 | ||
|
|
70e9a1c00b | ||
|
|
a780103e2f | ||
|
|
4014293760 | ||
|
|
e1ebcf0506 | ||
|
|
17a27dbe00 | ||
|
|
e36e943251 | ||
|
|
95ee1e2f2d | ||
|
|
37cde80bbe | ||
|
|
45852664fe | ||
|
|
00885b1f76 | ||
|
|
3a151469c7 | ||
|
|
e3b94dc458 | ||
|
|
596ba13b1e | ||
|
|
5e684bf41b | ||
|
|
356c8f7f53 | ||
|
|
185465351a | ||
|
|
8d84e7313a | ||
|
|
71e37adfed | ||
|
|
af912d9539 | ||
|
|
5b31c7df84 | ||
|
|
55b5939194 | ||
|
|
00ddfd4d05 | ||
|
|
2d23a3503e | ||
|
|
a838d11a26 | ||
|
|
5f28a28d8d | ||
|
|
e13df24539 | ||
|
|
4f7924338d | ||
|
|
3a1d609579 | ||
|
|
e441427031 | ||
|
|
469857a41e | ||
|
|
e47c82e7e8 | ||
|
|
a0f6dd935c | ||
|
|
e302b706ec | ||
|
|
8c7fc88d51 | ||
|
|
7b4d905eb6 | ||
|
|
a1a6f669b2 | ||
|
|
0298d884ef | ||
|
|
5a8b3467a1 | ||
|
|
17fb8304c3 | ||
|
|
5cd88dad43 | ||
|
|
c15e7a5e5e | ||
|
|
fad464be47 | ||
|
|
489db4c769 | ||
|
|
cc663af3df | ||
|
|
0b58cd5197 | ||
|
|
dbd16c21cc | ||
|
|
8ddabb35b6 | ||
|
|
8476206da8 | ||
|
|
ca3362ee84 | ||
|
|
e88ba87eae | ||
|
|
a420c7142f | ||
|
|
6f37dc94bf | ||
|
|
c59a200725 | ||
|
|
e56aa7711c | ||
|
|
b480b4caa3 | ||
|
|
db456ad458 | ||
|
|
9513d85157 | ||
|
|
dccc3fb10e | ||
|
|
bb7938a0f5 | ||
|
|
8164480b48 | ||
|
|
4155fdb4c5 | ||
|
|
19efd89c28 | ||
|
|
f911137624 | ||
|
|
d4ac34e4b5 | ||
|
|
5323bda372 | ||
|
|
1b1ac686e3 | ||
|
|
75e9323429 | ||
|
|
554b439bf8 | ||
|
|
2f9a0b3f21 | ||
|
|
cc2502737f |
File diff suppressed because one or more lines are too long
@@ -1567,28 +1567,31 @@ if (!$gconfig{'tempdelete_days'}) {
|
||||
print STDERR "Temp file clearing is disabled\n";
|
||||
return;
|
||||
}
|
||||
|
||||
# Cleanup files in /tmp/.webmin
|
||||
if ($gconfig{'tempdir'} && !$gconfig{'tempdirdelete'}) {
|
||||
print STDERR "Temp file clearing is not done for the custom directory $gconfig{'tempdir'}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
local $tempdir = &transname();
|
||||
$tempdir =~ s/\/([^\/]+)$//;
|
||||
if (!$tempdir || $tempdir eq "/") {
|
||||
$tempdir = "/tmp/.webmin";
|
||||
}
|
||||
|
||||
local $cutoff = time() - $gconfig{'tempdelete_days'}*24*60*60;
|
||||
opendir(DIR, $tempdir);
|
||||
foreach my $f (readdir(DIR)) {
|
||||
next if ($f eq "." || $f eq "..");
|
||||
local @st = lstat("$tempdir/$f");
|
||||
if ($st[9] < $cutoff) {
|
||||
&unlink_file("$tempdir/$f");
|
||||
else {
|
||||
local $tempdir = &transname();
|
||||
$tempdir =~ s/\/([^\/]+)$//;
|
||||
if (!$tempdir || $tempdir eq "/") {
|
||||
$tempdir = "/tmp/.webmin";
|
||||
}
|
||||
}
|
||||
closedir(DIR);
|
||||
|
||||
local $cutoff = time() - $gconfig{'tempdelete_days'}*24*60*60;
|
||||
opendir(DIR, $tempdir);
|
||||
foreach my $f (readdir(DIR)) {
|
||||
next if ($f eq "." || $f eq "..");
|
||||
local @st = lstat("$tempdir/$f");
|
||||
if ($st[9] < $cutoff) {
|
||||
&unlink_file("$tempdir/$f");
|
||||
}
|
||||
}
|
||||
closedir(DIR);
|
||||
}
|
||||
|
||||
# Delete stale lock files
|
||||
my $lockdir = $var_directory."/locks";
|
||||
opendir(DIR, $lockdir);
|
||||
foreach my $f (readdir(DIR)) {
|
||||
@@ -1600,6 +1603,11 @@ foreach my $f (readdir(DIR)) {
|
||||
}
|
||||
}
|
||||
closedir(DIR);
|
||||
|
||||
# Cleanup old websockets
|
||||
foreach (&get_miniserv_websockets_modules()) {
|
||||
&cleanup_miniserv_websockets(undef, $_);
|
||||
}
|
||||
}
|
||||
|
||||
=head2 list_cron_files()
|
||||
|
||||
@@ -349,8 +349,8 @@ if ($fh6) {
|
||||
}
|
||||
while(1) {
|
||||
$$port++;
|
||||
if ($$port >= 65536) {
|
||||
return "Failed to allocate a free port!";
|
||||
if ($$port < 0 || $$port > 65535) {
|
||||
return "Failed to allocate a free port number: $port";
|
||||
}
|
||||
$pack = pack_sockaddr_in($$port, INADDR_ANY);
|
||||
next if (!bind($fh, $pack));
|
||||
@@ -366,4 +366,3 @@ if ($fh6) {
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
skip_index=1
|
||||
lines=100
|
||||
others=1
|
||||
reverse=1
|
||||
log_any=0
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
skip_index=Open log view on module load,1,1-Yes,0-No
|
||||
lines=Default number of lines to display,0,6
|
||||
refresh=Seconds between log view refreshes,3,Never
|
||||
others=Show logs from other modules?,1,1-Yes,0-No
|
||||
others=Show logs from other modules,1,1-Yes,0-No
|
||||
extras=Extra log files to show,9,50,4,\t
|
||||
reverse=Log display order,1,1-Newest lines at top,0-Newest lines at bottom
|
||||
log_any=Can view any file as a log,1,1-Yes,0-No
|
||||
|
||||
@@ -4,26 +4,24 @@
|
||||
|
||||
require './logviewer-lib.pl';
|
||||
|
||||
&ui_print_header($text{'index_subtitle'}, $text{'index_title'}, "", undef, 1, 1, 0,
|
||||
&help_search_link("systemd-journal journalctl", "man", "doc"));
|
||||
|
||||
if (!&has_command('journalctl')) {
|
||||
# Not installed
|
||||
&ui_print_endpage(&text('index_econf', "<tt>$config{'syslog_conf'}</tt>", "../config.cgi?$module_name"));
|
||||
}
|
||||
|
||||
# Display syslog rules
|
||||
my @col0;
|
||||
my @col1;
|
||||
my @col2;
|
||||
my @col3;
|
||||
my @lnks;
|
||||
if ($access{'syslog'}) {
|
||||
my @systemctl_cmds = &get_systemctl_cmds();
|
||||
foreach $o (@systemctl_cmds) {
|
||||
local @cols;
|
||||
push(@cols, &text('index_cmd', "<tt>".$o->{'cmd'}."</tt>"));
|
||||
push(@cols, $o->{'desc'});
|
||||
push(@cols, &ui_link("view_log.cgi?idx=$o->{'id'}&view=1", $text{'index_view'}) );
|
||||
push(@col1, \@cols);
|
||||
push(@cols, &text('index_cmd', "<tt>".
|
||||
&cleanup_destination($o->{'cmd'})."</tt>"));
|
||||
my $icon = $o->{'id'} =~ /journal-(a|x)/ ? "◦ " : "";
|
||||
push(@cols, $icon.&cleanup_description($o->{'desc'}));
|
||||
push(@cols, &ui_link("view_log.cgi?idx=$o->{'id'}&view=1",
|
||||
$text{'index_view'}) );
|
||||
push(@lnks, "view_log.cgi?idx=$o->{'id'}&view=1");
|
||||
push(@col0, \@cols);
|
||||
}
|
||||
|
||||
# System logs from other modules
|
||||
@@ -47,6 +45,8 @@ if ($access{'syslog'}) {
|
||||
map { &html_escape($_) } @{$c->{'sel'}}));
|
||||
push(@cols, &ui_link("view_log.cgi?idx=syslog-".
|
||||
$c->{'index'}."&"."view=1", $text{'index_view'}) );
|
||||
push(@lnks, "view_log.cgi?idx=syslog-".
|
||||
$c->{'index'}."&"."view=1");
|
||||
push(@col1, \@cols);
|
||||
push(@foreign_syslogs, $c->{'file'});
|
||||
}
|
||||
@@ -73,6 +73,9 @@ if ($access{'syslog'}) {
|
||||
"view_log.cgi?idx=syslog-ng-".
|
||||
$dest->{'index'}."&"."view=1",
|
||||
$text{'index_view'}) );
|
||||
push(@lnks, "view_log.cgi?idx=syslog-ng-".
|
||||
$dest->{'index'}."&"."view=1");
|
||||
@cols = sort { $a->[2] cmp $b->[2] } @cols;
|
||||
push(@col1, \@cols);
|
||||
}
|
||||
}
|
||||
@@ -95,9 +98,12 @@ if ($config{'others'} && $access{'others'}) {
|
||||
push(@cols, &text('index_cmd',
|
||||
"<tt>".&html_escape($o->{'cmd'})."</tt>"));
|
||||
}
|
||||
push(@cols, &html_escape($o->{'desc'}));
|
||||
push(@cols, $o->{'desc'} ? &html_escape($o->{'desc'}) : "");
|
||||
push(@cols, &ui_link("view_log.cgi?oidx=$o->{'mindex'}".
|
||||
"&omod=$o->{'mod'}&view=1", $text{'index_view'}) );
|
||||
push(@lnks, "view_log.cgi?oidx=$o->{'mindex'}".
|
||||
"&omod=$o->{'mod'}&view=1");
|
||||
@cols = sort { $a->[2] cmp $b->[2] } @cols;
|
||||
push(@col2, \@cols);
|
||||
}
|
||||
}
|
||||
@@ -114,29 +120,49 @@ foreach $e (&extra_log_files()) {
|
||||
push(@cols, &text('index_cmd',
|
||||
"<tt>".&html_escape($e->{'cmd'})."</tt>"));
|
||||
}
|
||||
push(@cols, &html_escape($e->{'desc'}));
|
||||
push(@cols, $e->{'desc'} ? &html_escape($e->{'desc'}) : "");
|
||||
push(@cols, &ui_link("view_log.cgi?extra=".&urlize($e->{'file'} || $e->{'cmd'})."&view=1", $text{'index_view'}) );
|
||||
push(@lnks, "view_log.cgi?extra=".&urlize($e->{'file'} || $e->{'cmd'})."&view=1");
|
||||
@cols = sort { $a->[2] cmp $b->[2] } @cols;
|
||||
push(@col3, \@cols);
|
||||
}
|
||||
|
||||
# Print sorted table with logs files and commands
|
||||
my @acols = (@col1, @col2, @col3);
|
||||
my @acols = (@col0, @col1, @col2, @col3);
|
||||
|
||||
my $print_header = sub {
|
||||
# Print the header
|
||||
&ui_print_header($text{'index_subtitle'}, $text{'index_title'}, "", undef, 1, 1, 0,
|
||||
&help_search_link("systemd-journal journalctl", "man", "doc"));
|
||||
};
|
||||
|
||||
# If no logs are available just show the message
|
||||
if (!@acols) {
|
||||
$print_header->();
|
||||
&ui_print_endpage($text{'index_elogs'});
|
||||
}
|
||||
|
||||
# If we jump directly to logs just redirect
|
||||
if ($config{'skip_index'} == 1) {
|
||||
if ($lnks[0]) {
|
||||
&redirect($lnks[0]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
# Print the header
|
||||
$print_header->();
|
||||
|
||||
print &ui_columns_start( @acols ? [
|
||||
$text{'index_to'},
|
||||
$text{'index_rule'}, "" ] : [ ], 100);
|
||||
if (@acols) {
|
||||
@acols = sort { $a->[2] cmp $b->[2] } @acols;
|
||||
foreach my $col (@acols) {
|
||||
print &ui_columns_row($col);
|
||||
}
|
||||
}
|
||||
else {
|
||||
print &ui_columns_row([$text{'index_elogs'}], [" colspan='3' style='text-align: center'"], 3);
|
||||
foreach my $col (@acols) {
|
||||
print &ui_columns_row($col);
|
||||
}
|
||||
print &ui_columns_end();
|
||||
print "<p>\n";
|
||||
|
||||
if ($access{'any'}) {
|
||||
if ($access{'any'} && $config{'log_any'} == 1) {
|
||||
# Can view any log (under allowed dirs)
|
||||
print &ui_form_start("view_log.cgi");
|
||||
print &ui_hidden("view", 1),"\n";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
index_title=System Logs Viewer
|
||||
index_elogs=No logs were found to display
|
||||
index_to=Log destination
|
||||
index_rule=Messages selected
|
||||
index_title=System Logs
|
||||
index_elogs=The <tt>journalctl</tt> command is not available on your system, and other logs are configured not to be displayed in the module configuration.
|
||||
index_to=Log
|
||||
index_rule=Description
|
||||
index_file=File $1
|
||||
index_cmd=Output from $1
|
||||
index_return=system logs viewer
|
||||
index_return=system logs
|
||||
index_view=View..
|
||||
index_viewfile=View log file:
|
||||
index_viewok=View
|
||||
@@ -16,16 +16,35 @@ journal_journalctl_debug_info=Debug and info messages
|
||||
journal_journalctl_notice_warning=Notice and warning messages
|
||||
journal_journalctl_err_crit=Error and critical messages
|
||||
journal_journalctl_alert_emerg=Alert and emergency messages
|
||||
journal_journalctl_unit=Messages for specific unit
|
||||
journal_since0=Latest available
|
||||
journal_since1=Real-time follow
|
||||
journal_since2=Current boot
|
||||
journal_since3=7 days ago
|
||||
journal_since4=24 hours ago
|
||||
journal_since5=8 hours ago
|
||||
journal_since6=1 hour ago
|
||||
journal_since7=30 minutes ago
|
||||
journal_since8=10 minutes ago
|
||||
journal_since9=3 minutes ago
|
||||
journal_since10=1 minute ago
|
||||
journal_sincefollow=in
|
||||
journal_since=since
|
||||
|
||||
view_title=View Logfile
|
||||
view_titlejournal=View Journal
|
||||
view_header=Last $1 lines of $2
|
||||
view_header2=Last $1 lines
|
||||
view_header3=Lines of $1
|
||||
view_empty=Log file is empty
|
||||
view_refresh=Refresh
|
||||
view_loading=Log file is being watched .. No new lines yet.
|
||||
view_filter=Filter lines with text $1
|
||||
view_filter_btn=Filter
|
||||
|
||||
save_efile='$1' is not a valid filename : $2
|
||||
save_ecannot2=You are not allowed to view this log
|
||||
save_ecannot3=Error: You are not allowed to view this log
|
||||
save_ecannot4=Error: Could not open '$1'
|
||||
save_ecannot6=You are not allowed to view arbitrary logs
|
||||
save_ecannot7=You are not allowed to view this extra log
|
||||
save_emissing=Missing log file to view
|
||||
|
||||
@@ -27,33 +27,139 @@ foreach $f (@files) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#
|
||||
# Returns standard
|
||||
# get_journal_since
|
||||
# Returns a list of journalctl since commands
|
||||
sub get_journal_since
|
||||
{
|
||||
return [
|
||||
{ "" => $text{'journal_since0'} },
|
||||
{ "-f" => $text{'journal_since1'} },
|
||||
{ "-b" => $text{'journal_since2'} },
|
||||
{ "-S '7 days ago'" => $text{'journal_since3'} },
|
||||
{ "-S '24 hours ago'" => $text{'journal_since4'} },
|
||||
{ "-S '8 hours ago'" => $text{'journal_since5'} },
|
||||
{ "-S '1 hour ago'" => $text{'journal_since6'} },
|
||||
{ "-S '30 minutes ago'" => $text{'journal_since7'} },
|
||||
{ "-S '10 minutes ago'" => $text{'journal_since8'} },
|
||||
{ "-S '3 minutes ago'" => $text{'journal_since9'} },
|
||||
{ "-S '1 minute ago'" => $text{'journal_since10'} },
|
||||
];
|
||||
}
|
||||
|
||||
# get_systemctl_cmds([force-select])
|
||||
# Returns logs for journalctl
|
||||
sub get_systemctl_cmds
|
||||
{
|
||||
my $lines = $config{'lines'} || 1000;
|
||||
return !&has_command('journalctl') ? () : (
|
||||
{ 'cmd' => "journalctl --lines $lines -p alert..emerg",
|
||||
'desc' => $text{'journal_journalctl_alert_emerg'},
|
||||
'id' => "journal-1", },
|
||||
{ 'cmd' => "journalctl --lines $lines -p err..crit",
|
||||
'desc' => $text{'journal_journalctl_err_crit'},
|
||||
'id' => "journal-2", },
|
||||
{ 'cmd' => "journalctl --lines $lines -p notice..warning",
|
||||
'desc' => $text{'journal_journalctl_notice_warning'},
|
||||
'id' => "journal-3", },
|
||||
{ 'cmd' => "journalctl --lines $lines -p debug..info",
|
||||
'desc' => $text{'journal_journalctl_debug_info'},
|
||||
'id' => "journal-4", },
|
||||
{ 'cmd' => "journalctl --lines $lines -k ",
|
||||
'desc' => $text{'journal_journalctl_dmesg'},
|
||||
'id' => "journal-5", },
|
||||
{ 'cmd' => "journalctl --lines $lines -x ",
|
||||
'desc' => $text{'journal_expla_journalctl'},
|
||||
'id' => "journal-6", },
|
||||
{ 'cmd' => "journalctl --lines $lines",
|
||||
my $fselect = shift;
|
||||
my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'}) || 1000;
|
||||
my $journalctl_cmd = &has_command('journalctl');
|
||||
return () if (!$journalctl_cmd);
|
||||
my @rs = (
|
||||
{ 'cmd' => "journalctl -n $lines",
|
||||
'desc' => $text{'journal_journalctl'},
|
||||
'id' => "journal-1", },
|
||||
{ 'cmd' => "journalctl -n $lines -x ",
|
||||
'desc' => $text{'journal_expla_journalctl'},
|
||||
'id' => "journal-2", },
|
||||
{ 'cmd' => "journalctl -n $lines -p alert..emerg",
|
||||
'desc' => $text{'journal_journalctl_alert_emerg'},
|
||||
'id' => "journal-3", },
|
||||
{ 'cmd' => "journalctl -n $lines -p err..crit",
|
||||
'desc' => $text{'journal_journalctl_err_crit'},
|
||||
'id' => "journal-4", },
|
||||
{ 'cmd' => "journalctl -n $lines -p notice..warning",
|
||||
'desc' => $text{'journal_journalctl_notice_warning'},
|
||||
'id' => "journal-5", },
|
||||
{ 'cmd' => "journalctl -n $lines -p debug..info",
|
||||
'desc' => $text{'journal_journalctl_debug_info'},
|
||||
'id' => "journal-6", },
|
||||
{ 'cmd' => "journalctl -n $lines -k ",
|
||||
'desc' => $text{'journal_journalctl_dmesg'},
|
||||
'id' => "journal-7", } );
|
||||
|
||||
# Add more units from config if exists on the system
|
||||
my (%ucache, %uread);
|
||||
my $units_cache = "$module_config_directory/units.cache";
|
||||
&read_file($units_cache, \%ucache);
|
||||
if (!%ucache) {
|
||||
my $out = &backquote_command("systemctl list-units --all --no-legend ".
|
||||
"--no-pager");
|
||||
foreach my $line (split(/\r?\n/, $out)) {
|
||||
$line =~ s/^[^a-z0-9\-\_\.]+//i;
|
||||
my ($unit, $desc) = (split(/\s+/, $line, 5))[0, 4];
|
||||
$uread{$unit} = $desc;
|
||||
}
|
||||
}
|
||||
# All units
|
||||
%ucache = %uread if (%uread);
|
||||
# If forced to select, return full list
|
||||
if ($fselect) {
|
||||
my %units = %uread ? %uread : %ucache;
|
||||
foreach my $u (sort keys %units) {
|
||||
my $uname = $u;
|
||||
$uname =~ s/\\x([0-9A-Fa-f]{2})/pack('H2', $1)/eg;
|
||||
push(@rs, { 'cmd' => "journalctl -n ".
|
||||
"$lines -u $u",
|
||||
'desc' => $uname,
|
||||
'id' => "journal-a-$u", });
|
||||
}
|
||||
}
|
||||
# Otherwise, return only the pointer
|
||||
# element for the index page
|
||||
else {
|
||||
push(@rs,
|
||||
{ 'cmd' => "journalctl -n $lines -u",
|
||||
'desc' => $text{'journal_journalctl_unit'},
|
||||
'id' => "journal-u" });
|
||||
}
|
||||
|
||||
# Save cache
|
||||
if (%uread) {
|
||||
&lock_file($units_cache);
|
||||
&write_file($units_cache, \%ucache);
|
||||
&unlock_file($units_cache);
|
||||
}
|
||||
return @rs;
|
||||
}
|
||||
|
||||
# clear_systemctl_cache()
|
||||
# Clear the cache of systemctl units
|
||||
sub clear_systemctl_cache
|
||||
{
|
||||
unlink("$module_config_directory/units.cache");
|
||||
}
|
||||
|
||||
# cleanup_destination(cmd)
|
||||
# Returns a destination of some command cleaned up for display
|
||||
sub cleanup_destination
|
||||
{
|
||||
my $cmd = shift;
|
||||
$cmd =~ s/-n\s+\d+\s*//;
|
||||
$cmd =~ s/\.service$//;
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
# cleanup_description(desc)
|
||||
# Returns a description cleaned up for display
|
||||
sub cleanup_description
|
||||
{
|
||||
my $desc = shift;
|
||||
$desc =~ s/\s+\(Virtualmin\)//;
|
||||
return $desc;
|
||||
}
|
||||
|
||||
# fix_clashing_description(description, service)
|
||||
# Returns known clashing descriptions fixed
|
||||
sub fix_clashing_description
|
||||
{
|
||||
my ($desc, $serv) = @_;
|
||||
# EL systems name for PHP FastCGI Process Manager is repeated
|
||||
if ($serv =~ /php(\d+)-php-fpm/) {
|
||||
my $php_version = $1;
|
||||
$php_version = join(".", split(//, $php_version));
|
||||
$desc =~ s/PHP/PHP $php_version/;
|
||||
}
|
||||
return $desc;
|
||||
}
|
||||
|
||||
# all_log_files(file)
|
||||
@@ -138,5 +244,12 @@ foreach my $f (@rv) {
|
||||
return @rv;
|
||||
}
|
||||
|
||||
# config_post_save
|
||||
# Called after the module's configuration has been saved
|
||||
sub config_post_save
|
||||
{
|
||||
&clear_systemctl_cache();
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name=logviewer
|
||||
category=system
|
||||
os_support=*-linux
|
||||
desc=System Logs Viewer
|
||||
desc=System Logs
|
||||
depends=proc
|
||||
longdesc=View and search all logs available on system
|
||||
readonly=1
|
||||
|
||||
@@ -7,7 +7,7 @@ require './logviewer-lib.pl';
|
||||
&foreign_require("proc", "proc-lib.pl");
|
||||
|
||||
# Viewing a log file
|
||||
@extras = &extra_log_files();
|
||||
my @extras = &extra_log_files();
|
||||
if ($in{'idx'} =~ /^\//) {
|
||||
# The drop-down selector on this page has chosen a file
|
||||
if (&indexof($in{'idx'}, (map { $_->{'file'} } @extras)) >= 0) {
|
||||
@@ -21,11 +21,31 @@ if ($in{'idx'} =~ /^\//) {
|
||||
delete($in{'idx'});
|
||||
delete($in{'oidx'});
|
||||
}
|
||||
my $journal_since = &get_journal_since();
|
||||
if ($in{'idx'} ne '') {
|
||||
# From systemctl commands
|
||||
if ($in{'idx'} =~ /^journal-/) {
|
||||
my @systemctl_cmds = &get_systemctl_cmds();
|
||||
my ($log) = grep { $_->{'id'} eq $in{'idx'} } @systemctl_cmds;
|
||||
my @systemctl_cmds = &get_systemctl_cmds(1);
|
||||
my ($log);
|
||||
if ($in{'idx'} eq 'journal-u') {
|
||||
($log) = grep { $_->{'cmd'} =~ /-u\s+\w+/ }
|
||||
@systemctl_cmds;
|
||||
$in{'idx'} = $log->{'id'};
|
||||
}
|
||||
else {
|
||||
($log) = grep { $_->{'id'} eq $in{'idx'} }
|
||||
@systemctl_cmds;
|
||||
}
|
||||
# If reverse is set, add it to the command
|
||||
if ($reverse) {
|
||||
$log->{'cmd'} .= " -r";
|
||||
}
|
||||
# If since is set and allowed, add it to the command
|
||||
if ($in{'since'} &&
|
||||
grep { $_ eq $in{'since'} }
|
||||
map { keys %$_ } @$journal_since) {
|
||||
$log->{'cmd'} .= " $in{'since'}";
|
||||
}
|
||||
&can_edit_log($log) && $access{'syslog'} ||
|
||||
&error($text{'save_ecannot2'});
|
||||
$cmd = $log->{'cmd'};
|
||||
@@ -94,99 +114,204 @@ else {
|
||||
}
|
||||
print "Refresh: $config{'refresh'}\r\n"
|
||||
if ($config{'refresh'});
|
||||
&ui_print_header("<tt>".&html_escape($file || $cmd)."</tt>",
|
||||
$in{'linktitle'} || $text{'view_title'}, "", undef, undef, $in{'nonavlinks'});
|
||||
|
||||
$lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'});
|
||||
$filter = $in{'filter'} ? quotemeta($in{'filter'}) : "";
|
||||
my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'});
|
||||
my $jfilter = $in{'filter'} ? $in{'filter'} : "";
|
||||
my $filter = $jfilter ? quotemeta($jfilter) : "";
|
||||
my $reverse = $config{'reverse'} ? 1 : 0;
|
||||
my $follow = $in{'since'} eq '-f' ? 1 : 0;
|
||||
my $no_navlinks = $in{'nonavlinks'} == 1 ? 1 : undef;
|
||||
my $skip_index = $config{'skip_index'} == 1 ? 1 : undef;
|
||||
my $help_link = (!$no_navlinks && $skip_index) ?
|
||||
&help_search_link("systemd-journal journalctl", "man", "doc") : undef;
|
||||
my $no_links = $no_navlinks || $skip_index;
|
||||
my $cmd_unpacked = $cmd;
|
||||
$cmd_unpacked =~ s/\\x([0-9A-Fa-f]{2})/pack('H2', $1)/eg;
|
||||
$cmd_unpacked =~ s/\s+\-r// if ($follow);
|
||||
$cmd_unpacked =~ s/\s+\-n\s+\d+// if ($follow);
|
||||
$cmd_unpacked .= " -g \"@{[&html_escape($jfilter)]}\"" if ($filter);
|
||||
my $view_title = $in{'idx'} =~ /^journal/ ?
|
||||
$text{'view_titlejournal'} : $text{'view_title'};
|
||||
&ui_print_header("<tt>".&html_escape($file || $cmd_unpacked)."</tt>",
|
||||
$in{'linktitle'} || $view_title, "", undef,
|
||||
!$no_navlinks && $skip_index,
|
||||
($no_navlinks || $skip_index) ? 1 : undef,
|
||||
0, $help_link);
|
||||
|
||||
&filter_form();
|
||||
|
||||
$| = 1;
|
||||
print "<pre>";
|
||||
local $tailcmd = $config{'tail_cmd'} || "tail -n LINES";
|
||||
$tailcmd =~ s/LINES/$lines/g;
|
||||
if ($filter ne "") {
|
||||
# Are we supposed to filter anything? Then use grep.
|
||||
local @cats;
|
||||
if ($cmd) {
|
||||
# Getting output from a command
|
||||
push(@cats, $cmd);
|
||||
}
|
||||
elsif ($config{'compressed'}) {
|
||||
# All compressed versions
|
||||
foreach $l (&all_log_files($file)) {
|
||||
$c = &catter_command($l);
|
||||
push(@cats, $c) if ($c);
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Just the one log
|
||||
@cats = ( "cat ".quotemeta($file) );
|
||||
}
|
||||
$cat = "(".join(" ; ", @cats).")";
|
||||
if ($config{'reverse'}) {
|
||||
$tailcmd .= " | tac";
|
||||
}
|
||||
$eflag = $gconfig{'os_type'} =~ /-linux/ ? "-E" : "";
|
||||
$dashflag = $gconfig{'os_type'} =~ /-linux/ ? "--" : "";
|
||||
if (@cats) {
|
||||
$got = &proc::safe_process_exec(
|
||||
"$cat | grep -i -a $eflag $dashflag $filter ".
|
||||
"| $tailcmd",
|
||||
0, 0, STDOUT, undef, 1, 0, undef, 1);
|
||||
}
|
||||
else {
|
||||
$got = undef;
|
||||
}
|
||||
} else {
|
||||
# Not filtering .. so cat the most recent non-empty file
|
||||
if ($cmd) {
|
||||
# Getting output from a command
|
||||
$fullcmd = $cmd." | ".$tailcmd;
|
||||
}
|
||||
elsif ($config{'compressed'}) {
|
||||
# Cat all compressed files
|
||||
# Standard output
|
||||
if (!$follow) {
|
||||
$| = 1;
|
||||
print "<pre>";
|
||||
local $tailcmd = $config{'tail_cmd'} || "tail -n LINES";
|
||||
$tailcmd =~ s/LINES/$lines/g;
|
||||
my ($safe_proc_out, $safe_proc_out_got);
|
||||
if ($filter ne "") {
|
||||
# Are we supposed to filter anything? Then use grep.
|
||||
local @cats;
|
||||
$total = 0;
|
||||
foreach $l (reverse(&all_log_files($file))) {
|
||||
next if (!-s $l);
|
||||
$c = &catter_command($l);
|
||||
if ($c) {
|
||||
$len = int(&backquote_command(
|
||||
"$c | wc -l"));
|
||||
$total += $len;
|
||||
push(@cats, $c);
|
||||
last if ($total > $in{'lines'});
|
||||
if ($cmd) {
|
||||
# Getting output from a command
|
||||
push(@cats, $cmd);
|
||||
}
|
||||
elsif ($config{'compressed'}) {
|
||||
# All compressed versions
|
||||
foreach $l (&all_log_files($file)) {
|
||||
$c = &catter_command($l);
|
||||
push(@cats, $c) if ($c);
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Just the one log
|
||||
@cats = ( "cat ".quotemeta($file) );
|
||||
}
|
||||
$cat = "(".join(" ; ", @cats).")";
|
||||
if ($reverse) {
|
||||
$tailcmd .= " | tac" if ($cmd !~ /journalctl/);
|
||||
}
|
||||
$eflag = $gconfig{'os_type'} =~ /-linux/ ? "-E" : "";
|
||||
$dashflag = $gconfig{'os_type'} =~ /-linux/ ? "--" : "";
|
||||
if (@cats) {
|
||||
$cat = "(".join(" ; ", reverse(@cats)).")";
|
||||
$fullcmd = $cat." | ".$tailcmd;
|
||||
my $fcmd;
|
||||
if ($cmd =~ /journalctl/) {
|
||||
$fcmd = "$cmd -g $filter";
|
||||
}
|
||||
else {
|
||||
$fcmd = "$cat | grep -i -a $eflag $dashflag $filter ".
|
||||
"| $tailcmd";
|
||||
}
|
||||
open(my $output_fh, '>', \$safe_proc_out);
|
||||
$safe_proc_out_got = &proc::safe_process_exec(
|
||||
$fcmd, 0, 0, $output_fh, undef, 1, 0, undef, 1);
|
||||
close($output_fh);
|
||||
print $safe_proc_out if ($safe_proc_out !~ /-- No entries --/m);
|
||||
}
|
||||
else {
|
||||
$fullcmd = undef;
|
||||
$safe_proc_out_got = undef;
|
||||
}
|
||||
} else {
|
||||
# Not filtering .. so cat the most recent non-empty file
|
||||
if ($cmd) {
|
||||
# Getting output from a command
|
||||
$fullcmd = $cmd.($cmd =~ /journalctl/ ? "" : (" | ".$tailcmd));
|
||||
}
|
||||
elsif ($config{'compressed'}) {
|
||||
# Cat all compressed files
|
||||
local @cats;
|
||||
$total = 0;
|
||||
foreach $l (reverse(&all_log_files($file))) {
|
||||
next if (!-s $l);
|
||||
$c = &catter_command($l);
|
||||
if ($c) {
|
||||
$len = int(&backquote_command(
|
||||
"$c | wc -l"));
|
||||
$total += $len;
|
||||
push(@cats, $c);
|
||||
last if ($total > $in{'lines'});
|
||||
}
|
||||
}
|
||||
if (@cats) {
|
||||
$cat = "(".join(" ; ", reverse(@cats)).")";
|
||||
$fullcmd = $cat." | ".$tailcmd;
|
||||
}
|
||||
else {
|
||||
$fullcmd = undef;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Just run tail on the file
|
||||
$fullcmd = $tailcmd." ".quotemeta($file);
|
||||
}
|
||||
if ($reverse && $fullcmd) {
|
||||
$fullcmd .= " | tac" if ($fullcmd !~ /journalctl/);
|
||||
}
|
||||
if ($fullcmd) {
|
||||
open(my $output_fh, '>', \$safe_proc_out);
|
||||
$safe_proc_out_got = &proc::safe_process_exec(
|
||||
$fullcmd, 0, 0, $output_fh, undef, 1, 0, undef, 1);
|
||||
close($output_fh);
|
||||
print $safe_proc_out if ($safe_proc_out !~ /-- No entries --/m);
|
||||
}
|
||||
else {
|
||||
$safe_proc_out_got = undef;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Just run tail on the file
|
||||
$fullcmd = $tailcmd." ".quotemeta($file);
|
||||
}
|
||||
if ($config{'reverse'} && $fullcmd) {
|
||||
$fullcmd .= " | tac";
|
||||
}
|
||||
if ($fullcmd) {
|
||||
$got = &proc::safe_process_exec(
|
||||
$fullcmd, 0, 0, STDOUT, undef, 1, 0, undef, 1);
|
||||
}
|
||||
else {
|
||||
$got = undef;
|
||||
}
|
||||
print "<i data-empty>$text{'view_empty'}</i>\n"
|
||||
if (!$safe_proc_out_got || $safe_proc_out =~ /-- No entries --/m);
|
||||
print "</pre>\n";
|
||||
}
|
||||
# Progressive output
|
||||
else {
|
||||
print "<pre id='logdata' data-reversed='$reverse'>";
|
||||
print "<i data-loading>$text{'view_loading'}</i>\n";
|
||||
print "</pre>\n";
|
||||
my %tinfo = &get_theme_info($current_theme);
|
||||
my $spa_theme = $tinfo{'spa'} ? 1 : 0;
|
||||
print <<EOF;
|
||||
<script>
|
||||
// Abort previous log viewer progress fetch
|
||||
if (typeof fn_logviewer_progress_abort === 'function') {
|
||||
fn_logviewer_progress_abort();
|
||||
}
|
||||
// Update log viewer with new data from the server
|
||||
(async function () {
|
||||
const logviewer_progress_abort = new AbortController();
|
||||
const logDataElement = document.getElementById("logdata"),
|
||||
response = await fetch("view_log_progress.cgi?idx=$in{'idx'}&filter=$jfilter",
|
||||
{ signal: logviewer_progress_abort.signal }),
|
||||
reader = response.body.getReader(),
|
||||
decoder = new TextDecoder("utf-8"),
|
||||
processText = async function () {
|
||||
let { done, value } = await reader.read();
|
||||
while (!done) {
|
||||
const chunk = decoder.decode(value, { stream: true }).trim(),
|
||||
dataReversed = logDataElement.getAttribute("data-reversed");
|
||||
if (!processText.started) {
|
||||
processText.started = true;
|
||||
const loadingElement = logDataElement.querySelector("i[data-loading]");
|
||||
if (loadingElement) {
|
||||
loadingElement.remove();
|
||||
}
|
||||
}
|
||||
let lines = chunk.split("\\n");
|
||||
if (dataReversed === "1") {
|
||||
lines = lines.reverse();
|
||||
logDataElement.textContent =
|
||||
lines.join("\\n") + "\\n" + logDataElement.textContent;
|
||||
}
|
||||
else {
|
||||
logDataElement.textContent += lines.join("\\n") + "\\n";
|
||||
}
|
||||
if (typeof fn_logviewer_progress_update === 'function') {
|
||||
fn_logviewer_progress_update(chunk, dataReversed);
|
||||
}
|
||||
({ done, value } = await reader.read());
|
||||
}
|
||||
};
|
||||
if (typeof fn_logviewer_progress_status === 'function') {
|
||||
fn_logviewer_progress_status(response);
|
||||
}
|
||||
fn_logviewer_progress_abort = function () {
|
||||
logviewer_progress_abort.abort();
|
||||
fn_logviewer_progress_abort = null;
|
||||
}
|
||||
if ($spa_theme !== 1) {
|
||||
window.onbeforeunload = function() {
|
||||
if (typeof fn_logviewer_progress_abort === 'function') {
|
||||
fn_logviewer_progress_abort();
|
||||
}
|
||||
};
|
||||
}
|
||||
processText().catch((error) => {
|
||||
if (typeof fn_logviewer_progress_ended === 'function') {
|
||||
fn_logviewer_progress_ended(error);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
EOF
|
||||
}
|
||||
print "<i>$text{'view_empty'}</i>\n" if (!$got);
|
||||
print "</pre>\n";
|
||||
&filter_form();
|
||||
if ($in{'nonavlinks'}) {
|
||||
if ($no_links) {
|
||||
&ui_print_footer();
|
||||
}
|
||||
else {
|
||||
@@ -196,7 +321,9 @@ else {
|
||||
sub filter_form
|
||||
{
|
||||
print &ui_form_start("view_log.cgi");
|
||||
print &ui_hidden("nonavlinks", $in{'nonavlinks'} ? 1 : 0),"\n";
|
||||
if ($no_navlinks) {
|
||||
print &ui_hidden("nonavlinks", $no_navlinks),"\n";
|
||||
}
|
||||
print &ui_hidden("linktitle", $in{'linktitle'}),"\n";
|
||||
print &ui_hidden("oidx", $in{'oidx'}),"\n";
|
||||
print &ui_hidden("omod", $in{'omod'}),"\n";
|
||||
@@ -210,10 +337,11 @@ my $found = 0;
|
||||
my $text_view_header = 'view_header';
|
||||
if ($access{'syslog'}) {
|
||||
# Logs from syslog
|
||||
my @systemctl_cmds = &get_systemctl_cmds();
|
||||
my @systemctl_cmds = &get_systemctl_cmds(1);
|
||||
foreach $c (@systemctl_cmds) {
|
||||
next if (!&can_edit_log($c));
|
||||
push(@logfiles, [ $c->{'id'}, "$c->{'desc'}" ]);
|
||||
my $icon = $c->{'id'} =~ /journal-(a|x)/ ? "◦ " : "";
|
||||
push(@logfiles, [ $c->{'id'}, $icon.$c->{'desc'} ]);
|
||||
$found++ if ($c->{'id'} eq $in{'idx'});
|
||||
}
|
||||
|
||||
@@ -267,18 +395,35 @@ foreach $e (&extra_log_files()) {
|
||||
}
|
||||
if (@logfiles && $found) {
|
||||
$sel = &ui_select("idx", $in{'idx'} eq '' ? $file : $in{'idx'},
|
||||
[ @logfiles ], undef, undef, undef, undef, "onChange='form.submit()'");
|
||||
[ @logfiles ], undef, undef, undef, undef,
|
||||
"onChange='form.submit()' style='max-width: 240px'");
|
||||
if ($in{'idx'} =~ /^journal-/) {
|
||||
my $since_label = $follow ? $text{'journal_sincefollow'} :
|
||||
$text{'journal_since'};
|
||||
$sel .= "$since_label " .
|
||||
&ui_select("since", $in{'since'},
|
||||
[ map { my ($key) = keys %$_;
|
||||
[ $key, $_->{$key} ] }
|
||||
@$journal_since ],
|
||||
undef, undef, undef, undef,
|
||||
"onChange='form.submit()'");
|
||||
}
|
||||
}
|
||||
else {
|
||||
$text_view_header = 'view_header2';
|
||||
print &ui_hidden("idx", $in{'idx'}),"\n";
|
||||
}
|
||||
|
||||
print &text($text_view_header, " " . &ui_textbox("lines", $lines, 3), " $sel"),"\n";
|
||||
if ($follow) {
|
||||
print &text('view_header3', " $sel"),"\n";
|
||||
}
|
||||
else {
|
||||
print &text($text_view_header, " " . &ui_textbox("lines", $lines, 3), " $sel"),"\n";
|
||||
}
|
||||
print " \n";
|
||||
print &text('view_filter', " " . &ui_textbox("filter", $in{'filter'}, 25)),"\n";
|
||||
print &text('view_filter', " " . &ui_textbox("filter", $in{'filter'}, 12)),"\n";
|
||||
|
||||
print " \n";
|
||||
print &ui_submit($text{'view_refresh'});
|
||||
print &ui_submit($text{'view_filter_btn'});
|
||||
print &ui_form_end(),"<br>\n";
|
||||
}
|
||||
|
||||
|
||||
47
logviewer/view_log_progress.cgi
Normal file
47
logviewer/view_log_progress.cgi
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/local/bin/perl
|
||||
# view_log_progress.cgi
|
||||
# Returns progressive output for some system log
|
||||
|
||||
require './logviewer-lib.pl';
|
||||
&ReadParse();
|
||||
&foreign_require("proc", "proc-lib.pl");
|
||||
|
||||
# Send headers
|
||||
print "Content-Type: text/plain\n\n";
|
||||
|
||||
# System log to follow
|
||||
my @systemctl_cmds = &get_systemctl_cmds(1);
|
||||
my ($log) = grep { $_->{'id'} eq $in{'idx'} } @systemctl_cmds;
|
||||
if (!&can_edit_log($log) ||
|
||||
!$log->{'cmd'} ||
|
||||
$log->{'cmd'} !~ /^journalctl/) {
|
||||
print $text{'save_ecannot3'};
|
||||
exit;
|
||||
}
|
||||
|
||||
# Disable output buffering
|
||||
$| = 1;
|
||||
|
||||
# No lines for real time logs
|
||||
$log->{'cmd'} =~ s/\s+\-n\s+\d+//;
|
||||
|
||||
# Show real time logs
|
||||
$log->{'cmd'} .= " -f";
|
||||
|
||||
# Add filter to the command if present
|
||||
my $filter = $in{'filter'} ? quotemeta($in{'filter'}) : "";
|
||||
if ($filter) {
|
||||
$log->{'cmd'} .= " -g $filter";
|
||||
}
|
||||
|
||||
# Open a pipe to the journalctl command
|
||||
my $pid = open(my $fh, '-|', $log->{'cmd'}) ||
|
||||
print &text('save_ecannot4', $log->{'cmd'}).": $!";
|
||||
|
||||
# Read and output the log
|
||||
while (my $line = <$fh>) {
|
||||
print $line;
|
||||
}
|
||||
|
||||
# Clean up when done
|
||||
close($fh);
|
||||
@@ -4240,4 +4240,548 @@ foreach my $h (@{$mail->{'headers'}}) {
|
||||
return $rv;
|
||||
}
|
||||
|
||||
# parse_calendar_file(calendar-file|lines)
|
||||
# Parses an iCalendar file and returns a list of events
|
||||
sub parse_calendar_file
|
||||
{
|
||||
my ($calendar_file) = @_;
|
||||
my (@events, %event, $line);
|
||||
eval "use DateTime; use DateTime::TimeZone;";
|
||||
return \@events if ($@);
|
||||
# Timezone map
|
||||
my %timezone_map = (
|
||||
'Afghanistan Time' => 'AFT',
|
||||
'Alaskan Daylight Time' => 'AKDT',
|
||||
'Alaskan Standard Time' => 'AKST',
|
||||
'Anadyr Time' => 'ANAT',
|
||||
'Arabian Standard Time' => 'AST',
|
||||
'Argentina Time' => 'ART',
|
||||
'Atlantic Daylight Time' => 'ADT',
|
||||
'Atlantic Standard Time' => 'AST',
|
||||
'Australian Central Daylight Time' => 'ACDT',
|
||||
'Australian Central Standard Time' => 'ACST',
|
||||
'Australian Eastern Daylight Time' => 'AEDT',
|
||||
'Australian Eastern Standard Time' => 'AEST',
|
||||
'Bangladesh Standard Time' => 'BST',
|
||||
'Brasília Time' => 'BRT',
|
||||
'British Summer Time' => 'BST',
|
||||
'Central Africa Time' => 'CAT',
|
||||
'Central Asia Time' => 'ALMT',
|
||||
'Central Daylight Time' => 'CDT',
|
||||
'Central Daylight Time (US)' => 'CDT',
|
||||
'Central European Summer Time' => 'CEST',
|
||||
'Central European Time' => 'CET',
|
||||
'Central Indonesia Time' => 'WITA',
|
||||
'Central Standard Time (Australia)' => 'CST',
|
||||
'Central Standard Time (US)' => 'CST',
|
||||
'Central Standard Time' => 'CST',
|
||||
'Chamorro Daylight Time' => 'CHDT',
|
||||
'Chamorro Standard Time' => 'CHST',
|
||||
'China Standard Time' => 'CST',
|
||||
'Coordinated Universal Time' => 'UTC',
|
||||
'East Africa Time' => 'EAT',
|
||||
'Eastern Africa Time' => 'EAT',
|
||||
'Eastern Daylight Time' => 'EDT',
|
||||
'Eastern Daylight Time (US)' => 'EDT',
|
||||
'Eastern European Summer Time' => 'EEST',
|
||||
'Eastern European Time' => 'EET',
|
||||
'Eastern Indonesia Time' => 'WIT',
|
||||
'Eastern Standard Time (Australia)' => 'EST',
|
||||
'Eastern Standard Time (US)' => 'EST',
|
||||
'Eastern Standard Time' => 'EST',
|
||||
'Fiji Time' => 'FJT',
|
||||
'Greenwich Mean Time' => 'GMT',
|
||||
'Hawaii-Aleutian Daylight Time' => 'HADT',
|
||||
'Hawaii-Aleutian Standard Time' => 'HAST',
|
||||
'Hawaiian Standard Time' => 'HST',
|
||||
'Hong Kong Time' => 'HKT',
|
||||
'Indian Standard Time' => 'IST',
|
||||
'Iran Standard Time' => 'IRST',
|
||||
'Irish Standard Time' => 'IST',
|
||||
'Israel Standard Time' => 'IST',
|
||||
'Japan Standard Time' => 'JST',
|
||||
'Korea Standard Time' => 'KST',
|
||||
'Magadan Time' => 'MAGT',
|
||||
'Malaysia Time' => 'MYT',
|
||||
'Moscow Standard Time' => 'MSK',
|
||||
'Mountain Daylight Time' => 'MDT',
|
||||
'Mountain Standard Time' => 'MST',
|
||||
'Myanmar Standard Time' => 'MMT',
|
||||
'Nepal Time' => 'NPT',
|
||||
'New Caledonia Time' => 'NCT',
|
||||
'New Zealand Daylight Time' => 'NZDT',
|
||||
'New Zealand Standard Time' => 'NZST',
|
||||
'Newfoundland Daylight Time' => 'NDT',
|
||||
'Newfoundland Standard Time' => 'NST',
|
||||
'Pacific Daylight Time' => 'PDT',
|
||||
'Pacific Standard Time' => 'PST',
|
||||
'Pakistan Standard Time' => 'PKT',
|
||||
'Philippine Time' => 'PHT',
|
||||
'Sakhalin Time' => 'SAKT',
|
||||
'Samoa Standard Time' => 'SST',
|
||||
'Singapore Standard Time' => 'SGT',
|
||||
'South Africa Standard Time' => 'SAST',
|
||||
'Tahiti Time' => 'TAHT',
|
||||
'Venezuelan Standard Time' => 'VET',
|
||||
'West Africa Time' => 'WAT',
|
||||
'Western European Summer Time' => 'WEST',
|
||||
'Western European Time' => 'WET',
|
||||
'Western Indonesia Time' => 'WIB',
|
||||
'Western Standard Time (Australia)' => 'WST',
|
||||
);
|
||||
# Make a date from a special timestamp
|
||||
my $adjust_time_with_timezone = sub {
|
||||
my ($time, $tzid) = @_;
|
||||
my $dt = DateTime->new(
|
||||
year => substr($time, 0, 4),
|
||||
month => substr($time, 4, 2),
|
||||
day => substr($time, 6, 2),
|
||||
hour => substr($time, 9, 2),
|
||||
minute => substr($time, 11, 2),
|
||||
second => substr($time, 13, 2),
|
||||
time_zone => $tzid);
|
||||
my $local_dt = $dt->clone->set_time_zone('local');
|
||||
return {
|
||||
formatted => $dt->strftime("%Y-%m-%d %H:%M:%S"),
|
||||
timestamp => $dt->epoch,
|
||||
formatted_local => $local_dt->strftime('%Y-%m-%d %H:%M:%S'),
|
||||
timestamp_local => $local_dt->epoch,
|
||||
};
|
||||
};
|
||||
# Lines processor
|
||||
my $process_line = sub
|
||||
{
|
||||
my ($line) = @_;
|
||||
# Start a new event
|
||||
if ($line =~ /^BEGIN:VEVENT/) {
|
||||
%event = ();
|
||||
$event{'description'} = [ ];
|
||||
$event{'attendees'} = [ ];
|
||||
}
|
||||
# Convert times using the timezone
|
||||
elsif ($line =~ /^END:VEVENT/) {
|
||||
# Local timezone
|
||||
$event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name();
|
||||
$event{'tzid'} = 'UTC', $event{'tzid_missing'} = 1 if (!$event{'tzid'});
|
||||
# Adjust times with timezone
|
||||
my ($adjusted_start, $adjusted_end);
|
||||
$event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'};
|
||||
# Add single start/end time
|
||||
if ($event{'dtstart'}) {
|
||||
$adjusted_start =
|
||||
$adjust_time_with_timezone->($event{'dtstart'},
|
||||
$event{'tzid'});
|
||||
$event{'dtstart_timestamp'} = $adjusted_start->{'timestamp'};
|
||||
my $dtstart_date =
|
||||
&make_date($event{'dtstart_timestamp'},
|
||||
{ tz => $event{'tzid'} });
|
||||
$event{'dtstart_date'} =
|
||||
"$dtstart_date->{'short'} $dtstart_date->{'timeshort'}";
|
||||
$event{'dtstart_local_timestamp'} =
|
||||
$adjusted_start->{'timestamp_local'};
|
||||
$event{'dtstart_local_date'} =
|
||||
&make_date($event{'dtstart_local_timestamp'});
|
||||
}
|
||||
if ($event{'dtend'}) {
|
||||
$adjusted_end =
|
||||
$adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'});
|
||||
$event{'dtend_timestamp'} = $adjusted_end->{'timestamp'};
|
||||
my $dtend_date = &make_date($event{'dtend_timestamp'},
|
||||
{ tz => $event{'tzid'} });
|
||||
$event{'dtend_date'} =
|
||||
"$dtend_date->{'short'} $dtend_date->{'timeshort'}";
|
||||
$event{'dtend_local_timestamp'} =
|
||||
$adjusted_end->{'timestamp_local'};
|
||||
$event{'dtend_local_date'} =
|
||||
&make_date($event{'dtend_local_timestamp'});
|
||||
}
|
||||
if ($event{'dtstart'} && $event{'dtend'}) {
|
||||
# Try to add local 'when (period)'
|
||||
my $dtstart_local_obj =
|
||||
$event{'_obj_dtstart_local_time'} =
|
||||
make_date($event{'dtstart_local_timestamp'}, { _ });
|
||||
my $dtend_local_obj =
|
||||
$event{'_obj_dtend_local_time'} =
|
||||
make_date($event{'dtend_local_timestamp'}, { _ });
|
||||
# Build when local, e.g.:
|
||||
# Tue Jun 04, 2024 04:30 PM – 05:15
|
||||
# PM (Asia/Nicosia +0300)
|
||||
# or
|
||||
# Tue Jun 04, 2024 04:30 PM – Wed Jun 05, 2024 01:15
|
||||
# AM (Asia/Nicosia +0300)
|
||||
$event{'dtwhen_local'} =
|
||||
# Start local
|
||||
$dtstart_local_obj->{'week'}.' '.
|
||||
$dtstart_local_obj->{'month'}.' '.
|
||||
$dtstart_local_obj->{'day'}.', '.
|
||||
$dtstart_local_obj->{'year'}.' '.
|
||||
$dtstart_local_obj->{'timeshort'}.' – ';
|
||||
# End local
|
||||
if ($dtstart_local_obj->{'year'} eq
|
||||
$dtend_local_obj->{'year'} &&
|
||||
$dtstart_local_obj->{'month'} eq
|
||||
$dtend_local_obj->{'month'} &&
|
||||
$dtstart_local_obj->{'day'} eq
|
||||
$dtend_local_obj->{'day'}) {
|
||||
$event{'dtwhen_local'} .=
|
||||
$dtend_local_obj->{'timeshort'};
|
||||
}
|
||||
else {
|
||||
$event{'dtwhen_local'} .=
|
||||
$dtend_local_obj->{'week'}.' '.
|
||||
$dtend_local_obj->{'month'}.' '.
|
||||
$dtend_local_obj->{'day'}.', '.
|
||||
$dtend_local_obj->{'year'}.' '.
|
||||
$dtend_local_obj->{'timeshort'};
|
||||
}
|
||||
# Timezone local
|
||||
if ($event{'tzid_local'} ||
|
||||
$dtstart_local_obj->{'tz'}) {
|
||||
if ($event{'tzid_local'} &&
|
||||
$dtstart_local_obj->{'tz'}) {
|
||||
if ($event{'tzid_local'} eq
|
||||
$dtstart_local_obj->{'tz'}) {
|
||||
$event{'dtwhen_local'} .=
|
||||
" ($event{'tzid_local'})";
|
||||
}
|
||||
else {
|
||||
$event{'dtwhen_local'} .=
|
||||
" ($event{'tzid_local'} ".
|
||||
"$dtstart_local_obj->{'tz'})";
|
||||
}
|
||||
}
|
||||
elsif ($event{'tzid_local'}) {
|
||||
$event{'dtwhen_local'} .=
|
||||
" ($event{'tzid_local'})";
|
||||
}
|
||||
else {
|
||||
$event{'dtwhen_local'} .=
|
||||
" ($dtstart_local_obj->{'tz'})";
|
||||
}
|
||||
}
|
||||
# Try to add original 'when (period)'
|
||||
my $dtstart_obj =
|
||||
$event{'_obj_dtstart_time'} =
|
||||
make_date($event{'dtstart_timestamp'},
|
||||
{ tz => $event{'tzid'} });
|
||||
my $dtend_obj =
|
||||
$event{'_obj_dtend_time'} =
|
||||
make_date($event{'dtend_timestamp'},
|
||||
{ tz => $event{'tzid'} });
|
||||
# Build original when
|
||||
if (!$event{'tzid_missing'}) {
|
||||
$event{'dtwhen'} =
|
||||
# Start original
|
||||
$dtstart_obj->{'week'}.' '.
|
||||
$dtstart_obj->{'month'}.' '.
|
||||
$dtstart_obj->{'day'}.', '.
|
||||
$dtstart_obj->{'year'}.' '.
|
||||
$dtstart_obj->{'timeshort'}.' – ';
|
||||
# End original
|
||||
if ($dtstart_obj->{'year'} eq
|
||||
$dtend_obj->{'year'} &&
|
||||
$dtstart_obj->{'month'} eq
|
||||
$dtend_obj->{'month'} &&
|
||||
$dtstart_obj->{'day'} eq
|
||||
$dtend_obj->{'day'}) {
|
||||
$event{'dtwhen'} .=
|
||||
$dtend_obj->{'timeshort'};
|
||||
}
|
||||
else {
|
||||
$event{'dtwhen'} .=
|
||||
$dtend_obj->{'week'}.' '.
|
||||
$dtend_obj->{'month'}.' '.
|
||||
$dtend_obj->{'day'}.', '.
|
||||
$dtend_obj->{'year'}.' '.
|
||||
$dtend_obj->{'timeshort'};
|
||||
}
|
||||
# Timezone original
|
||||
if ($dtstart_obj->{'tz'}) {
|
||||
$event{'dtwhen'} .=
|
||||
" ($dtstart_obj->{'tz'})";
|
||||
}
|
||||
}
|
||||
}
|
||||
# Add the event to the list
|
||||
push(@events, { %event });
|
||||
}
|
||||
# Parse fields
|
||||
elsif ($line =~ /^SUMMARY.*?:(.*)$/) {
|
||||
$event{'summary'} = $1;
|
||||
}
|
||||
elsif ($line =~ /^DTSTART:(.*)$/) {
|
||||
$event{'dtstart'} = $1;
|
||||
}
|
||||
elsif ($line =~ /^DTSTART;TZID=(.*?):(.*)$/) {
|
||||
$event{'tzid'} = $1;
|
||||
$event{'dtstart'} = $2;
|
||||
}
|
||||
elsif ($line =~ /^DTEND:(.*)$/) {
|
||||
$event{'dtend'} = $1;
|
||||
}
|
||||
elsif ($line =~ /^DTEND;TZID=(.*?):(.*)$/) {
|
||||
$event{'tzid'} = $1;
|
||||
$event{'dtend'} = $2;
|
||||
}
|
||||
elsif ($line =~ /^DESCRIPTION:(.*)$/) {
|
||||
my $description = $1;
|
||||
$description =~ s/\\n/<br>/g;
|
||||
$description =~ s/\\//g;
|
||||
unshift(@{$event{'description'}}, $description);
|
||||
}
|
||||
elsif ($line =~ /^DESCRIPTION;LANGUAGE=([a-z]{2}-[A-Z]{2}):(.*)$/) {
|
||||
my $description = $2;
|
||||
$description =~ s/\\n/<br>/g;
|
||||
$description =~ s/\\//g;
|
||||
unshift(@{$event{'description'}}, $description);
|
||||
}
|
||||
elsif ($line =~ /^LOCATION.*?:(.*)$/) {
|
||||
$event{'location'} = $1;
|
||||
}
|
||||
elsif ($line =~ /^ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=(.*?):mailto:(.*)$/ ||
|
||||
$line =~ /^ATTENDEE;.*CN=(.*?);.*mailto:(.*)$/ ||
|
||||
$line =~ /^ATTENDEE:mailto:(.*)$/) {
|
||||
push(@{$event{'attendees'}}, { 'name' => $1, 'email' => $2 });
|
||||
}
|
||||
elsif ($line =~ /^ORGANIZER;CN=(.*?):(?:mailto:)?(.*)$/) {
|
||||
$event{'organizer_name'} = $1;
|
||||
$event{'organizer_email'} = $2;
|
||||
}
|
||||
};
|
||||
# Read the ICS file lines or just use the lines
|
||||
my $ics_file_lines =
|
||||
-r $calendar_file ?
|
||||
&read_file_lines($calendar_file, 1) :
|
||||
[ split(/\r?\n/, $calendar_file) ];
|
||||
# Process each line of the ICS file
|
||||
foreach my $ics_file_line (@$ics_file_lines) {
|
||||
# Check if the line is a continuation of the previous line
|
||||
if ($ics_file_line =~ /^[ \t](.*)$/) {
|
||||
$line .= $1; # Concatenate with the previous line
|
||||
}
|
||||
else {
|
||||
# Process the previous line
|
||||
$process_line->($line) if ($line);
|
||||
$line = $ics_file_line; # Start a new line
|
||||
}
|
||||
}
|
||||
# Process the last line
|
||||
$process_line->($line) if ($line);
|
||||
# Return the list of events
|
||||
return \@events;
|
||||
}
|
||||
|
||||
# get_calendar_data(&calendars)
|
||||
# Returns HTML for all parsed calendars
|
||||
sub get_calendar_data
|
||||
{
|
||||
my ($calendars) = @_;
|
||||
my @calendars = @{$calendars};
|
||||
$calendars = { };
|
||||
if (@calendars) {
|
||||
# CSS for HTML version
|
||||
$calendars->{'html'} .= <<STYLE;
|
||||
<style>
|
||||
.calendar-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #99999933;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.calendar-table-inner {
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.calendar-table td {
|
||||
padding: 5px;
|
||||
vertical-align: top;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.calendar-table .calendar-cell {
|
||||
background-color: #99999916;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
padding: 2px;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.calendar-month {
|
||||
font-size: 21px;
|
||||
color: #1d72ff;
|
||||
text-align: center;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
.calendar-day {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.calendar-week {
|
||||
font-size: 16px;
|
||||
border-top: 1px dotted #999999aa;
|
||||
padding: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
.calendar-details h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.calendar-details p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
.calendar-details .title {
|
||||
font-size: 20px;
|
||||
}
|
||||
.calendar-details .detail strong {
|
||||
opacity: 0.66;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.calendar-details .detail + .desc p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
details.calendar-details {
|
||||
font-size: 90%;
|
||||
display: inline-block;
|
||||
margin-left: 9px;
|
||||
}
|
||||
details.calendar-details summary {
|
||||
cursor: help;
|
||||
}
|
||||
details.calendar-details tr:has(>.detail+td:empty),
|
||||
.calendar-details tr:has(>.detail+td:empty) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
STYLE
|
||||
foreach my $calendar (@calendars) {
|
||||
my $title = $calendar->{'summary'} || $calendar->{'description'};
|
||||
my $orginizer = $calendar->{'organizer_name'};
|
||||
my @attendees;
|
||||
foreach my $a (@{$calendar->{'attendees'}}) {
|
||||
push(@attendees, { name => $a->{'name'},
|
||||
email => $a->{'email'} });
|
||||
}
|
||||
my $who = join(", ", map { $_->{'name'} } @attendees);
|
||||
if ($who && $orginizer) {
|
||||
$who .= ", ${orginizer}*";
|
||||
}
|
||||
elsif ($orginizer) {
|
||||
$who = "${orginizer}*";
|
||||
}
|
||||
# HTML version
|
||||
$calendars->{'html'} .= <<HTML;
|
||||
<table class="calendar-table">
|
||||
<tr>
|
||||
<td class="calendar-cell">
|
||||
<div class="calendar-block">
|
||||
<div class="calendar-month">
|
||||
$calendar->{'_obj_dtstart_local_time'}->{'month'}
|
||||
</div>
|
||||
<div class="calendar-day">
|
||||
$calendar->{'_obj_dtstart_local_time'}->{'day'}
|
||||
</div>
|
||||
<div class="calendar-week">
|
||||
$calendar->{'_obj_dtstart_local_time'}->{'week'}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar-details">
|
||||
<table class="calendar-table-inner">
|
||||
<tr>
|
||||
<td class="title" colspan="2">
|
||||
<strong>$title</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_when'}</strong>
|
||||
</td>
|
||||
<td>$calendar->{'dtwhen_local'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_where'}</strong>
|
||||
</td>
|
||||
<td>$calendar->{'location'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_who'}</strong>
|
||||
</td>
|
||||
<td>$who</td>
|
||||
</tr>
|
||||
</table>
|
||||
<details class="calendar-details">
|
||||
<summary data-resize="iframe"></summary>
|
||||
<table class="calendar-table-inner">
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_orginizertime'}</strong>
|
||||
</td>
|
||||
<td>$calendar->{'dtwhen'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_orginizername'}</strong>
|
||||
</td>
|
||||
<td>$calendar->{'organizer_name'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_orginizeremail'}</strong>
|
||||
</td>
|
||||
<td>$calendar->{'organizer_email'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_attendees'}</strong>
|
||||
</td>
|
||||
<td class="desc">@{[join('', map {
|
||||
"<p>$_->{'name'}<br>$_->{'email'}</p>"
|
||||
} @attendees)]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="detail">
|
||||
<strong>$text{'view_ical_desc'}</strong>
|
||||
</td>
|
||||
<td class="desc">@{[join('<br>',
|
||||
@{$calendar->{'description'}})]}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
HTML
|
||||
# Text version
|
||||
my %textical = (
|
||||
'view_ical' => $title,
|
||||
'view_ical_when' => $calendar->{'dtwhen_local'},
|
||||
'view_ical_where' => $calendar->{'location'},
|
||||
'view_ical_who' => $who
|
||||
);
|
||||
my $max_label_length = 0;
|
||||
foreach my $key (sort keys %textical) {
|
||||
my $label_length = length($text{$key});
|
||||
if ($label_length > $max_label_length) {
|
||||
$max_label_length = $label_length;
|
||||
}
|
||||
}
|
||||
$calendars->{'text'} = "=" x 79 . "\n";
|
||||
foreach my $key (sort keys %textical) {
|
||||
my $label = $text{$key};
|
||||
my $value = $textical{$key};
|
||||
my $spaces .= " " x ($max_label_length - length($label));
|
||||
$calendars->{'text'} .= "$label$spaces : $value\n";
|
||||
}
|
||||
$calendars->{'text'} .= "=" x 79 . "\n";
|
||||
}
|
||||
}
|
||||
return $calendars;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -154,6 +154,15 @@ view_sub=Attached Email
|
||||
view_sub2=Attached email from $1
|
||||
view_egone=This message no longer exists
|
||||
view_eugone=This user does not exist
|
||||
view_ical=Event
|
||||
view_ical_when=When
|
||||
view_ical_where=Where
|
||||
view_ical_who=Who
|
||||
view_ical_orginizertime=Organizer time
|
||||
view_ical_orginizername=Organizer name
|
||||
view_ical_orginizeremail=Organizer email
|
||||
view_ical_attendees=Attendees details
|
||||
view_ical_desc=Event description
|
||||
|
||||
view_gnupg=GnuPG signature verification
|
||||
view_gnupg_0=Signature by $1 is valid.
|
||||
|
||||
@@ -1287,4 +1287,3 @@ return $rv;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
@@ -89,6 +89,16 @@ foreach $s (@sub) {
|
||||
@attach = grep { $_ ne $body && $_ ne $dstatus } @attach;
|
||||
@attach = grep { !$_->{'attach'} } @attach;
|
||||
|
||||
# Calendar attachments
|
||||
my @calendars;
|
||||
eval {
|
||||
foreach my $i (grep { $_->{'data'} }
|
||||
grep { $_->{'type'} =~ /^text\/calendar/ } @attach) {
|
||||
my $calendars = &parse_calendar_file($i->{'data'});
|
||||
push(@calendars, @{$calendars});
|
||||
}};
|
||||
|
||||
# Mail buttons
|
||||
if ($config{'top_buttons'} == 2 && &editable_mail($mail)) {
|
||||
&show_mail_buttons(1, scalar(@sub));
|
||||
print "<p class='mail_buttons_divide'></p>\n";
|
||||
@@ -138,11 +148,15 @@ else {
|
||||
print &ui_table_end();
|
||||
|
||||
# Show body attachment, with properly linked URLs
|
||||
@bodyright = ( );
|
||||
my $bodycontents;
|
||||
my @bodyright = ( );
|
||||
my $calendars = &get_calendar_data(\@calendars);
|
||||
if ($body && $body->{'data'} =~ /\S/) {
|
||||
if ($body eq $textbody) {
|
||||
# Show plain text
|
||||
$bodycontents = "<pre>";
|
||||
$bodycontents .= $calendars->{'text'}
|
||||
if ($calendars->{'text'});
|
||||
foreach $l (&wrap_lines(&eucconv($body->{'data'}),
|
||||
$config{'wrap_width'})) {
|
||||
$bodycontents .= &link_urls_and_escape($l,
|
||||
@@ -156,7 +170,9 @@ if ($body && $body->{'data'} =~ /\S/) {
|
||||
}
|
||||
elsif ($body eq $htmlbody) {
|
||||
# Attempt to show HTML
|
||||
$bodycontents = $body->{'data'};
|
||||
$bodycontents = $calendars->{'html'}
|
||||
if ($calendars->{'html'});
|
||||
$bodycontents .= $body->{'data'};
|
||||
my @imageurls;
|
||||
my $image_mode = int(defined($in{'images'}) ? $in{'images'} : $config{'view_images'});
|
||||
$bodycontents = &disable_html_images($bodycontents, $image_mode, \@imageurls);
|
||||
|
||||
@@ -86,10 +86,11 @@ print &ui_table_start($text{'general_title_others'}, "width=100%", 4);
|
||||
|
||||
&option_radios_freefield("mydomain", 40, $text{'opts_mydomain_default'});
|
||||
|
||||
&option_radios_freefield("mynetworks", 60, $text{'opts_mynetworks_default'});
|
||||
&option_radios_freefield("mynetworks", 60, $text{'default'});
|
||||
|
||||
&option_select("mynetworks_style",
|
||||
[ [ "subnet", $text{'opts_mynetworks_subnet'} ],
|
||||
[ [ "", $text{'default'} ],
|
||||
[ "subnet", $text{'opts_mynetworks_subnet'} ],
|
||||
[ "class", $text{'opts_mynetworks_class'} ],
|
||||
[ "host", $text{'opts_mynetworks_host'} ] ]);
|
||||
|
||||
|
||||
@@ -249,7 +249,6 @@ opts_mydomain_default=Default (provided by system)
|
||||
opts_myhostname=Internet hostname of this mail system
|
||||
opts_myhostname_default=Default (provided by system)
|
||||
opts_mynetworks=Local networks
|
||||
opts_mynetworks_default=Default (all attached networks)
|
||||
opts_mynetworks_style=Automatic local networks
|
||||
opts_mynetworks_subnet=Same IP subnet
|
||||
opts_mynetworks_class=Same network class
|
||||
|
||||
@@ -62,11 +62,11 @@ if ($site{'size'} != $st[7] || !$site{'version'} || !$site{'fullversion'}) {
|
||||
|
||||
# Get the list of modules
|
||||
local @mods;
|
||||
open(MODS, "$config{'proftpd_path'} -l |");
|
||||
open(MODS, "$config{'proftpd_path'} -vv |");
|
||||
while(<MODS>) {
|
||||
s/\r|\n//g;
|
||||
if (/^\s*(\S+)\.c$/) {
|
||||
push(@mods, $1);
|
||||
if (/^\s*(?<mod_built_in>\S+)\.c$|\s*(?<mod_loaded>mod_[a-zA-Z0-9_]+)\//) {
|
||||
push(@mods, $+{mod_loaded} || $+{mod_built_in});
|
||||
}
|
||||
}
|
||||
close(MODS);
|
||||
|
||||
@@ -9,7 +9,10 @@ $ver = &get_syslog_ng_version();
|
||||
my $index_econf2;
|
||||
if (&has_command('systemctl')) {
|
||||
if (&foreign_available('logviewer')) {
|
||||
$index_econf2 = &text('index_econf2', "System Logs Viewer", "@{[&get_webprefix()]}/logviewer") . "<p><br>";
|
||||
my %logviewer_text = &load_language('logviewer');
|
||||
$index_econf2 = &text('index_econf2',
|
||||
$logviewer_text{'index_title'},
|
||||
"@{[&get_webprefix()]}/logviewer") . "<p><br>";
|
||||
}
|
||||
}
|
||||
if (!$ver) {
|
||||
|
||||
@@ -26,7 +26,10 @@ if (!-r $config{'syslog_conf'}) {
|
||||
my $index_econf2;
|
||||
if (&has_command('systemctl')) {
|
||||
if (&foreign_available('logviewer')) {
|
||||
$index_econf2 = &text('index_econf2', "System Logs Viewer", "@{[&get_webprefix()]}/logviewer") . "<p><br>";
|
||||
my %logviewer_text = &load_language('logviewer');
|
||||
$index_econf2 = &text('index_econf2',
|
||||
$logviewer_text{'index_title'},
|
||||
"@{[&get_webprefix()]}/logviewer") . "<p><br>";
|
||||
}
|
||||
}
|
||||
# Not installed (maybe using syslog-ng)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
index_title=System Logs
|
||||
index_title=System Logs RS
|
||||
index_m4msg=Your system log configuration file $1 appears to contain <tt>m4</tt> directives. Before it can be edited, Webmin needs to pass the file through <tt>m4</tt> to safely remove these directives.
|
||||
index_m4=Remove m4 directives from config file
|
||||
index_econf=The syslog configuration file $1 was not found on your system. Maybe syslog is not installed, or a newer version like syslog-ng is in use, or the <a href='$2'>module configuration</a> is incorrect.
|
||||
@@ -16,7 +16,7 @@ index_cmd=Output from $1
|
||||
index_all=All users
|
||||
index_users=Users $1
|
||||
index_add=Add a new system log.
|
||||
index_return=system logs
|
||||
index_return=module index
|
||||
index_restart=Apply Changes
|
||||
index_restartmsg=Click this button to make the current configuration active by killing the running <tt>syslog</tt> process and restarting it.
|
||||
index_start=Start Syslog Server
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name=Syslog
|
||||
category=system
|
||||
os_support=solaris *-linux freebsd openbsd macos hpux irix unixware aix netbsd openserver
|
||||
desc=System Logs
|
||||
desc=System Logs RS
|
||||
depends=proc
|
||||
longdesc=Configure the syslog server on your system and view its log files.
|
||||
readonly=1
|
||||
|
||||
@@ -8,5 +8,5 @@ no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
package system_status;
|
||||
require './system-status-lib.pl';
|
||||
&scheduled_collect_system_info();
|
||||
|
||||
&scheduled_collect_system_info();
|
||||
|
||||
@@ -64,6 +64,18 @@ else {
|
||||
$miniserv{'bind'} = $first->[0];
|
||||
}
|
||||
$miniserv{'sockets'} = join(" ", map { "$_->[0]:$_->[1]" } @sockets);
|
||||
if ($in{'websocket_base_port_def'}) {
|
||||
delete($miniserv{'websocket_base_port'});
|
||||
}
|
||||
else {
|
||||
$miniserv{'websocket_base_port'} = $in{'websocket_base_port'};
|
||||
}
|
||||
if ($in{'websocket_host_def'}) {
|
||||
delete($miniserv{'websocket_host'});
|
||||
}
|
||||
else {
|
||||
$miniserv{'websocket_host'} = $in{'websocket_host'};
|
||||
}
|
||||
$miniserv{'ipv6'} = $in{'ipv6'};
|
||||
if ($in{'listen_def'}) {
|
||||
delete($miniserv{'listen'});
|
||||
|
||||
@@ -47,6 +47,22 @@ if (&foreign_check("firewall")) {
|
||||
}
|
||||
print &ui_table_row($text{'bind_sockets'}, $stable);
|
||||
|
||||
# WebSocket based port
|
||||
print &ui_table_row($text{'bind_websocport'},
|
||||
&ui_radio("websocket_base_port_def",
|
||||
$miniserv{"websocket_base_port"} ? 0 : 1,
|
||||
[ [ 1, $text{'bind_websocport_none'} ],
|
||||
[ 0, &ui_textbox("websocket_base_port",
|
||||
$miniserv{"websocket_base_port"}, 6) ] ]));
|
||||
|
||||
# Hostname for WebSocket connections
|
||||
print &ui_table_row($text{'bind_websoc_host'},
|
||||
&ui_radio("websocket_host_def",
|
||||
$miniserv{"websocket_host"} ? 0 : 1,
|
||||
[ [ 1, $text{'bind_websoc_host_auto'} ],
|
||||
[ 0, &ui_textbox("websocket_host",
|
||||
$miniserv{"websocket_host"}, 25) ] ]));
|
||||
|
||||
# IPv6 enabled?
|
||||
print &ui_table_row($text{'bind_ipv6'},
|
||||
&ui_yesno_radio("ipv6", $miniserv{'ipv6'}));
|
||||
|
||||
133
web-lib-funcs.pl
133
web-lib-funcs.pl
@@ -13522,6 +13522,139 @@ my $dir = $var_directory."/locks/".$$;
|
||||
return $dir;
|
||||
}
|
||||
|
||||
# allocate_miniserv_websocket([module])
|
||||
# Allocate a new websocket and
|
||||
# stores it miniserv.conf file
|
||||
sub allocate_miniserv_websocket
|
||||
{
|
||||
my ($module) = @_;
|
||||
$module ||= $module_name;
|
||||
# Find ports already in use
|
||||
&lock_file(&get_miniserv_config_file());
|
||||
my %miniserv;
|
||||
&get_miniserv_config(\%miniserv);
|
||||
my %inuse;
|
||||
foreach my $k (keys %miniserv) {
|
||||
if ($k =~ /^websockets_/ && $miniserv{$k} =~ /port=(\d+)/) {
|
||||
$inuse{$1} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# Pick a port and configure Webmin to proxy it
|
||||
my $port = $miniserv{'websocket_base_port'} || 555;
|
||||
while(1) {
|
||||
if (!$inuse{$port}) {
|
||||
&open_socket("127.0.0.1", $port, my $fh, \$err);
|
||||
last if ($err);
|
||||
close($fh);
|
||||
}
|
||||
$port++;
|
||||
}
|
||||
my $wspath = "/$module/ws-".$port;
|
||||
my $now = time();
|
||||
$miniserv{'websockets_'.$wspath} = "host=127.0.0.1 port=$port wspath=/ user=$remote_user time=$now";
|
||||
&put_miniserv_config(\%miniserv);
|
||||
&unlock_file(&get_miniserv_config_file());
|
||||
&reload_miniserv();
|
||||
return $port;
|
||||
}
|
||||
|
||||
# get_miniserv_websocket_url(port, [host], [module])
|
||||
# Returns the URL for a websocket
|
||||
sub get_miniserv_websocket_url
|
||||
{
|
||||
my ($port, $host, $module) = @_;
|
||||
$module ||= $module_name;
|
||||
my $ws_proto = lc($ENV{'HTTPS'}) eq 'on' ? 'wss' : 'ws';
|
||||
my %miniserv;
|
||||
&get_miniserv_config(\%miniserv);
|
||||
my $http_host_conf = &trim($miniserv{'websocket_host'} || $host);
|
||||
if ($http_host_conf) {
|
||||
if ($http_host_conf !~ /^wss?:\/\//) {
|
||||
$http_host_conf = "$ws_proto://$http_host_conf";
|
||||
}
|
||||
$http_host_conf =~ s/[\/]+$//g;
|
||||
}
|
||||
my $http_host = $http_host_conf || "$ws_proto://$ENV{'HTTP_HOST'}";
|
||||
return "$http_host/$module/ws-$port";
|
||||
}
|
||||
|
||||
# remove_miniserv_websocket(port, [module])
|
||||
# Remove old websocket from miniserv.conf
|
||||
sub remove_miniserv_websocket
|
||||
{
|
||||
my ($port, $module) = @_;
|
||||
$module ||= $module_name;
|
||||
my %miniserv;
|
||||
if ($port) {
|
||||
&lock_file(&get_miniserv_config_file());
|
||||
&get_miniserv_config(\%miniserv);
|
||||
my $wspath = "/$module/ws-".$port;
|
||||
if ($miniserv{'websockets_'.$wspath}) {
|
||||
delete($miniserv{'websockets_'.$wspath});
|
||||
&put_miniserv_config(\%miniserv);
|
||||
&reload_miniserv();
|
||||
}
|
||||
&unlock_file(&get_miniserv_config_file());
|
||||
}
|
||||
}
|
||||
|
||||
# cleanup_miniserv_websockets([&skip-ports], [module])
|
||||
# Called by scheduled status collection to remove any
|
||||
# websockets in miniserv.conf that are no longer used
|
||||
sub cleanup_miniserv_websockets
|
||||
{
|
||||
my ($skip, $module) = @_;
|
||||
$skip ||= [ ];
|
||||
$module ||= $module_name;
|
||||
&lock_file(&get_miniserv_config_file());
|
||||
my %miniserv;
|
||||
&get_miniserv_config(\%miniserv);
|
||||
my $now = time();
|
||||
my @clean;
|
||||
foreach my $k (keys %miniserv) {
|
||||
$k =~ /^websockets_\/$module\/ws-(\d+)$/ || next;
|
||||
my $port = $1;
|
||||
next if (&indexof($port, @$skip) >= 0);
|
||||
my $when = 0;
|
||||
if ($miniserv{$k} =~ /time=(\d+)/) {
|
||||
$when = $1;
|
||||
}
|
||||
if ($now - $when > 60) {
|
||||
# Has been open for a while, check if the port is still in use?
|
||||
my $err;
|
||||
&open_socket("127.0.0.1", $port, my $fh, \$err);
|
||||
if ($err) {
|
||||
# Closed now, can clean up
|
||||
push(@clean, $k);
|
||||
}
|
||||
else {
|
||||
# Still active
|
||||
close($fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (@clean) {
|
||||
foreach my $k (@clean) {
|
||||
delete($miniserv{$k});
|
||||
}
|
||||
&put_miniserv_config(\%miniserv);
|
||||
&reload_miniserv();
|
||||
}
|
||||
&unlock_file(&get_miniserv_config_file());
|
||||
}
|
||||
|
||||
# get_miniserv_websockets_modules()
|
||||
# Returns a list of modules and themes that use websockets
|
||||
sub get_miniserv_websockets_modules
|
||||
{
|
||||
my @rv;
|
||||
foreach my $i (&get_all_module_infos(), &list_themes()) {
|
||||
push(@rv, $i->{'dir'}) if ($i->{'websockets'});
|
||||
}
|
||||
return @rv;
|
||||
}
|
||||
|
||||
$done_web_lib_funcs = 1;
|
||||
|
||||
1;
|
||||
|
||||
@@ -84,6 +84,18 @@ else {
|
||||
$miniserv{'bind'} = $first->[0];
|
||||
}
|
||||
$miniserv{'sockets'} = join(" ", map { "$_->[0]:$_->[1]" } @sockets);
|
||||
if ($in{'websocket_base_port_def'}) {
|
||||
delete($miniserv{'websocket_base_port'});
|
||||
}
|
||||
else {
|
||||
$miniserv{'websocket_base_port'} = $in{'websocket_base_port'};
|
||||
}
|
||||
if ($in{'websocket_host_def'}) {
|
||||
delete($miniserv{'websocket_host'});
|
||||
}
|
||||
else {
|
||||
$miniserv{'websocket_host'} = $in{'websocket_host'};
|
||||
}
|
||||
$miniserv{'ipv6'} = $in{'ipv6'};
|
||||
if ($in{'listen_def'}) {
|
||||
delete($miniserv{'listen'});
|
||||
|
||||
@@ -44,6 +44,22 @@ if (&foreign_check("firewall")) {
|
||||
}
|
||||
print &ui_table_row($text{'bind_sockets'}, $stable);
|
||||
|
||||
# WebSocket based port
|
||||
print &ui_table_row($text{'bind_websocport'},
|
||||
&ui_radio("websocket_base_port_def",
|
||||
$miniserv{"websocket_base_port"} ? 0 : 1,
|
||||
[ [ 1, $text{'bind_websocport_none'} ],
|
||||
[ 0, &ui_textbox("websocket_base_port",
|
||||
$miniserv{"websocket_base_port"}, 6) ] ]));
|
||||
|
||||
# Hostname for WebSocket connections
|
||||
print &ui_table_row($text{'bind_websoc_host'},
|
||||
&ui_radio("websocket_host_def",
|
||||
$miniserv{"websocket_host"} ? 0 : 1,
|
||||
[ [ 1, $text{'bind_websoc_host_auto'} ],
|
||||
[ 0, &ui_textbox("websocket_host",
|
||||
$miniserv{"websocket_host"}, 25) ] ]));
|
||||
|
||||
# IPv6 enabled?
|
||||
print &ui_table_row($text{'bind_ipv6'},
|
||||
&ui_yesno_radio("ipv6", $miniserv{'ipv6'}));
|
||||
|
||||
@@ -46,6 +46,10 @@ bind_sport0=Same as first
|
||||
bind_sport1=Specific port ..
|
||||
bind_listen=Listen for broadcasts on UDP port
|
||||
bind_none=Don't listen
|
||||
bind_websocport=Base port number for WebSockets connections
|
||||
bind_websocport_none=Default (555)
|
||||
bind_websoc_host=Hostname for WebSocket connections
|
||||
bind_websoc_host_auto=Automatic
|
||||
bind_hostname=Web server hostname
|
||||
bind_auto=Work out from browser
|
||||
bind_err=Failed to change address
|
||||
|
||||
@@ -6,6 +6,7 @@ Functions for creating and listing Webmin scheduled functions.
|
||||
|
||||
BEGIN { push(@INC, ".."); };
|
||||
use WebminCore;
|
||||
use feature 'state';
|
||||
&init_config();
|
||||
|
||||
$webmin_crons_directory = "$module_config_directory/crons";
|
||||
@@ -72,11 +73,12 @@ Create or update a webmin cron function. Also locks the file being written to.
|
||||
sub save_webmin_cron
|
||||
{
|
||||
my ($cron) = @_;
|
||||
state $cnt = 0;
|
||||
if (!-d $webmin_crons_directory) {
|
||||
&make_dir($webmin_crons_directory, 0700);
|
||||
}
|
||||
if (!$cron->{'id'}) {
|
||||
$cron->{'id'} = time().$$;
|
||||
$cron->{'id'} = time().$$.($cnt++);
|
||||
}
|
||||
my $file = "$webmin_crons_directory/$cron->{'id'}.cron";
|
||||
my %wcron = %$cron;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
xterm=Set <tt>TERM</tt> environmental variable to,4,xterm+256color-xterm-256color,xterm+16color-xterm-16color,xterm-xterm,vt102-vt102,vt100-vt100,vt52-vt52,rxvt-rxvt,nsterm-nsterm,dtterm-dtterm,ansi-ansi
|
||||
base_port=Base port number for WebSockets connections,0,5
|
||||
host=Hostname for WebSocket connections,3,Automatic,32,,,Manual
|
||||
size=Terminal width and height in characters,3,Automatic,5,,,Static (80x24)
|
||||
locale=Set shell character encoding,10,0-Shell default,1-<tt>en_US.UTF-8</tt>,Custom
|
||||
rcfile=Execute initialization commands from file,10,0-Shell default,1-Module default,Custom
|
||||
|
||||
@@ -194,23 +194,13 @@ my $shellserver_cmd = "$module_config_directory/shellserver.pl";
|
||||
if (!-r $shellserver_cmd) {
|
||||
&create_wrapper($shellserver_cmd, $module_name, "shellserver.pl");
|
||||
}
|
||||
my $tmpdir = &tempname_dir();
|
||||
$ENV{'SESSION_ID'} = $main::session_id;
|
||||
&system_logged($shellserver_cmd." ".quotemeta($port)." ".quotemeta($user).
|
||||
($dir ? " ".quotemeta($dir) : "").
|
||||
" >$tmpdir/ws-$port.out 2>&1 </dev/null");
|
||||
" >$module_var_directory/websocket-connection-$port.out 2>&1 </dev/null");
|
||||
|
||||
# Open the terminal
|
||||
my $ws_proto = lc($ENV{'HTTPS'}) eq 'on' ? 'wss' : 'ws';
|
||||
my $http_host_conf = &trim($config{'host'});
|
||||
if ($http_host_conf) {
|
||||
if ($http_host_conf !~ /^wss?:\/\//) {
|
||||
$http_host_conf = "$ws_proto://$http_host_conf";
|
||||
}
|
||||
$http_host_conf =~ s/[\/]+$//g;
|
||||
}
|
||||
my $http_host = $http_host_conf || "$ws_proto://$ENV{'HTTP_HOST'}";
|
||||
my $url = "$http_host/$module_name/ws-$port";
|
||||
my $url = &get_miniserv_websocket_url($port, $config{'host'});
|
||||
my $canvasAddon = $termlinks->{'js'}[3];
|
||||
my $webGLAddon = $termlinks->{'js'}[4];
|
||||
my $term_script = <<EOF;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
desc=Terminal
|
||||
name=xterm
|
||||
longdesc=Access the shell on your system without the need for a separate SSH client, using Xterm.js over Webmin WebSockets
|
||||
websockets=1
|
||||
|
||||
@@ -87,7 +87,7 @@ if (!$pid) {
|
||||
die "Failed to run shell with $shcmd\n";
|
||||
}
|
||||
else {
|
||||
print STDERR "Running shell $shcmd with pid $pid\n";
|
||||
&error_stderr("Running shell $shcmd for user $user with pid $pid");
|
||||
}
|
||||
|
||||
# Detach from controlling terminal
|
||||
@@ -103,15 +103,15 @@ $SIG{'ALRM'} = sub {
|
||||
die "timeout waiting for connection";
|
||||
};
|
||||
alarm(60);
|
||||
print STDERR "listening on port $port\n";
|
||||
&error_stderr("Listening on port $port");
|
||||
my ($wsconn, $shellbuf);
|
||||
Net::WebSocket::Server->new(
|
||||
listen => $port,
|
||||
on_connect => sub {
|
||||
my ($serv, $conn) = @_;
|
||||
print STDERR "got websockets connection\n";
|
||||
&error_stderr("WebSocket connection established");
|
||||
if ($wsconn) {
|
||||
print STDERR "Unexpected second connection to the same port\n";
|
||||
&error_stderr("Unexpected second connection to the same port");
|
||||
$conn->disconnect();
|
||||
return;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ Net::WebSocket::Server->new(
|
||||
$key =~ s/\s//g;
|
||||
$dsess =~ s/\s//g;
|
||||
if (!$key || !$dsess || $key ne $dsess) {
|
||||
print STDERR "Key $key does not match session ID $dsess\n";
|
||||
&error_stderr("Key $key does not match session ID $dsess");
|
||||
$conn->disconnect();
|
||||
}
|
||||
},
|
||||
@@ -140,7 +140,7 @@ Net::WebSocket::Server->new(
|
||||
# Check for resize escape sequence explicitly
|
||||
if ($msg =~ /^\\033\[8;\((\d+)\);\((\d+)\)t$/) {
|
||||
my ($rows, $cols) = ($1, $2);
|
||||
print STDERR "got resize to $rows $cols\n";
|
||||
&error_stderr("Got resize to $rows $cols");
|
||||
eval {
|
||||
$shellfh->set_winsize($rows, $cols);
|
||||
};
|
||||
@@ -153,13 +153,13 @@ Net::WebSocket::Server->new(
|
||||
return;
|
||||
}
|
||||
if (!syswrite($shellfh, $msg, length($msg))) {
|
||||
print STDERR "write to shell failed : $!\n";
|
||||
&error_stderr("Write to shell failed : $!");
|
||||
&remove_miniserv_websocket($port);
|
||||
exit(1);
|
||||
}
|
||||
},
|
||||
disconnect => sub {
|
||||
print STDERR "websocket connection closed\n";
|
||||
&error_stderr("WebSocket connection closed");
|
||||
&remove_miniserv_websocket($port);
|
||||
kill('KILL', $pid) if ($pid);
|
||||
exit(0);
|
||||
@@ -172,7 +172,7 @@ Net::WebSocket::Server->new(
|
||||
my $buf;
|
||||
my $ok = sysread($shellfh, $buf, 1024);
|
||||
if ($ok <= 0) {
|
||||
print STDERR "end of output from shell\n";
|
||||
&error_stderr("End of output from shell");
|
||||
&remove_miniserv_websocket($port);
|
||||
exit(0);
|
||||
}
|
||||
@@ -185,6 +185,6 @@ Net::WebSocket::Server->new(
|
||||
},
|
||||
],
|
||||
)->start;
|
||||
print STDERR "exited websockets server\n";
|
||||
&error_stderr("Exited WebSocket server");
|
||||
&remove_miniserv_websocket($port);
|
||||
&cleanup_miniserv_websockets([$port]);
|
||||
@@ -1,100 +0,0 @@
|
||||
# allocate_miniserv_websocket()
|
||||
# Allocate a new websocket and
|
||||
# stores it miniserv.conf file
|
||||
sub allocate_miniserv_websocket
|
||||
{
|
||||
# Find ports already in use
|
||||
&lock_file(&get_miniserv_config_file());
|
||||
my %miniserv;
|
||||
&get_miniserv_config(\%miniserv);
|
||||
my %inuse;
|
||||
foreach my $k (keys %miniserv) {
|
||||
if ($k =~ /^websockets_/ && $miniserv{$k} =~ /port=(\d+)/) {
|
||||
$inuse{$1} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# Pick a port and configure Webmin to proxy it
|
||||
my $port = $config{'base_port'} || 555;
|
||||
while(1) {
|
||||
if (!$inuse{$port}) {
|
||||
&open_socket("127.0.0.1", $port, my $fh, \$err);
|
||||
last if ($err);
|
||||
close($fh);
|
||||
}
|
||||
$port++;
|
||||
}
|
||||
my $wspath = "/$module_name/ws-".$port;
|
||||
my $now = time();
|
||||
$miniserv{'websockets_'.$wspath} = "host=127.0.0.1 port=$port wspath=/ user=$remote_user time=$now";
|
||||
&put_miniserv_config(\%miniserv);
|
||||
&unlock_file(&get_miniserv_config_file());
|
||||
&reload_miniserv();
|
||||
return $port;
|
||||
}
|
||||
|
||||
# remove_miniserv_websocket(port)
|
||||
# Remove old websocket
|
||||
# from miniserv.conf
|
||||
sub remove_miniserv_websocket
|
||||
{
|
||||
my ($port) = @_;
|
||||
my %miniserv;
|
||||
if ($port) {
|
||||
&lock_file(&get_miniserv_config_file());
|
||||
&get_miniserv_config(\%miniserv);
|
||||
my $wspath = "/$module_name/ws-".$port;
|
||||
if ($miniserv{'websockets_'.$wspath}) {
|
||||
delete($miniserv{'websockets_'.$wspath});
|
||||
&put_miniserv_config(\%miniserv);
|
||||
&reload_miniserv();
|
||||
}
|
||||
&unlock_file(&get_miniserv_config_file());
|
||||
}
|
||||
}
|
||||
|
||||
# cleanup_miniserv_websockets([&skip-ports])
|
||||
# Called by scheduled status collection to remove any
|
||||
# websockets in miniserv.conf that are no longer used
|
||||
sub cleanup_miniserv_websockets
|
||||
{
|
||||
my ($skip) = @_;
|
||||
$skip ||= [ ];
|
||||
&lock_file(&get_miniserv_config_file());
|
||||
my %miniserv;
|
||||
&get_miniserv_config(\%miniserv);
|
||||
my $now = time();
|
||||
my @clean;
|
||||
foreach my $k (keys %miniserv) {
|
||||
$k =~ /^websockets_\/$module_name\/ws-(\d+)$/ || next;
|
||||
my $port = $1;
|
||||
next if (&indexof($port, @$skip) >= 0);
|
||||
my $when = 0;
|
||||
if ($miniserv{$k} =~ /time=(\d+)/) {
|
||||
$when = $1;
|
||||
}
|
||||
if ($now - $when > 60) {
|
||||
# Has been open for a while, check if the port is still in use?
|
||||
my $err;
|
||||
&open_socket("127.0.0.1", $port, my $fh, \$err);
|
||||
if ($err) {
|
||||
# Closed now, can clean up
|
||||
push(@clean, $k);
|
||||
}
|
||||
else {
|
||||
# Still active
|
||||
close($fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (@clean) {
|
||||
foreach my $k (@clean) {
|
||||
delete($miniserv{$k});
|
||||
}
|
||||
&put_miniserv_config(\%miniserv);
|
||||
&reload_miniserv();
|
||||
}
|
||||
&unlock_file(&get_miniserv_config_file());
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -4,7 +4,6 @@ BEGIN { push(@INC, ".."); };
|
||||
use WebminCore;
|
||||
&init_config();
|
||||
our %access = &get_module_acl();
|
||||
do "$module_root_directory/websockets-lib-funcs.pl";
|
||||
|
||||
# config_pre_load(mod-info-ref, [mod-order-ref])
|
||||
# Check if some config options are conditional,
|
||||
@@ -12,22 +11,13 @@ do "$module_root_directory/websockets-lib-funcs.pl";
|
||||
sub config_pre_load
|
||||
{
|
||||
my ($modconf_info, $modconf_order) = @_;
|
||||
|
||||
if ($ENV{'HTTP_X_REQUESTED_WITH'} eq "XMLHttpRequest") {
|
||||
|
||||
# Process forbidden keys
|
||||
my @forbidden_keys;
|
||||
|
||||
# Size is not supported in Authentic, because resize works flawlessly and
|
||||
# making it work would just add addition complexity for no good reason
|
||||
push(@forbidden_keys, 'size');
|
||||
|
||||
# Remove forbidden from display
|
||||
foreach my $fkey (@forbidden_keys) {
|
||||
delete($modconf_info->{$fkey});
|
||||
@{$modconf_order} = grep { $_ ne $fkey } @{$modconf_order}
|
||||
if ($modconf_order);
|
||||
}
|
||||
# Size is not supported in Authentic, because resize works flawlessly
|
||||
# and making it work would just add addition complexity for no good
|
||||
# reason
|
||||
delete($modconf_info->{'size'});
|
||||
@{$modconf_order} = grep { $_ ne 'size' } @{$modconf_order}
|
||||
if ($modconf_order);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user