fixed most of the issues i could find with to little sleep

This commit is contained in:
Kendrick Bollens
2026-01-30 00:17:49 +01:00
parent cb39532320
commit 41938a5c47
4 changed files with 370 additions and 15 deletions

BIN
cleaning_logs.db Normal file

Binary file not shown.

323
dashboard.py Normal file
View File

@@ -0,0 +1,323 @@
import sqlite3
from flask import Flask, render_template_string, jsonify
from datetime import datetime, date
app = Flask(__name__)
DB_FILE = "cleaning_logs.db"
# Room definitions
BUILDINGS = {
"Haus A-C": [
list(range(101, 131)),
list(range(201, 231)),
list(range(301, 331))
],
"Haus D-F": [
list(range(131, 151)),
list(range(231, 251)),
list(range(331, 351))
],
"Haus O": [
list(range(401, 413)),
list(range(420, 436)),
list(range(440, 457))
]
}
def get_all_defined_rooms():
"""Get a set of all room numbers defined in buildings"""
rooms = set()
for floors in BUILDINGS.values():
for floor in floors:
rooms.update(str(r) for r in floor)
return rooms
def get_room_statuses():
"""Returns dict: {room_number: (last_cleaned_time, is_today)}"""
today = date.today().strftime("%Y-%m-%d")
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.execute("""
SELECT room_number, MAX(cleaned_at) as last_cleaned
FROM cleaning_events
GROUP BY room_number
""")
rows = cursor.fetchall()
statuses = {}
for room, last_cleaned in rows:
is_today = last_cleaned.startswith(today) if last_cleaned else False
statuses[room] = {
"is_today": is_today,
"last_cleaned": last_cleaned
}
return statuses
def get_special_rooms(statuses):
"""Get rooms that aren't in any defined building"""
defined_rooms = get_all_defined_rooms()
special_rooms = []
for room_number in statuses.keys():
if room_number not in defined_rooms:
special_rooms.append(room_number)
return sorted(special_rooms)
@app.route('/')
def dashboard():
statuses = get_room_statuses()
special_rooms = get_special_rooms(statuses)
return render_template_string(
TEMPLATE,
buildings=BUILDINGS,
statuses=statuses,
special_rooms=special_rooms
)
@app.route('/api/status')
def api_status():
"""API endpoint for live updates"""
statuses = get_room_statuses()
special_rooms = get_special_rooms(statuses)
return jsonify({
"statuses": statuses,
"special_rooms": special_rooms
})
TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>
<title>Room Cleaning Dashboard</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
h1 { color: #333; }
.controls { margin-bottom: 20px; }
.toggle { padding: 10px 20px; font-size: 16px; cursor: pointer; }
.building { margin-bottom: 30px; background: white; padding: 15px; border-radius: 8px; }
.building h2 { margin-top: 0; color: #555; }
.floor { margin-bottom: 15px; }
.floor-label { font-weight: bold; margin-bottom: 5px; color: #777; }
.rooms { display: flex; flex-wrap: wrap; gap: 8px; }
.room {
padding: 10px 15px;
border-radius: 5px;
min-width: 80px;
text-align: center;
font-weight: bold;
transition: background 0.3s ease;
}
.room.cleaned { background: #4CAF50; color: white; }
.room.not-cleaned { background: #f44336; color: white; }
.room.cleaned .time-display, .room.not-cleaned .time-display {
display: block;
font-size: 14px;
font-weight: normal;
margin-top: 6px;
line-height: 1.4;
}
.hidden { display: none !important; }
</style>
</head>
<body>
<h1>🧹 Room Cleaning Status</h1>
<div class="controls">
<button class="toggle" onclick="toggleFilter()">
<span id="toggle-text">Show Only Cleaned Today</span>
</button>
</div>
{% for building_name, floors in buildings.items() %}
<div class="building" data-building="{{ building_name }}">
<h2>{{ building_name }}</h2>
{% for floor in floors %}
<div class="floor" data-floor="{{ building_name }}-{{ loop.index }}">
<div class="floor-label">Etage {{ loop.index }}</div>
<div class="rooms">
{% for room in floor %}
{% set room_str = room|string %}
{% set status = statuses.get(room_str, {"is_today": False, "last_cleaned": None}) %}
<div class="room {{ 'cleaned' if status.is_today else 'not-cleaned' }}"
data-cleaned="{{ 'yes' if status.is_today else 'no' }}"
data-room="{{ room }}">
{{ room }}
<span class="time-display">
{% if status.last_cleaned %}
{{ status.last_cleaned[5:10] }}<br>{{ status.last_cleaned[11:16] }}
{% else %}Never{% endif %}
</span>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
{% if special_rooms %}
<div class="building" data-building="Sonderräume">
<h2>Sonderräume</h2>
<div class="floor" data-floor="Sonderräume-1">
<div class="rooms" id="special-rooms-container">
{% for room in special_rooms %}
{% set status = statuses.get(room, {"is_today": False, "last_cleaned": None}) %}
<div class="room {{ 'cleaned' if status.is_today else 'not-cleaned' }}"
data-cleaned="{{ 'yes' if status.is_today else 'no' }}"
data-room="{{ room }}"
data-special="true">
{{ room }}
<span class="time-display">
{% if status.last_cleaned %}
{{ status.last_cleaned[5:10] }}<br>{{ status.last_cleaned[11:16] }}
{% else %}Never{% endif %}
</span>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<script>
let filterActive = false;
function formatDateTime(datetimeStr) {
if (!datetimeStr) return 'Never';
const date = datetimeStr.slice(5, 10);
const time = datetimeStr.slice(11, 16);
return date + '<br>' + time;
}
function applyFilter() {
const rooms = document.querySelectorAll('.room');
const floors = document.querySelectorAll('.floor');
const buildings = document.querySelectorAll('.building');
if (filterActive) {
rooms.forEach(room => {
if (room.dataset.cleaned === 'no') {
room.classList.add('hidden');
} else {
room.classList.remove('hidden');
}
});
floors.forEach(floor => {
const visibleRooms = floor.querySelectorAll('.room:not(.hidden)');
if (visibleRooms.length === 0) {
floor.classList.add('hidden');
} else {
floor.classList.remove('hidden');
}
});
buildings.forEach(building => {
const visibleFloors = building.querySelectorAll('.floor:not(.hidden)');
if (visibleFloors.length === 0) {
building.classList.add('hidden');
} else {
building.classList.remove('hidden');
}
});
} else {
rooms.forEach(room => room.classList.remove('hidden'));
floors.forEach(floor => floor.classList.remove('hidden'));
buildings.forEach(building => building.classList.remove('hidden'));
}
}
function toggleFilter() {
filterActive = !filterActive;
const toggleText = document.getElementById('toggle-text');
if (filterActive) {
toggleText.textContent = 'Show All Rooms';
} else {
toggleText.textContent = 'Show Only Cleaned Today';
}
applyFilter();
}
async function updateStatuses() {
try {
const response = await fetch('/api/status');
const data = await response.json();
const statuses = data.statuses;
const specialRooms = data.special_rooms;
// Update existing rooms
document.querySelectorAll('.room').forEach(roomDiv => {
const roomNumber = roomDiv.dataset.room;
const status = statuses[roomNumber] || { is_today: false, last_cleaned: null };
if (status.is_today) {
roomDiv.classList.remove('not-cleaned');
roomDiv.classList.add('cleaned');
roomDiv.dataset.cleaned = 'yes';
} else {
roomDiv.classList.remove('cleaned');
roomDiv.classList.add('not-cleaned');
roomDiv.dataset.cleaned = 'no';
}
const timeDisplay = roomDiv.querySelector('.time-display');
timeDisplay.innerHTML = formatDateTime(status.last_cleaned);
});
// Update special rooms section
const specialContainer = document.getElementById('special-rooms-container');
if (specialContainer && specialRooms.length > 0) {
const existingSpecialRooms = new Set(
Array.from(specialContainer.querySelectorAll('.room'))
.map(r => r.dataset.room)
);
// Add new special rooms
specialRooms.forEach(roomNumber => {
if (!existingSpecialRooms.has(roomNumber)) {
const status = statuses[roomNumber];
const roomDiv = document.createElement('div');
roomDiv.className = 'room ' + (status.is_today ? 'cleaned' : 'not-cleaned');
roomDiv.dataset.cleaned = status.is_today ? 'yes' : 'no';
roomDiv.dataset.room = roomNumber;
roomDiv.dataset.special = 'true';
roomDiv.innerHTML = `
${roomNumber}
<span class="time-display">${formatDateTime(status.last_cleaned)}</span>
`;
specialContainer.appendChild(roomDiv);
}
});
// Show Sonderräume section if it was hidden
const sonderraumeBuilding = document.querySelector('[data-building="Sonderräume"]');
if (sonderraumeBuilding) {
sonderraumeBuilding.classList.remove('hidden');
}
} else if (specialRooms.length === 0) {
// Hide Sonderräume if no special rooms exist
const sonderraumeBuilding = document.querySelector('[data-building="Sonderräume"]');
if (sonderraumeBuilding) {
sonderraumeBuilding.classList.add('hidden');
}
}
applyFilter();
} catch (error) {
console.error('Failed to update statuses:', error);
}
}
setInterval(updateStatuses, 1000);
</script>
</body>
</html>
'''
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=True)

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1769461804,
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

35
test.py
View File

@@ -3,37 +3,42 @@ from flask import Flask, request
from datetime import datetime from datetime import datetime
app = Flask(__name__) app = Flask(__name__)
DB_FILE = "logs.db" DB_FILE = "cleaning_logs.db"
def init_db(): def init_db():
with sqlite3.connect(DB_FILE) as conn: with sqlite3.connect(DB_FILE) as conn:
conn.execute(''' conn.execute('''
CREATE TABLE IF NOT EXISTS post_logs ( CREATE TABLE IF NOT EXISTS cleaning_events (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT, room_number TEXT,
content_type TEXT, cleaned_at TEXT
body TEXT
) )
''') ''')
@app.route('/', methods=['POST']) @app.route('/', methods=['POST'])
def handle_post(): def handle_post():
content_type = request.headers.get('Content-Type') # Extract the room number from the POST body
body = request.get_data(as_text=True) # .strip('"') removes extra quotes if the sender includes them
timestamp = datetime.now().isoformat() room_number = request.get_data(as_text=True).strip().strip('"')
# Log to Console if not room_number:
print(f"[{timestamp}] Logging POST: {body[:50]}...") return "Missing room number", 400
# Log to SQLite timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Log to Console for your visibility
print(f"Room {room_number} marked as cleaned at {timestamp}")
# Save to SQLite
with sqlite3.connect(DB_FILE) as conn: with sqlite3.connect(DB_FILE) as conn:
conn.execute( conn.execute(
"INSERT INTO post_logs (timestamp, content_type, body) VALUES (?, ?, ?)", "INSERT INTO cleaning_events (room_number, cleaned_at) VALUES (?, ?)",
(timestamp, content_type, body) (room_number, timestamp)
) )
return "Logged successfully", 200 return f"Room {room_number} logged.", 200
if __name__ == '__main__': if __name__ == '__main__':
init_db() init_db()
app.run(host='0.0.0.0', port=5000, debug=True) # Binding to 0.0.0.0 so devices on your network can reach it
app.run(host='0.0.0.0', port=5000)