:qErster Commit
This commit is contained in:
commit
fd8c64c9fe
7 changed files with 470 additions and 0 deletions
24
README.md
Normal file
24
README.md
Normal 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
73
check_fds.sh
Executable 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
6
credentials-sample.conf
Normal 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
0
echo
Normal file
7
hosts-sample.conf
Normal file
7
hosts-sample.conf
Normal 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
299
html/mon.html
Normal 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 Ü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
61
mail_fds.py
Executable 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()
|
||||
Loading…
Add table
Reference in a new issue