:qErster Commit

This commit is contained in:
fds2610 2025-11-10 14:31:17 +00:00
commit fd8c64c9fe
7 changed files with 470 additions and 0 deletions

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# 🖥️ monitor
**monitor** ist ein leichtgewichtiges Monitoring-Tool, das mehrere Systeme per **Ping** überwacht und die Ergebnisse in einer **statischen HTML-Webseite** anzeigt.
Ideal für kleine Netzwerke, Homelabs oder interne Statusseiten.
---
## 🚀 Features
- Überwacht beliebig viele Hosts per **ICMP-Ping**
- Generiert automatisch eine **statische HTML-Statusseite**
- Anzeige von:
- Antwortzeit (ms)
- Online-/Offline-Status
- Letztem Check-Zeitpunkt
- Einfache Konfiguration über JSON oder YAML
- Keine Datenbank, kein Serverprozess – alles rein statisch
- Optional: automatischer Refresh der Webseite
---
## 📁 Projektstruktur

73
check_fds.sh Executable file
View file

@ -0,0 +1,73 @@
#!/bin/bash
# Pfad zur Textdatei mit Hostname-IP-Paaren (pro Zeile: hostname ip)
HOST_FILE="hosts.conf"
# Pfad zum mail_fds.py-Skript
MAIL_SCRIPT="mail_fds.py"
# Pfad zur Statusdatei
STATUS_FILE="/var/www/html/status.txt"
# Funktion zum Prüfen einer IP und Schreiben des Status
check_ip() {
local hostname="$1"
local ip="$2"
local success_count=0
# echo "Prüfe $hostname ($ip) ..."
for i in {1..3}; do
if ping -c 1 -W 2 "$ip" &> /dev/null; then
((success_count++))
else
echo " Ping $i an $ip fehlgeschlagen."
fi
done
local status="online"
if [ "$success_count" -lt 2 ]; then
status="offline"
echo " Mehr als ein Ping fehlgeschlagen für $hostname ($ip). Führe $MAIL_SCRIPT aus."
./"$MAIL_SCRIPT" --subject "Ping-Fehler bei $hostname $ip" --credentials credentials.conf
else
echo " $hostname ($ip) ist erreichbar."
fi
# Status in status.conf schreiben/aktualisieren
local now=$(date +"%Y-%m-%d %H:%M")
awk -v h="$hostname" -v i="$ip" -v s="$status" -v d="$now" '
BEGIN { found=0 }
{
if ($1 == h && $2 == i) {
print h, i, s, d;
found=1;
} else {
print $0;
}
}
END {
if (!found && h != "" && i != "") {
print h, i, s, d;
}
}' "$STATUS_FILE" > "$STATUS_FILE.tmp" && mv "$STATUS_FILE.tmp" "$STATUS_FILE"
}
# Hauptskript
echo "Pruefung: $(date)"
if [ ! -f "$HOST_FILE" ]; then
echo "Fehler: Datei $HOST_FILE nicht gefunden."
exit 1
fi
if [ ! -f "$MAIL_SCRIPT" ]; then
echo "Fehler: Skript $MAIL_SCRIPT nicht gefunden."
exit 1
fi
# Statusdatei anlegen, falls nicht vorhanden
touch "$STATUS_FILE"
while read -r line; do
hostname=$(echo "$line" | awk '{print $1}')
ip=$(echo "$line" | awk '{print $2}')
if [ -n "$hostname" ] && [ -n "$ip" ]; then
check_ip "$hostname" "$ip"
fi
done < "$HOST_FILE"

6
credentials-sample.conf Normal file
View file

@ -0,0 +1,6 @@
smtp.strato.de:587
proxmox@sonnengetrock.net
~%2VNAY@9J9u@Xkwo
proxmox@sonnengetrock.net
felix@schepmann.de

0
echo Normal file
View file

7
hosts-sample.conf Normal file
View file

@ -0,0 +1,7 @@
OpenCCU 192.168.2.4
HomeAssistant 192.168.2.11
Paperless 192.168.2.19
Server1 192.168.2.18
Server2 192.168.2.17
Server3 192.168.2.16

299
html/mon.html Normal file
View file

@ -0,0 +1,299 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FDS Server-Status</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
color: #333;
}
h1 {
text-align: center;
color: #2c3e50;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #3498db;
color: white;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
tr:hover {
background-color: #e3f2fd;
}
.status {
padding: 5px 10px;
border-radius: 4px;
font-weight: bold;
text-align: center;
}
.online {
background-color: #2ecc71;
color: white;
}
.offline {
background-color: #e74c3c;
color: white;
}
.unknown {
background-color: #f39c12;
color: white;
}
.last-check {
font-size: 0.9em;
color: #7f8c8d;
}
/* Navigation */
.hamburger {
/* display: none; */
cursor: pointer;
font-size: 24px;
position: fixed;
top: 15px;
left: 15px;
z-index: 1000;
}
.nav-menu {
display: none;
position: fixed;
top: 0;
left: 0;
width: 250px;
height: 100%;
background-color: #2c3e50;
color: white;
z-index: 999;
padding-top: 60px;
}
.nav-menu a {
color: white;
text-decoration: none;
display: block;
padding: 15px;
border-bottom: 1px solid #34495e;
}
.nav-menu a:hover {
background-color: #3498db;
}
.close-btn {
position: absolute;
top: 15px;
right: 15px;
font-size: 24px;
cursor: pointer;
}
.settings-form {
padding: 20px;
max-width: 500px;
margin: 0 auto;
}
.settings-form label {
display: block;
margin-bottom: 5px;
}
.settings-form select {
width: 100%;
padding: 8px;
margin-bottom: 15px;
}
.settings-form button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
}
@media (max-width: 768px) {
.hamburger {
display: block;
}
}
</style>
</head>
<body>
<div class="hamburger" id="hamburger">☰</div>
<div class="nav-menu" id="navMenu">
<span class="close-btn" id="closeBtn">×</span>
<a href="#" id="homeLink">Startseite</a>
<a href="#" id="settingsLink">Einstellungen</a>
</div>
<div id="content">
<h1>Server-Status &Uuml;bersicht</h1>
<table id="serverTable">
<thead>
<tr>
<th>Hostname</th>
<th>IP-Adresse</th>
<th>Status</th>
<th>Letzte Prüfung</th>
</tr>
</thead>
<tbody id="serverData">
<!-- Daten werden per JavaScript eingefügt -->
</tbody>
</table>
</div>
<div id="settingsPage" style="display: none;">
<h1>Einstellungen</h1>
<form class="settings-form" id="timezoneForm">
<label for="timezone">Zeitzone:</label>
<select id="timezone" name="timezone">
<option value="UTC">UTC</option>
<option value="Europe/Berlin">UTC+1 / UTC+2 (Berlin, MEZ/MESZ)</option>
<option value="UTC">UTC (Koordinierte Weltzeit)</option>
<option value="Europe/London">UTC+0 / UTC+1 (London, GMT/BST)</option>
<option value="Europe/Berlin">UTC+1 / UTC+2 (Berlin, MEZ/MESZ)</option>
<option value="Europe/Moscow">UTC+3 (Moskau, MSK)</option>
<option value="Asia/Dubai">UTC+4 (Dubai, GST)</option>
<option value="Asia/Karachi">UTC+5 (Karachi, PKT)</option>
<option value="Asia/Dhaka">UTC+6 (Dhaka, BST)</option>
<option value="Asia/Bangkok">UTC+7 (Bangkok, ICT)</option>
<option value="Asia/Shanghai">UTC+8 (Shanghai, CST)</option>
<option value="Asia/Tokyo">UTC+9 (Tokio, JST)</option>
<option value="Australia/Sydney">UTC+10 / UTC+11 (Sydney, AEST/AEDT)</option>
<option value="Pacific/Auckland">UTC+12 / UTC+13 (Auckland, NZST/NZDT)</option>
<option value="Pacific/Guadalcanal">UTC+11 (Guadalcanal, SBT)</option>
<option value="Asia/Kamchatka">UTC+12 (Kamtschatka, PETT)</option>
<option value="Pacific/Chatham">UTC+12:45 (Chatham, CHAST/CHADT)</option>
<option value="America/New_York">UTC-5 / UTC-4 (New York, EST/EDT)</option>
<option value="America/Chicago">UTC-6 / UTC-5 (Chicago, CST/CDT)</option>
<option value="America/Denver">UTC-7 / UTC-6 (Denver, MST/MDT)</option>
<option value="America/Los_Angeles">UTC-8 / UTC-7 (Los Angeles, PST/PDT)</option>
<option value="America/Anchorage">UTC-9 / UTC-8 (Anchorage, AKST/AKDT)</option>
<option value="Pacific/Honolulu">UTC-10 (Honolulu, HST)</option>
<option value="Pacific/Pago_Pago">UTC-11 (Pago Pago, SST)</option>
<option value="Pacific/Kiritimati">UTC+14 (Kiritimati, LINT)</option>
<option value="Atlantic/Azores">UTC-1 / UTC+0 (Azoren, AZOT/AZOST)</option>
<option value="Atlantic/Cape_Verde">UTC-1 (Kap Verde, CVT)</option>
</select>
<button type="submit">Speichern</button>
</form>
</div>
<script>
// Navigation
document.getElementById('hamburger').addEventListener('click', function() {
document.getElementById('navMenu').style.display = 'block';
});
document.getElementById('closeBtn').addEventListener('click', function() {
document.getElementById('navMenu').style.display = 'none';
});
document.getElementById('homeLink').addEventListener('click', function(e) {
e.preventDefault();
document.getElementById('content').style.display = 'block';
document.getElementById('settingsPage').style.display = 'none';
document.getElementById('navMenu').style.display = 'none';
});
document.getElementById('settingsLink').addEventListener('click', function(e) {
e.preventDefault();
document.getElementById('content').style.display = 'none';
document.getElementById('settingsPage').style.display = 'block';
document.getElementById('navMenu').style.display = 'none';
});
// Timezone Cookie
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
function setCookie(name, value, days = 365) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`;
}
function loadTimezone() {
const savedTimezone = getCookie('userTimezone');
if (savedTimezone) {
document.getElementById('timezone').value = savedTimezone;
}
}
document.getElementById('timezoneForm').addEventListener('submit', function(e) {
e.preventDefault();
const timezone = document.getElementById('timezone').value;
setCookie('userTimezone', timezone);
alert('Zeitzone gespeichert!');
loadServerData();
});
// Serverdaten laden
function loadServerData() {
fetch('status.txt')
.then(response => response.text())
.then(data => {
const lines = data.split('\n');
const tbody = document.getElementById('serverData');
tbody.innerHTML = '';
const userTimezone = getCookie('userTimezone') || 'UTC';
lines.forEach(line => {
if (line.trim() === '') return;
const [hostname, ip, status, checkdat, checktim] = line.split(' ');
const tr = document.createElement('tr');
// Kombiniere Datum und Uhrzeit zu einem ISO-String
const isoString = `${checkdat}T${checktim}Z`;
const date = new Date(isoString);
// Formatiere das Datum in die gewünschte Zeitzone
const formatter = new Intl.DateTimeFormat('de-DE', {
timeZone: userTimezone,
dateStyle: 'short',
timeStyle: 'medium',
hour12: false
});
const localTimeStr = formatter.format(date);
let dateStr = `${checkdat.replace(/T/, ' ')} ${checktim.replace(/T/, ' ')}`;
if (userTimezone !== 'UTC') {
dateStr += ` (${userTimezone})`;
}
tr.innerHTML = `
<td>${hostname}</td>
<td>${ip}</td>
<td><span class="status ${status.toLowerCase()}">${status}</span></td>
<td class="last-check">${localTimeStr} (${userTimezone}}</td>
`;
tbody.appendChild(tr);
});
})
.catch(error => {
console.error('Fehler beim Laden der status.conf:', error);
document.getElementById('serverData').innerHTML = `
<tr>
<td colspan="4" style="text-align: center; color: red;">
Fehler beim Laden der Serverdaten.
</td>
</tr>
`;
});
}
document.addEventListener('DOMContentLoaded', function() {
loadTimezone();
loadServerData();
});
</script>
</body>
</html>

61
mail_fds.py Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env python3
import smtplib
import argparse
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def read_credentials(file_path):
"""Liest SMTP-Serverdetails und Credentials aus einer Textdatei.
Format der Datei (jeweils eine Zeile):
smtp_server:port
username
password
from_address
to_address
"""
with open(file_path, 'r') as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
if len(lines) != 5:
raise ValueError("Die Credential-Datei muss genau 5 Zeilen enthalten.")
smtp_server, smtp_port = lines[0].split(':')
username = lines[1]
password = lines[2]
from_addr = lines[3]
to_addr = lines[4]
return smtp_server, smtp_port, username, password, from_addr, to_addr
def send_email(smtp_server, smtp_port, username, password, from_addr, to_addr, subject, body):
"""Versendet eine E-Mail mit den angegebenen Parametern."""
msg = MIMEMultipart()
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
try:
with smtplib.SMTP(smtp_server, int(smtp_port)) as server:
server.starttls()
server.login(username, password)
server.send_message(msg)
print("E-Mail erfolgreich versendet.")
except Exception as e:
print(f"Fehler beim Versand der E-Mail: {e}")
def main():
parser = argparse.ArgumentParser(description='Versendet eine E-Mail mit festem Textinhalt und dynamischem Betreff.')
parser.add_argument('--subject', type=str, required=True, help='Betreff der E-Mail')
parser.add_argument('--credentials', type=str, required=True, help='Pfad zur Credential-Datei')
args = parser.parse_args()
smtp_server, smtp_port, username, password, from_addr, to_addr = read_credentials(args.credentials)
body = """Hallo,
dies ist eine automatisch generierte E-Mail.
Mit freundlichen Grüßen,
Dein Skript"""
send_email(smtp_server, smtp_port, username, password, from_addr, to_addr, args.subject, body)
if __name__ == "__main__":
main()