diff --git a/requirements.txt b/requirements.txt
index 47b2242..60327ed 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
paho-mqtt>=1.6.1
-schedule>=1.2.0
\ No newline at end of file
+schedule>=1.2.0
+flask>=2.0.0
\ No newline at end of file
diff --git a/run_web.sh b/run_web.sh
new file mode 100755
index 0000000..293c79e
--- /dev/null
+++ b/run_web.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# Start the Flask web application
+
+cd "$(dirname "$0")"
+
+# Check if virtual environment exists, if not create it
+if [ ! -d "venv" ]; then
+ echo "Creating virtual environment..."
+ python3 -m venv venv
+fi
+
+# Activate virtual environment
+source venv/bin/activate
+
+# Install dependencies
+echo "Installing dependencies..."
+pip install -q -r requirements.txt
+
+# Check if database exists
+if [ ! -f "karung_counts.db" ]; then
+ echo "Error: Database karung_counts.db not found!"
+ echo "Please run the frigate_counter.py first to initialize the database."
+ exit 1
+fi
+
+# Run the Flask application
+echo "Starting Flask web application..."
+echo "Access the dashboard at: http://localhost:5000"
+python web_app.py
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..09f75cb
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,222 @@
+
+
+
+
+
+ {% block title %}Frigate Counter Dashboard{% endblock %}
+
+ {% block extra_css %}{% endblock %}
+
+
+
+
+
Frigate Counter Dashboard
+
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+
+ {% block extra_js %}{% endblock %}
+
+
diff --git a/templates/camera_detail.html b/templates/camera_detail.html
new file mode 100644
index 0000000..76fecc1
--- /dev/null
+++ b/templates/camera_detail.html
@@ -0,0 +1,52 @@
+{% extends "base.html" %}
+
+{% block title %}{{ camera_name }} - Camera Details{% endblock %}
+
+{% block content %}
+
+
Camera: {{ camera_name }}
+
+
+
+
{{ counts|length }}
+
Days with Data
+
+
+
{{ total }}
+
Total Counts
+
+
+
{{ (total / counts|length)|round(1) if counts|length > 0 else 0 }}
+
Average per Day
+
+
+
+ {% if counts %}
+
+
+
+ | Date |
+ Count |
+ Timestamp |
+
+
+
+ {% for item in counts %}
+
+ | {{ item.date }} |
+ {{ item.counter_value }} |
+ {{ item.timestamp }} |
+
+ {% endfor %}
+
+
+ {% else %}
+
+
No data for this camera
+
There are no records for camera {{ camera_name }}.
+
+ {% endif %}
+
+
← Back to Dashboard
+
+{% endblock %}
diff --git a/templates/date_detail.html b/templates/date_detail.html
new file mode 100644
index 0000000..5809e93
--- /dev/null
+++ b/templates/date_detail.html
@@ -0,0 +1,58 @@
+{% extends "base.html" %}
+
+{% block title %}{{ date }} - Daily Details{% endblock %}
+
+{% block content %}
+
+
Details for {{ date }}
+
+
+
+
+
+
+
+
+
{{ counts|length }}
+
Camera Entries
+
+
+
{{ total }}
+
Total Counts
+
+
+
+ {% if counts %}
+
+
+
+ | Camera Name |
+ Count |
+ Timestamp |
+
+
+
+ {% for item in counts %}
+
+ | {{ item.camera_name }} |
+ {{ item.counter_value }} |
+ {{ item.timestamp }} |
+
+ {% endfor %}
+
+
+ {% else %}
+
+
No data for this date
+
There are no records for {{ date }}.
+
+ {% endif %}
+
+
← Back to Dashboard
+
+{% endblock %}
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..07977c2
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,123 @@
+{% extends "base.html" %}
+
+{% block title %}Dashboard - Frigate Counter{% endblock %}
+
+{% block content %}
+
+
+
+
{{ today_data|length }}
+
Cameras Active Today
+
+
+
{{ today_data|sum(attribute='counter_value') }}
+
Total Counts Today
+
+
+
{{ summary|length }}
+
Days with Data
+
+
+
{{ cameras|sum(attribute='total_count') }}
+
All Time Total
+
+
+
+
+
+
Today's Activity ({{ today }})
+ {% if today_data %}
+
+
+
+ | Camera |
+ Count |
+ Last Updated |
+
+
+
+ {% for item in today_data %}
+
+ | {{ item.camera_name }} |
+ {{ item.counter_value }} |
+ {{ item.timestamp }} |
+
+ {% endfor %}
+
+
+ {% else %}
+
+
No data for today yet
+
Counts will appear here once the counter starts receiving events.
+
+ {% endif %}
+
+
+
+
+
Daily Summary
+ {% if summary %}
+
+
+
+ | Date |
+ Cameras |
+ Total Counts |
+ Last Update |
+ Action |
+
+
+
+ {% for day in summary %}
+
+ | {{ day.date }} |
+ {{ day.camera_count }} |
+ {{ day.total_count }} |
+ {{ day.last_update }} |
+ View Details |
+
+ {% endfor %}
+
+
+ {% else %}
+
+
No data available
+
Daily summaries will appear here once data is recorded.
+
+ {% endif %}
+
+
+
+
+
Camera Summary
+ {% if cameras %}
+
+
+
+ | Camera Name |
+ Days Active |
+ Total Counts |
+ Last Update |
+ Action |
+
+
+
+ {% for camera in cameras %}
+
+ | {{ camera.camera_name }} |
+ {{ camera.day_count }} |
+ {{ camera.total_count }} |
+ {{ camera.last_update }} |
+ View Details |
+
+ {% endfor %}
+
+
+ {% else %}
+
+
No cameras registered
+
Camera data will appear here once events are processed.
+
+ {% endif %}
+
+{% endblock %}
diff --git a/web_app.py b/web_app.py
new file mode 100644
index 0000000..34160ea
--- /dev/null
+++ b/web_app.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+"""
+Flask Web Application for Frigate Counter
+Displays daily karung counts from SQLite database
+"""
+
+from flask import Flask, render_template, jsonify, request
+import sqlite3
+from datetime import datetime, date, timedelta
+from typing import List, Dict, Optional
+import os
+
+app = Flask(__name__)
+DB_PATH = 'karung_counts.db'
+
+
+def get_db_connection():
+ """Create a database connection"""
+ conn = sqlite3.connect(DB_PATH)
+ conn.row_factory = sqlite3.Row
+ return conn
+
+
+def get_daily_counts(selected_date: Optional[str] = None) -> List[Dict]:
+ """Get counts for a specific date or all dates"""
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ if selected_date:
+ cursor.execute('''
+ SELECT camera_name, date, counter_value, timestamp
+ FROM karung_counts
+ WHERE date = ?
+ ORDER BY timestamp DESC
+ ''', (selected_date,))
+ else:
+ cursor.execute('''
+ SELECT camera_name, date, counter_value, timestamp
+ FROM karung_counts
+ ORDER BY date DESC, timestamp DESC
+ ''')
+
+ rows = cursor.fetchall()
+ conn.close()
+
+ return [{
+ 'camera_name': row['camera_name'],
+ 'date': row['date'],
+ 'counter_value': row['counter_value'],
+ 'timestamp': row['timestamp']
+ } for row in rows]
+
+
+def get_summary_by_date() -> List[Dict]:
+ """Get daily summary grouped by date"""
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT
+ date,
+ COUNT(DISTINCT camera_name) as camera_count,
+ SUM(counter_value) as total_count,
+ MAX(timestamp) as last_update
+ FROM karung_counts
+ GROUP BY date
+ ORDER BY date DESC
+ ''')
+
+ rows = cursor.fetchall()
+ conn.close()
+
+ return [{
+ 'date': row['date'],
+ 'camera_count': row['camera_count'],
+ 'total_count': row['total_count'] or 0,
+ 'last_update': row['last_update']
+ } for row in rows]
+
+
+def get_available_dates() -> List[str]:
+ """Get list of all dates with data"""
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT DISTINCT date
+ FROM karung_counts
+ ORDER BY date DESC
+ ''')
+
+ rows = cursor.fetchall()
+ conn.close()
+
+ return [row['date'] for row in rows]
+
+
+def get_camera_summary() -> List[Dict]:
+ """Get summary by camera"""
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT
+ camera_name,
+ COUNT(DISTINCT date) as day_count,
+ SUM(counter_value) as total_count,
+ MAX(timestamp) as last_update
+ FROM karung_counts
+ GROUP BY camera_name
+ ORDER BY camera_name
+ ''')
+
+ rows = cursor.fetchall()
+ conn.close()
+
+ return [{
+ 'camera_name': row['camera_name'],
+ 'day_count': row['day_count'],
+ 'total_count': row['total_count'] or 0,
+ 'last_update': row['last_update']
+ } for row in rows]
+
+
+@app.route('/')
+def index():
+ """Main page showing daily summary"""
+ summary = get_summary_by_date()
+ cameras = get_camera_summary()
+ available_dates = get_available_dates()
+
+ today = date.today().isoformat()
+ today_data = get_daily_counts(today)
+
+ return render_template('index.html',
+ summary=summary,
+ cameras=cameras,
+ today_data=today_data,
+ available_dates=available_dates,
+ today=today)
+
+
+@app.route('/date/')
+def date_detail(date_str: str):
+ """Show details for a specific date"""
+ counts = get_daily_counts(date_str)
+ available_dates = get_available_dates()
+
+ # Calculate total for this date
+ total = sum(c['counter_value'] for c in counts)
+
+ return render_template('date_detail.html',
+ date=date_str,
+ counts=counts,
+ total=total,
+ available_dates=available_dates)
+
+
+@app.route('/camera/')
+def camera_detail(camera_name: str):
+ """Show details for a specific camera"""
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT date, counter_value, timestamp
+ FROM karung_counts
+ WHERE camera_name = ?
+ ORDER BY date DESC
+ ''', (camera_name,))
+
+ rows = cursor.fetchall()
+ conn.close()
+
+ counts = [{
+ 'date': row['date'],
+ 'counter_value': row['counter_value'],
+ 'timestamp': row['timestamp']
+ } for row in rows]
+
+ total = sum(c['counter_value'] for c in counts)
+
+ return render_template('camera_detail.html',
+ camera_name=camera_name,
+ counts=counts,
+ total=total)
+
+
+@app.route('/api/counts')
+def api_counts():
+ """API endpoint to get all counts"""
+ date_filter = request.args.get('date')
+ counts = get_daily_counts(date_filter)
+ return jsonify({
+ 'counts': counts,
+ 'date_filter': date_filter
+ })
+
+
+@app.route('/api/summary')
+def api_summary():
+ """API endpoint to get daily summary"""
+ summary = get_summary_by_date()
+ return jsonify({'summary': summary})
+
+
+@app.route('/api/cameras')
+def api_cameras():
+ """API endpoint to get camera summary"""
+ cameras = get_camera_summary()
+ return jsonify({'cameras': cameras})
+
+
+if __name__ == '__main__':
+ # Ensure database exists
+ if not os.path.exists(DB_PATH):
+ print(f"Error: Database {DB_PATH} not found!")
+ exit(1)
+
+ # Run Flask app on all interfaces, port 5000
+ app.run(host='0.0.0.0', port=5000, debug=True)