Compare commits

..

2 Commits

Author SHA1 Message Date
023691d062 Add systemd monitor for our all services like mysql, mongod and some container
Refer : https://wiki.marcoaiot.com/index.php/Systemd_Monitor  for more details about setup and maintenance
2025-09-30 20:36:55 +05:30
3c379a9bf2 version 1 2025-09-19 18:11:31 +05:30
13 changed files with 1461 additions and 710 deletions

View File

@ -3,24 +3,17 @@
"SMPTSERVER": "smtp.gmail.com",
"PORT": 587,
"SENDER_EMAIL": "marcoioitsoft@gmail.com",
"SENDER_PASSWORD": "qrtq wfuj hwpp fhqr",
"RECIPIENT_EMAILS": "ashutosh.nehete@marcoaiot.com,vikas@marcoaiot.com,amol@marcosolutions.co.in,vinod@marcofire.in,umesh@marcoaiot.com"
"SENDER_PASSWORD": "qrtq wfuj hwpp fhqr"
},
"API": {
"BASE_URL": "https://stageapi.marcoaiot.com/api",
"BASE_URL": "http://localhost:5032/api",
"USERNAME": "admin@marcoaiot.com",
"PASSWORD": "User@123",
"TENANTID": "b3466e83-7e11-464c-b93a-daf047838b26"
},
"WEB":{
"BASE_URL": "https://stageapp.marcoaiot.com"
"PASSWORD": "User@123"
},
"MONGODB":{
"MONGO_CONNECTION_STRING": "mongodb://localhost:27017",
"DATABASE_NAME": "MarcoBMS_Caches",
"COLLECTION_NAME": "ProjectReportMail"
},
"UNIQUE_IDENTIFIER":{
"PROJECT_IDS":"2618eb89-2823-11f0-9d9e-bc241163f504,08dda31f-25c6-4ad7-8252-14a64ba96fce,08dda508-e7df-4e90-86a5-4c2d30e32069,08dda7db-1f08-4db4-863a-c494201a1156,08dda8cd-0522-47d8-82ef-407276490b68,08dda8cd-8dc2-4e0a-8638-f16f63e16afe,2618f2ef-2823-11f0-9d9e-bc241163f504"
}
}

View File

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Donut Variants</title>
<style>
.donut {
--percentage: 65; /* Change this per chart */
--primary: #e63946; /* Fill color */
--track: #e9ecef; /* Background track */
--size: 120px; /* Default size */
--thickness: 20px; /* Default thickness */
width: var(--size);
height: var(--size);
border-radius: 50%;
background: conic-gradient(
var(--primary) calc(var(--percentage) * 1%),
var(--track) 0
);
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
font-weight: bold;
color: #333;
}
.donut::before {
content: "";
position: absolute;
width: calc(var(--size) - var(--thickness));
height: calc(var(--size) - var(--thickness));
border-radius: 50%;
background: #fff; /* Inner cut-out */
}
.donut span {
position: absolute;
font-size: calc(var(--size) / 6);
}
/* Variants */
.donut.thin {
--size: 80px;
--thickness: 12px;
}
.donut.medium {
--size: 120px;
--thickness: 25px;
}
.donut.large {
--size: 180px;
--thickness: 35px;
}
.progress {
width: 100%;
background-color: #e9ecef;
border-radius: 0.375rem;
overflow: hidden;
height: 0.51rem; /* Default height */
margin-bottom: 1rem;
font-family: Arial, sans-serif;
font-size: 0.375rem; /* Default size */
font-weight: 500;
}
.progress.thin {
height: 0.7rem;
font-size: 0.6rem;
}
.progress.medium {
height: 1rem;
font-size: 0.7rem;
}
.progress.large {
height: 1.5rem;
font-size: 1.2rem;
}
.progress-bar {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background-color: #0d6efd; /* default = Bootstrap primary */
transition: width 0.6s ease;
}
/* Color variants */
.progress-bar-success {
background-color: #198754;
}
.progress-bar-warning {
background-color: #ffc107;
color: #000;
}
.progress-bar-danger {
background-color: #dc3545;
}
</style>
</head>
<body style="display:block; gap:40px; align-items:center; justify-content:center; min-height:100vh;">
<div style="
display: flex;
vertical-align: middle;
">
<!-- Thin -->
<div class="donut thin" style="--percentage: 45;">
<span>45%</span>
</div>
<!-- Medium -->
<div class="donut medium" style="--percentage: 73;">
<span>73%</span>
</div>
<!-- Large -->
<div class="donut large" style="--percentage: 90;">
<span>90%</span>
</div>
</div>
<div style="
margin-top: 100px;
">
<div class="progress" style="width:200px">
<div class="progress-bar" style="width: 25%;">25%</div>
</div>
<div class="progress thin " style="width:200px">
<div class="progress-bar progress-bar-success" style="width: 50%;">50%</div>
</div>
<div class="progress medium" style="width:200px">
<div class="progress-bar progress-bar-warning" style="width: 75%;">75%</div>
</div>
<div class="progress large" style="width:50%">
<div class="progress-bar progress-bar-danger" style="width: 90%;">90%</div>
</div>
</div>
</body>
</html>

View File

@ -23,7 +23,7 @@
}
.header {
background: #49bf3c;
background: #b10000;
color: #fff;
padding: 20px;
display: flex;
@ -57,8 +57,6 @@
}
.card {
display: flex;
flex-direction: column;
flex: 1;
min-width: 200px;
border: 1px solid #ddd;
@ -69,58 +67,10 @@
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
/* <-- added shadow */
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-top: 1px solid #49bf3c;
border-top: 1px solid #e63946;
}
.card-link {
position: absolute;
top: 8px;
right: 8px;
width: 28px;
height: 28px;
display: none;
align-items: center;
justify-content: center;
border-radius: 6px;
text-decoration: none;
/* arrow color */
background: #f5f5f5;
/* badge background */
box-shadow: 0 1px 3px rgba(0, 0, 0, .15);
}
.card-link-success {
color: #49bf3c;
}
.card-link-warning {
color: #ffc107;
}
.card-link-primary {
color: #007bff;
}
.card-link:hover {
background: #ececec;
}
.card-link:focus {
outline: 2px solid #9ca3af;
outline-offset: 2px;
}
.card-link .arrow {
font-size: 16px;
line-height: 1;
}
.card:hover .card-link {
display: inline-flex;
/* <— show only on this card */
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
@ -148,7 +98,7 @@
}
.attendance {
color: #49bf3c;
color: #b10000;
}
.tasks {
@ -186,8 +136,7 @@
}
.footer {
/* background: #b10000; */
background: #49bf3c;
background: #b10000;
color: #fff;
text-align: center;
padding: 15px;
@ -220,7 +169,7 @@
.legend {
margin-top: 10px;
display: flex;
justify-content: flex-start;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
font-size: 12px;
@ -241,7 +190,7 @@
}
.legend-red {
background: #e63946;
background: #b10000;
}
.legend-blue {
@ -256,23 +205,19 @@
background: #ccc;
}
.legend-yellow {
background: #ffc107;
}
.donut {
--percentage: 0;
--percentage: 65;
/* Change this per chart */
--danger: #e63946;
--primary: #007bff;
--warning: #ffc107;
--success: #28a745;
--success: #198754;
/* Fill color */
--track: #e9ecef;
/* Background track */
--size: 200px;
--size: 120px;
/* Default size */
--thickness: 20px;
/* Default thickness */
@ -280,9 +225,8 @@
width: var(--size);
height: var(--size);
border-radius: 50%;
/* background: conic-gradient(var(--danger) calc(var(--percentage) * 1%),
var(--track) 0); */
background: conic-gradient(var(--track) 100%);
background: conic-gradient(var(--danger) calc(var(--percentage) * 1%),
var(--track) 0);
position: relative;
display: flex;
align-items: center;
@ -305,13 +249,6 @@
.donut span {
position: absolute;
font-size: calc(var(--size) / 6);
width:80%;
box-sizing:border-box;
display:flex;
gap: 4px;
flex-wrap: wrap;
justify-content:center;
align-items:center;
}
/* Variants */
@ -322,7 +259,7 @@
.donut.medium {
--size: 120px;
--thickness: 15px;
--thickness: 25px;
}
.donut.large {
@ -334,35 +271,25 @@
.donut-success {
background: conic-gradient(var(--success) calc(var(--percentage) * 1%),
var(--track) 0);
color: var(--success)
}
.donut-warning {
background: conic-gradient(var(--warning) calc(var(--percentage) * 1%),
var(--track) 0);
color: var(--warning)
}
.donut-danger {
background: conic-gradient(var(--danger) calc(var(--percentage) * 1%),
var(--track) 0);
color: var(--danger)
}
.donut-primary {
background: conic-gradient(var(--primary) calc(var(--percentage) * 1%),
var(--track) 0);
}
.values {
display: flex;
flex-wrap: nowrap;
gap: 8px;
}
.values p {
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
color: var(--primary)
}
</style>
</head>
@ -373,246 +300,298 @@
<div class="header">
<h1>Daily Progress Report</h1>
<div class="project-info">
<strong>Project:</strong> {{projectName}}<br>
<strong>Date:</strong> {{date}}
<strong>Project:</strong> ANP ultimas wakad<br>
<strong>Date:</strong> 17 September 2025
</div>
</div>
<!-- Status Note -->
<div class="status-note">
* Project Status Reported - Generated at {{timeStamp}} UTC
* Project Status Reported - Generated at 18-Sep-2025 03:30:03 UTC
</div>
<!-- Status Cards -->
<div class="status-cards">
<div class="card">
<a class="card-link card-link-warning" href={{webUrl}} aria-label="Open original website" title="Open website" target="_blank"
rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">TODAY'S ATTENDANCE</h4>
<div style="display:flex; flex-wrap:wrap;">
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<div class="donut thin donut-warning" style="--percentage: {{attendancePercentage}};">
<span>
<p style="color:#ffc107;">{{todaysAttendances}}</p><p>/</p><p style="color:#ccc;">{{totalEmployees}}</p>
</span>
<div class="donut thin" style="--percentage: 66;">
<span>20 / 30</span>
</div>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;">
<span class="legend-color legend-green"></span> Completed
</div>
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-yellow"></span> Today's Attendance
<span class="legend-color legend-blue"></span> In Progress
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Total Employees
<span class="legend-color legend-gray"></span> Pending
</div>
</div>
</div>
<div>
<div style="padding:10px; text-align:center;">
<p class="text-muted">Team members present on the site</p>
</div>
</div>
</div>
<div class="card">
<a class="card-link card-link-primary" href={{webUrl}} aria-label="Open original website" title="Open website" target="_blank"
rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">DAILY TASKS COMPLETED</h4>
<div style="display:flex; flex-wrap:wrap;">
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<div class="donut thin donut-primary" style="--percentage: {{taskPercentage}};">
<span >
<p style="color:#007bff;">{{totalCompletedTask}}</p><p>/</p><p style="color:#ccc;">{{totalPlannedTask}}</p>
</span>
<div class="donut thin donut-primary" style="--percentage: 66;">
<span>20 / 30</span>
</div>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;">
<span class="legend-color legend-green"></span> Completed
</div>
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-blue"></span> Completed Work
<span class="legend-color legend-blue"></span> In Progress
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Planned Work
<span class="legend-color legend-gray"></span> Pending
</div>
</div>
</div>
<div>
<div style="padding:10px; text-align:center;">
<p class="text-muted">Team members present on the site</p>
</div>
</div>
<div style="text-align: end!important;">
<p style="font-size: xx-small;color: #ccc;">*Today's Total Work</p>
</div>
<div class="card">
<h4 class="card-title">DAILY TASKS COMPLETED</h4>
<p class="value tasks">20 / 30</p>
<p>Team member present</p>
<div class="legend">
<div class="legend-item"><span class="legend-color legend-blue"></span> Completed</div>
<div class="legend-item"><span class="legend-color legend-green"></span> In Progress</div>
<div class="legend-item"><span class="legend-color legend-gray"></span> Pending</div>
</div>
</div>
<div class="card">
<a class="card-link card-link-success" href={{webUrl}} aria-label="Open original website" title="Open website" target="_blank"
rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">PROJECT COMPLETION STATUS</h4>
<p class=" value completion">20 / 30</p>
<p>Team member present</p>
<div class="legend">
<div class="legend-item"><span class="legend-color legend-green"></span> Completed</div>
<div class="legend-item"><span class="legend-color legend-blue"></span> In Progress</div>
<div class="legend-item"><span class="legend-color legend-gray"></span> Pending</div>
</div>
</div>
<div class="card">
<h4 class="card-title">Regularization Pending</h4>
<p class="value tasks">28/32</p>
<p class="text-muted">Regularization Pending</p>
<div class="legend">
<div class="legend-item"><span class="legend-color legend-green"></span> Completed</div>
<div class="legend-item"><span class="legend-color legend-blue"></span> In Progress</div>
<div class="legend-item"><span class="legend-color legend-gray"></span> Pending</div>
</div>
</div>
<div class="card">
<!-- Row 1: Header -->
<div>
<h4 class="card-title">Checkout Pending</h4>
</div>
<!-- Row 2: Two Columns -->
<div style="display:flex; flex-wrap:wrap;">
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<div class="donut thin donut-success" style="--percentage: {{completionStatus}};">
<span>
<p style="color:#28a745;">{{totalCompletedWork}}</p> <p>/</p> <p style="color:#ccc;">{{totalPlannedWork}}</p>
</span>
<div class="donut thin donut-success" style="--percentage: 73;">
<span>73%</span>
</div>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;">
<span class="legend-color legend-green"></span> Completed
</div>
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-green"></span> Completed Work
<span class="legend-color legend-blue"></span> In Progress
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Planned Work
<span class="legend-color legend-gray"></span> Pending
</div>
</div>
</div>
<!-- Row 3: Full Width -->
<div>
<div style="padding:10px; text-align:center;">
<p class="text-muted">Team members present on the site</p>
</div>
</div>
<div style="text-align: end!important;">
<p style="font-size: xx-small;color: #ccc;">*Project's Total Work</p>
</div>
</div>
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website" title="Open website" target="_blank"
rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<div>
<h4 class="card-title">Pending Attendance</h4>
</div>
<table style="width: 100%;">
<tr>
<td style="text-align: left;">Regularization Pending</td>
<td style="text-align: right;">{{regularizationPending}}</td>
</tr>
<tr>
<td style="text-align: left;">Checkout Pending</td>
<td style="text-align: right;">{{checkoutPending}}</td>
</tr>
</table>
</div>
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website" title="Open website" target="_blank"
rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<div>
<h4 class="card-title">Activity Report Pending</h4>
</div>
<table style="width: 100%;">
<tr>
<td style="text-align: left;">Total Pending Tasks</td>
<td style="text-align: right;">{{reportPending}}</td>
</tr>
<tr>
<td style="text-align: left;">Today's Assigned Tasks</td>
<td style="text-align: right;">{{todaysAssignTasks}}</td>
</tr>
<tr>
<td style="text-align: left;">Today's Completed Tasks</td>
<td style="text-align: right;">{{todaysCompletedTasks}}</td>
</tr>
</table>
</div>
<div style="display:flex; flex-wrap:wrap;">
{% if teamOnSite and teamOnSite|length > 0 %}
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<div class="donut thin donut-warning" style="--percentage: 73;">
<span>73%</span>
</div>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;">
<span class="legend-color legend-green"></span> Completed
</div>
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-blue"></span> In Progress
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Pending
</div>
</div>
</div>
<div>
<div style="padding:10px; text-align:center;">
<p class="text-muted">Team members present on the site</p>
</div>
</div>
</div>
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website" title="Open website" target="_blank"
rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<!-- Row 1: Header -->
<div>
<h4 class="card-title">Team Strength on Site</h4>
</div>
<table style="width: 100%;">
{% for a in teamOnSite %}
<tr>
<td style="text-align: left;">{{a.roleName}}</td>
<td style="text-align: right;">{{a.numberofEmployees}}</td>
<td style="text-align: left;">Site Engineer</td>
<td style="text-align: right;">1</td>
</tr>
<tr>
<td style="text-align: left;">Weilder</td>
<td style="text-align: right;">15</td>
</tr>
<tr>
<td style="text-align: left;">Helper</td>
<td style="text-align: right;">2</td>
</tr>
<tr>
<td style="text-align: left;">Painter</td>
<td style="text-align: right;">1</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
</div>
<!-- Activities
{% if performedTasks and performedTasks|length > 0 %}
<div class="activities">
<h2>Activities (Tasks) Performed {{date}}</h2>
<table class="table">
<thead>
<tr>
<th>Activity/Location</th>
<th>Assigned Today/Pending</th>
<th>Completed Today</th>
<th>Date</th>
<th>Team Members</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
{% for a in performedTasks %}
<tr>
<td>{{a.activity}} {{a.location}}</td>
<td>{{a.assignedToday}} / {{a.pending}}</td>
<td>{{a.completedToday}}</td>
<td>{{date}}</td>
<td></td>
<td>{{a.comment}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Activities -->
<div class="activities">
<h2>Activities (Tasks) Performed 17-Sep-2025</h2>
<table class="table">
<thead>
<tr>
<th>NAME</th>
<th>JOB ROLE</th>
<th>CHECK IN</th>
<th>CHECK OUT</th>
</tr>
</thead>
<tbody>
<tr>
<td>Siddharth Barde</td>
<td>Site Engineer</td>
<td>17-Sep-2025 11:47 AM</td>
<td>-</td>
</tr>
<tr>
<td>Siddharth Barde</td>
<td>Site Engineer</td>
<td>17-Sep-2025 11:47 AM</td>
<td>-</td>
</tr>
<tr>
<td>Siddharth Barde</td>
<td>Site Engineer</td>
<td>17-Sep-2025 11:47 AM</td>
<td>-</td>
</tr>
</tbody>
</table>
</div>
<!-- Footer -->
<div class="footer" style="display:flex; flex-wrap:wrap;">
<div style="width: 50%;text-align: left;">
Contact Us: contact[at]marcoaiot.com<br>
Marco AIoT technologies Pvt. Ltd. ©2025 All Rights Reserved
</div>
{% endif %} -->
</div>
</div>
<!-- Footer -->
<div class="footer" style="display:flex; flex-wrap:wrap;">
<div style="width: 50%;text-align: left;">
Contact Us: contact[at]marcoaiot.com<br>
Marco AIoT technologies Pvt. Ltd. ©2025 All Rights Reserved
</div>
<div style="width: 50%; text-align: right;">
<!-- <a href="#">Instagram</a> | -->
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/google.png" style="height: 15px;" /></a> |
<div style="width: 50%; text-align: right;">
<!-- <a href="#">Instagram</a> | -->
<a href="https://www.linkedin.com/company/marco-aiot">Linkedin</a> |
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/twitter.png" style="height: 15px;" /></a> |
<a href="https://x.com/marcoaiot"><img src="https://cdn.marcoaiot.com/icons/brands/twitter.png" style="height: 15px;" /></a> |
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/facebook.png" style="height: 15px;" /></a> |
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/instagram.png" style="height: 15px;" /></a>
<!-- <a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/youtube.png" style="height: 15px;" /></a> | <a
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/facebook.png" style="height: 15px;" /></a> |
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/instagram.png" style="height: 15px;" /></a>
<!-- <a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/youtube.png" style="height: 15px;" /></a> | <a
href="#">LinkedIn</a> |
<a href="#">YouTube</a> -->
</div>
</div>
</div>
<div style="text-align: center;width: 100%;background-color: #fff;margin:10px;margin-bottom: 30px!important;font-size: small;color: #6c757d ;">
You have received this email because it contains important information about your {{websiteName}} Account account.
</div>
</div>
<div style="text-align: center;width: 100%;background-color: #fff;margin:10px;font-size: small;color: #6c757d ;">
You have received this email because it contains important information about your Marco PMS Account account.
</div>
</div>
</body>

View File

@ -1,324 +0,0 @@
import json
import smtplib
import os
import datetime
import requests
import pandas as pd
from email.message import EmailMessage
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
# --- Config and color map ---
color_map = {
"all_ok": "4CAF50", #Green present
"act4_true": "009688", # teal Regularization is accepted
"act1_nullOut": "FFC107", #Amber Check out pending
"act2": "2196F3", # Bule Regularization pending
"act5": "B71C1C", #Dark Red Regularization rejected
"all_null": "F6635C", #Red Absent
"sunday":"FF0000"
}
# Add Legend sheet to the workbook
legend_data = {
"Color Description": [
"Present (All Ok)",
"Regularization Accepted",
"Check-out Pending",
"Regularization Pending",
"Regularization Rejected",
"Absent",
"Sundays"
],
"Hex Color": [
color_map["all_ok"],
color_map["act4_true"],
color_map["act1_nullOut"],
color_map["act2"],
color_map["act5"],
color_map["all_null"],
color_map["sunday"]
]
}
def login_api():
payload = {"username": API_USERNAME, "password": API_PASSWORD}
headers = {"Content-Type": "application/json"}
try:
response = requests.post(f"{BASE_URL}/auth/login", json=payload, headers=headers)
response.raise_for_status()
data = response.json()["data"]
jwt = data["token"]
print("API login successful.")
return jwt
except Exception as e:
print(f"Login API error: {e}")
return None
def select_tenant(jwt):
headers = {"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"}
try:
response = requests.post(f"{BASE_URL}/auth/select-tenant/{API_TENANT}", headers=headers)
response.raise_for_status()
data = response.json()["data"]
jwt = data["token"]
print("Tenant selected successful.")
return jwt
except Exception as e:
print(f"Select tenant error: {e}")
return None
def attendance_report(jwt,is_current_month):
headers = {"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"}
response = requests.get(f"{BASE_URL}/report/report-attendance?isCurrentMonth={is_current_month}", headers=headers)
response.raise_for_status()
data = response.json()
projects = data.get('data', [])
# Get current date and time
today = datetime.datetime.now()
if not is_current_month:
first_day_current_month = today.replace(day=1)
last_month_last_day = first_day_current_month - datetime.timedelta(days=1)
month_name = last_month_last_day.strftime("%B")
year = last_month_last_day.year
else:
month_name = today.strftime("%B")
year = today.year
excel_file = f"{month_name}-{year}_attendance_report.xlsx"
writer = pd.ExcelWriter(excel_file, engine="openpyxl")
# Border style
thin = Side(border_style="thin", color="000000")
border = Border(left=thin, right=thin, top=thin, bottom=thin)
for proj_idx, project in enumerate(projects):
project_name = project.get('projectName', 'UnknownProject')[:31] # Sheet name max 31 chars
attendances = project.get('projectAttendance', [])
# Collect all unique dates in this project
all_dates = set()
for user in attendances:
for att in user.get('attendances', []):
all_dates.add(att.get('attendanceDate', '').split('T')[0])
all_dates = sorted(all_dates)
# Prepare rows
rows = []
for user in attendances:
row = {'Name': f"{user.get('firstName','')} {user.get('lastName','')}".strip()}
date_map = {a.get('attendanceDate', '').split('T')[0]: a for a in user.get('attendances', [])}
for date in all_dates:
att = date_map.get(date, {})
# Store also needed fields for coloring below
row[f"{date}_checkin"] = ''
row[f"{date}_checkout"] = ''
if att.get('checkIn'):
# Extract time part only, e.g. "09:30:00"
row[f"{date}_checkin"] = att.get('checkIn').split('T')[1]
if att.get('checkOut'):
# Extract time part only
row[f"{date}_checkout"] = att.get('checkOut').split('T')[1]
row[f"{date}_activity"] = att.get('activity', None)
row[f"{date}_isapproved"] = att.get('isApproved', None)
row['CheckInCheckOutDone'] = user.get('checkInCheckOutDone', 0)
row['CheckOutPending'] = user.get('checkOutPending', 0)
row['CheckInDone'] = user.get('checkInDone', 0)
row['AbsentAttendance'] = user.get('absentAttendance', 0)
row['RejectedRegularize'] = user.get('rejectedRegularize', 0)
rows.append(row)
legend_df = pd.DataFrame(legend_data)
# Use the same writer to add sheet after all sheets written
legend_df.to_excel(writer, sheet_name="Legend", index=False)
# Access Legend sheet for coloring the color sample cells
ws_legend = writer.book["Legend"]
# Assuming data starts from row 2 (header in row 1)
for row_idx in range(2, 2 + len(legend_data["Color Description"])):
# Color sample cell: B column (2nd col)
cell = ws_legend.cell(row=row_idx, column=2)
hex_color = cell.value
fill = PatternFill(start_color=hex_color, fill_type="solid")
cell.fill = fill
# Optional: bold and center description column
desc_cell = ws_legend.cell(row=row_idx, column=1)
desc_cell.font = Font(bold=True)
desc_cell.alignment = Alignment(horizontal="left", vertical="center")
# Optionally adjust headers and column width for visual clarity
for col in [1, 2]:
ws_legend.column_dimensions[get_column_letter(col)].width = 30
ws_legend["A1"].font = Font(bold=True)
ws_legend["B1"].font = Font(bold=True)
ws_legend["A1"].alignment = ws_legend["B1"].alignment = Alignment(horizontal="center")
# Columns: Name, checkin/checkout pairs for dates, and summary columns
columns = ['Name']
for date in all_dates:
columns.extend([f"{date}_checkin", f"{date}_checkout"])
columns.extend(['CheckInCheckOutDone', 'CheckInDone','CheckOutPending', 'AbsentAttendance', 'RejectedRegularize'])
df = pd.DataFrame(rows, columns=columns)
# Write to sheet
df.to_excel(writer, sheet_name=project_name, index=False, startrow=2)
# Post-process sheet using openpyxl
wb = writer.book
ws = wb[project_name]
# Merged header with date and subheaders
ws["A3"].value = "Name"
ws["A3"].font = Font(bold=True)
ws["A3"].alignment = Alignment(horizontal="center", vertical="center")
col = 2
for date in all_dates:
col_letter_1 = get_column_letter(col)
col_letter_2 = get_column_letter(col + 1)
ws.merge_cells(f"{col_letter_1}2:{col_letter_2}2")
ws[f"{col_letter_1}2"].value = pd.to_datetime(date).strftime("%d-%m-%Y")
ws[f"{col_letter_1}2"].font = Font(bold=True)
ws[f"{col_letter_1}2"].alignment = Alignment(horizontal="center", vertical="center")
ws[f"{col_letter_1}3"].value = "Check-in"
ws[f"{col_letter_2}3"].value = "Check-out"
ws[f"{col_letter_1}3"].alignment = ws[f"{col_letter_2}3"].alignment = Alignment(horizontal="center")
col += 2
# Add headers for summary columns
summary_headers = ['CheckIn-CheckOut Done', 'CheckIn Done', 'CheckOut Pending', 'Absent Attendance', 'Rejected Regularize']
summary_start_col = col
for i, header in enumerate(summary_headers):
col_letter = get_column_letter(summary_start_col + i)
ws.merge_cells(f"{col_letter}2:{col_letter}3")
ws[f"{col_letter}2"].value = header
ws[f"{col_letter}2"].font = Font(bold=True)
ws[f"{col_letter}2"].alignment = Alignment(horizontal="center", vertical="center")
# Apply borders and coloring
max_row = ws.max_row
max_col = ws.max_column
for row_idx in range(3, max_row + 1): # Starting from 3 to include header rows
for col_idx in range(1, max_col + 1):
cell = ws.cell(row=row_idx, column=col_idx)
cell.border = border
# Coloring according to rules for check-in/out pairs
for row_idx, user_row in enumerate(rows, start=4):
for date_idx, date in enumerate(all_dates):
checkin_col = 2 + date_idx * 2
checkout_col = checkin_col + 1
activity = user_row.get(f"{date}_activity")
isapproved = user_row.get(f"{date}_isapproved")
c_in = user_row.get(f"{date}_checkin")
c_out = user_row.get(f"{date}_checkout")
# --- Sunday RED coloring ---
dt = datetime.datetime.strptime(date, "%Y-%m-%d")
if dt.weekday() == 6: # Sunday
fill = PatternFill(start_color="FF0000", fill_type="solid")
ws.cell(row=row_idx, column=checkin_col).fill = fill
ws.cell(row=row_idx, column=checkout_col).fill = fill
continue # Skip further coloring for this date
fill_color = None
if not c_in and not c_out:
fill_color = color_map["all_null"]
elif c_in and c_out and activity == 4 and not bool(isapproved):
fill_color = color_map["all_ok"]
elif activity == 4 and bool(isapproved):
fill_color = color_map["act4_true"]
elif activity == 1 and not c_out:
fill_color = color_map["act1_nullOut"]
elif activity == 2:
fill_color = color_map["act2"]
elif activity == 5:
fill_color = color_map["act5"]
if fill_color:
fill = PatternFill(start_color=fill_color, fill_type="solid")
ws.cell(row=row_idx, column=checkin_col).fill = fill
ws.cell(row=row_idx, column=checkout_col).fill = fill
writer.close()
print(f"Excel '{excel_file}' generated with sheets per project, summary columns and conditional coloring.")
# --- 2. Compose the Email ----
msg = EmailMessage()
msg["Subject"] = f"Attendance Report for {month_name}-{year}"
msg["From"] = SENDER_EMAIL
msg["To"] = RECIPIENT_EMAILS
msg.set_content("Please find the attached Excel file.")
# Add the Excel attachment
with open(excel_file, "rb") as f:
file_data = f.read()
file_name = f.name
msg.add_attachment(file_data, maintype="application", subtype="vnd.openxmlformats-officedocument.spreadsheetml.sheet", filename=file_name)
# --- 3. Send the Email ----
with smtplib.SMTP(SMPTSERVER, PORT) as smtp:
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
smtp.login(SENDER_EMAIL, SENDER_PASSWORD)
smtp.send_message(msg)
# After all operations (e.g., emailing) are done, delete the file
if os.path.exists(excel_file):
os.remove(excel_file)
print(f"Deleted file: {excel_file}")
else:
print("File not found, cannot delete.")
# --- Main execution logic ---
if __name__ == "__main__":
import sys
# Load your real config before calling any API
GLOBAL_CONFIG_PATH = "config.json"
try:
with open(GLOBAL_CONFIG_PATH, "r", encoding="utf-8") as f:
config = json.load(f)
except Exception as e:
print(f"Failed to load config: {e}")
sys.exit(1)
API_CONFIG = config.get("API", {})
BASE_URL = API_CONFIG.get("BASE_URL")
API_USERNAME = API_CONFIG.get("USERNAME")
API_PASSWORD = API_CONFIG.get("PASSWORD")
API_TENANT = API_CONFIG.get("TENANTID")
SMPT_CONFIG = config.get("SMPT", {})
SMPTSERVER = SMPT_CONFIG.get("SMPTSERVER")
PORT = SMPT_CONFIG.get("PORT")
SENDER_EMAIL = SMPT_CONFIG.get("SENDER_EMAIL")
SENDER_PASSWORD = SMPT_CONFIG.get("SENDER_PASSWORD")
RECIPIENT_EMAILS = SMPT_CONFIG.get("RECIPIENT_EMAILS")
token = login_api()
if not token:
print("Login failed, aborting.")
sys.exit(1)
jwt_token = select_tenant(token)
if not jwt_token:
print("Tenant selection failed, aborting.")
sys.exit(1)
attendance_report(jwt_token, False)

View File

@ -1,148 +0,0 @@
import sys
import json
import smtplib
import datetime
import requests
from pathlib import Path
from numbers import Real
from email.message import EmailMessage
from jinja2 import Environment, FileSystemLoader, select_autoescape
# Load your real config before calling any API
GLOBAL_CONFIG_PATH = "config.json"
try:
with open(GLOBAL_CONFIG_PATH, "r", encoding="utf-8") as f:
config = json.load(f)
except Exception as e:
print(f"Failed to load config: {e}")
sys.exit(1)
WEB_CONFIG = config.get("WEB", {})
WEB_BASE_URL = WEB_CONFIG.get("BASE_URL")
API_CONFIG = config.get("API", {})
BASE_URL = API_CONFIG.get("BASE_URL")
SMPT_CONFIG = config.get("SMPT", {})
SMPTSERVER = SMPT_CONFIG.get("SMPTSERVER")
PORT = SMPT_CONFIG.get("PORT")
SENDER_EMAIL = SMPT_CONFIG.get("SENDER_EMAIL")
SENDER_PASSWORD = SMPT_CONFIG.get("SENDER_PASSWORD")
RECIPIENT_EMAILS = SMPT_CONFIG.get("RECIPIENT_EMAILS")
UNIQUE_IDENTIFIER_CONFIG = config.get("UNIQUE_IDENTIFIER", {})
PROJECT_IDS = UNIQUE_IDENTIFIER_CONFIG.get("PROJECT_IDS")
def render_template_from_file(template_name,context):
base_dir = Path(__file__).parent
env = Environment(
loader=FileSystemLoader(searchpath=str(base_dir)),
autoescape=select_autoescape(["html", "xml"])
)
tmpl = env.get_template(template_name)
return tmpl.render(**context)
def fetch_Project_report(project_id):
headers = {"Content-Type": "application/json"}
try:
response = requests.get(f"{BASE_URL}/market/get/project/report/{project_id}", headers=headers)
response.raise_for_status()
data = response.json()["data"]
print(f"Project report for project \"{data["projectName"]}\" fetched successfully.")
return data
except Exception as e:
print(f"Select tenant error: {e}")
return None
def get_percentage(part, whole, decimals: int = 2):
if not isinstance(part, Real) or not isinstance(whole, Real):
raise TypeError("part and whole must be numbers")
if whole == 0:
return 0.0
return round((part / whole) * 100.0, decimals)
def value_minization(minimize_value):
if minimize_value >= 1000:
minimized_value = round((minimize_value/1000),2)
result = f"{minimized_value}K"
else:
result = f"{minimize_value}"
return result
if __name__ == "__main__":
template_name = "dpr.html"
project_ids = [p.strip() for p in PROJECT_IDS.split(",") if p.strip()]
for project_id in project_ids:
data = fetch_Project_report(project_id)
if "attendancePercentage" in data:
attendance_percentage = data["attendancePercentage"]
else:
attendance_percentage = get_percentage(data["todaysAttendances"], data["totalEmployees"], 2)
if "taskPercentage" in data:
task_percentage = data["taskPercentage"]
else:
task_percentage = get_percentage(data["totalCompletedTask"], data["totalPlannedTask"], 2)
if "todaysCompletedTasks" in data:
todays_completed_tasks = data["todaysCompletedTasks"]
else:
todays_completed_tasks = 0
web_url = f"{WEB_BASE_URL}/auth/login"
dt = datetime.datetime.strptime(data["date"], "%Y-%m-%dT%H:%M:%SZ")
api_formatted_date = dt.strftime("%d-%b-%Y")
total_planned_work = value_minization(data["totalPlannedWork"])
total_completed_work = value_minization(data["totalCompletedWork"])
total_planned_task = value_minization(data["totalPlannedTask"])
total_completed_task = value_minization(data["totalCompletedTask"])
context = {
"webUrl":web_url,
"date": api_formatted_date,
"projectName": data["projectName"],
"timeStamp": data["timeStamp"],
"todaysAttendances": data["todaysAttendances"],
"totalEmployees": data["totalEmployees"],
"attendancePercentage":attendance_percentage,
"taskPercentage":task_percentage,
"regularizationPending": data["regularizationPending"],
"checkoutPending": data["checkoutPending"],
"totalPlannedWork": total_planned_work,
"totalCompletedWork": total_completed_work,
"totalPlannedTask": total_planned_task,
"totalCompletedTask": total_completed_task,
"completionStatus": round(data["completionStatus"],2),
"reportPending": data["reportPending"],
"todaysAssignTasks": data["todaysAssignTasks"],
"todaysCompletedTasks": todays_completed_tasks,
"websiteName":"OnFieldWork.com",
"teamOnSite": data["teamOnSite"],
"performedTasks": data["performedTasks"],
"performedAttendance": data["performedAttendance"]
}
# print(context)
project_name = data["projectName"]
html = render_template_from_file(template_name,context)
# print(html)
msg = EmailMessage()
msg["Subject"] = f"DPR - {api_formatted_date} - {project_name}"
msg["From"] = SENDER_EMAIL
msg["To"] = RECIPIENT_EMAILS
msg.set_content("HTML version attached as alternative.")
msg.add_alternative(html, subtype="html")
with smtplib.SMTP(SMPTSERVER, PORT) as smtp:
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
smtp.login(SENDER_EMAIL, SENDER_PASSWORD)
smtp.send_message(msg)

View File

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Donut Variants</title>
<style>
.donut {
--percentage: 65; /* Change this per chart */
--primary: #e63946; /* Fill color */
--track: #e9ecef; /* Background track */
--size: 120px; /* Default size */
--thickness: 20px; /* Default thickness */
width: var(--size);
height: var(--size);
border-radius: 50%;
background: conic-gradient(
var(--primary) calc(var(--percentage) * 1%),
var(--track) 0
);
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
font-weight: bold;
color: #333;
}
.donut::before {
content: "";
position: absolute;
width: calc(var(--size) - var(--thickness));
height: calc(var(--size) - var(--thickness));
border-radius: 50%;
background: #fff; /* Inner cut-out */
}
.donut span {
position: absolute;
font-size: calc(var(--size) / 6);
}
/* Variants */
.donut.thin {
--size: 80px;
--thickness: 12px;
}
.donut.medium {
--size: 120px;
--thickness: 25px;
}
.donut.large {
--size: 180px;
--thickness: 35px;
}
.progress {
width: 100%;
background-color: #e9ecef;
border-radius: 0.375rem;
overflow: hidden;
height: 0.51rem; /* Default height */
margin-bottom: 1rem;
font-family: Arial, sans-serif;
font-size: 0.375rem; /* Default size */
font-weight: 500;
}
.progress.thin {
height: 0.7rem;
font-size: 0.6rem;
}
.progress.medium {
height: 1rem;
font-size: 0.7rem;
}
.progress.large {
height: 1.5rem;
font-size: 1.2rem;
}
.progress-bar {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background-color: #0d6efd; /* default = Bootstrap primary */
transition: width 0.6s ease;
}
/* Color variants */
.progress-bar-success {
background-color: #198754;
}
.progress-bar-warning {
background-color: #ffc107;
color: #000;
}
.progress-bar-danger {
background-color: #dc3545;
}
</style>
</head>
<body style="display:block; gap:40px; align-items:center; justify-content:center; min-height:100vh;">
<div style="
display: flex;
vertical-align: middle;
">
<!-- Thin -->
<div class="donut thin" style="--percentage: 45;">
<span>45%</span>
</div>
<!-- Medium -->
<div class="donut medium" style="--percentage: 73;">
<span>73%</span>
</div>
<!-- Large -->
<div class="donut large" style="--percentage: 90;">
<span>90%</span>
</div>
</div>
<div style="
margin-top: 100px;
">
<div class="progress" style="width:200px">
<div class="progress-bar" style="width: 25%;">25%</div>
</div>
<div class="progress thin " style="width:200px">
<div class="progress-bar progress-bar-success" style="width: 50%;">50%</div>
</div>
<div class="progress medium" style="width:200px">
<div class="progress-bar progress-bar-warning" style="width: 75%;">75%</div>
</div>
<div class="progress large" style="width:50%">
<div class="progress-bar progress-bar-danger" style="width: 90%;">90%</div>
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
{
"EMAIL_CONFIGURATION": {
"EMAIL_HOST": "mail.marcoaiot.com",
"EMAIL_PORT": 587,
"EMAIL_USER": "admin@marcoaiot.com",
"EMAIL_PASS": "xxx",
"EMAIL_SUBJECT": "Database backup process",
"EMAIL_RECEIVERS": "vikas@marcoaiot.com,umesh@marcoaiot.com"
}
}

View File

@ -0,0 +1,84 @@
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
import os
import cairosvg
def generate_donut_svg(percentage, color="#0d6efd", size=120, thickness=4):
"""
Generate an inline SVG donut chart.
"""
svg = f"""
<svg width="{size}" height="{size}" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<!-- Track -->
<path
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#e9ecef"
stroke-width="{thickness}"
/>
<!-- Progress -->
<path
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="{color}"
stroke-width="{thickness}"
stroke-dasharray="{percentage}, 100"
/>
<!-- Label -->
<text x="18" y="20.35" fill="#333" font-size="5" text-anchor="middle">{percentage}%</text>
</svg>
"""
return svg
# Sender and receiver
sender_email = "marcoioitsoft@gmail.com"
receiver_emails = ["vikasnale@gmail.com", "vikas@marcoaiot.com", "umeshvdesai@outlook.com"]
password = "qrtq wfuj hwpp fhqr" # Use Gmail App Password here
# Read HTML body from file
file_path = os.path.join(os.path.dirname(__file__), "body.txt")
with open(file_path, "r", encoding="utf-8") as f:
body = f.read()
# Create the email
message = MIMEMultipart("related")
message["From"] = sender_email
message["To"] = ", ".join(receiver_emails)
message["Subject"] = "Test HTML Email with Donut Charts"
# Generate SVGs and convert to PNGs
donut_svgs = [
generate_donut_svg(45, "#e63946"),
generate_donut_svg(73, "#0d6efd"),
generate_donut_svg(90, "#198754")
]
# Attach donuts as inline images
for i, svg in enumerate(donut_svgs, start=1):
png_bytes = cairosvg.svg2png(bytestring=svg.encode("utf-8"))
img = MIMEImage(png_bytes, "png")
cid = f"donut{i}"
img.add_header("Content-ID", f"<{cid}>")
message.attach(img)
body += f'<br><img src="cid:{cid}" alt="Donut{i}">'
# Attach the final HTML body
message.attach(MIMEText(f"<html><body>{body}</body></html>", "html"))
# Send the email
try:
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(sender_email, password)
server.sendmail(sender_email, receiver_emails, message.as_string())
print("✅ Email sent successfully with embedded donut PNGs!")
except Exception as e:
print("❌ Error:", e)
finally:
server.quit()

View File

@ -0,0 +1,35 @@
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os
# Sender and receiver
sender_email = "marcoioitsoft@gmail.com"
receiver_emails = ["vikasnale@gmail.com", "vikas@marcoaiot.com"]
password = "qrtq wfuj hwpp fhqr" # Use Gmail App Password here
# Read body from text file (e.g., body.txt in the same folder)
file_path = os.path.join(os.path.dirname(__file__), "body.txt")
with open(file_path, "r", encoding="utf-8") as f:
body = f.read()
# Create the email
message = MIMEMultipart()
message["From"] = sender_email
message["To"] =", ".join(receiver_emails)
message["Subject"] = "Test Email from Python"
# Attach body
message.attach(MIMEText(body, "html"))
try:
# Connect to Gmail SMTP server
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls() # Secure the connection
server.login(sender_email, password)
server.sendmail(sender_email, receiver_emails, message.as_string())
print("✅ Email sent successfully!")
except Exception as e:
print("❌ Error:", e)
finally:
server.quit()

View File

@ -0,0 +1,84 @@
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
import os
import cairosvg
def generate_donut_svg(percentage, color="#0d6efd", size=120, thickness=4):
"""
Generate an inline SVG donut chart.
"""
svg = f"""
<svg width="{size}" height="{size}" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<!-- Track -->
<path
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#e9ecef"
stroke-width="{thickness}"
/>
<!-- Progress -->
<path
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="{color}"
stroke-width="{thickness}"
stroke-dasharray="{percentage}, 100"
/>
<!-- Label -->
<text x="18" y="20.35" fill="#333" font-size="5" text-anchor="middle">{percentage}%</text>
</svg>
"""
return svg
# Sender and receiver
sender_email = "marcoioitsoft@gmail.com"
receiver_emails = ["vikasnale@gmail.com", "vikas@marcoaiot.com", "umeshvdesai@outlook.com"]
password = "qrtq wfuj hwpp fhqr" # Use Gmail App Password here
# Read HTML body from file
file_path = os.path.join(os.path.dirname(__file__), "body.txt")
with open(file_path, "r", encoding="utf-8") as f:
body = f.read()
# Create the email
message = MIMEMultipart("related")
message["From"] = sender_email
message["To"] = ", ".join(receiver_emails)
message["Subject"] = "Test HTML Email with Donut Charts"
# Generate SVGs and convert to PNGs
donut_svgs = [
generate_donut_svg(45, "#e63946"),
generate_donut_svg(73, "#0d6efd"),
generate_donut_svg(90, "#198754")
]
# Attach donuts as inline images
for i, svg in enumerate(donut_svgs, start=1):
png_bytes = cairosvg.svg2png(bytestring=svg.encode("utf-8"))
img = MIMEImage(png_bytes, "png")
cid = f"donut{i}"
img.add_header("Content-ID", f"<{cid}>")
message.attach(img)
body += f'<br><img src="cid:{cid}" alt="Donut{i}">'
# Attach the final HTML body
message.attach(MIMEText(f"<html><body>{body}</body></html>", "html"))
# Send the email
try:
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(sender_email, password)
server.sendmail(sender_email, receiver_emails, message.as_string())
print("✅ Email sent successfully with embedded donut PNGs!")
except Exception as e:
print("❌ Error:", e)
finally:
server.quit()

View File

@ -0,0 +1,11 @@
# Services & Containers
SERVICES=nginx,rocketchat,mongod,mysql
CONTAINERS=redmine-app,mediawiki-app,sonarqube,postgres-sonar,ecc269bb3ba3
# Mail settings
SENDER_EMAIL=marcoioitsoft@gmail.com
RECEIVER_EMAIL=umesh@marcoaiot.com,vikas@marcoaiot.com
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=marcoioitsoft@gmail.com
SMTP_PASSWORD=qrtq wfuj hwpp fhqr

View File

@ -0,0 +1,114 @@
#!/usr/bin/env python3
import subprocess
import smtplib
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from dotenv import load_dotenv
load_dotenv()
# ----------------- CONFIG -----------------
services = os.getenv("SERVICES", "").split(",")
containers = os.getenv("CONTAINERS", "").split(",") # update with your container names
sender_email = os.getenv("SENDER_EMAIL", "")
receiver_email =os.getenv("RECEIVER_EMAIL", "").split(",")
smtp_server = os.getenv("SMTP_SERVER", "")
smtp_port = os.getenv("SMTP_PORT", "")
smtp_user = os.getenv("SMTP_USER", "")
smtp_password = os.getenv("SMTP_PASSWORD", "")
# ------------------------------------------
def run_cmd(cmd):
return subprocess.getoutput(cmd)
def check_service(service):
status = subprocess.run(["systemctl", "is-active", service], capture_output=True, text=True)
if status.returncode == 0:
return True, ""
else:
for attempt in range(2):
print("Attempting to start: " + service)
subprocess.run(["systemctl", "restart", service])
status = subprocess.run(["systemctl", "is-active", service], capture_output=True, text=True)
if status.returncode == 0:
return True, ""
logs = run_cmd(f"journalctl -u {service} -n 20 --no-pager")
return False, logs
def check_container(container):
status = run_cmd(f"docker inspect -f '{{{{.State.Running}}}}' {container}")
if status.strip() == "true":
return True, ""
else:
for attempt in range(2):
run_cmd(f"docker restart {container}")
status = run_cmd(f"docker inspect -f '{{{{.State.Running}}}}' {container}")
if status.strip() == "true":
return True, ""
logs = run_cmd(f"docker logs --tail 20 {container}")
return False, logs
def send_email(failures):
if not failures:
return
msg = MIMEMultipart("alternative")
msg["From"] = sender_email
msg["To"] = ", ".join(receiver_email)
msg["Subject"] = "Service/Container Failure Report"
# HTML Email Body
html = """
<html>
<body style="font-family: Arial, sans-serif; background-color: #f8f9fa; padding: 20px;">
<h2 style="color: #dc3545;"> Failure Report</h2>
<p>The following services/containers failed even after restart attempts:</p>
<hr>
"""
for name, logs in failures.items():
html += f"""
<div style="margin-bottom: 20px; padding: 10px; background-color: #fff3cd; border-left: 5px solid #dc3545;">
<h3 style="margin: 0; color: #721c24;"> {name} Failed</h3>
<p><b>Last 20 log lines:</b></p>
<pre style="background-color: #f1f1f1; padding: 10px; border-radius: 5px; max-height: 300px; overflow-y: auto; font-size: 12px; color: #212529;">{logs}</pre>
</div>
"""
html += """
<hr>
<p style="color: #6c757d;">This is an automated alert. Please check the server immediately.</p>
</body>
</html>
"""
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(smtp_user, smtp_password)
server.sendmail(sender_email, receiver_email, msg.as_string())
if __name__ == "__main__":
failures = {}
print("inside __main__")
for service in services:
print("looping services: " + service)
ok, logs = check_service(service)
print(ok)
print(logs)
if not ok:
failures[service] = logs
for container in containers:
ok, logs = check_container(container)
print("looping containers: " + container)
print(ok)
print(logs)
if not ok:
failures[f"Docker: {container}"] = logs
print(failures)
send_email(failures)