Compare commits
117 Commits
1.1-18.el8
...
master
Author | SHA1 | Date | |
---|---|---|---|
34fd81cd51 | |||
1258b41ad8 | |||
9453031df3 | |||
756e0f94c3 | |||
f885ab684e | |||
7504f32ce7 | |||
9dc7d3d7a1 | |||
89475c0aa3 | |||
b513dfc9be | |||
32d91c4a24 | |||
2d1824553b | |||
e610abd351 | |||
8d8c97d1fa | |||
a58191f667 | |||
7fcdfccfa6 | |||
cd4a0b1725 | |||
3b0b574171 | |||
da71021889 | |||
4d29da7f3d | |||
736315d47e | |||
40daa827c4 | |||
2d54c4f7f5 | |||
fce93e1dcd | |||
eff56815da | |||
d1ddf5d04c | |||
f2f4078bb8 | |||
9bfaa754e6 | |||
9739f78b19 | |||
d4961059b6 | |||
93b5eb22ab | |||
f57b0c6e43 | |||
dce1df37db | |||
e1250779de | |||
372d2b45dd | |||
9be485a1a9 | |||
9ebe02b80e | |||
8be2103dec | |||
20a8d3b4ef | |||
51dd523249 | |||
a20b570e11 | |||
382489ecac | |||
c1c4251361 | |||
1fc294e8f6 | |||
076e844898 | |||
136a9416ec | |||
f2f570c87e | |||
1132efb828 | |||
edb71ad684 | |||
8a506beb0f | |||
c335e93def | |||
471cb25ad1 | |||
b661e436fb | |||
3c1dc868aa | |||
54ceac1ee8 | |||
7eb3ff0048 | |||
51912e5525 | |||
a1c9a698ee | |||
c7cf518477 | |||
7ad144e3b0 | |||
aa0ebc76e9 | |||
a9be56deae | |||
e014d91060 | |||
ddcde8fa07 | |||
b0c63e61fd | |||
4997bbffa2 | |||
ae0e133918 | |||
ce1db0a31b | |||
f4f4c8173e | |||
61f9872e66 | |||
ebff1f3d78 | |||
e94c96cb26 | |||
fcc2a6fce8 | |||
c5a708a382 | |||
6cb877d358 | |||
55575811e7 | |||
906448378f | |||
68928375d8 | |||
1ef07f3acc | |||
85dc97aa05 | |||
0947689c0f | |||
44b811d09e | |||
6e81bf1600 | |||
1adf1b83db | |||
49095c3830 | |||
9c6aae8ea7 | |||
9db68b263d | |||
3d7f2407b6 | |||
d5c387d12e | |||
767ade0e0d | |||
5e77fd4c82 | |||
1917053811 | |||
cfe5d57656 | |||
765bcb5896 | |||
528bebf7a7 | |||
b5970977e6 | |||
4a22b47580 | |||
389175c392 | |||
52c1bfba48 | |||
0bc452c38a | |||
c0cc10f417 | |||
997de8ca9f | |||
6de00553c2 | |||
5f737912e4 | |||
4a0c17e1c0 | |||
1dd11f04f1 | |||
c647574cb0 | |||
5db9ddf82f | |||
b2440be6d0 | |||
ad1962753b | |||
f64ff2feea | |||
a5a38bae43 | |||
5768306bc8 | |||
d7ae6e9106 | |||
67f4621dd7 | |||
731233cce1 | |||
02deabb6af | |||
eefac0a502 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -2,3 +2,13 @@
|
||||
*.log
|
||||
*spec-20*
|
||||
*.tgz
|
||||
current.*
|
||||
*.xz
|
||||
current1
|
||||
current2
|
||||
*.html
|
||||
*.txt
|
||||
accounts
|
||||
configuration
|
||||
domains
|
||||
hosts
|
||||
|
49
README.md
49
README.md
@@ -6,12 +6,49 @@ SMEServer Koozali developed git repo for smeserver-mailstats smecontribs
|
||||
<br />https://wiki.koozali.org/Mailstats
|
||||
|
||||
## Bugzilla
|
||||
Show list of outstanding bugs: [here](https://bugs.koozali.org/buglist.cgi?component=smeserver-mailstats&product=SME%20Contribs&query_format=advanced&limit=0&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=CONFIRMED)
|
||||
Show list of outstanding bugs:
|
||||
[All](https://bugs.koozali.org/buglist.cgi?action=wrap&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=NEEDINFO&bug_status=IN_PROGRESS&bug_status=RESOLVED&bug_status=VERIFIED&classification=Contribs&component=smeserver-mailstats&list_id=105781&order=changeddate+DESC%2Ccomponent%2Cpriority%2Cbug_severity&product=SME+Contribs&query_format=advanced)
|
||||
[Confirmed](https://bugs.koozali.org/buglist.cgi?action=wrap&bug_status=CONFIRMED&classification=Contribs&component=smeserver-mailstats&list_id=105781&order=changeddate+DESC%2Ccomponent%2Cpriority%2Cbug_severity&product=SME+Contribs&query_format=advanced)
|
||||
[Unconfirmed](https://bugs.koozali.org/buglist.cgi?action=wrap&bug_status=UNCONFIRMED&classification=Contribs&component=smeserver-mailstats&list_id=105781&order=changeddate+DESC%2Ccomponent%2Cpriority%2Cbug_severity&product=SME+Contribs&query_format=advanced)
|
||||
[Need Info](https://bugs.koozali.org/buglist.cgi?action=wrap&bug_status=NEEDINFO&classification=Contribs&component=smeserver-mailstats&list_id=105781&order=changeddate+DESC%2Ccomponent%2Cpriority%2Cbug_severity&product=SME+Contribs&query_format=advanced)
|
||||
[In Progress](https://bugs.koozali.org/buglist.cgi?action=wrap&bug_status=IN_PROGRESS&classification=Contribs&component=smeserver-mailstats&list_id=105781&order=changeddate+DESC%2Ccomponent%2Cpriority%2Cbug_severity&product=SME+Contribs&query_format=advanced)
|
||||
[Verified](https://bugs.koozali.org/buglist.cgi?action=wrap&bug_status=VERIFIED&classification=Contribs&component=smeserver-mailstats&list_id=105781&order=changeddate+DESC%2Ccomponent%2Cpriority%2Cbug_severity&product=SME+Contribs&query_format=advanced)
|
||||
[Resolved](https://bugs.koozali.org/buglist.cgi?action=wrap&bug_status=RESOLVED&classification=Contribs&component=smeserver-mailstats&list_id=105781&order=changeddate+DESC%2Ccomponent%2Cpriority%2Cbug_severity&product=SME+Contribs&query_format=advanced)
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
`smeserver-mailstats` is a package designed for the SME Server environment to provide comprehensive email traffic statistics and monitoring. It helps administrators track and analyze email usage, ensuring effective email management and enhanced security.
|
||||
|
||||
<br />*This description has been generated by an LLM AI system and cannot be relied on to be fully correct.*
|
||||
*Once it has been checked, then this comment will be deleted*
|
||||
<br />
|
||||
## Functions
|
||||
|
||||
The Mailstats contrib is an invaluable tool for small and medium-sized businesses that use the SME Server operating system. This software is designed to give detailed statistics about an email server, providing crucial information such as the number of emails sent and received, and the average response time. The data it provides is essential for businesses to ensure their email system is running as efficiently as possible. Mailstats is an easy-to-use, graphical interface that gives clear, comprehensive information at a glance, enabling businesses to quickly identify any potential problems or areas that may need improvement. The Mailstats contrib is an invaluable asset to any business running the SME Server operating system, and can ensure the smooth running of their email system in the most efficient way possible.
|
||||
1. **Email Traffic Analysis**:
|
||||
- Tracks all inbound and outbound email traffic, providing detailed statistics on email usage patterns.
|
||||
|
||||
2. **User Activity Logging**:
|
||||
- Logs email activities for each user, enabling detailed monitoring and accountability.
|
||||
|
||||
3. **Reporting**:
|
||||
- Generates clear, reports that offer visual insights into email traffic data.
|
||||
|
||||
4. **Historical Data Maintenance**:
|
||||
- Maintains historical email statistics, allowing for trend analysis over different periods.
|
||||
|
||||
5. **Customizable Alerts**:
|
||||
- Enables administrators to set up alerts for specific conditions, such as volume thresholds or unusual email activity.
|
||||
|
||||
6. **Security Monitoring**:
|
||||
- Helps identify unusual email patterns or spikes, which may indicate spam attacks or unauthorized access.
|
||||
|
||||
7. **Performance Optimization**:
|
||||
- Provides data that helps in optimizing server performance by identifying resource bottlenecks.
|
||||
|
||||
8. **User-Friendly Interface**:
|
||||
- Offers an easy-to-use interface for configuring and accessing email statistics and reports.
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Enhanced Security**: Quickly identify and respond to potential email-based threats.
|
||||
- **Resource Management**: Efficiently manage server resources based on email traffic analysis.
|
||||
- **User Accountability**: Maintain detailed logs for compliance and auditing.
|
||||
- **Proactive Administration**: Set up alerts to stay ahead of potential issues.
|
||||
|
||||
For more detailed information, refer to the official [SME Server wiki](https://wiki.contribs.org/Main_Page) and the respective package documentation.
|
||||
|
@@ -8,6 +8,7 @@ $event = 'smeserver-mailstats-update';
|
||||
foreach my $file (qw(
|
||||
/etc/systemd/system-preset/49-koozali.preset
|
||||
/etc/e-smith/sql/init/99smeserver-mailstats.sql
|
||||
/etc/httpd/conf/httpd.conf
|
||||
))
|
||||
{
|
||||
templates2events( $file, $event );
|
||||
@@ -18,7 +19,7 @@ event_link('systemd-reload', $event, '50');
|
||||
#action specific to this package
|
||||
#event_link('action', $event, '30');
|
||||
#services we need to restart
|
||||
#safe_symlink('restart', 'root/etc/e-smith/events/$event/services2adjust/<service>)
|
||||
safe_symlink('restart', "root/etc/e-smith/events/$event/services2adjust/httpd-e-smith");
|
||||
#and Server Mmanager panel link
|
||||
#panel_link('somefunction', 'manager');
|
||||
|
||||
|
@@ -91,4 +91,7 @@ CREATE TABLE IF NOT EXISTS `time` (
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
|
||||
|
||||
|
||||
grant all privileges on mailstats.* to 'mailstats'@'localhost' identified by 'mailstats';
|
||||
CREATE USER 'mailstats'@'localhost' IDENTIFIED BY 'mailstats';
|
||||
GRANT ALL PRIVILEGES ON mailstats.* TO 'mailstats'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
|
@@ -0,0 +1,45 @@
|
||||
{
|
||||
# mailstats
|
||||
my $status = $mailstats{'Status'} || 'disabled';
|
||||
|
||||
if ($status eq 'enabled')
|
||||
{
|
||||
$OUT .="#-------------------------------------------------\n";
|
||||
$OUT .="# mailstats settings from smeserver-mailstats\n";
|
||||
$OUT .="#-------------------------------------------------\n";
|
||||
$OUT .="\n";
|
||||
$OUT .= qq(
|
||||
# Alias for mailstats
|
||||
Alias "/mailstats/js" "/opt/mailstats/js"
|
||||
Alias "/mailstats/css" "/opt/mailstats/css"
|
||||
Alias "/mailstats" "/opt/mailstats/html"
|
||||
|
||||
<Directory "/opt/mailstats/html">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
);
|
||||
$OUT .= (($mailstats{access} || 'private' ) eq "public" ) ? " Require all granted": " Require ip $localAccess $externalSSLAccess";
|
||||
$OUT .= qq(
|
||||
<FilesMatch .php\$\>
|
||||
SetHandler "proxy:unix:/var/run/php-fpm/php74.sock|fcgi://localhost"
|
||||
</FilesMatch>
|
||||
</Directory>
|
||||
|
||||
<Directory "/opt/mailstats/css">
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<Directory "/opt/mailstats/js">
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$OUT .= "# mailstats is disabled";
|
||||
}
|
||||
}
|
210
root/opt/mailstats/css/mailstats.css
Normal file
210
root/opt/mailstats/css/mailstats.css
Normal file
@@ -0,0 +1,210 @@
|
||||
table {
|
||||
xxborder:2px solid;
|
||||
xxborder-collapse:collapse;
|
||||
}
|
||||
|
||||
|
||||
tr.row-total, tr.row-percent , td.col-15, td.col-16 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table {
|
||||
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.headerpanel {
|
||||
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
|
||||
border:1px solid;
|
||||
width:98%;
|
||||
}
|
||||
|
||||
.innerheaderpanel {
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
tr,td,th {
|
||||
border:1px solid;
|
||||
}
|
||||
|
||||
.mailstats-detail-1stcol{
|
||||
width:174px;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
border-bottom :2px solid;
|
||||
color:black;
|
||||
background-color:darkgrey;
|
||||
}
|
||||
|
||||
tfoot tr {
|
||||
border-top:2px solid;
|
||||
color:black;
|
||||
font-weight: bold;
|
||||
background-color:darkgrey;
|
||||
}
|
||||
|
||||
.stripes tbody tr:nth-child(odd) {background-color: #dfdfdf}
|
||||
|
||||
.no-stripes tbody tr:nth-child(odd) {
|
||||
background-color: transparent; /* or whatever background color you want */
|
||||
}
|
||||
|
||||
|
||||
div.linksattop {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
a.prevlink {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.divshowindex {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.nextlink {
|
||||
text-align: right;
|
||||
}
|
||||
/* Basic styling for the tab container */
|
||||
.tab-container {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Styling for the tabs */
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
/* Styling for the active tab */
|
||||
.tab-active {
|
||||
background-color: #ffffff;
|
||||
border-top: 2px solid #007bff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Hide all content sections by default */
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Display the active content section */
|
||||
.tab-content-active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cssclass1 {background-color:#ffff99;}
|
||||
.cssclass2 {background-color:lightcoral;}
|
||||
.cssclass3 {background-color:lightcyan;}
|
||||
.cssclass4 {background-color:lightgoldenrodyellow;}
|
||||
.cssclass5 {background-color:lightgray;}
|
||||
.cssclass6 {background-color:lightgreen;}
|
||||
.cssclass7 {background-color:lightpink;}
|
||||
.cssclass8 {background-color:lightsalmon;}
|
||||
.cssclass9 {background-color:lightseagreen;}
|
||||
.cssclass10 {background-color:lightskyblue;}
|
||||
.cssclass11 {background-color:lightslategray;}
|
||||
.cssclass12 {background-color:lightsteelblue;}
|
||||
|
||||
p.cssvalid,p.htmlvalid {float:left;margin-right:20px}
|
||||
|
||||
.maintable {}
|
||||
|
||||
.subtables {
|
||||
display: flex;
|
||||
flex-wrap: nowrap; /* Use wrap if you want the tables to wrap to the next line when the screen is too narrow */
|
||||
gap: 10px; /* Optional: Adds space between the tables */
|
||||
}
|
||||
.subtables > div {
|
||||
flex: 1; /* Equal width tables, remove or adjust based on your preference */
|
||||
margin-left:0px;
|
||||
}
|
||||
|
||||
.footer {}
|
||||
|
||||
.iframe-container {
|
||||
width: 100%;
|
||||
height: 500px; /* Adjust as needed */
|
||||
border: none;
|
||||
}
|
||||
|
||||
.parent-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.greyed-out {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.subtables {
|
||||
display: flex;
|
||||
flex-wrap: wrap; /* Allows the items to wrap onto the next line */
|
||||
justify-content: space-between; /* Distributes space between the tables */
|
||||
margin: -10px; /* Negative margin to offset the table margins */
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap; /* Allowing wrapping of the tables */
|
||||
width: 100%; /* Makes the container full width */
|
||||
box-sizing: border-box; /* Adjust size calculations to include padding and borders */
|
||||
}
|
||||
|
||||
.Incoming, .Junk, .Geoip, .Qpsmtpd, .Viruses, .Blacklist {
|
||||
flex: 0 1 calc(25% - 20px); /* Each table will take 25% of the width minus margins */
|
||||
margin: 10px; /* Margin for spacing */
|
||||
box-sizing: border-box; /* Include padding and border in the element's total width and height */
|
||||
}
|
||||
|
||||
/* Ensure tables adapt on smaller screens */
|
||||
/* Default styling for large screens (5 columns) */
|
||||
.Incoming, .Junk, .Geoip, .Qpsmtpd, .Viruses, .Blacklist {
|
||||
flex: 0 1 calc(20% - 20px); /* 20% width for 5 columns */
|
||||
margin: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 4 columns layout */
|
||||
@media (max-width: 1600px) {
|
||||
.Incoming, .Junk, .Geoip, .Qpsmtpd, .Viruses, .Blacklist {
|
||||
flex: 0 1 calc(25% - 20px); /* 25% width for 4 columns */
|
||||
}
|
||||
}
|
||||
|
||||
/* 3 columns layout */
|
||||
@media (max-width: 1200px) {
|
||||
.Incoming, .Junk, .Geoip, .Qpsmtpd, .Viruses, .Blacklist {
|
||||
flex: 0 1 calc(33.333% - 20px); /* 33.333% width for 3 columns */
|
||||
}
|
||||
}
|
||||
|
||||
/* 2 columns layout */
|
||||
@media (max-width: 600px) {
|
||||
.Incoming, .Junk, .Geoip, .Qpsmtpd, .Viruses, .Blacklist {
|
||||
flex: 0 1 calc(50% - 20px); /* 50% width for 2 columns */
|
||||
}
|
||||
}
|
||||
|
||||
/* 1 column layout for mobile */
|
||||
@media (max-width: 300px) {
|
||||
.Incoming, .Junk, .Geoip, .Qpsmtpd, .Viruses, .Blacklist{
|
||||
flex: 0 1 100%; /* 100% width for 1 column */
|
||||
}
|
||||
}
|
||||
|
||||
/* Taken from inline in the chameleon templates */
|
||||
.maindiv {width:100%;overflow-x:auto;font-size:1cqw}
|
||||
.traffictable {border-collapse:collapse;width:98%}
|
||||
.divseeinbrowser{text-align:center;}
|
||||
.bordercollapse{border-collapse:collapse;}
|
51
root/opt/mailstats/html/ShowDetailedLogs.php
Normal file
51
root/opt/mailstats/html/ShowDetailedLogs.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
header('Content-Type: text/plain');
|
||||
|
||||
$input_param = isset($_GET['id']) ? $_GET['id'] : '9999';
|
||||
|
||||
// Set the directory and file names
|
||||
$directory = "/opt/mailstats/logs";
|
||||
$files = ['current1', 'current2'];
|
||||
|
||||
function process_file($file_path, $input_param) {
|
||||
$file = fopen($file_path, 'r');
|
||||
$match = "/ $input_param /";
|
||||
$endmatch = "/cleaning up after $input_param/";
|
||||
while (($line = fgets($file)) !== false) {
|
||||
// Check if the line contains the input_parameter
|
||||
if (preg_match($match,$line) === 1) {
|
||||
echo $line;
|
||||
} elseif (preg_match($endmatch,$line) === 1) {
|
||||
echo $line;
|
||||
exit();
|
||||
}
|
||||
}
|
||||
fclose($file);
|
||||
}
|
||||
|
||||
function tai64nToDate($tai64n) {
|
||||
// Check if the input TAI64N string is valid
|
||||
if (preg_match('/^@([0-9a-f]{8})([0-9a-f]{8})$/', $tai64n, $matches)) {
|
||||
// First part: seconds since epoch
|
||||
$sec_hex = $matches[1];
|
||||
// Second part: nanoseconds in hex
|
||||
$nsec_hex = $matches[2];
|
||||
|
||||
// Convert hex to decimal
|
||||
$seconds = hexdec($sec_hex);
|
||||
$nanoseconds = hexdec($nsec_hex);
|
||||
|
||||
// Calculate the full timestamp in seconds
|
||||
$timestamp = $seconds + ($nanoseconds / 1e9); // Nanoseconds to seconds
|
||||
|
||||
// Format timestamp to 'Y-m-d H:i:s'
|
||||
return date('Y-m-d H:i:s', $timestamp);
|
||||
} else {
|
||||
throw new InvalidArgumentException("Invalid TAI64N format.");
|
||||
}
|
||||
}
|
||||
chdir($directory);
|
||||
foreach ($files as $file) {
|
||||
process_file($file, $input_param);
|
||||
}
|
||||
?>
|
166
root/opt/mailstats/html/showSummaryLogs.php
Normal file
166
root/opt/mailstats/html/showSummaryLogs.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
// Database configuration
|
||||
$servername = "localhost";
|
||||
$username = "mailstats";
|
||||
$password = "mailstats";
|
||||
$dbname = "mailstats";
|
||||
|
||||
// Default date to yesterday
|
||||
$date = isset($_GET['date']) ? $_GET['date'] : date('Y-m-d', strtotime('-1 day'));
|
||||
|
||||
// Default hour to 99 (means all the hours)
|
||||
$hour = isset($_GET['hour']) ? $_GET['hour'] : 99;
|
||||
|
||||
// Create connection
|
||||
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||
|
||||
// Check connection
|
||||
if ($conn->connect_error) {
|
||||
die("Connection failed: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
// Prepare and execute the query
|
||||
if ($hour == 99){
|
||||
$sql = "SELECT * FROM SummaryLogs WHERE Date = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("s", $date);
|
||||
} else {
|
||||
$sql = "SELECT * FROM SummaryLogs WHERE Date = ? AND Hour = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("si", $date, $hour);
|
||||
}
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$result_count = $result->num_rows;
|
||||
|
||||
function generateLogDataTable($logData) {
|
||||
$data = json_decode($logData, true);
|
||||
if (is_null($data)) {
|
||||
return "Invalid JSON data";
|
||||
}
|
||||
|
||||
//// Remove entries with the key "logterse"
|
||||
//if (isset($data['logterse'])) {
|
||||
//unset($data['logterse']);
|
||||
//}
|
||||
|
||||
// Remove entries with the key "logterse" and remove entries with empty values
|
||||
foreach ($data as $key => $value) {
|
||||
if ($key === 'logterse' || empty($value)) {
|
||||
unset($data[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle adjacent duplicates by merging keys
|
||||
$mergedData = [];
|
||||
$previousValue = null;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($value === $previousValue) {
|
||||
// Merge the current key with the previous key
|
||||
end($mergedData);
|
||||
$lastKey = key($mergedData);
|
||||
$newKey = "$lastKey/$key";
|
||||
$mergedData[$newKey] = $value;
|
||||
// Remove the old entry
|
||||
unset($mergedData[$lastKey]);
|
||||
} else {
|
||||
// Otherwise, add a new entry
|
||||
$mergedData[$key] = $value;
|
||||
}
|
||||
$previousValue = $value;
|
||||
}
|
||||
|
||||
|
||||
$keys = array_keys($mergedData);
|
||||
$values = array_values($mergedData);
|
||||
|
||||
$output = '<table class="stripes" style="border-collapse: collapse; width:95%;overflow-x:auto; margin: 0.6% auto 0.6% auto;"><tbody>';
|
||||
#$output = '<table class="stripes" style="border-collapse: collapse; width:95%;overflow-x:auto; margin:2%"><tbody>';
|
||||
|
||||
// Divide keys and values into sets of 6
|
||||
$chunks = array_chunk($keys, 6);
|
||||
foreach ($chunks as $chunkIndex => $chunk) {
|
||||
if ($chunkIndex > 0) {
|
||||
// Add spacing between different sets
|
||||
#$output .= '<tr><td colspan="6" style="height: 1em;"></td></tr>';
|
||||
}
|
||||
|
||||
$output .= '<tr>';
|
||||
foreach ($chunk as $key) {
|
||||
$output .= '<th>' . htmlspecialchars($key) . '</th>';
|
||||
}
|
||||
$output .= '</tr><tr>';
|
||||
foreach ($chunk as $i => $key) {
|
||||
$val = htmlspecialchars($values[$chunkIndex * 6+ $i]);
|
||||
if ($key == 'id'){
|
||||
$output .= '<td>' . "<a href='./ShowDetailedLogs.php?id=".$val."'</a>".$val."</td>";
|
||||
} else {
|
||||
$output .= '<td>' . $val . '</td>';
|
||||
}
|
||||
}
|
||||
$output .= '</tr>';
|
||||
}
|
||||
|
||||
$output .= '</tbody></table>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel='stylesheet' type='text/css' href='css/mailstats.css' />
|
||||
<title>Summary Logs</title>
|
||||
<!-- <style>
|
||||
table {
|
||||
xxwidth: 100%;
|
||||
xxborder-collapse: collapse;
|
||||
}
|
||||
table, th, td {
|
||||
xxborder: 1px solid black;
|
||||
}
|
||||
th, td {
|
||||
xxpadding: 8px;
|
||||
xxtext-align: left;
|
||||
}
|
||||
</style>
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;overflow-x:auto;font-size:0.726cqw">"
|
||||
<h1>Summary Logs for Date: <?= htmlspecialchars($date) ?> <?= $hour == 99 ? 'for All Hours' : 'and Hour: ' . htmlspecialchars($hour) ?></h1>
|
||||
<h3>Found <?= $result_count ?> records.</h3>
|
||||
<table style="border-collapse:collapse;width:98%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<!--<th>Date</th>-->
|
||||
<!--<th>Hour</th>-->
|
||||
<th>Log Data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($result->num_rows > 0): ?>
|
||||
<?php while($row = $result->fetch_assoc()): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($row['id']) ?></td>
|
||||
<td><?= generateLogDataTable($row['logData']) ?></td>
|
||||
</tr>
|
||||
<?php endwhile; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="4">No records found for the specified date and hour.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
// Close the connection
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
?>
|
||||
</body>
|
||||
</html>
|
98
root/opt/mailstats/js/mailstats.js
Normal file
98
root/opt/mailstats/js/mailstats.js
Normal file
@@ -0,0 +1,98 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
doNavs(); // Your initialization code
|
||||
});
|
||||
//function openTab(event, tabId){
|
||||
//// Get all elements with class="tab-content" and hide them
|
||||
//const tabContents = document.querySelectorAll('.tab-content');
|
||||
//tabContents.forEach(content => content.classList.remove('tab-content-active'));
|
||||
|
||||
//// Get all elements with class="tab" and remove the class "tab-active"
|
||||
//const tabs = document.querySelectorAll('.tab');
|
||||
//tabs.forEach(tab => tab.classList.remove('tab-active'));
|
||||
|
||||
//// Show the current tab content, and add an "active" class to the clicked tab
|
||||
//document.getElementById(tabId).classList.add('tab-content-active');
|
||||
//event.target.classList.add('tab-active');}
|
||||
|
||||
//function LinkCheck(url){
|
||||
//var http = new XMLHttpRequest();
|
||||
//http.open('HEAD', url, false);
|
||||
//http.send();
|
||||
//return http.status!=404;
|
||||
//}
|
||||
|
||||
async function LinkCheck(url) {
|
||||
//Not allowed by CSP rules.
|
||||
//try {
|
||||
//const response = await fetch(url, { mode: "no-cors" });
|
||||
//return response.ok; // Returns true if the resource exists
|
||||
//} catch (error) {
|
||||
//console.error("Error checking link:", error);
|
||||
//return false;
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
||||
function doNavs() {
|
||||
const isInIframe = window.self !== window.top;
|
||||
var aTags = document.getElementsByTagName('a'),
|
||||
atl = aTags.length,i;
|
||||
for (i = 0; i < atl; i++) {
|
||||
if (aTags[i].innerText == "Previous") {
|
||||
if (isInIframe){ //!LinkCheck(aTags[i].href)) {
|
||||
aTags[i].style.visibility = "hidden";
|
||||
} else {
|
||||
aTags[i].style.visibility = "visible";
|
||||
}
|
||||
} else if (aTags[i].innerText == "Next") {
|
||||
if (isInIframe){ //!LinkCheck(aTags[i].href)) {
|
||||
aTags[i].style.visibility = "hidden";
|
||||
} else {
|
||||
aTags[i].style.visibility = "visible";
|
||||
}
|
||||
} else if (aTags[i].innerText == "Index of files") {
|
||||
if (isInIframe){ //!LinkCheck(aTags[i].href)) {
|
||||
aTags[i].style.visibility = "hidden";
|
||||
} else {
|
||||
aTags[i].style.visibility = "visible";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openTab(evt, tabName) {
|
||||
// Declare all variables
|
||||
var i, tab_content, tab;
|
||||
|
||||
// Get all elements with class="tab_content" and hide them
|
||||
tab_content = document.getElementsByClassName("tab-content");
|
||||
for (i = 0; i < tab_content.length; i++) {
|
||||
tab_content[i].style.display = "none";
|
||||
}
|
||||
|
||||
// Get all elements with class="tab" and remove the class "active"
|
||||
tab = document.getElementsByClassName("tab");
|
||||
for (i = 0; i < tab.length; i++) {
|
||||
tab[i].className = tab[i].className.replace(" tab-active", "");
|
||||
}
|
||||
|
||||
// Show the current tab, and add an "active" class to the link that opened the tab
|
||||
document.getElementById(tabName).style.display = "block";
|
||||
evt.currentTarget.className += " tab-active";
|
||||
|
||||
// Store the active tab index
|
||||
sessionStorage.setItem('activeTab', evt.currentTarget.getAttribute("data-index"));
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Attach click event handler for all divs with the class "tab"
|
||||
document.querySelectorAll(".tab").forEach(function(tab) {
|
||||
tab.addEventListener("click", function(event) {
|
||||
// Get the data-index attribute value
|
||||
const tabIndex = this.getAttribute("data-index");
|
||||
|
||||
// Dynamically call openTab with the correct tab parameter
|
||||
openTab(event, `tab${tabIndex}`);
|
||||
});
|
||||
});
|
||||
});
|
0
root/opt/mailstats/logs/.gitignore
vendored
Normal file
0
root/opt/mailstats/logs/.gitignore
vendored
Normal file
22
root/opt/mailstats/templates/mailstats-sub-table.html.pt
Normal file
22
root/opt/mailstats/templates/mailstats-sub-table.html.pt
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="${classname}">
|
||||
<h2>${title}</h2>
|
||||
<tal:block condition="threshold != 0">
|
||||
<span class='greyed-out'>Display threshold set to ${threshold}%</span>
|
||||
</tal:block>
|
||||
<tal:block condition="threshold == 0">
|
||||
<br>
|
||||
</tal:block>
|
||||
<table class="bordercollapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th tal:repeat="header column_headers">${header}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr tal:repeat="item array_2d">
|
||||
<td tal:repeat="cell item">${cell}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
122
root/opt/mailstats/templates/mailstats.html.pt
Normal file
122
root/opt/mailstats/templates/mailstats.html.pt
Normal file
@@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="utf-8">
|
||||
<title>SMEServer Mailstats</title>
|
||||
<link rel='stylesheet' type='text/css' href='css/mailstats.css' />
|
||||
<!-- Check links -->
|
||||
<!--css here-->
|
||||
</head>
|
||||
<body>
|
||||
<div class=maindiv>
|
||||
<!---Navigation here-->
|
||||
<div class='linksattop'>
|
||||
<a class='prevlink' href='http://${SystemName}.${DomainName}/mailstats/mailstats_for_${PreviousDate}.html'>Previous</a>
|
||||
<div class='divshowindex'><a class='showindex' href='http://${SystemName}.${DomainName}/mailstats/'>Index of files</a></div>
|
||||
<a class='nextlink' href='http://${SystemName}.${DomainName}/mailstats/mailstats_for_${NextDate}.html'>Next</a></div> <!--format important here - used to insert see in browser for email -->
|
||||
<br />
|
||||
<h2>${structure:title}</h2>
|
||||
<br />
|
||||
<div class="headerpanel">
|
||||
<div class = "innerheaderpanel">
|
||||
<!---Add in header information here -->
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<!--Tabs -->
|
||||
<div class="tab-container">
|
||||
<div class="tab tab-active" data-index="0" >Table</div>
|
||||
<div tal:condition="enable_graphs" class="tab" data-index="1">Line Graph</div>
|
||||
<div tal:condition="enable_graphs" class="tab" data-index="2">Stacked Bar Graph</div>
|
||||
<div tal:condition="enable_graphs" class="tab" data-index="3">Scatter Graph</div>
|
||||
<div tal:condition="enable_graphs" class="tab" data-index="4">Pie Chart</div>
|
||||
</div>
|
||||
|
||||
<div id="tab0" class="tab-content tab-content-active">
|
||||
<div class = "maintable">
|
||||
<table class="traffictable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date/Time</th>
|
||||
<th tal:repeat="header column_headers" tal:content="header">Header</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr tal:repeat="row array_2d" tal:attributes="class python: 'row-total' if repeat.row.index == 24 else 'row-percent' if repeat.row.index == 25 else None">
|
||||
<td tal:condition="repeat.row.index == 24" tal:attributes="class python:'col-total'" tal:content="'TOTALS'">Totals</td>
|
||||
<td tal:condition="repeat.row.index == 25" tal:attributes="class python:'col-percent'" tal:content="'PERCENT'">Percent</td>
|
||||
<td tal:condition="repeat.row.index < 24" tal:content="string:${reporting_date}, ${repeat.row.index}">Hour</td>
|
||||
<td tal:repeat="cell row" tal:attributes="class python: 'col-' + str(repeat.cell.index)">
|
||||
<!-- Check if 'nolinks' is true. If not, generate links for rows 0 to 23 except 'PERCENT' column -->
|
||||
<tal:case tal:condition="not: nolinks">
|
||||
<tal:case tal:condition="repeat.row.index >= 0 and repeat.row.index < 24 and repeat.cell.index != 16">
|
||||
<a tal:attributes="href string:./showSummaryLogs.php?date=${reporting_date}&hour=${repeat.row.index}">
|
||||
<!-- Check if cell value is zero and print "" -->
|
||||
<tal:case tal:condition="cell != 0" tal:content="cell">Cell</tal:case>
|
||||
<tal:case tal:condition="cell == 0" tal:content="''">-</tal:case>
|
||||
</a>
|
||||
</tal:case>
|
||||
<!-- For 'PERCENT' column or other rows, just display the cell content -->
|
||||
<tal:case tal:condition="not (repeat.row.index >= 0 and repeat.row.index < 24 and repeat.cell.index != 16)">
|
||||
<!-- Check if cell value is zero and print "" -->
|
||||
<tal:case tal:condition="cell != 0" tal:content="cell">Cell</tal:case>
|
||||
<tal:case tal:condition="cell == 0" tal:content="''">-</tal:case>
|
||||
</tal:case>
|
||||
</tal:case>
|
||||
<tal:case tal:condition="nolinks">
|
||||
<!-- Display cell content without link if 'nolinks' is true -->
|
||||
<tal:case tal:condition="cell != 0" tal:content="cell">Cell</tal:case>
|
||||
<tal:case tal:condition="cell == 0" tal:content="''">-</tal:case>
|
||||
</tal:case>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class = "subtables">
|
||||
<div class="table-container">
|
||||
<!---Add in sub tables here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Tab-->
|
||||
<div tal:condition="enable_graphs" id="tab1" class="tab-content">
|
||||
<img src="line_graph_${reporting_date}.png">
|
||||
</div>
|
||||
<!-- Next Tab-->
|
||||
<div tal:condition="enable_graphs" id="tab2" class="tab-content">
|
||||
<img src="bar_graph_${reporting_date}.png">
|
||||
</div>
|
||||
<!-- Next Tab-->
|
||||
<div tal:condition="enable_graphs" id="tab3" class="tab-content">
|
||||
<img src="scatter_graph_${reporting_date}.png">
|
||||
</div>
|
||||
<!-- Next Tab-->
|
||||
<div tal:condition="enable_graphs" id="tab4" class="tab-content">
|
||||
<img src="pie_chart_${reporting_date}.png">
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<footer class="footer">${version}</footer>
|
||||
|
||||
<script type='text/javascript' src='js/mailstats.js' ></script>
|
||||
|
||||
<!--
|
||||
<p class="cssvalid">
|
||||
<a href="http://jigsaw.w3.org/css-validator/check/referer">
|
||||
<img style="border:0;width:88px;height:31px"
|
||||
src="http://jigsaw.w3.org/css-validator/images/vcss"
|
||||
alt="Valid CSS!" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p class="htmlvalid">
|
||||
<a href="https://validator.w3.org/check?uri=referer"><img
|
||||
src="http://www.w3.org/Icons/valid-xhtml10"
|
||||
alt="Valid XHTML 1.0!" height="31" width="88" /></a>
|
||||
</p>
|
||||
</div>
|
||||
-->
|
||||
</body>
|
||||
</html>
|
97
root/usr/bin/mailstats-convert-log-sme10-to-sme11.py
Normal file
97
root/usr/bin/mailstats-convert-log-sme10-to-sme11.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
import glob # Import the glob module
|
||||
|
||||
def tai64n_to_datetime(tai64n):
|
||||
"""Convert TAI64N formatted timestamp to a datetime object."""
|
||||
if len(tai64n) < 16:
|
||||
raise ValueError(f"Invalid TAI64N timestamp length: {tai64n}")
|
||||
|
||||
high_bits = int(tai64n[:15], 16)
|
||||
low_bits = int(tai64n[15:23], 16)
|
||||
|
||||
seconds_since_epoch = high_bits
|
||||
nanoseconds = low_bits
|
||||
|
||||
# Create datetime object
|
||||
epoch = datetime(1970, 1, 1)
|
||||
dt = epoch + timedelta(seconds=seconds_since_epoch)
|
||||
dt += timedelta(microseconds=nanoseconds // 1000)
|
||||
|
||||
return dt
|
||||
|
||||
def convert_log(file_paths, output_path):
|
||||
host_name = "sme11"
|
||||
total_files = 0
|
||||
total_lines = 0
|
||||
|
||||
# Input file validation
|
||||
for file_path in file_paths:
|
||||
if not os.path.isfile(file_path):
|
||||
print(f"Input file {file_path} does not exist.")
|
||||
return
|
||||
with open(output_path, 'w') as output_file:
|
||||
for file_path in file_paths:
|
||||
print(f"{file_path}")
|
||||
# Determine the process name based on the file being read
|
||||
if "sqpsmtpd" in file_path:
|
||||
process_name = "sqpsmtpd-forkserver"
|
||||
else:
|
||||
process_name = "qpsmtpd-forkserver"
|
||||
|
||||
with open(file_path, 'r', encoding='latin1') as log_file:
|
||||
total_files += 1
|
||||
try:
|
||||
for line in log_file:
|
||||
total_lines += 1
|
||||
match = re.match(r'@(\w+) (\d+) \((.*?)\) (.*)', line.strip())
|
||||
if match:
|
||||
tai64n_timestamp, pid, context, message = match.groups()
|
||||
try:
|
||||
log_time = tai64n_to_datetime(tai64n_timestamp[1:]) # Ignore '@'
|
||||
formatted_time = log_time.strftime('%b %d %H:%M:%S')
|
||||
|
||||
# Replace "bjsystems.co.uk" with "thereadclan.me.uk" in the message
|
||||
#message = message.replace("bjsystems.co.uk", "thereadclan.me.uk")
|
||||
|
||||
# Correctly format the output line
|
||||
formatted_line = f"{formatted_time} {host_name} {process_name}[{pid}]: {pid} ({context}) {message}\n"
|
||||
output_file.write(formatted_line)
|
||||
except Exception as e:
|
||||
with open("error_log.txt", 'a') as error_file:
|
||||
error_file.write(f"Could not convert timestamp {tai64n_timestamp}: {e}\n")
|
||||
print(f"Error logged for timestamp {tai64n_timestamp}.")
|
||||
else:
|
||||
#does not mathc the logterse line, but still needed
|
||||
match = re.match(r'@(\w+) (\d+) (.*)', line.strip())
|
||||
if match:
|
||||
tai64n_timestamp, pid, message = match.groups()
|
||||
try:
|
||||
log_time = tai64n_to_datetime(tai64n_timestamp[1:]) # Ignore '@'
|
||||
formatted_time = log_time.strftime('%b %d %H:%M:%S')
|
||||
# Replace "bjsystems.co.uk" with "thereadclan.me.uk" in the message
|
||||
#message = message.replace("bjsystems.co.uk", "thereadclan.me.uk")
|
||||
# Correctly format the output line
|
||||
formatted_line = f"{formatted_time} {host_name} {process_name}[{pid}]: {pid} {message}\n"
|
||||
output_file.write(formatted_line)
|
||||
except Exception as e:
|
||||
with open("error_log.txt", 'a') as error_file:
|
||||
error_file.write(f"Could not convert timestamp {tai64n_timestamp}: {e}\n")
|
||||
print(f"Error logged for timestamp {tai64n_timestamp}.")
|
||||
except Exception as e:
|
||||
print(f"Error reading file {file_path}: {e}")
|
||||
continue
|
||||
print(f"Processed {total_files} files and {total_lines} lines.")
|
||||
# Specify the input and output file paths
|
||||
# Use glob to expand file patterns
|
||||
input_log_files = (
|
||||
glob.glob("/var/log/qpsmtpd/@*.s") +
|
||||
["/var/log/qpsmtpd/current", "/var/log/sqpsmtpd/current"] +
|
||||
glob.glob("/var/log/sqpsmtpd/@*.s") # Adjust the asterisk * as needed
|
||||
)
|
||||
output_log_file = "output_log.txt" # Specify your desired output file path
|
||||
|
||||
# Convert the log
|
||||
convert_log(input_log_files, output_log_file)
|
||||
print(f"Log conversion complete. Check the output at: {output_log_file}")
|
@@ -33,6 +33,7 @@ use strict;
|
||||
# bjr - 18Oct20 - Alter use of lc to avoid uninitialised messages - bug 11044
|
||||
# bjr - 02Apr21 - Fix up lc to try to avoif uninit messages - and alter warning status - bug 11519
|
||||
# bjr - 15Feb23 - Add in auth::auth_imap after change to use dovecot as incoming authorisation Bugzilla 12327
|
||||
# bjr - 29Dec24 - Convert to SME11 log date format
|
||||
#
|
||||
#############################################################################
|
||||
#
|
||||
@@ -70,6 +71,11 @@ use strict;
|
||||
#
|
||||
|
||||
# internal modules (part of core perl distribution)
|
||||
#
|
||||
# New format for SME11
|
||||
#
|
||||
#Dec 17 19:34:34 sme11 qpsmtpd-forkserver[441318]: 441318 (deny) logging::logterse: ` 192.168.1.4 pc-00004.thereadclan.me.uk bjsystems.co.uk <biodiversityadvanced@bjsystems.co.uk> check_goodrcptto 901 relaying denied smeserver@thereadclan.me.uk msg denied before queued
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
@@ -105,7 +111,7 @@ if ($cdb->get('mailstats')){
|
||||
|
||||
#Configuration section
|
||||
my %opt = (
|
||||
version => '0.7.16', # please update at each change.
|
||||
version => '0.8.01', # please update at each change.
|
||||
debug => 0, # guess what ?
|
||||
sendmail => '/usr/sbin/sendmail', # Path to sendmail stub
|
||||
from => 'spamfilter-stats', # Who is the mail from
|
||||
@@ -370,17 +376,30 @@ my $makeHTMLpage = "no";
|
||||
|
||||
|
||||
# Init the hashes
|
||||
my $nhour = floor( $start / 3600 );
|
||||
my $nhour = 0;
|
||||
#print "Hour:".$nhour."\n";
|
||||
my $ncateg;
|
||||
while ( $nhour < $end / 3600 ) {
|
||||
$counts{$nhour}=();
|
||||
$ncateg = 0;
|
||||
while ( $ncateg < @categs) {
|
||||
$counts{$nhour}{$categs[$ncateg-1]} = 0;
|
||||
$ncateg++
|
||||
while ( $nhour < 24 ) {
|
||||
$counts{$nhour} = {}; # Initialize as a hash reference
|
||||
#print "Hour:".$nhour."\n";
|
||||
my $ncateg = 0; # Reset $ncateg for each hour
|
||||
|
||||
while ( $ncateg < @categs ) {
|
||||
$counts{$nhour}{$categs[$ncateg]} = 0; # Corrected index
|
||||
$ncateg++; # Increment $ncateg
|
||||
}
|
||||
$nhour++;
|
||||
$nhour++; # Increment $nhour
|
||||
}
|
||||
|
||||
#while ( $nhour < $end / 3600 ) {
|
||||
#$counts{$nhour}=();
|
||||
#$ncateg = 0;
|
||||
#while ( $ncateg < @categs) {
|
||||
#$counts{$nhour}{$categs[$ncateg-1]} = 0;
|
||||
#$ncateg++
|
||||
#}
|
||||
#$nhour++;
|
||||
#}
|
||||
# and grand totals, percent and display status from db entries, and column widths
|
||||
$ncateg = 0;
|
||||
my $colpadding = 0;
|
||||
@@ -402,8 +421,17 @@ while ( $ncateg < @categs) {
|
||||
$ncateg++
|
||||
}
|
||||
|
||||
my $starttai = Time::TAI64::unixtai64n($start);
|
||||
my $endtai = Time::TAI64::unixtai64n($end);
|
||||
#foreach my $hour (sort keys %counts) {
|
||||
#printf "Hour: %2d\n", $hour; # Right-aligned hour
|
||||
#foreach my $categ (sort keys %{ $counts{$hour} }) {
|
||||
#printf " %-12s: %d\n", $categ, $counts{$hour}{$categ}; # Left-aligned category
|
||||
#}
|
||||
#}
|
||||
|
||||
#die("die");
|
||||
|
||||
#my $starttai = Time::TAI64::unixtai64n($start);
|
||||
#my $endtai = Time::TAI64::unixtai64n($end);
|
||||
my $sum_SARules = 0;
|
||||
|
||||
# we remove non valid files
|
||||
@@ -424,13 +452,23 @@ my $count = -1; #for loop reduction in debugging mode
|
||||
my $CurrentMailId = "";
|
||||
|
||||
LINE: while (<>) {
|
||||
chomp;
|
||||
|
||||
next LINE if !(my($tai,$log) = split(' ',$_,2));
|
||||
# Use a regex to extract the date, time, and the rest of the log
|
||||
if (/^(\w+\s+\d+\s+\d{2}:\d{2}:\d{2})\s+\S+\s+\S+\[(\d+)\]:\s+(.*)/) {
|
||||
my $datetime = $1; # Get the date and time
|
||||
my $log_id = $2; # Get the log ID
|
||||
my $log_entry = $3; # The rest of the log line
|
||||
|
||||
# Convert datetime to epoch
|
||||
my $current_epoch = convert_to_epoch($datetime);
|
||||
|
||||
#If date specified, only process lines matching date
|
||||
next LINE if ( $tai lt $starttai );
|
||||
next LINE if ( $tai gt $endtai );
|
||||
# If date specified, only process lines matching date
|
||||
#print $datetime." ".$current_epoch." ".$start." ".$end."\n";
|
||||
next LINE if ($current_epoch < $start);
|
||||
next LINE if ($current_epoch > $end);
|
||||
|
||||
#print $datetime."\n";
|
||||
|
||||
#Count lines and skip out if debugging
|
||||
$count++;
|
||||
@@ -493,20 +531,36 @@ LINE: while (<>) {
|
||||
#only select Logterse output
|
||||
next LINE unless m/logging::logterse:/;
|
||||
|
||||
my $abstime = Time::TAI64::tai2unix($tai);
|
||||
my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
|
||||
#my $abstime = Time::TAI64::tai2unix($tai);
|
||||
#my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
|
||||
|
||||
# Create a timestamp for the previous hour
|
||||
my $previous_hour_epoch = $current_epoch; # - 3600; # Subtract 3600 seconds (1 hour)
|
||||
|
||||
# Convert epoch time to local time
|
||||
my ($sec, $min, $hour) = localtime($previous_hour_epoch);
|
||||
#print $sec." ".$min." ".$hour."\n";
|
||||
#$hour = ($hour==23)?0:$hour;
|
||||
my $abshour = $hour;
|
||||
#print "Abs:".$abshour." ".strftime('%Y-%m-%dT%H:%M:%SZ',gmtime($previous_hour_epoch))."\n";
|
||||
|
||||
|
||||
my ($timestamp_part, $log_part) = split('`',$_,2); #bjr 0.6.12
|
||||
my (@log_items) = split $FS, $log_part;
|
||||
#print "0".$log_items[0]."\n";
|
||||
#print "1".$log_items[1]."\n";
|
||||
#print "5".$log_items[5]."\n";
|
||||
#print "7".$log_items[7]."\n";
|
||||
|
||||
my (@timestamp_items) = split(' ',$timestamp_part);
|
||||
#print $timestamp_items[5];
|
||||
#print "\n";
|
||||
|
||||
my $result= "rejected"; #Tag as rejected unti we know otherwise
|
||||
my $result= "rejected"; #Tag as rejected unti we know otherwise
|
||||
# we store the more recent recipient domain, for domain statistics
|
||||
# in fact, we only store the first recipient. Could be sort of headhache
|
||||
# to obtain precise stats with many recipients on more than one domain !
|
||||
my $proc = $timestamp_items[1] ; #numeric Id for the email
|
||||
my $proc = $timestamp_items[5] ; #numeric Id for the email
|
||||
my $emailnum = $proc; #proc gets modified later...
|
||||
|
||||
if ($emailnum == 23244) {
|
||||
@@ -534,7 +588,7 @@ LINE: while (<>) {
|
||||
$counts{$abshour}{$CATRELAY}++;
|
||||
}
|
||||
|
||||
elsif (($log_items[2] =~ m/$WebmailIP/) and (!test_for_private_ip($log_items[0]))) {
|
||||
elsif (($log_items[2] =~ m/$WebmailIP/) and (!test_for_private_ip($log_items[0]))) {
|
||||
#Webmail
|
||||
$localflag = 1;
|
||||
$WebMailsendtotal++;
|
||||
@@ -556,33 +610,33 @@ LINE: while (<>) {
|
||||
$localflag = 1;
|
||||
}
|
||||
else {
|
||||
#Or sent to the DMARC server
|
||||
#check for email address in $DMARC_Report_emails string
|
||||
my $logemail = $log_items[4];
|
||||
if ((index($DMARC_Report_emails,$logemail)>=0) or ($logemail =~ m/$DMARCDomain/)){
|
||||
$localsendtotal++;
|
||||
$DMARCSendCount++;
|
||||
$localflag = 1;
|
||||
}
|
||||
else {
|
||||
if (exists $log_items[8]){
|
||||
# ignore incoming localhost spoofs
|
||||
if ( $log_items[8] =~ m/msg denied before queued/ ) { }
|
||||
else {
|
||||
#Webmail
|
||||
$localflag = 1;
|
||||
$WebMailsendtotal++;
|
||||
$counts{$abshour}{$CATWEBMAIL}++;
|
||||
$WebMailflag = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$localflag = 1;
|
||||
$WebMailsendtotal++;
|
||||
$counts{$abshour}{$CATWEBMAIL}++;
|
||||
$WebMailflag = 1;
|
||||
}
|
||||
}
|
||||
#Or sent to the DMARC server
|
||||
#check for email address in $DMARC_Report_emails string
|
||||
my $logemail = $log_items[4];
|
||||
if ((index($DMARC_Report_emails,$logemail)>=0) or ($logemail =~ m/$DMARCDomain/)){
|
||||
$localsendtotal++;
|
||||
$DMARCSendCount++;
|
||||
$localflag = 1;
|
||||
}
|
||||
else {
|
||||
if (exists $log_items[8]){
|
||||
# ignore incoming localhost spoofs
|
||||
if ( $log_items[8] =~ m/msg denied before queued/ ) { }
|
||||
else {
|
||||
#Webmail
|
||||
$localflag = 1;
|
||||
$WebMailsendtotal++;
|
||||
$counts{$abshour}{$CATWEBMAIL}++;
|
||||
$WebMailflag = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$localflag = 1;
|
||||
$WebMailsendtotal++;
|
||||
$counts{$abshour}{$CATWEBMAIL}++;
|
||||
$WebMailflag = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -716,22 +770,24 @@ LINE: while (<>) {
|
||||
#extract the spam score
|
||||
# Remove count for rejectred as it looks as if it might get through!!
|
||||
$result= "queued";
|
||||
if ($log_items[8] =~ ".*score=([+-]?\\d+\.?\\d*).* required=([0-9\.]+)") {
|
||||
$score = trim($1);
|
||||
if ($score =~ /^[+-]?\d+\.?\d*$/ ) #check its numeric
|
||||
{
|
||||
if ($score < $SATagLevel) { $hamcount++;$counts{$abshour}{$CATHAM}++;$hamavg += $score;}
|
||||
else {$spamcount++;$counts{$abshour}{$CATSPAM}++;$spamavg += $score;$result= "spam";}
|
||||
if (defined($log_items[8])){
|
||||
if ($log_items[8] =~ ".*score=([+-]?\\d+\.?\\d*).* required=([0-9\.]+)") {
|
||||
$score = trim($1);
|
||||
if ($score =~ /^[+-]?\d+\.?\d*$/ ) #check its numeric
|
||||
{
|
||||
if ($score < $SATagLevel) { $hamcount++;$counts{$abshour}{$CATHAM}++;$hamavg += $score;}
|
||||
else {$spamcount++;$counts{$abshour}{$CATSPAM}++;$spamavg += $score;$result= "spam";}
|
||||
} else {
|
||||
print "Unexpected non numeric found in $proc:".$log_items[8]."($score)\n";
|
||||
}
|
||||
} else {
|
||||
print "Unexpected non numeric found in $proc:".$log_items[8]."($score)\n";
|
||||
# no SA score - treat it as ham
|
||||
$hamcount++;$counts{$abshour}{$CATHAM}++;
|
||||
}
|
||||
if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
|
||||
$byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
|
||||
$currentrcptdomain{ $proc } = '' ;
|
||||
}
|
||||
} else {
|
||||
# no SA score - treat it as ham
|
||||
$hamcount++;$counts{$abshour}{$CATHAM}++;
|
||||
}
|
||||
if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
|
||||
$byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
|
||||
$currentrcptdomain{ $proc } = '' ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,12 +878,16 @@ LINE: while (<>) {
|
||||
}
|
||||
}
|
||||
#exit if $emailnum == 15858;
|
||||
#print "Counts:*$abshour* ".$counts{$abshour}{$CATNONCONF}."\n";
|
||||
|
||||
;
|
||||
|
||||
} # end of if regexp
|
||||
} #END OF MAIN LOOP
|
||||
|
||||
#total up grand total Columns
|
||||
$nhour = floor( $start / 3600 );
|
||||
while ( $nhour < $end / 3600 ) {
|
||||
$nhour = 0;
|
||||
while ( $nhour < 24 ) {
|
||||
$ncateg = 0; #past the where it came from columns
|
||||
while ( $ncateg < @categs) {
|
||||
#total columns
|
||||
@@ -846,8 +906,8 @@ while ( $nhour < $end / 3600 ) {
|
||||
|
||||
|
||||
#Compute row totals and row percentages
|
||||
$nhour = floor( $start / 3600 );
|
||||
while ( $nhour < $end / 3600 ) {
|
||||
$nhour = 0; #floor( $start / 3600 );
|
||||
while ( $nhour < 24 ){ #}$end / 3600 ) {
|
||||
$counts{$nhour}{$categs[@categs-1]} = $counts{$nhour}{$categs[@categs-2]}*100/$totalexamined if $totalexamined;
|
||||
$nhour++;
|
||||
|
||||
@@ -865,8 +925,8 @@ while ( $nhour < $end / 3600 ) {
|
||||
}
|
||||
|
||||
#compute sum of row percentages
|
||||
$nhour = floor( $start / 3600 );
|
||||
while ( $nhour < $end / 3600 ) {
|
||||
$nhour = 0; #floor( $start / 3600 );
|
||||
while ( $nhour < 24 ){ #}$end / 3600 ) {
|
||||
$counts{$GRANDTOTAL}{$categs[@categs-1]} += $counts{$nhour}{$categs[@categs-1]};
|
||||
$nhour++;
|
||||
|
||||
@@ -995,15 +1055,15 @@ if ( !$disabled ) {
|
||||
my $Totals; #Corresponding totals
|
||||
my $Percent; # and column percentages
|
||||
|
||||
my $hour = floor( $start / 3600 );
|
||||
my $hour = 0; #floor( $start / 3600 );
|
||||
$Line1 = '';
|
||||
$Line2 = '';
|
||||
$Titles = '';
|
||||
$Values = '';
|
||||
$Totals = '';
|
||||
$Percent = '';
|
||||
while ( $hour < $end / 3600 ) {
|
||||
if ($hour == floor( $start / 3600 )){
|
||||
while ( $hour < 24){ #}$end / 3600 ) {
|
||||
if ($hour == 0){ #}floor( $start / 3600 )){
|
||||
#Do all the once only things
|
||||
$ncateg = 0;
|
||||
while ( $ncateg < @categs) {
|
||||
@@ -1030,10 +1090,11 @@ if ( !$disabled ) {
|
||||
}
|
||||
|
||||
$ncateg = 0;
|
||||
my $table_start_time = $start;
|
||||
while ( $ncateg < @categs) {
|
||||
if ($finaldisplay[$ncateg]){
|
||||
if ($ncateg == 0) {
|
||||
$Values .= strftime( "%F, %H", localtime( $hour * 3600 ) )." "
|
||||
$Values .= strftime( "%F, %H", localtime( $table_start_time + $hour * 3600 ) )." "
|
||||
} elsif ($ncateg == @categs-1) {
|
||||
#percentages in last column
|
||||
$Values .= sprintf('%'.($colwidth[$ncateg]-2).'.1f',$counts{$hour}{$categs[$ncateg]})."%";
|
||||
@@ -1289,13 +1350,16 @@ sub analysis_period {
|
||||
$sec=0;$min=0;$hour=$base;
|
||||
};
|
||||
#$mday="05"; #$mday="03"; #$mday="16"; #Temp!!
|
||||
#print $sec." ".$min." ".$hour." ".$mday." ".$mon." ".$year." ".$wday." ".$yday."\n";
|
||||
$time = timelocal($sec,$min,$hour,$mday,$mon,$year);
|
||||
#print $time."\n"
|
||||
}
|
||||
|
||||
my $start = str2time( $startdate );
|
||||
my $end = $enddate ? str2time( $enddate ) :
|
||||
$startdate ? $start + $secsininterval : $time;
|
||||
$start = $startdate ? $start : $end - $secsininterval;
|
||||
#print "Analysis".$start." ".$end;
|
||||
return ( $start > $end ) ? ( $end, $start ) : ( $start, $end );
|
||||
}
|
||||
|
||||
@@ -1667,7 +1731,7 @@ sub save_data
|
||||
"mailstats", "mailstats" )
|
||||
or die "Cannot open mailstats db - has it beeen created?";
|
||||
|
||||
my $hour = floor( $start / 3600 );
|
||||
my $hour = 0; #floor( $start / 3600 );
|
||||
my $reportdate = strftime( "%F", localtime( $hour * 3600 ) );
|
||||
my $dateid = get_dateid($dbh,$reportdate);
|
||||
my $reccount = 0; #count number of records written
|
||||
@@ -1740,9 +1804,9 @@ sub save_data
|
||||
}
|
||||
# finally - the hourly breakdown
|
||||
# need to remember here that the date might change during the 24 hour span
|
||||
my $nhour = floor( $start / 3600 );
|
||||
my $nhour = 0; #floor( $start / 3600 );
|
||||
my $ncateg;
|
||||
while ( $nhour < $end / 3600 ) {
|
||||
while ( $nhour < 24){ #}$end / 3600 ) {
|
||||
#see if the time record has been created
|
||||
# print strftime("%H",localtime( $nhour * 3600 ) ).":00:00\n";
|
||||
my $sth =
|
||||
@@ -1891,10 +1955,32 @@ sub get_dateid
|
||||
#} else { return 0}
|
||||
#}
|
||||
|
||||
sub convert_to_epoch {
|
||||
|
||||
my ($sec, $min, $hour1, $mday, $mon, $year) = localtime();
|
||||
$year += 1900; # localtime returns year as years since 1900
|
||||
|
||||
my ($datetime) = @_;
|
||||
my ($month, $day, $time) = split(' ', $datetime);
|
||||
my ($hour, $minute, $second) = split(':', $time);
|
||||
|
||||
my $month_num = {
|
||||
Jan => 0, Feb => 1, Mar => 2, Apr => 3,
|
||||
May => 4, Jun => 5, Jul => 6, Aug => 7,
|
||||
Sep => 8, Oct => 9, Nov => 10, Dec => 11,
|
||||
}->{$month};
|
||||
|
||||
|
||||
return timelocal($second, $minute, $hour, $day, $month_num, $year); # Adjust the year as necessary
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub test_for_private_ip {
|
||||
use NetAddr::IP;
|
||||
$_ = shift;
|
||||
# Remove leading whitespace
|
||||
s/^\s+//;
|
||||
return unless /(\d+\.\d+\.\d+\.\d+)/;
|
||||
my $ip = NetAddr::IP->new($1);
|
||||
return unless $ip;
|
||||
|
1914
root/usr/bin/mailstats.py
Normal file
1914
root/usr/bin/mailstats.py
Normal file
File diff suppressed because it is too large
Load Diff
22
root/usr/bin/runmailstats.sh
Normal file → Executable file
22
root/usr/bin/runmailstats.sh
Normal file → Executable file
@@ -1,3 +1,21 @@
|
||||
#!/bin/bash
|
||||
exec 1> >(logger -t $(basename $0)) 2>&1
|
||||
perl /usr/bin/mailstats.pl /var/log/qpsmtpd/\@* /var/log/qpsmtpd/current /var/log/sqpsmtpd/\@* /var/log/sqpsmtpd/current
|
||||
|
||||
# Validate date format (YYYY-MM-DD)
|
||||
validate_date() {
|
||||
local date_regex="^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$"
|
||||
if [[ ! $1 =~ $date_regex ]]; then
|
||||
echo "Error: Invalid date format. Use YYYY-MM-DD" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Set date (default: yesterday)
|
||||
if [ -n "$1" ]; then
|
||||
run_date="$1"
|
||||
validate_date "$run_date"
|
||||
else
|
||||
run_date=$(date -d "yesterday" +%F)
|
||||
fi
|
||||
|
||||
# Run mailstats with validated date
|
||||
python3 /usr/bin/mailstats.py -d "$run_date"
|
17
root/usr/bin/runmailstatsSME10.sh
Executable file
17
root/usr/bin/runmailstatsSME10.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
#exec 1> >(logger -t $(basename $0)) 2>&1
|
||||
perl /usr/bin/mailstats.pl /var/log/qpsmtpd/\@* /var/log/qpsmtpd/current /var/log/sqpsmtpd/\@* /var/log/sqpsmtpd/current
|
||||
# and run new python one - start by copying and decoding log files
|
||||
yesterday_date=$(date -d "yesterday" +'%mm %d')
|
||||
#cd /var/log/qpsmtpd
|
||||
#cat \@* current >/opt/mailstats/logs/current1 2>/dev/null
|
||||
#cd /var/log/sqpsmtpd
|
||||
#cat \@* current >/opt/mailstats/logs/current2 2>/dev/null
|
||||
cd /opt/mailstats/logs
|
||||
#cat current1 current2 2>/dev/null | /usr/local/bin/tai64nlocal | grep "$yesterday_date" > current1.log
|
||||
python3 /usr/bin/mailstats-convert-log-sme10-to-sme11.py
|
||||
yesterday_date=$(date -d "yesterday" +'%b %d')
|
||||
cat output_log.txt | grep "$yesterday_date" | sort >current.log
|
||||
ls -l
|
||||
python3 /usr/bin/mailstats.py
|
||||
echo "Done"
|
@@ -0,0 +1,321 @@
|
||||
#
|
||||
# Generated by SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-04 12:46:00
|
||||
#
|
||||
#
|
||||
# Routines to be edited by the developer to provide content and validation for parameters
|
||||
# and provison of the control data for table(s)
|
||||
#
|
||||
use esmith::util;
|
||||
use esmith::util::network;
|
||||
use esmith::ConfigDB;
|
||||
use esmith::HostsDB;
|
||||
use esmith::AccountsDB;
|
||||
use esmith::NetworksDB;
|
||||
use esmith::DomainsDB;
|
||||
|
||||
use POSIX 'strftime';
|
||||
|
||||
use constant FALSE => 0;
|
||||
use constant TRUE => 1;
|
||||
|
||||
|
||||
#The most common ones
|
||||
#my $cdb
|
||||
#my $adb
|
||||
#my $ndb
|
||||
#my $hdb
|
||||
#my $ddb
|
||||
|
||||
# Validation routines - parameters for each panel
|
||||
|
||||
sub validate_TABLE {
|
||||
my $c = shift;
|
||||
my $prefix_data = shift; #Data hash as parameter
|
||||
# Validation for each field
|
||||
my $ret = "";
|
||||
|
||||
if (! TRUE) #validate $c->param('StatsDate')
|
||||
{$ret .= 'Validation for StatsDate failed';}
|
||||
if ($ret eq "") {$ret = 'ok';}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub validate_CONFIG {
|
||||
my $c = shift;
|
||||
my $prefix_data = shift; #Data hash as parameter
|
||||
# Validation for each field
|
||||
my $ret = "";
|
||||
|
||||
if (! TRUE) #validate $c->param('TextorHTML')
|
||||
{$ret .= 'Validation for TextorHTML failed';}
|
||||
if (! TRUE) #validate $c->param('Email')
|
||||
{$ret .= 'Validation for Email failed';}
|
||||
if (! TRUE) #validate $c->param('EmailHost')
|
||||
{$ret .= 'Validation for EmailHost failed';}
|
||||
if (! TRUE) #validate $c->param('EmailUser')
|
||||
{$ret .= 'Validation for EmailUser failed';}
|
||||
if (! TRUE) #validate $c->param('DBSave')
|
||||
{$ret .= 'Validation for DBSave failed';}
|
||||
if (! TRUE) #validate $c->param('DBHost')
|
||||
{$ret .= 'Validation for DBHost failed';}
|
||||
if (! TRUE) #validate $c->param('DBUser')
|
||||
{$ret .= 'Validation for DBUser failed';}
|
||||
if (! TRUE) #validate $c->param('CountrySelect')
|
||||
{$ret .= 'Validation for CountrySelect failed';}
|
||||
if (! TRUE) #validate $c->param('AccumCountryCodes')
|
||||
{$ret .= 'Validation for AccumCountryCodes failed';}
|
||||
if (! TRUE) #validate $c->param('EnableRHSBL')
|
||||
{$ret .= 'Validation for EnableRHSBL failed';}
|
||||
if (! TRUE) #validate $c->param('EnableRHSBL')
|
||||
{$ret .= 'Validation for EnableRHSBL failed';}
|
||||
if (! TRUE) #validate $c->param('RBLLIST')
|
||||
{$ret .= 'Validation for RBLLIST failed';}
|
||||
if (! TRUE) #validate $c->param('SBLLIST')
|
||||
{$ret .= 'Validation for SBLLIST failed';}
|
||||
if (! TRUE) #validate $c->param('UBLLIST')
|
||||
{$ret .= 'Validation for UBLLIST failed';}
|
||||
if ($ret eq "") {$ret = 'ok';}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
# Get singleton data for each panel
|
||||
|
||||
sub get_data_for_panel_TABLE {
|
||||
# Return a hash with the fields required which will be loaded into the shared data
|
||||
my $c = shift;
|
||||
my %ret = (
|
||||
'Data1'=>'Data for TABLE', #Example
|
||||
# fields from Inputs in TABLE $fields['TABLE']
|
||||
'StatsDate'=>'StatsDate contents',
|
||||
|
||||
);
|
||||
return %ret;
|
||||
}
|
||||
|
||||
sub get_data_for_panel_CONFIG {
|
||||
# Return a hash with the fields required which will be loaded into the shared data
|
||||
my $c = shift;
|
||||
my $cdb = esmith::ConfigDB->open() || die("Couldn't open config db");
|
||||
my $key = 'mailstats';
|
||||
my %ret = (
|
||||
'Data1'=>'Data for CONFIG', #Example
|
||||
# fields from Inputs in CONFIG $fields['CONFIG']
|
||||
'TextorHTML'=>$cdb->get_prop($key,'TextorHTML') || 'HTML',
|
||||
'Email'=>$cdb->get_prop($key,'Email') || 'admin',
|
||||
'EmailHost'=>$cdb->get_prop($key,'EmailHost') || 'localhost',
|
||||
'EmailPort'=>$cdb->get_prop($key,'EmailPORT') || '25',
|
||||
'EmailUser'=>$cdb->get_prop($key,'EmailUser') || 'admin',
|
||||
'DBSave'=>$cdb->get_prop($key,'SaveDataToMySQL') || 'yes',
|
||||
'DBHost'=>$cdb->get_prop($key,'DBHost') || 'localhost',
|
||||
'DBUser'=>$cdb->get_prop($key,'DBUser') || 'admin',
|
||||
'DBPort'=>$cdb->get_prop($key,'DBPort') || '3306',
|
||||
#'CountrySelect'=>'CountrySelect ',
|
||||
'AccumCountryCodes'=>$cdb->get_prop('qpsmtpd','BadCountries'),
|
||||
'EnableRHSBL'=>$cdb->get_prop('qpsmtpd','RHSBL'),
|
||||
'EnableDNSBL'=>$cdb->get_prop('qpsmtpd','DNSBL'),
|
||||
'EnableURIBL'=>$cdb->get_prop('qpsmtpd','URIBL'),
|
||||
'RBLList'=>$cdb->get_prop('qpsmtpd','RBLList'),
|
||||
'SBLList'=>$cdb->get_prop('qpsmtpd','SBLList'),
|
||||
'UBLList'=>$cdb->get_prop('qpsmtpd','UBLList'),
|
||||
'TagLevel'=>$cdb->get_prop('spamassassin','TagLevel') || '5',
|
||||
'RejectLevel'=>$cdb->get_prop('spamassassin','RejectLevel') || '12'
|
||||
|
||||
|
||||
);
|
||||
return %ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Get control data for table(s)
|
||||
|
||||
|
||||
|
||||
# Return hash with values from row in which link clicked on table
|
||||
|
||||
sub get_selected_TABLE {
|
||||
my $c = shift;
|
||||
my $selected = shift; #Parameter is name of selected row.
|
||||
my $is_new_record = shift; #Indicates new record required (defaults)
|
||||
my %ret = {};
|
||||
return %ret;
|
||||
}
|
||||
|
||||
sub get_selected_CONFIG {
|
||||
my $c = shift;
|
||||
my $selected = shift; #Parameter is name of selected row.
|
||||
my $is_new_record = shift; #Indicates new record required (defaults)
|
||||
my %ret = {};
|
||||
return %ret;
|
||||
}
|
||||
|
||||
|
||||
#after sucessful modify or create or whatever and submit then perfom (if the params validate)
|
||||
|
||||
sub perform_TABLE {
|
||||
my $c = shift;
|
||||
my $prefix_data = shift; #Data hash as parameter
|
||||
my $ret = "";
|
||||
my $db = $cdb; #maybe one of the others
|
||||
my $dbkey = 'ChangeThis';
|
||||
# To make it write to DB as comment, delete this (regex) string in each if statement "TRUE\) \#copy or perform with value: .* e.g."
|
||||
|
||||
if (! TRUE) #copy or perform with value: StatsDate e.g. $db->set_prop($dbkey,'StatsDate',$c->param('StatsDate'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for StatsDate';}
|
||||
if ($ret eq "") {$ret = 'ok';}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub perform_CONFIG {
|
||||
my $c = shift;
|
||||
my $prefix_data = shift; #Data hash as parameter
|
||||
my $ret = "";
|
||||
my $cdb = esmith::ConfigDB->open() || die("Couldn't open config db");
|
||||
my $db = $cdb; #maybe one of the others
|
||||
my $dbkey = 'mailstats';
|
||||
# To make it write to DB as comment, delete this (regex) string in each if statement "TRUE\) \#copy or perform with value: .* e.g."
|
||||
|
||||
if (! $db->set_prop($dbkey,'TextorHTML',$c->param('TextorHTML'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for TextorHTML';}
|
||||
if (! $db->set_prop($dbkey,'Email',$c->param('Email'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for Email';}
|
||||
if (! $db->set_prop($dbkey,'EmailHost',$c->param('EmailHost'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for EmailHost';}
|
||||
if (! $db->set_prop($dbkey,'EmailPort',$c->param('EmailPort'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for EmailPort';}
|
||||
if (! $db->set_prop($dbkey,'EmailUser',$c->param('EmailUser'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for EmailUser';}
|
||||
if (! $db->set_prop($dbkey,'SaveDataToMySQL',$c->param('DBSave'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for DBSave';}
|
||||
if (! $db->set_prop($dbkey,'DBHost',$c->param('DBHost'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for DBHost';}
|
||||
if (! $db->set_prop($dbkey,'DBUser',$c->param('DBUser'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for DBUser';}
|
||||
if (! $db->set_prop($dbkey,'DBPort',$c->param('DBPort'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for DBPort';}
|
||||
#if (! $db->set_prop($dbkey,'CountrySelect',$c->param('CountrySelect'),type=>'service'))
|
||||
# {$ret .= 'Perform/save failed for CountrySelect';}
|
||||
if (! $db->set_prop('qpsmtpd','BadCountries',$c->param('AccumCountryCodes'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for AccumCountryCodes';}
|
||||
if (! $db->set_prop('qpsmtpd','RHSBL',$c->param('EnableRHSBL'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for EnableRHSBL';}
|
||||
if (! $db->set_prop('qpsmtpd','DNSBL',$c->param('EnableDNSBL'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for EnableDNSBL';}
|
||||
if (! $db->set_prop('qpsmtpd','URIBL',$c->param('EnableURIBL'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for EnableURIBL';}
|
||||
if (! $db->set_prop('qpsmtpd','RBLList',$c->param('RBLList'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for RBLLIST';}
|
||||
if (! $db->set_prop('qpsmtpd','SBLList',$c->param('SBLList'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for SBLLIST';}
|
||||
if (! $db->set_prop('qpsmtpd','UBLList',$c->param('UBLList'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for UBLLIST';}
|
||||
if (! $db->set_prop('spamassassin','TagLevel',$c->param('TagLevel'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for TagLevel';}
|
||||
if (! $db->set_prop('spamassassin','RejectLevel',$c->param('RejectLevel'),type=>'service'))
|
||||
{$ret .= 'Perform/save failed for RejectLevel';}
|
||||
if ($ret eq "") {$ret = 'ok';}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
sub create_link{
|
||||
# WIP
|
||||
my ($c,$route, $panel, $index) = @_;
|
||||
my $link = "$route?trt=$panel&Selected=$index";
|
||||
return $link;
|
||||
}
|
||||
|
||||
sub get_StatsDate{
|
||||
return ['yesterday']
|
||||
}
|
||||
|
||||
sub get_CountryCodes {
|
||||
return [
|
||||
['Afghanistan' => 'AF'], # Frequent political/malware spam
|
||||
['Argentina' => 'AR'], # 52% spam call rate (highest globally)
|
||||
['Brazil' => 'BR'], # Top spam call origin
|
||||
['China' => 'CN'], # High spam volume
|
||||
['France' => 'FR'], # 43% spam call rate, top email spam source
|
||||
['Germany' => 'DE'], # Significant email spam volume
|
||||
['India' => 'IN'], # High spam call/text volume
|
||||
['Indonesia' => 'ID'], # 51.5% spam call rate
|
||||
['Italy' => 'IT'], # 35.1% spam call rate, email spam source
|
||||
['Malaysia' => 'MY'], # 63% scam calls
|
||||
['Mexico' => 'MX'], # Emerging spam source
|
||||
['Nigeria' => 'NG'], # "Nigerian prince" scams
|
||||
['Pakistan' => 'PK'], # Phishing campaigns
|
||||
['Peru' => 'PE'], # Emerging spam source
|
||||
['Russia' => 'RU'], # Cybercrime associations
|
||||
['Saudi Arabia' => 'SA'], # High spam volume
|
||||
['Spain' => 'ES'], # 43.9% spam call rate, email spam
|
||||
['Turkey' => 'TR'], # Significant spam activity
|
||||
['Ukraine' => 'UA'], # Spam proxy servers
|
||||
['United States' => 'US'], # High email spam volume
|
||||
['Viet Nam' => 'VN'] # Phishing/malware origins
|
||||
];
|
||||
}
|
||||
|
||||
sub get_SBL_lists {
|
||||
return [
|
||||
['sbl.spamhaus.org' => 'sbl.spamhaus.org', title => 'Spamhaus Blocklist'],
|
||||
['xbl.spamhaus.org' => 'xbl.spamhaus.org', title => 'eXploits Blocklist'],
|
||||
['pbl.spamhaus.org' => 'pbl.spamhaus.org', title => 'Policy Blocklist'],
|
||||
['auth.spamhaus.org' => 'auth.spamhaus.org', title => 'Auth Blocklist'],
|
||||
['multi.surbl.org' => 'multi.surbl.org', title => 'SURBL\'s multi-level URI checker (domains in email content)'],
|
||||
['rhsbl.sorbs.net' => 'rhsbl.sorbs.net', title => 'Right-Hand Side Blocklist (domain-based, not IP-based)']
|
||||
];
|
||||
}
|
||||
|
||||
sub get_URIBL_lists {
|
||||
return [
|
||||
['uribl.com' => 'uribl.com', title => 'Primary URIBL service'],
|
||||
['multi.uribl.com' => 'multi.uribl.com', title => 'Combined URIBL checks'],
|
||||
['black.uribl.com' => 'black.uribl.com', title => 'Aggressive blocking list'],
|
||||
['grey.uribl.com' => 'grey.uribl.com', title => 'Suspicious URI list'],
|
||||
['white.uribl.com' => 'white.uribl.com', title => 'Verified safe URI list']
|
||||
];
|
||||
}
|
||||
|
||||
sub get_RBL_lists {
|
||||
return [
|
||||
['zen.spamhaus.org' => 'zen.spamhaus.org', title => 'Combines SBL, XBL, and PBL'],
|
||||
['bl.spamcop.net' => 'bl.spamcop.net', title => 'SpamCop Blocklist (user-reported spam)'],
|
||||
['cbl.abuseat.org' => 'cbl.abuseat.org', title => 'Composite Blocking List (bot-infected hosts)'],
|
||||
['b.barracudacentral.org' => 'b.barracudacentral.org', title => 'Barracuda Reputation Blocklist'],
|
||||
['dun.dnsrbl.net' => 'dun.dnsrbl.net', title => 'DNSRBL (DNS-based blacklist)'],
|
||||
['psbl.surriel.com' => 'psbl.surriel.com', title => 'Passive Spam Block List (passive spam traps)'],
|
||||
['backscatterer.org' => 'backscatterer.org', title => 'Backscatter/Out-of-Bounce spam sources'],
|
||||
['dronebl.org' => 'dronebl.org', title => 'Drones/Proxy/DDoS sources'],
|
||||
['dnsbl-1.uceprotect.net' => 'dnsbl-1.uceprotect.net', title => 'UCEPROTECT Level 1 (entry-level blocking)'],
|
||||
['dnsbl-2.uceprotect.net' => 'dnsbl-2.uceprotect.net', title => 'UCEPROTECT Level 2 (more aggressive)']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
sub get_mailstat_dates {
|
||||
my ($directory) = '/opt/mailstats/html';
|
||||
my @date_pairs;
|
||||
|
||||
# Find all matching files in directory
|
||||
opendir(my $dh, $directory) or die "Can't open directory: $!";
|
||||
|
||||
while (my $file = readdir($dh)) {
|
||||
next unless $file =~ /mailstats_for_(\d{4}-\d{2}-\d{2})\.html$/;
|
||||
my $date = $1;
|
||||
|
||||
if ($date =~ /^(\d{4})-(\d{2})-(\d{2})$/) {
|
||||
my $formatted_date = strftime("%B %-d %Y", 0, 0, 0, $3, $2-1, $1-1900);
|
||||
push @date_pairs, [$formatted_date, $date];
|
||||
}
|
||||
}
|
||||
|
||||
closedir($dh);
|
||||
|
||||
# Sort dates chronologically
|
||||
@date_pairs = sort { $a->[1] cmp $b->[1] } @date_pairs;
|
||||
|
||||
return \@date_pairs;
|
||||
}
|
||||
|
||||
1;
|
315
root/usr/share/smanager/lib/SrvMngr/Controller/Mailstats.pm
Normal file
315
root/usr/share/smanager/lib/SrvMngr/Controller/Mailstats.pm
Normal file
@@ -0,0 +1,315 @@
|
||||
package SrvMngr::Controller::Mailstats;
|
||||
#
|
||||
# Generated by SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-05 11:59:08
|
||||
#
|
||||
#----------------------------------------------------------------------
|
||||
# heading : Investigation
|
||||
# description : Mailstats
|
||||
# navigation : 4000 700
|
||||
#
|
||||
# name : mailstats, method : get, url : /mailstats, ctlact : Mailstats#main
|
||||
# name : mailstatsu, method : post, url : /mailstatsu, ctlact : Mailstats#do_update
|
||||
# name : mailstatsd, method : get, url : /mailstatsd, ctlact : Mailstats#do_display
|
||||
#
|
||||
# routes : end
|
||||
#
|
||||
# Documentation: https://wiki.contribs.org/Mailstats
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# Scheme of things:
|
||||
#
|
||||
# TBA!!
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use constant FALSE => 0;
|
||||
use constant TRUE => 1;
|
||||
|
||||
use Locale::gettext;
|
||||
use SrvMngr::I18N;
|
||||
use SrvMngr qw(theme_list init_session);
|
||||
|
||||
use Data::Dumper;
|
||||
|
||||
use esmith::util;
|
||||
use esmith::util::network;
|
||||
use esmith::ConfigDB;
|
||||
use esmith::AccountsDB;
|
||||
use esmith::NetworksDB;
|
||||
use esmith::HostsDB;
|
||||
use esmith::DomainsDB;
|
||||
|
||||
my $cdb;
|
||||
my $adb;
|
||||
my $ndb;
|
||||
my $hdb;
|
||||
my $ddb;
|
||||
|
||||
require '/usr/share/smanager/lib/SrvMngr/Controller/Mailstats-Custom.pm'; #The code that is to be added by the developer
|
||||
|
||||
sub main {
|
||||
#
|
||||
# Initial entry - route is "/<whatever>"
|
||||
#
|
||||
#set initial panel
|
||||
#for initial panel:
|
||||
#Specifiy panel to enter
|
||||
#load up _data hash with DB fields
|
||||
#load up stash with pointer(s) to control fields hash(= get-))
|
||||
#and a pointer to the prefix_data hash
|
||||
#render initial panel
|
||||
|
||||
my $c = shift;
|
||||
$c->app->log->info( $c->log_req );
|
||||
|
||||
#The most common ones
|
||||
$cdb = esmith::ConfigDB->open() || die("Couldn't open config db");
|
||||
$adb = esmith::AccountsDB->open() || die("Couldn't open Accounts db");
|
||||
$ndb = esmith::NetworksDB->open() || die("Couldn't open Network db");
|
||||
$hdb = esmith::HostsDB->open() || die("Couldn't open Hosts db");
|
||||
$ddb = esmith::DomainsDB->open() || die("Couldn't open Domains db");
|
||||
|
||||
my %mst_data = ();
|
||||
my $title = $c->l('mst_Mailstats');
|
||||
my $modul = '';
|
||||
|
||||
$mst_data{'trt'} = 'TABLE';
|
||||
|
||||
#Load any DB entries into the <prefix>_data area so as they are preset in the form
|
||||
# which DB - this only really works if the initial panel is a PARAMS type panel and not a TABLE
|
||||
my $db = $cdb; #pickup local or global db or Default to config
|
||||
|
||||
|
||||
$c->do_display($mst_data{'trt'});
|
||||
|
||||
}
|
||||
|
||||
# Post request with params - submit from the form
|
||||
sub do_update {
|
||||
#
|
||||
# Return after submit pushed on panel (this is a post) - route is "/<whatever>u"
|
||||
# parameters in the params hash.
|
||||
#
|
||||
#load up all params into prefix_data hash:
|
||||
#By panel (series of if statements - only one executed):
|
||||
#call validate-PANEL() - return ret = ok or error message
|
||||
|
||||
#if validation not ok:
|
||||
#render back to current panel with error message in stash
|
||||
#otherwise:
|
||||
#By panel (series of if statements - only one executed):
|
||||
#do whatever is required: call perform-PANEL() - return "ok" or Error Message
|
||||
#call signal-event for any global actions specified (check it exists - error and continue?)
|
||||
#if action smeserver-<whatever>-update exists
|
||||
#signal_event smeserver-<whatever>-update
|
||||
#call signal-event for any specific actions for thids panel (check it exists first - error and continue)
|
||||
#set success in stash
|
||||
#if no "nextpanel" entry:
|
||||
#set firstpanel
|
||||
#else
|
||||
#set nextpanel
|
||||
#call render
|
||||
|
||||
my $c = shift;
|
||||
$c->app->log->info($c->log_req);
|
||||
my $modul = '';
|
||||
|
||||
#The most common ones - you might want to comment out any not used.
|
||||
$cdb = esmith::ConfigDB->open() || die("Couldn't open config db");
|
||||
$adb = esmith::AccountsDB->open() || die("Couldn't open Accounts db");
|
||||
$ndb = esmith::NetworksDB->open() || die("Couldn't open Network db");
|
||||
$hdb = esmith::HostsDB->open() || die("Couldn't open Hosts db");
|
||||
$ddb = esmith::DomainsDB->open() || die("Couldn't open Domains db");
|
||||
|
||||
my %mst_data = ();
|
||||
my $title = $c->l('mst_Mailstats');
|
||||
|
||||
# Accessing all POST/GET parameters
|
||||
my $params = $c->req->params->to_hash;
|
||||
|
||||
# Get number of POST parameters
|
||||
#my $num_params = keys scaler %$params;
|
||||
|
||||
#Params are available in the hash "params" - copy to the prefix_data hash
|
||||
#while (my ($key, $value) = each %{$c->req->params->to_hash}) {
|
||||
# $mst_data{$key} = $value;
|
||||
#}
|
||||
|
||||
# the value of trt will tell you which panel has returned
|
||||
my $trt = $c->param('trt') || 'TABLE'; #hidden control on every form.
|
||||
my $ret = 'ok';
|
||||
|
||||
#Validate the parameters in a custom sub one for each panel (although only one of these will be executed)
|
||||
my $thispanel;
|
||||
|
||||
if ($trt eq 'TABLE'){
|
||||
#Validate form parameters for panel TABLE
|
||||
$ret = $c->validate_TABLE(\%mst_data);
|
||||
$thispanel = 'TABLE';
|
||||
}
|
||||
|
||||
if ($trt eq 'CONFIG'){
|
||||
#Validate form parameters for panel CONFIG
|
||||
$ret = $c->validate_CONFIG(\%mst_data);
|
||||
$thispanel = 'CONFIG';
|
||||
}
|
||||
|
||||
if ($ret ne "ok"){
|
||||
$c->do_display($thispanel);
|
||||
} else {
|
||||
#Do whatever is needed, including writing values to the DB
|
||||
|
||||
|
||||
if ($trt eq 'TABLE'){
|
||||
#do whatever is required ...
|
||||
$ret = $c->perform_TABLE(\%mst_data);
|
||||
if ($ret ne "ok") {
|
||||
# return to the panel with error message
|
||||
$c->stash(error => $c->l($ret));
|
||||
$c->stash(
|
||||
title => $title,
|
||||
modul => $modul,
|
||||
mst_data => \%mst_data
|
||||
);
|
||||
$c->render(template => "mailstats");
|
||||
} else {
|
||||
$c->stash( success => $c->l('mst_TABLE_panel_action_was_successful')); #A bit bland - edit it in the lex file
|
||||
}
|
||||
}
|
||||
|
||||
if ($trt eq 'CONFIG'){
|
||||
#do whatever is required ...
|
||||
$ret = $c->perform_CONFIG(\%mst_data);
|
||||
if ($ret ne "ok") {
|
||||
# return to the panel with error message
|
||||
$c->stash(error => $c->l($ret));
|
||||
$c->stash(
|
||||
title => $title,
|
||||
modul => $modul,
|
||||
mst_data => \%mst_data
|
||||
);
|
||||
$c->render(template => "mailstats");
|
||||
} else {
|
||||
$c->stash( success => $c->l('mst_CONFIG_panel_action_was_successful')); #A bit bland - edit it in the lex file
|
||||
}
|
||||
}
|
||||
|
||||
# and call any signal-events needed
|
||||
#TBD
|
||||
# Setup shared data and call panel
|
||||
if ('none' eq 'none') {
|
||||
$mst_data{'trt'} = 'TABLE';
|
||||
} else {
|
||||
$mst_data{'trt'} = 'none';
|
||||
}
|
||||
$c->do_display($mst_data{'trt'});
|
||||
}
|
||||
}
|
||||
|
||||
sub do_display {
|
||||
#
|
||||
# Return after link clicked in table (this is a get) - route is "/<whatever>d"
|
||||
# Expects ?trt=PANEL&selected="TableRowName" plus any other required
|
||||
#
|
||||
# OR it maybe a post from the main panel to add a new record
|
||||
#
|
||||
#load up all supplied params into prefix_data hash
|
||||
#call get-selected-PANEL() - returns hash of all relevent parameters
|
||||
#load up returned hash into prefix_data
|
||||
#render - to called panel
|
||||
|
||||
my ($c,$trt) = @_;
|
||||
$c->app->log->info($c->log_req);
|
||||
|
||||
#The most common ones - you might want to comment out any not used.
|
||||
$cdb = esmith::ConfigDB->open() || die("Couldn't open config db");
|
||||
$adb = esmith::AccountsDB->open() || die("Couldn't open Accounts db");
|
||||
$ndb = esmith::NetworksDB->open() || die("Couldn't open Network db");
|
||||
$hdb = esmith::HostsDB->open() || die("Couldn't open Hosts db");
|
||||
$ddb = esmith::DomainsDB->open() || die("Couldn't open Domains db");
|
||||
|
||||
my %mst_data = ();
|
||||
my $title = $c->l('mst_Mailstats');
|
||||
my $modul = "";
|
||||
|
||||
# Accessing all parameters
|
||||
my %params = $c->req->params->to_hash;
|
||||
|
||||
# Get number of parameters
|
||||
my $num_params = keys %params;
|
||||
|
||||
#Tag as Post or Get (ie. create new entry or edit existing one
|
||||
my $is_new_record = ($c->req->method() eq 'POST');
|
||||
|
||||
#Params are available in the hash "params" - copy to the prefix_data hash
|
||||
#while (my ($key, $value) = each %{$c->req->params->to_hash}) {
|
||||
# $mst_data{$key} = $value;
|
||||
#}
|
||||
|
||||
# the value of trt will tell you which panel has returned
|
||||
if (! $trt){
|
||||
$trt = $c->param('trt') || 'TABLE'; #Indicates where to go now
|
||||
}
|
||||
|
||||
# Now add in the params from the selected row from the table
|
||||
|
||||
my %selectedrow;
|
||||
|
||||
if ($trt eq 'TABLE'){
|
||||
#Validate Get selected row (if applicable) TABLE
|
||||
%selectedrow = $c->get_selected_TABLE($mst_data{'Selected'},$is_new_record);
|
||||
}
|
||||
|
||||
if ($trt eq 'CONFIG'){
|
||||
#Validate Get selected row (if applicable) CONFIG
|
||||
%selectedrow = $c->get_selected_CONFIG($mst_data{'Selected'},$is_new_record);
|
||||
}
|
||||
|
||||
|
||||
#Copy in the selected row params to the prefix_data hash to pass to the panel
|
||||
while (my ($key, $value) = each %selectedrow){
|
||||
$mst_data{$key} = $value;
|
||||
}
|
||||
# Where to go now
|
||||
$mst_data{'trt'} = $trt;
|
||||
|
||||
# Set up other shared data according to the panel to go to
|
||||
|
||||
if ($trt eq 'TABLE'){
|
||||
# pickup any other contents needed and load them into hash shared with panel
|
||||
my %returned_hash;
|
||||
# subroutine returns a hash directly
|
||||
%returned_hash = $c->get_data_for_panel_TABLE();
|
||||
# Copy each key-value pair from the returned hash to the prefix data hash
|
||||
while (my ($key, $value) = each %returned_hash) {
|
||||
$mst_data{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($trt eq 'CONFIG'){
|
||||
# pickup any other contents needed and load them into hash shared with panel
|
||||
my %returned_hash;
|
||||
# subroutine returns a hash directly
|
||||
%returned_hash = $c->get_data_for_panel_CONFIG();
|
||||
# Copy each key-value pair from the returned hash to the prefix data hash
|
||||
while (my ($key, $value) = each %returned_hash) {
|
||||
$mst_data{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# and table control fields
|
||||
|
||||
|
||||
# Data for panel
|
||||
$c->stash(
|
||||
title => $title,
|
||||
modul => $modul,
|
||||
mst_data => \%mst_data
|
||||
);
|
||||
$c->render(template => "mailstats");
|
||||
}
|
||||
1;
|
@@ -0,0 +1,40 @@
|
||||
#
|
||||
# Generated by SM2Gen version: SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-05 11:59:08
|
||||
#
|
||||
'mst_Host_name_for_DB_server' => 'Host name for DB server',
|
||||
'mst_Enable_RHSBL_checking' => 'Enable RHSBL checking',
|
||||
'mst_RBL_Servers_to_use' => 'RBL Servers to use',
|
||||
'mst_Port_number_for_email_server' => 'Port number for email server',
|
||||
'mst_User_name_for_DB_sending' => 'User name for DB sending',
|
||||
'mst_Table_of_email_status' => '',
|
||||
'mst_Score_to_fully_reject_emmail' => 'Score to fully reject email',
|
||||
'mst_User_Password_for_email_sending' => 'User Password for email sending',
|
||||
'mst_UBL_Servers_to_use' => 'UBL Servers to use',
|
||||
'mst_Would_you_like_to_save' => 'Would you like to save data in the DB?',
|
||||
'mst_Specify_if_you_would_like' => 'Specify if you would like to receive email in text or HTML form',
|
||||
'mst_Port_number_for_DB_server' => 'Port number for DB server',
|
||||
'mst_Mailstats' => 'Daily Email Status Table',
|
||||
'mst_Email_filtering_/_exclusion' => 'Email filtering / exclusion',
|
||||
'mst_Score_for_tagging_as_spam,' => 'Score for tagging as spam but queued',
|
||||
'mst_Accumulated_country_codes_(editable)' => 'Accumulated country codes editable',
|
||||
'mst_Date_for_Stats_display' => 'Date for Stats display',
|
||||
'mst_Enable_URIBL_checking' => 'Enable URIBL checking',
|
||||
'mst_CONFIG_panel_action_was_successful' => 'CONFIG panel action was successful',
|
||||
'mst_Email_details' => 'Email details',
|
||||
'mst_User_Password_for_DB_sending' => 'User Password for DB sending',
|
||||
'mst_Email_for_stats' => 'Email for stats',
|
||||
'mst_Select_the_countries_you_would' => 'Select the countries you would like to reject',
|
||||
'mst_Spamassassin_scores_-_tag_and' => 'Spamassassin scores - tag and reject levels',
|
||||
'mst_SBL_Servers_to_use' => 'SBL Servers to use',
|
||||
'mst_Save' => 'Save',
|
||||
'mst_Descriptive_paragraph' => 'The Mailstats contrib analyzes your qpsmtpd log files and creates a webpage and sends a periodic email to the address you specify summarizing your server\'s email activity.
|
||||
<br>You can use the <I>Configure Mailstats</I> button to set up mailstats and some associated qpsmtpd and spamassassin properties. ',
|
||||
'mst_APPLY' => 'Apply',
|
||||
'mst_Enable_DNSBL_checking' => 'Enable DNSBL checking',
|
||||
'mst_Host_name_for_email_server' => 'Host name for email server',
|
||||
'mst_Details_for_connection_to_database' => 'Details for connection to database for saving email status',
|
||||
'mst_TABLE_panel_action_was_successful' => 'TABLE panel action was successful',
|
||||
'mst_Configure_Mailstats' => 'Configure Mailstats',
|
||||
'mst_User_name_for_email_sending' => 'User name for email sending',
|
||||
'mst_Full_Window' => 'Full Window',
|
||||
'mst_Use_Cursor_keys' => '(Use the cursor keys to select the date)'
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Generated by: SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-05 11:59:08
|
||||
*/
|
||||
object {
|
||||
border: 1px,solid, darkgrey;
|
||||
}
|
||||
|
||||
.inline-buttons {
|
||||
display: flex; /* Use flexbox to arrange items horizontally */
|
||||
gap: 10px; /* Optional: Add space between buttons */
|
||||
}
|
||||
|
||||
.inline-buttons .link {
|
||||
/* Additional styling can be added here if needed */
|
||||
}
|
||||
|
||||
|
||||
.inline-buttons .link {
|
||||
display: inline-block; /* Keep links as inline-block for button shape */
|
||||
padding: 7px 14px; /* Adjusted padding to approximate 70% of the original */
|
||||
margin: 0; /* Remove margin */
|
||||
background-color: #efefef; /* Light gray background color */
|
||||
color: black; /* Text color */
|
||||
text-decoration: none; /* Remove underline */
|
||||
border: 2px solid #bbb; /* Thin, light gray border */
|
||||
border-radius: 3px; /* Slightly rounded corners */
|
||||
font-size: 11.2px; /* Adjusted font size to approximate 70% of the original */
|
||||
text-align: center; /* Center the text */
|
||||
cursor: pointer; /* Pointer cursor on hover */ }
|
||||
|
||||
/* Hover and active effects for better interaction */
|
||||
.inline-buttons .link:hover {
|
||||
background-color: #d9d9d9; /* Darker shade on hover */
|
||||
}
|
||||
|
||||
.inline-buttons .link:active {
|
||||
background-color: #c0c0c0; /* Even darker shade on click */
|
||||
}
|
||||
|
||||
.Mailstats-panel {}
|
||||
.name {}
|
||||
.rout {}
|
||||
.grou1 {}
|
||||
.link1 {}
|
||||
.endg1 {}
|
||||
.subh1 {}
|
||||
.para1 {}
|
||||
.sele1 {}
|
||||
.obje2 {}
|
||||
.name {}
|
||||
.rout {}
|
||||
.sele4 {}
|
||||
.grou1 {}
|
||||
.subh {}
|
||||
.emai3 {}
|
||||
.text5 {}
|
||||
.numb6 {}
|
||||
.text7 {}
|
||||
.pass8 {}
|
||||
.endg1 {}
|
||||
.sele9 {}
|
||||
.grou2 {}
|
||||
.subh2 {}
|
||||
.text10 {}
|
||||
.numb11 {}
|
||||
.text12 {}
|
||||
.pass13 {}
|
||||
.endg2 {}
|
||||
.subh3 {}
|
||||
.mult14 {}
|
||||
.text15 {}
|
||||
.sele16 {}
|
||||
.sele17 {}
|
||||
.sele24 {}
|
||||
.text18 {}
|
||||
.text19 {}
|
||||
.text20 {}
|
||||
.subh4 {}
|
||||
.numb21 {}
|
||||
.numb22 {}
|
||||
.subm23 {}
|
224
root/usr/share/smanager/themes/default/public/js/mailstats.js
Normal file
224
root/usr/share/smanager/themes/default/public/js/mailstats.js
Normal file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
//Generated by: SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-05 11:17:38
|
||||
//
|
||||
$(document).ready(function() {
|
||||
});
|
||||
|
||||
// Initialize multiselect with existing text values
|
||||
// Initialize multiselect based on text input values
|
||||
function initMultiselect(multiselectId, textInputId) {
|
||||
const multiselect = document.getElementById(multiselectId);
|
||||
const textInput = document.getElementById(textInputId);
|
||||
|
||||
if (!multiselect || !textInput) {
|
||||
console.error('Could not find elements with provided IDs');
|
||||
return;
|
||||
}
|
||||
|
||||
textInput.value.split(',').forEach(value => {
|
||||
const option = Array.from(multiselect.options)
|
||||
.find(opt => opt.value === value.trim());
|
||||
if (option) option.selected = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Update text input when multiselect changes
|
||||
function updateTextInput(multiselectId, textInputId) {
|
||||
const multiselect = document.getElementById(multiselectId);
|
||||
const textInput = document.getElementById(textInputId);
|
||||
|
||||
if (!multiselect || !textInput) {
|
||||
console.error('Could not find elements with provided IDs');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedValues = Array.from(multiselect.selectedOptions)
|
||||
.map(option => option.value);
|
||||
|
||||
const currentValues = textInput.value.split(',')
|
||||
.map(code => code.trim())
|
||||
.filter(code => code !== '');
|
||||
|
||||
const updatedValuesSet = new Set(currentValues);
|
||||
|
||||
selectedValues.forEach(value => updatedValuesSet.add(value));
|
||||
|
||||
currentValues.forEach(code => {
|
||||
if (!selectedValues.includes(code) &&
|
||||
multiselect.querySelector(`option[value="${code}"]`)) {
|
||||
updatedValuesSet.delete(code);
|
||||
}
|
||||
});
|
||||
|
||||
textInput.value = Array.from(updatedValuesSet).join(',');
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initMultiselect('CountrySelect_select','AccumCountryCodes_text');
|
||||
initMultiselect('RBLList_select','RBLList_text');
|
||||
initMultiselect('SBLList_select','SBLList_text');
|
||||
initMultiselect('URIBLList_select','URIBLList_text');
|
||||
|
||||
|
||||
// Add change listener to multiselect
|
||||
document.getElementById('CountrySelect_select')
|
||||
.addEventListener('click', () => updateTextInput('CountrySelect_select','AccumCountryCodes_text'));
|
||||
document.getElementById('RBLList_select')
|
||||
.addEventListener('click', () => updateTextInput('RBLList_select','RBLList_text'));
|
||||
document.getElementById('SBLList_select')
|
||||
.addEventListener('click', () => updateTextInput('SBLList_select','SBLList_text'));
|
||||
document.getElementById('URIBLList_select')
|
||||
.addEventListener('click', () => updateTextInput('URIBLList_select','URIBLList_text'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Date Select Manager
|
||||
*
|
||||
* This script manages an HTML select element with dates and updates an HTML object
|
||||
* based on the selected date. It sets the initial value to yesterday's date (or the latest date available),
|
||||
* updates the object's data parameter, and refreshes the display.
|
||||
*/
|
||||
|
||||
// Wait for DOM to be fully loaded before executing
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all date selects and their corresponding objects
|
||||
initializeSelects();
|
||||
});
|
||||
|
||||
function initializeSelects() {
|
||||
// Find all select elements with IDs starting with "StatsDate_select"
|
||||
const selects = document.querySelectorAll('select[id^="StatsDate_select"]');
|
||||
|
||||
selects.forEach(function(dateSelect) {
|
||||
const selectId = dateSelect.id;
|
||||
// Find the corresponding object element
|
||||
// If the select has a numeric suffix, look for object with same suffix
|
||||
const objectId = selectId === 'StatsDate_select' ?
|
||||
'mailstats_object' :
|
||||
'mailstats_object' + selectId.replace('StatsDate_select', '');
|
||||
|
||||
const mailstatsObject = document.getElementById(objectId);
|
||||
const mailstatsfull = document.getElementById('mailstats-full-window');
|
||||
|
||||
// Check if elements exist before proceeding
|
||||
if (!dateSelect) {
|
||||
console.error(`Select element with ID "${selectId}" not found.`);
|
||||
return; // Skip this pair if select doesn't exist
|
||||
}
|
||||
|
||||
if (!mailstatsObject) {
|
||||
console.error(`Object element with ID "${objectId}" not found.`);
|
||||
// Continue anyway, as we can still set the select to the right date
|
||||
}
|
||||
|
||||
// Function to update the object's data parameter and refresh it
|
||||
function updateMailstatsObject(date) {
|
||||
if (date && mailstatsObject) {
|
||||
try {
|
||||
// Create the full URL including the selected date
|
||||
const currentDomain = window.location.origin;
|
||||
const fullUrl = `${currentDomain}/mailstats/mailstats_for_${date}.html`;
|
||||
|
||||
// Update the data attribute with the full URL
|
||||
mailstatsObject.setAttribute('data', fullUrl);
|
||||
mailstatsfull.setAttribute('href', fullUrl);
|
||||
|
||||
// Force a refresh of the object
|
||||
// [refresh code here]
|
||||
|
||||
console.log(`Updated object to show data from: ${fullUrl}`);
|
||||
} catch (error) {
|
||||
console.error(`Error updating object:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the initial value of the select
|
||||
const yesterdayDate = getYesterdayDate();
|
||||
let dateFound = false;
|
||||
|
||||
// Find and select the option with yesterday's date
|
||||
for (let i = 0; i < dateSelect.options.length; i++) {
|
||||
if (dateSelect.options[i].value === yesterdayDate) {
|
||||
dateSelect.selectedIndex = i;
|
||||
dateFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If yesterday's date is not found, select the latest date (last option)
|
||||
if (!dateFound && dateSelect.options.length > 0) {
|
||||
dateSelect.selectedIndex = dateSelect.options.length - 1;
|
||||
console.log(`Yesterday's date (${yesterdayDate}) not found in options for ${selectId}. Selected the latest date instead: ${dateSelect.value}`);
|
||||
}
|
||||
|
||||
// Update the object with the initial date
|
||||
if (dateSelect.value) {
|
||||
updateMailstatsObject(dateSelect.value);
|
||||
}
|
||||
|
||||
// Add event listener for changes to the select element
|
||||
dateSelect.addEventListener('change', function() {
|
||||
const selectedDate = this.value;
|
||||
updateMailstatsObject(selectedDate);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get yesterday's date in YYYY-MM-DD format
|
||||
function getYesterdayDate() {
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
|
||||
const year = yesterday.getFullYear();
|
||||
const month = String(yesterday.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(yesterday.getDate()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
//
|
||||
// disable enable RBL/SBL/URIBL list selection
|
||||
//
|
||||
// Example html formatting to use it.
|
||||
//<div data-mailstats-group>
|
||||
//<select id="EnableRHSBL_select" data-mailstats-control data-mailstats-target="RBL_group">
|
||||
//<option value="disabled">Disabled</option>
|
||||
//<option value="enabled">Enabled</option>
|
||||
//</select>
|
||||
|
||||
//<div id="RBL_group">
|
||||
//<!-- Content -->
|
||||
//</div>
|
||||
//</div>
|
||||
// or , 'data-mailstats-control' => undef, 'data-mailstats-target' => "RBL_group" for mojo html helper
|
||||
//
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize all control pairs
|
||||
document.querySelectorAll('[data-mailstats-group]').forEach(group => {
|
||||
const select = group.querySelector('select[data-mailstats-control]');
|
||||
const target = document.getElementById(select.dataset.mailstatsTarget);
|
||||
|
||||
if (!select || !target) {
|
||||
console.error('Control group misconfigured', group);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial state
|
||||
updateState(target, select.value === 'enabled');
|
||||
|
||||
// Change listener
|
||||
select.addEventListener('change', () =>
|
||||
updateState(target, select.value === 'enabled')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function updateState(target, enabled) {
|
||||
target.style.opacity = enabled ? '1' : '0.5';
|
||||
target.style.pointerEvents = enabled ? 'auto' : 'none';
|
||||
target.style.filter = enabled ? 'none' : 'grayscale(50%)';
|
||||
target.setAttribute('aria-disabled', !enabled);
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
%#
|
||||
%# Generated by SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-05 11:59:08
|
||||
%#
|
||||
% layout 'default', title => "Sme server 2 - Mailstats", share_dir => './';
|
||||
%# css specific to this panel:
|
||||
% content_for 'module' => begin
|
||||
%= stylesheet '/css/mailstats.css'
|
||||
%= javascript '/js/mailstats.js'
|
||||
<div id="module" class="module Mailstats-panel">
|
||||
|
||||
% if (config->{debug} == 1) {
|
||||
<pre>
|
||||
%= dumper $c->current_route
|
||||
%= dumper $mst_data->{trt}
|
||||
</pre>
|
||||
% }
|
||||
|
||||
<h1><%=$title%></h1>
|
||||
|
||||
% if ( stash('modul')) {
|
||||
%= $c->render_to_string(inline => stash('modul') );
|
||||
% }
|
||||
|
||||
%if ($c->stash('first')) {
|
||||
<br><p>
|
||||
%=$c->render_to_string(inline =>$c->l($c->stash('first')))
|
||||
</p>
|
||||
|
||||
%} elsif ($c->stash('success')) {
|
||||
<div class='success '>
|
||||
<p>
|
||||
%= $c->l($c->stash('success'));
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
%} elsif ($c->stash('error')) {
|
||||
<div class='sme-error'>
|
||||
<p>
|
||||
%= $c->l($c->stash('error'));
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
%}
|
||||
|
||||
%#Routing to partials according to trt parameter.
|
||||
%#This ought to be cascading if/then/elsif, but is easier to just stack the if/then's rather like a case statement'
|
||||
|
||||
% if ($mst_data->{trt} eq "TABLE") {
|
||||
%= include 'partials/_mst_TABLE'
|
||||
%}
|
||||
|
||||
% if ($mst_data->{trt} eq "CONFIG") {
|
||||
%= include 'partials/_mst_CONFIG'
|
||||
%}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
%end
|
@@ -0,0 +1,237 @@
|
||||
%#
|
||||
%# Generated by SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-05 11:17:38
|
||||
%#
|
||||
<div id="Mailstats-CONFIG" class="partial Mailstats-CONFIG">
|
||||
% if (config->{debug} == 1) {
|
||||
<pre>
|
||||
%= dumper $mst_data
|
||||
</pre>
|
||||
% }
|
||||
% my $btn = l('mst_APPLY');
|
||||
%= form_for "mailstatsu" => (method => 'POST') => begin
|
||||
% param 'trt' => $mst_data->{trt} unless param 'trt';
|
||||
%= hidden_field 'trt' => $mst_data->{trt}
|
||||
%# Inputs etc in here.
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Specify_if_you_would_like')
|
||||
</span><span class=data>
|
||||
% my @TextorHTML_options = [['HTML' => 'HTML'], ['Text' => 'Text'], ['Both' => 'Both'], ['Neither' => 'Neither']];
|
||||
% param 'TextorHTML' => $mst_data->{TextorHTML} unless param 'TextorHTML';
|
||||
%= select_field 'TextorHTML' => @TextorHTML_options, class => 'input', id => 'TextorHTML_select'
|
||||
<br></span> </p>
|
||||
|
||||
<div class=emailwanted>
|
||||
|
||||
|
||||
<h2 class='subh'><%=l('mst_Email_details')%></h2>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Email_for_stats')
|
||||
</span><span class=data>
|
||||
% param 'Email' => $mst_data->{Email} unless param 'Email';
|
||||
%=email_field 'Email', class => 'emai3'
|
||||
</span></p>
|
||||
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Host_name_for_email_server')
|
||||
</span><span class=data>
|
||||
% param 'EmailHost' => $mst_data->{EmailHost} unless param 'EmailHost';
|
||||
%= text_field 'EmailHost', size => '50', class => 'textinput EmailHost' , pattern=>'.*' , placeholder=>'EmailHost', title =>'Pattern regex mismatch'
|
||||
<br></span></p>
|
||||
|
||||
<p><span class='label'>
|
||||
%=l('mst_Port_number_for_email_server')
|
||||
</span><span class=data>
|
||||
% param 'EmailPort' => $mst_data->{EmailPort} unless param 'EmailPort';
|
||||
%=number_field 'EmailPort', class => 'numb6'
|
||||
</span></p>
|
||||
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_User_name_for_email_sending')
|
||||
</span><span class=data>
|
||||
% param 'EmailUser' => $mst_data->{EmailUser} unless param 'EmailUser';
|
||||
%= text_field 'EmailUser', size => '50', class => 'textinput EmailUser' , pattern=>'.*' , placeholder=>'EmailUser', title =>'Pattern regex mismatch'
|
||||
<br></span></p>
|
||||
|
||||
<p><span class='label'>
|
||||
%=l('mst_User_Password_for_email_sending')
|
||||
</span><span class=data>
|
||||
% param 'EmailPassword' => $mst_data->{EmailPassword} unless param 'EmailPassword';
|
||||
%=password_field 'EmailPassword', class => 'pass8 sme-password', autocomplete => 'off'
|
||||
</span></p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Would_you_like_to_save')
|
||||
</span><span class=data>
|
||||
% my @DBSave_options = [['yes' => 'yes'], ['no' => 'no']];
|
||||
% param 'DBSave' => $mst_data->{DBSave} unless param 'DBSave';
|
||||
%= select_field 'DBSave' => @DBSave_options, class => 'input', id => 'DBSave_select'
|
||||
<br></span> </p>
|
||||
|
||||
<div class=dbwanted>
|
||||
|
||||
|
||||
<h2 class='subh2'><%=l('mst_Details_for_connection_to_database')%></h2>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Host_name_for_DB_server')
|
||||
</span><span class=data>
|
||||
% param 'DBHost' => $mst_data->{DBHost} unless param 'DBHost';
|
||||
%= text_field 'DBHost', size => '50', class => 'textinput DBHost' , pattern=>'.*' , placeholder=>'DBHost', title =>'Pattern regex mismatch'
|
||||
<br></span></p>
|
||||
|
||||
<p><span class='label'>
|
||||
%=l('mst_Port_number_for_DB_server')
|
||||
</span><span class=data>
|
||||
% param 'DBPort' => $mst_data->{DBPort} unless param 'DBPort';
|
||||
%=number_field 'DBPort', class => 'numb11'
|
||||
</span></p>
|
||||
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_User_name_for_DB_sending')
|
||||
</span><span class=data>
|
||||
% param 'DBUser' => $mst_data->{DBUser} unless param 'DBUser';
|
||||
%= text_field 'DBUser', size => '50', class => 'textinput DBUser' , pattern=>'.*' , placeholder=>'DBUser', title =>'Pattern regex mismatch'
|
||||
<br></span></p>
|
||||
|
||||
<p><span class='label'>
|
||||
%=l('mst_User_Password_for_DB_sending')
|
||||
</span><span class=data>
|
||||
% param 'DBPassword' => $mst_data->{DBPassword} unless param 'DBPassword';
|
||||
%=password_field 'DBPassword', class => 'pass13 sme-password', autocomplete => 'off'
|
||||
</span></p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h2 class='subh3'><%=l('mst_Email_filtering_/_exclusion')%></h2>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Select_the_countries_you_would')
|
||||
</span><span class=data>
|
||||
% my @CountrySelect_options = $c->get_CountryCodes();
|
||||
% param 'CountrySelect' => $mst_data->{CountrySelect} unless param 'CountrySelect';
|
||||
%= select_field 'CountrySelect' => @CountrySelect_options, class => 'input', id => 'CountrySelect_select', multiple => 'multiple'
|
||||
<br></span> </p>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Accumulated_country_codes_(editable)')
|
||||
</span><span class=data>
|
||||
% param 'AccumCountryCodes' => $mst_data->{AccumCountryCodes} unless param 'AccumCountryCodes';
|
||||
%= text_field 'AccumCountryCodes', size => '50', class => 'textinput AccumCountryCodes' , pattern=>'.*' , placeholder=>'AccumCountryCodes', title =>'Pattern regex mismatch', id => 'AccumCountryCodes_text'
|
||||
<br></span></p>
|
||||
|
||||
<div data-mailstats-group>
|
||||
<p><span class=label>
|
||||
%=l('mst_Enable_RHSBL_checking')
|
||||
</span><span class=data>
|
||||
% my @EnableRHSBL_options = [['yes' => 'enabled'], ['no' => 'disabled']];
|
||||
% param 'EnableRHSBL' => $mst_data->{EnableRHSBL} unless param 'EnableRHSBL';
|
||||
%= select_field 'EnableRHSBL' => @EnableRHSBL_options, class => 'input', id => 'EnableRHSBL_select', 'data-mailstats-control' => undef, 'data-mailstats-target' => "RBL_group"
|
||||
<br></span> </p>
|
||||
<div id=RBL_group>
|
||||
<p><span class=label>
|
||||
%=l('mst_Select_the_RBL_Lists')
|
||||
</span><span class=data>
|
||||
% my @RBLSelect_options = $c->get_RBL_lists();
|
||||
% param 'RBLSelect' => $mst_data->{RBLSelect} unless param 'RBLSelect';
|
||||
%= select_field 'RBLSelect' => @RBLSelect_options, class => 'input', id => 'RBLList_select', multiple => 'multiple'
|
||||
<br></span> </p>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_RBL_Servers_to_use')
|
||||
</span><span class=data>
|
||||
% param 'RBLList' => $mst_data->{RBLList} unless param 'RBLList';
|
||||
%= text_field 'RBLList', size => '50', class => 'textinput RBLList' , pattern=>'.*' , placeholder=>'RBLList', title =>'Pattern regex mismatch', id => 'RBLList_text'
|
||||
<br></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-mailstats-group>
|
||||
<p><span class=label>
|
||||
%=l('mst_Enable_DNSBL_checking')
|
||||
</span><span class=data>
|
||||
% my @EnableDNSBL_options = [['yes' => 'enabled'], ['no' => 'disabled']];
|
||||
% param 'EnableDNSBL' => $mst_data->{EnableDNSBL} unless param 'EnableDNSBL';
|
||||
%= select_field 'EnableDNSBL' => @EnableDNSBL_options, class => 'input', id => 'EnableDNSBL_select' , 'data-mailstats-control' => undef, 'data-mailstats-target' => "SBL_group"
|
||||
<br></span> </p>
|
||||
|
||||
<div id=SBL_group>
|
||||
<p><span class=label>
|
||||
%=l('mst_Select_the_SBL_Lists')
|
||||
</span><span class=data>
|
||||
% my @SBLSelect_options = $c->get_SBL_lists();
|
||||
% param 'SBLSelect' => $mst_data->{SBLSelect} unless param 'SBLSelect';
|
||||
%= select_field 'SBLSelect' => @SBLSelect_options, class => 'input', id => 'SBLList_select', multiple => 'multiple'
|
||||
<br></span> </p>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_SBL_Servers_to_use')
|
||||
</span><span class=data>
|
||||
% param 'SBLList' => $mst_data->{SBLList} unless param 'SBLList';
|
||||
%= text_field 'SBLList', size => '50', class => 'textinput SBLList' , pattern=>'.*' , placeholder=>'SBLList', title =>'Pattern regex mismatch', id => "SBLList_text"
|
||||
<br></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-mailstats-group>
|
||||
<p><span class=label>
|
||||
%=l('mst_Enable_URIBL_checking')
|
||||
</span><span class=data>
|
||||
% my @EnableURIBL_options = [['yes' => 'enabled'], ['no' => 'disabled']];
|
||||
% param 'EnableURIBL' => $mst_data->{EnableURIBL} unless param 'EnableURIBL';
|
||||
%= select_field 'EnableURIBL' => @EnableURIBL_options, class => 'input', id => 'EnableURIBL_select' , 'data-mailstats-control' => undef, 'data-mailstats-target' => "URIBL_group"
|
||||
<br></span> </p>
|
||||
|
||||
<div id=URIBL_group>
|
||||
<p><span class=label>
|
||||
%=l('mst_Select_the_URIBL_Lists')
|
||||
</span><span class=data>
|
||||
% my @URIBLSelect_options = $c->get_URIBL_lists();
|
||||
% param 'URIBLSelect' => $mst_data->{URIBLSelect} unless param 'URIBLSelect';
|
||||
%= select_field 'URIBLSelect' => @URIBLSelect_options, class => 'input', id => 'URIBLList_select', multiple => 'multiple'
|
||||
<br></span> </p>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_URIBL_Servers_to_use')
|
||||
</span><span class=data>
|
||||
% param 'URIBLList' => $mst_data->{URIBLList} unless param 'URIBLList';
|
||||
%= text_field 'URIBLList', size => '50', class => 'textinput URIBLList' , pattern=>'.*' , placeholder=>'URIBLList', title =>'Pattern regex mismatch', id => 'URIBLList_text'
|
||||
<br></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class='subh4'><%=l('mst_Spamassassin_scores_-_tag_and')%></h2>
|
||||
|
||||
<p><span class='label'>
|
||||
%=l('mst_Score_to_fully_reject_emmail')
|
||||
</span><span class=data>
|
||||
% param 'RejectLevel' => $mst_data->{RejectLevel} unless param 'RejectLevel';
|
||||
%=number_field 'RejectLevel', class => 'numb21'
|
||||
</span></p>
|
||||
|
||||
|
||||
<p><span class='label'>
|
||||
%=l('mst_Score_for_tagging_as_spam,')
|
||||
</span><span class=data>
|
||||
% param 'TagLevel' => $mst_data->{TagLevel} unless param 'TagLevel';
|
||||
%=number_field 'TagLevel', class => 'numb22'
|
||||
</span></p>
|
||||
|
||||
|
||||
<span class='data'>
|
||||
%= submit_button l('mst_Save'), class => 'action subm23'
|
||||
</span>
|
||||
|
||||
%# Probably finally by a submit.
|
||||
%end
|
||||
</div>
|
@@ -0,0 +1,46 @@
|
||||
%#
|
||||
%# Generated by SM2Gen version:0.9(20Jan2025) Chameleon version:4.5.4 On Python:3.12.3 at 2025-04-05 11:59:08
|
||||
%#
|
||||
<div id="Mailstats-TABLE" class="partial Mailstats-TABLE">
|
||||
|
||||
% if (config->{debug} == 1) {
|
||||
<pre>
|
||||
%= dumper $mst_data
|
||||
</pre>
|
||||
% }
|
||||
% my $btn = l('mst_APPLY');
|
||||
%= form_for "mailstatsu" => (method => 'POST') => begin
|
||||
% param 'trt' => $mst_data->{trt} unless param 'trt';
|
||||
%= hidden_field 'trt' => $mst_data->{trt}
|
||||
%# Inputs etc in here.
|
||||
<br>
|
||||
<div class=inline-buttons>
|
||||
<a href='mailstatsd?trt=CONFIG' class='link link1'>
|
||||
%= l('mst_Configure_Mailstats')
|
||||
</a>
|
||||
<a id='mailstats-full-window' href='' class='link link1'>
|
||||
%= l('mst_Full_Window')
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<h2 class='subh1'><%=l('mst_Table_of_email_status')%></h2>
|
||||
|
||||
<p class='paragraph para1'>
|
||||
%= $c->render_to_string(inline=>$c->l('mst_Descriptive_paragraph'))
|
||||
</p>
|
||||
|
||||
<p><span class=label>
|
||||
%=l('mst_Date_for_Stats_display')
|
||||
</span><span class=data>
|
||||
% my @StatsDate_options = $c->get_mailstat_dates();
|
||||
% param 'StatsDate' => $mst_data->{StatsDate} unless param 'StatsDate';
|
||||
%= select_field 'StatsDate' => @StatsDate_options, class => 'input', id => 'StatsDate_select'
|
||||
%=l('mst_Use_Cursor_keys')
|
||||
<br></span> </p>
|
||||
<object id = 'mailstats_object' data="<%='' %>" title="<%= $c->stash('title') %>" type="text/html" height='600px' width='100%' ><%= $c->stash('title') %> not found</object>
|
||||
|
||||
|
||||
%# Probably finally by a submit.
|
||||
%end
|
||||
</div>
|
@@ -1,17 +1,17 @@
|
||||
# $Id: smeserver-mailstats.spec,v 1.8 2023/02/15 09:40:09 brianr Exp $
|
||||
# $Id: smeserver-mailstats.spec,v 1.7 2021/04/02 11:11:44 brianr Exp $
|
||||
# Authority: brianread
|
||||
# Name: Brian Read
|
||||
|
||||
Summary: Daily mail statistics for SME Server
|
||||
%define name smeserver-mailstats
|
||||
Name: %{name}
|
||||
%define version 1.1
|
||||
%define release 18
|
||||
%define version 11.1
|
||||
%define release 2
|
||||
Version: %{version}
|
||||
Release: %{release}%{?dist}
|
||||
License: GPL
|
||||
Group: SME/addon
|
||||
Source: %{name}-%{version}.tar.xz
|
||||
Source: %{name}-%{version}.tgz
|
||||
|
||||
BuildRoot: /var/tmp/%{name}-%{version}-%{release}-buildroot
|
||||
BuildArchitectures: noarch
|
||||
@@ -19,18 +19,27 @@ Requires: smeserver-release => 9.0
|
||||
Requires: qpsmtpd >= 0.96
|
||||
BuildRequires: e-smith-devtools >= 1.13.1-03
|
||||
Requires: perl-Switch
|
||||
BuildRequires: python36
|
||||
Requires: python36
|
||||
Requires: html2text
|
||||
Requires: python3-chameleon
|
||||
Requires: python3-mysql
|
||||
Requires: python3-matplotlib
|
||||
AutoReqProv: no
|
||||
|
||||
%description
|
||||
A script that via cron.d e-mails mail statistics to admin on a daily basis.
|
||||
See http://www.contribs.org/bugzilla/show_bug.cgi?id=819
|
||||
|
||||
%changelog
|
||||
* Sun Jul 09 2023 cvs2git.sh aka Brian Read <brianr@koozali.org> 1.1-18.sme
|
||||
- Roll up patches and move to git repo [SME: 12338]
|
||||
* Sun Apr 06 2025 Brian Read <brianr@koozali.org> 11.2-2.sme
|
||||
- Add in SM2 panel [SME: ]
|
||||
|
||||
* Sun Jul 09 2023 BogusDateBot
|
||||
- Eliminated rpmbuild "bogus date" warnings due to inconsistent weekday,
|
||||
by assuming the date is correct and changing the weekday.
|
||||
* Mon Dec 30 2024 Brian Read <brianr@koozali.org> 11.2-1.sme
|
||||
- Update mailstats.pl to accomodate change in log format for SME11 [SME: 12841]
|
||||
|
||||
* Fri Jun 07 2024 Brian Read <brianr@koozali.org> 1.1-18.sme
|
||||
- Pull in python re-write from SME11 dev [SME: ]
|
||||
|
||||
* Wed Feb 15 2023 Brian Read <brianr@bjsystems.co.uk> 1.1-17.sme
|
||||
- Add-in-imap-authorisation-log-string [SME: 12327]
|
||||
@@ -109,8 +118,19 @@ perl createlinks
|
||||
/bin/rm -rf $RPM_BUILD_ROOT
|
||||
(cd root ; /usr/bin/find . -depth -print | /bin/cpio -dump $RPM_BUILD_ROOT)
|
||||
chmod +x $RPM_BUILD_ROOT/usr/bin/runmailstats.sh
|
||||
# Define the placeholder and generate the current date and time
|
||||
now=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Replace the placeholder in the Python program located at %{BUILDROOT}/usr/bin
|
||||
sed -i "s|__BUILD_DATE_TIME__|$now|" $RPM_BUILD_ROOT/usr/bin/mailstats.py
|
||||
|
||||
/bin/rm -f %{name}-%{version}-filelist
|
||||
/sbin/e-smith/genfilelist $RPM_BUILD_ROOT > %{name}-%{version}-filelist
|
||||
/sbin/e-smith/genfilelist $RPM_BUILD_ROOT | grep -v "\.pyc" | grep -v "\.pyo" > %{name}-%{version}-filelist
|
||||
|
||||
%pre
|
||||
/usr/bin/pip3 install -q pymysql
|
||||
/usr/bin/pip3 install -q numpy
|
||||
/usr/bin/pip3 install -q pandas
|
||||
|
||||
%clean
|
||||
/bin/rm -rf $RPM_BUILD_ROOT
|
||||
|
Reference in New Issue
Block a user