* Mon Mar 17 2025 Brian Read <brianr@koozali.org> 11.0.0-67.sme

- re-write qmailanalog for postfix [SME: 12951]
- Clean up backup.pm
- Enhance module panel - used by mail log analysis and Licence display
This commit is contained in:
Brian Read 2025-03-17 12:08:40 +00:00
parent 63973f2bb4
commit 5be2152e89
8 changed files with 523 additions and 84 deletions

View File

@ -32,6 +32,10 @@ use esmith::util;
use esmith::lockfile;
use esmith::BlockDevices;
use constant DEBUG => $ENV{MOJO_SMANAGER_DEBUG} || 0;
use constant FALSE => 0;
use constant TRUE => 1;
#our $cdb = esmith::ConfigDB->open || die "Couldn't open config db"; #remove as cached gives problems
#our $adb = esmith::AccountsDB->open || die "Couldn't open accounts db";
#our $rdb = esmith::ConfigDB->open('/etc/e-smith/restore') || die "Couldn't open restore db";
@ -932,7 +936,7 @@ sub workstnVerify {
sub workstnRestore {
my ($c) = @_;
my $out;
my $out = '';
my $restoreref = $c->param('Backupset');
my $set = $restoreref;
$set =~ s/\/[^\/]*$//;
@ -1015,6 +1019,7 @@ sub workstnRestore {
return "$c->l('bac_OPERATION_STATUS_REPORT') $c->l('bac_ERR_PRE_RESTORE')";
}
$| = 1;
my $RD;
if (open(RD, "-|")) {
@ -1047,8 +1052,8 @@ sub workstnRestore {
or warn($c->l('bac_ERR_RESTORING_INITIAL_GRP') . "\n");
esmith::util::backgroundCommand(0, "/sbin/e-smith/signal-event", "post-upgrade");
# system("/sbin/e-smith/signal-event", "post-upgrade") == 0
# or die ($c->l('bac_ERROR_UPDATING_CONFIGURATION')."\n");
#system("/sbin/e-smith/signal-event", "post-upgrade") == 0
# or die ($c->l('bac_ERROR_UPDATING_CONFIGURATION')."\n");
} else {
$message = $c->l('bac_RESTORE_FAILED');
}
@ -1076,9 +1081,111 @@ sub workstnRestore {
die($error_message) if $error_message;
exit(0);
} ## end else [ if (open(RD, "-|")) ]
$rdb->reload;
$error_message .= $c->bunmount($mntdir, $VFSType);
return '#OK#' . $out;
#my $RD;
## Fork-safe open with explicit error handling
#unless (open($RD, "-|")) {
## Child process
#local $SIG{__DIE__} = sub { exit 255 };
#$| = 1; # Autoflush
#eval {
#foreach my $file (@restorefiles) {
## Security: strict filename validation
#unless ($file =~ m{^[\w\/.-]+$}) {
#die "Invalid filename: $file";
#}
## Check file existence
#unless (-e $file) {
#die "Backup file $file does not exist";
#}
## Execute dar with error checking
#system("/usr/bin/dar", "-Q", "-x", $file, "-v", "-N", "-R", "/", "-wa");
#if ($? == -1) {
#die "Failed to execute dar: $!";
#} elsif ($? & 127) {
#die sprintf("dar died with signal %d, %s coredump",
#($? & 127), ($? & 128) ? 'with' : 'without');
#} elsif ($? >> 8 != 0) {
#die "dar exited with error code " . ($? >> 8);
#}
#}
## Unmount with error checking
#if (my $unmount_err = $c->bunmount($mntdir, $VFSType)) {
#die "Unmount failed: $unmount_err";
#}
#};
#if (my $child_err = $@) {
#print STDERR "CHILD ERROR: $child_err";
#exit 254;
#}
#exit 0;
#}
#else {
## Parent process
#eval {
## Verify fork succeeded
#unless (defined $RD) {
#die "Fork failed: $!";
#}
#$out .= $c->l('bac_FILES_HAVE_BEEN_RESTORED') . "\n<UL>";
#my $complete = 0;
## Read from child process
#while (<$RD>) {
#$complete++ if /etc\/samba\/smbpasswd$/;
#$out .= "<li>$_</li>\n";
#}
#$out .= "</UL>";
## Close pipe and check status
#unless (close $RD) {
#die "Pipe close failed: $!";
#}
#my $child_status = $?;
#if ($child_status != 0) {
#die "Child process failed with status " . ($child_status >> 8);
#}
## Post-restore actions
#if ($complete) {
#system("/usr/sbin/groupmod", "-g", $www_gid, "www");
#if ($? != 0) {
#die $c->l('bac_ERR_RESTORING_GID') . ": $! (status $?)";
#}
#system("/usr/sbin/usermod", "-g", $www_gid, "www");
#if ($? != 0) {
#die $c->l('bac_ERR_RESTORING_INITIAL_GRP') . ": $! (status $?)";
#}
#my $bg_result = esmith::util::backgroundCommand(0, "/sbin/e-smith/signal-event", "post-upgrade");
#unless ($bg_result) {
#die "Failed to schedule post-upgrade event";
#}
#} else {
#die $c->l('bac_RESTORE_FAILED');
#}
#};
## Error handling
#if (my $err = $@) {
#$rec->set_prop('state', 'failed');
#$rec->set_prop('error', "$err");
#esmith::lockfile::UnlockFile($file_handle);
#return $c->l('bac_RESTORE_FAILED_MSG') . ": $err";
#}
#}
$rdb->reload;
$error_message .= $c->bunmount($mntdir, $VFSType);
return '#OK#' . $out;
} ## end sub workstnRestore
sub workstnSelRestore() {
@ -1455,6 +1562,7 @@ sub performReboot {
#print "$c->l('bac_SERVER_REBOOT')";
#print "$c->l('bac_SERVER_WILL_REBOOT')";
warn "reboot coming";
esmith::util::backgroundCommand(2, "/sbin/e-smith/signal-event", "reboot");
return "#OK#" . $c->l('bac_SERVER_WILL_REBOOT');
} ## end sub performReboot
@ -1985,7 +2093,7 @@ sub bunmount {
system('/bin/umount', '-f', $mount) == 0
or return ($c->l('bac_ERR_WHILE_UNMOUNTING'));
}
return;
return "";
} ## end sub bunmount
sub findmnt {

View File

@ -14,6 +14,9 @@ use SrvMngr qw(gen_locale_date_string);
use Locale::gettext;
use SrvMngr::I18N;
use SrvMngr qw(theme_list init_session);
use List::Util qw(sum);
#use Mail::Log::Trace::Postfix;
sub main {
my $c = shift;
@ -48,44 +51,63 @@ sub generateReport {
my $c = shift;
my $report_type = shift;
my $out = '';
#------------------------------------------------------------
# Looks good; go ahead and generate the report.
# Go ahead and generate the report.
#------------------------------------------------------------
# $| = 1;
my $now_string = $c->gen_locale_date_string();
my $log_path = '/var/log/maillog';
$out .= sprintf("<h3>%s %s </h3>", $c->l('REPORT_GENERATED'), $now_string);
if ($report_type =~ /^qmail-q/) {
open(QMAILQUEUEREPORT, "/var/qmail/bin/$report_type |");
$out .= sprintf "<pre>";
while (<QMAILQUEUEREPORT>) {
$out .= sprintf("%s", $_);
}
close QMAILQUEUEREPORT;
$out .= sprintf "</pre>";
$out .= sprintf("<h3>%s</h3>", $c->l('END_OF_REPORT'));
return '';
} ## end if ($report_type =~ /^qmail-q/)
chdir "/var/log/qmail";
open(QMAILANALOG,
"/bin/cat \@* current 2>/dev/null"
. "| /usr/local/bin/tai64nunix"
. "| /usr/local/qmailanalog/bin/matchup 5>/dev/null"
. "| /usr/local/qmailanalog/bin/$report_type |");
$out .= sprintf "<pre>";
# Get the selected report from the form submission
my $selected_report = $report_type;
while (<QMAILANALOG>) {
# Call the relevant report sub based on the selection
if ($selected_report eq 'daily_summary') {
$out .= daily_summary_report($log_path);
}
elsif ($selected_report eq 'daily_summary_today') {
$out .= daily_summary_report_today($log_path);
}
elsif ($selected_report eq 'top_senders') {
$out .= top_senders_and_recipients($log_path);
}
elsif ($selected_report eq 'bounce_analysis') {
$out .= bounce_rate_analysis($log_path);
}
elsif ($selected_report eq 'spam_and_virus') {
$out .= spam_and_virus_filter_report($log_path);
}
elsif ($selected_report eq 'delivery_status') {
$out .= delivery_status_report($log_path);
}
elsif ($selected_report eq 'geo_analysis') {
$out .= geographical_analysis_of_email($log_path);
}
elsif ($selected_report eq 'traffic_analysis') {
$out .= traffic_analysis($log_path);
}
elsif ($selected_report eq 'auth_analysis') {
$out .= authentication_analysis($log_path);
}
elsif ($selected_report eq 'user_activity') {
$out .= user_activity_report($log_path);
}
elsif ($selected_report eq 'error_reporting') {
$out .= error_reporting($log_path);
}
elsif ($selected_report eq 'comparison_reports') {
$out .= comparison_reports($log_path, '/var/log/mail.log.1');
}
elsif ($selected_report eq 'customized_reports') {
$out .= customized_reports($log_path);
}
else {
$out .= 'Invalid report selected';
}
# Cook any special HTML characters
s/\&/\&amp;/g;
s/\"/\&quot;/g;
s/\>/\&gt;/g;
s/\</\&lt;/g;
$out .= sprintf("%s", $_);
} ## end while (<QMAILANALOG>)
close QMAILANALOG;
# The $output variable now contains the generated report output.
# Further processing can be done here, or you can render it later.
$out .= sprintf "</pre>";
$out .= sprintf("<h3>%s</h3>", $c->l('END_OF_REPORT'));
return $out;
@ -94,22 +116,236 @@ sub generateReport {
sub reportType_list {
my $c = shift;
my @array = (
# [ $c->l('qma_LIST_OUTGOING') => 'qmail-qread' ],
# [ $c->l('qma_SUMMARIZE_QUEUE') => 'qmail-qstat' ],
[ $c->l('qma_SUCCESSFUL_DELIVERY_DELAY') => 'zddist' ],
[ $c->l('qma_REASONS_DEFERRAL') => 'zdeferrals' ],
[ $c->l('qma_REASONS_FAILURE') => 'zfailures' ],
[ $c->l('qma_BASIC_STATS') => 'zoverall' ],
[ $c->l('qma_RECIP_STATS') => 'zrecipients' ],
[ $c->l('qma_RECIP_HOSTS') => 'zrhosts' ],
[ $c->l('qma_RECIP_ORDERED') => 'zrxdelay' ],
[ $c->l('qma_SENDER_STATS') => 'zsenders' ],
[ $c->l('qma_SENDMAIL_STYLE') => 'zsendmail' ],
[ $c->l('qma_REASONS_SUCCESS') => 'zsuccesses' ],
[ $c->l('qma_SENDER_UIDS') => 'zsuids' ]
);
[$c->l('qma_Daily_Summary_Report_yesterday') => 'daily_summary'],
[$c->l('qma_Daily_Summary_Report_today') => 'daily_summary_today'],
#[$c->l('qma_Top Senders and Recipients') => 'top_senders'],
#[$c->l('qma_Bounce Rate Analysis') => 'bounce_analysis'],
#[$c->l('qma_Spam and Virus Filtering Report') => 'spam_and_virus'],
#[$c->l('qma_Delivery Status Report') => 'delivery_status'],
#[$c->l('qma_Geographic Analysis of Email') => 'geo_analysis'],
#[$c->l('qma_Traffic Analysis') => 'traffic_analysis'],
#[$c->l('qma_Authentication Analysis') => 'auth_analysis'],
#[$c->l('qma_User Activity Report') => 'user_activity'],
#[$c->l('qma_Error Reporting') => 'error_reporting'],
#[$c->l('qma_Comparison Reports') => 'comparison_reports'],
#[$c->l('qma_Customized Reports') => 'customized_reports'],
);
my @sorted_array = sort { $a->[0] cmp $b->[0] } @array;
return \@sorted_array;
} ## end sub reportType_list
1;
sub daily_summary_report {
my $log_file = shift; # Path to log file
my $output = qx(ls -1 /var/log/maillog* | xargs cat |pflogsumm -d yesterday --detail 0 --no-no-msg-size);
return format_as_html("Daily Summary Report", $output);
}
sub daily_summary_report_today {
my $log_file = shift; # Path to log file
my $output = qx(ls -1 /var/log/maillog* | xargs cat |pflogsumm -d today --detail 0 --no-no-msg-size);
return format_as_html("Daily Summary Report", $output);
}
sub top_senders_and_recipients {
my $log_file = shift;
my $output = qx(pflogsumm --smtpd-stats $log_file);
return format_as_html("Top Senders and Recipients", $output);
}
sub bounce_rate_analysis {
my $log_file = shift;
my $output = qx(pflogsumm --bounce-detail 10 $log_file); # Show up to 10 bounce details
return format_as_html("Bounce Rate Analysis", $output);
}
sub spam_and_virus_filter_report {
my $log_file = shift;
my $output = qx(pflogsumm -u 10 $log_file); # User report with up to 10 entries
return format_as_html("Spam and Virus Filtering Report", $output);
}
sub delivery_status_report {
my $log_file = shift;
my $output = qx(pflogsumm --deferral-detail 10 $log_file); # Show deferral details
return format_as_html("Delivery Status Report", $output);
}
sub geographical_analysis_of_email {
my $log_file = shift;
# `pflogsumm` doesn't have a specific option for geographic analysis in the help text;
# It's assumed this could be replaced with something relevant, like a SMTP detail.
my $output = qx(pflogsumm --smtp-detail 10 $log_file); # Show up to 10 SMTP details
return format_as_html("Geographic Analysis of Email", $output);
}
sub traffic_analysis {
my $log_file = shift;
my $output = qx(pflogsumm --verbose-msg-detail $log_file); # Request verbose detail
return format_as_html("Traffic Analysis", $output);
}
sub authentication_analysis {
my $log_file = shift;
my $output = qx(pflogsumm -u 10 --verbose-msg-detail $log_file); # User detailed report
return format_as_html("Authentication Analysis", $output);
}
sub user_activity_report {
my $log_file = shift;
my $output = qx(pflogsumm -u 20 $log_file); # Show user activity for up to 20 users
return format_as_html("User Activity Report", $output);
}
sub error_reporting {
my $log_file = shift;
my $output = qx(pflogsumm --problems-first $log_file); # This will show problems first
return format_as_html("Error Reporting", $output);
}
sub comparison_reports {
my ($log_file1, $log_file2) = @_; # Comparing two log files
my $output = qx(pflogsumm $log_file1 $log_file2); # Standard comparison without special flags
return format_as_html("Comparison Reports", $output);
}
sub customized_reports {
my $log_file = shift;
# Because we don't have a concrete custom flag, we'll consider using -d with specific detail.
my $output = qx(pflogsumm --detail 10 $log_file); # Generally show detailed summary
return format_as_html("Customized Reports", $output);
}
sub format_as_html {
my ($title, $content) = @_;
return <<HTML;
<h2>$title</h2>
<pre>$content</pre>
HTML
}
### 1. Message Tracking
#sub trace_message {
#my ($log_path, $message_id) = @_;
##my $tracer = Mail::Log::Trace::Postfix->new({log_file => $log_path});
##$tracer->set_message_id($message_id);
#my $output = "Message Tracking Report for ID: $message_id\n";
##$output .= "=" x 50 . "\n";
##$output .= sprintf "%-12s: %s\n", 'From', $tracer->get_from_address;
##$output .= sprintf "%-12s: %s\n", 'Status', $tracer->get_final_status;
##$output .= "\nRecipients:\n";
##$output .= join("\n", map { "- $_" } $tracer->get_recipient_addresses);
##$output .= "\n\nTimeline:\n";
##my $timeline = $tracer->get_timestamps;
##while (my ($stage, $time) = each %$timeline) {
##$output .= sprintf "%-10s: %s\n", ucfirst($stage), $time;
##}
#return $output || "No records found for message ID: $message_id";
#}
#### 2. Queue Analysis
#sub get_queue_stats {
#my $spool_dir = '/var/spool/postfix';
#my %queues = map { $_ => 0 } qw(active deferred bounce hold corrupt);
#foreach my $q (keys %queues) {
#opendir(my $dh, "$spool_dir/$q");
#$queues{$q} = scalar(grep { -f "$spool_dir/$q/$_" } readdir($dh));
#closedir($dh);
#}
#my $output = "Current Postfix Queue Status\n";
#$output .= "=" x 30 . "\n";
#$output .= sprintf "%-10s: %3d messages\n", ucfirst($_), $queues{$_}
#for sort keys %queues;
#$output .= "\nTotal: " . sum(values %queues) . " messages in queue";
#return $output;
#}
#### 3. Message Statistics
#sub get_message_stats {
#my ($log_path) = @_;
#my %stats = (received => 0, rejected => 0, delivered => 0,
#deferred => 0, bounced => 0, held => 0);
#open(my $fh, '<', $log_path);
#while(<$fh>) {
#$stats{received}++ if /qmgr.*: [A-Z0-9]+: from=/;
#$stats{delivered}++ if /status=sent/;
#$stats{rejected}++ if /NOQUEUE: reject/;
#$stats{deferred}++ if /status=deferred/;
#$stats{bounced}++ if /status=bounced/;
#$stats{held}++ if /status=hold/;
#}
#close($fh);
#my $output = "Message Statistics for " . localtime . "\n";
#$output .= "=" x 40 . "\n";
#$output .= sprintf "%-12s: %6d\n", 'Received', $stats{received};
#$output .= sprintf "%-12s: %6d (%.1f%%)\n", 'Delivered', $stats{delivered},
#($stats{received} ? ($stats{delivered}/$stats{received}*100) : 0);
#$output .= sprintf "%-12s: %6d\n", 'Rejected', $stats{rejected};
#$output .= sprintf "%-12s: %6d\n", 'Deferred', $stats{deferred};
#$output .= sprintf "%-12s: %6d\n", 'Bounced', $stats{bounced};
#$output .= sprintf "%-12s: %6d\n", 'Held', $stats{held};
#return $output;
#}
#### 4. User Activity Audit
#sub get_user_activity {
#my ($log_path, $email) = @_;
##my $tracer = Mail::Log::Trace::Postfix->new({log_file => $log_path});
##my $sent = scalar $tracer->find_messages_by_sender($email);
##my $received = scalar $tracer->find_messages_by_recipient($email);
#my $output = "Activity Report for: $email\n";
##$output .= "=" x (length($email) + 18) . "\n";
##$output .= "Messages sent: $sent\n";
##$output .= "Messages received: $received\n\n";
##$output .= "Last week's activity:\n";
##$output .= join("\n", map { sprintf "- %s: %d messages", $_->[0], $_->[1] }
##$tracer->get_weekly_stats($email));
#return $output || "No activity found for $email";
#}
#### 5. Security Monitoring
#sub detect_auth_failures {
#my ($log_path) = @_;
#my %failures;
#open(my $fh, '<', $log_path);
#while(<$fh>) {
#if(/SASL (?:LOGIN|PLAIN) authentication failed.*?\[([0-9.]+)\]/) {
#$failures{$1}++;
#}
#}
#close($fh);
#return "No authentication failures found" unless keys %failures;
#my $output = "Authentication Failure Report\n";
#$output .= "=" x 30 . "\n";
#$output .= sprintf "%-15s %s\n", 'IP Address', 'Attempts';
#$output .= sprintf "%-15s %s\n", '-' x 15, '-' x 7;
#foreach my $ip (sort { $failures{$b} <=> $failures{$a} } keys %failures) {
#$output .= sprintf "%-15s %5d\n", $ip, $failures{$ip};
#}
#$output .= "\nTotal failures: " . sum(values %failures);
#return $output;
#}
1;

View File

@ -151,4 +151,4 @@ SMALL => 'Small',
MEDIUM => 'Medium',
LARGE => 'Large',
FIELD_INVALID_CHARS => 'A field you entered contains invalid characters.',
'REPORT_GENERATED' => "Report generated",

View File

@ -8,6 +8,14 @@ reports are available.</P>
these reports</P>',
'qma_REPORT_TYPE' => 'Choose a report type',
'qma_GENERATE_REPORT' => 'Generate report',
'qma_INVALID_REPORT_TYPE' => 'Invalid report type: ',
'qma_REPORT_GENERATED' => 'Report generated: ',
'qma_END_OF_REPORT' => 'End of Report',
'Mail log file analysis' => 'Mail log file analysis',
'qma_Daily_Summary_Report_yesterday' => 'Summary report for yesterday',
'qma_Daily_Summary_Report_today' => 'Summary report for today up to now',
'qma_LIST_OUTGOING' => 'List outgoing messages and recipients',
'qma_SUMMARIZE_QUEUE' => 'Summarize status of mail queue',
'qma_SUCCESSFUL_DELIVERY_DELAY' => 'Successful delivery delay distribution',
@ -20,9 +28,4 @@ these reports</P>',
'qma_SENDER_STATS' => 'Sender statistics',
'qma_SENDMAIL_STYLE' => 'Sendmail style log',
'qma_REASONS_SUCCESS' => 'Reasons for success',
'qma_SENDER_UIDS' => 'Sender uids',
'qma_INVALID_REPORT_TYPE' => 'Invalid report type: ',
'qma_REPORT_GENERATED' => 'Report generated: ',
'qma_END_OF_REPORT' => 'End of Report',
'Mail log file analysis' => 'Mail log file analysis',
'qma_SENDER_UIDS' => 'Sender uids',

View File

@ -0,0 +1,43 @@
/* General styles for the module panel */
#module {
padding: 20px;
border: 1px solid #ccc;
border-radius: 10px;
background-color: #f9f9f9;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Debug information styling */
#module p {
font-family: monospace;
color: #555;
}
#module pre {
background-color: #eee;
padding: 10px;
border-radius: 5px;
}
/* Error message styling */
.sme-error {
color: #d9534f;
font-weight: bold;
margin-bottom: 15px;
}
/* Title styling */
#module h1 {
font-family: Arial, sans-serif;
color: #333;
text-align: center;
margin-bottom: 20px;
}
/* Content styling */
.module-content {
font-family: Georgia, serif;
font-size: 13px;
line-height: 1.2;
color: #555;
}

View File

@ -0,0 +1,25 @@
document.addEventListener('DOMContentLoaded', function() {
const analysisType = document.getElementById('analysis_type');
const messageIdGroup = document.getElementById('message_id_group');
const emailAddressGroup = document.getElementById('email_address_group');
// Initially hide both controls
messageIdGroup.style.display = 'none';
emailAddressGroup.style.display = 'none';
analysisType.addEventListener('change', function() {
// Hide both controls first
messageIdGroup.style.display = 'none';
emailAddressGroup.style.display = 'none';
// Show the relevant control based on the selected option
switch(this.value) {
case 'trace_message':
messageIdGroup.style.display = 'block';
break;
case 'user_activity':
emailAddressGroup.style.display = 'block';
break;
}
});
});

View File

@ -1,20 +1,27 @@
% layout 'default', title => "Sme server 2 - module";
% content_for 'module' => begin
%= stylesheet '/css/module.css'
<div id='module' class='module module-panel'>
% if (config->{debug} == 1) {
<p>
%= dumper $c->current_route
</p>
<p>
<strong>Debug Info:</strong>
<pre><%= dumper $c->current_route %></pre>
</p>
% }
% if ( stash 'error' ) {
<br><div class=sme-error>
%= $c->render_to_string(inline => stash 'error')
</div>
%}
<h1> <%=$title %></h1><br>
%= $c->render_to_string( inline => stash 'modul' )
% if (stash 'error') {
<div class="sme-error">
<strong>Error:</strong><br>
<%= $c->render_to_string(inline => stash 'error') %>
</div>
% }
<h1><%= $title %></h1>
<div class="module-content">
<%= $c->render_to_string(inline => stash 'modul') %>
</div>
</div>
%end
% end

View File

@ -1,6 +1,6 @@
% layout 'default', title => "Sme server 2 - qmailanalog";
% content_for 'module' => begin
%= javascript '/js/qmailanalog.js'
<div id='module' class='module qmailanalog-panel'>
%if (config->{debug} == 1) {
<p>
@ -11,17 +11,34 @@
<br>
<%= $modul %>
<% my $btn = l('qma_GENERATE_REPORT'); %>
%= form_for 'qmailanalog' => (method => 'POST') => begin
<p>
<span class=label>
%=l 'qma_REPORT_TYPE'
</span><span class=data>
<!-- (to be sorted) -->
%= select_field 'report_type' => $c->reportType_list()
</span>
</p>
%= submit_button "$btn", class => 'action'
<p>
<span class=label>
%=l 'qma_REPORT_TYPE'
</span><span class=data>
<!-- (to be sorted) -->
%= select_field 'report_type' => $c->reportType_list(), id => 'analysis_type'
</span>
</p>
<!--And optional parameters, depending on the choice.-->
<div id="message_id_group">
<span class=label>
%= label_for message_id => 'Message ID:'
</span><span class=data>
%= text_field 'message_id', placeholder => 'Enter Postfix Message ID', id => 'message_id_group'
</span>
</div>
<div id="email_address_group">
<span class=label>
%= label_for email_address => 'Email Address:'
</span><span class=data>
%= email_field 'email_address', placeholder => 'Enter email address', id => 'email_address_group'
</span>
</div>
%= submit_button "$btn", class => 'action'
% end
</div>
%end