Files
WebChecker/CLAUDE.md
T
2026-04-22 12:22:26 -04:00

11 KiB
Raw Blame History

CLAUDE.md — Website Checker: Complete Technical Knowledge Base

This document captures the full architecture, design decisions, database schema, module inventory, feature set, and operational notes for the Website Checker desktop application.


1. Application Overview

Website Checker is a Python/Tkinter desktop application for shift-based website monitoring. Regular users log in, view websites assigned to their shift, open each site, and mark it checked. Administrators manage users, websites, shifts, credentials, and run reports.

  • UI: Python tkinter + ttk
  • Database: Remote MySQL 5.7+ / MariaDB 10.3+ via mysql-connector-python
  • Entry point: app.pyclass App(tk.Tk)
  • Default window: 1100×700, minimum 960×620
  • Default theme: Light (toggleable to Dark)

2. Project File Structure

website_checker/
├── app.py                          Entry point, shell, navigation, session management
├── config.py                       DB config, connection pool, schema DDL, migrations
├── models.py                       All database access (data layer)
├── requirements.txt
├── CLAUDE.md                       This file
├── utils/
│   ├── crypto.py                   Fernet credential encryption
│   ├── export.py                   CSV + Excel export
│   ├── scheduler.py                Daily email report background daemon
│   └── ui_helpers.py               ThemeManager, COLOURS proxy, DateEntry, widgets
└── views/
    ├── admin_dashboard_view.py     Admin home — KPI cards + per-user progress table
    ├── admin_log_view.py           Activity log treeview
    ├── admin_shifts_view.py        Shift CRUD + PDF export
    ├── admin_users_view.py         User CRUD
    ├── admin_websites_view.py      Website CRUD + visibility + credentials
    ├── change_password_view.py     Self-service password change (all roles)
    ├── email_settings_view.py      SMTP / scheduled report configuration
    ├── login_view.py               Login form with rate-limiting countdown
    ├── reports_view.py             Shift Detail / Unchecked / Summary / Chart tabs
    ├── settings_view.py            DB connection settings dialog
    └── user_dashboard_view.py      User checklist — search, bulk check, notifications

3. Runtime Files

config.ini

Created on first launch. Contains [database], [email], and [crypto] sections.

[database]
host=your-mysql-host
port=3306
database=website_checker
user=your-db-user
password=your-db-password

[email]
enabled=false
smtp_host=smtp.example.com
smtp_port=587
smtp_user=sender@example.com
smtp_password=secret
use_tls=true
recipients=admin@example.com
send_time=18:00

[crypto]
salt=<base64 32-byte salt — auto-generated>

app.log

UTF-8 log file in working directory. All INFO/WARNING/ERROR from every module.


4. Database Schema

All tables use InnoDB/utf8mb4. Created by initialize_database() on first launch. Safe ALTER TABLE migrations run automatically for columns added post-deployment.

users

Column Type Notes
id INT PK AUTO
username VARCHAR(100) UNIQUE
password VARCHAR(255) bcrypt hash. Legacy SHA-256 (64 hex) auto-migrated on login
role ENUM('admin','user')
full_name VARCHAR(200)
is_active TINYINT(1)
failed_attempts TINYINT UNSIGNED Incremented on bad login; reset on success
locked_until DATETIME NULL Set when failed_attempts >= MAX_FAILED_ATTEMPTS (5)
created_at / updated_at DATETIME

websites

Column Type Notes
id INT PK AUTO
name VARCHAR(200)
url TEXT
check_type ENUM('daily','weekly') weekly = once per ISO week
visibility ENUM('all','assigned') assigned = only users in website_users table
note TEXT Shown on user dashboard card
is_active TINYINT(1) Soft-delete
created_by INT FK users SET NULL

website_credentials

Column Notes
website_id FK
username Plaintext
password Fernet-encrypted with enc: prefix
label e.g. "Admin", "Read-only"

shift_checks

One row per user per site per day (upserted). user_note optional.

activity_log

action codes: LOGIN, LOGOUT, SESSION_TIMEOUT, ACCOUNT_LOCKED, CREATE/UPDATE/DELETE_USER, CHANGE_PASSWORD, CHANGE_PASSWORD_FAIL, CREATE/UPDATE/DELETE_WEBSITE, ADD/REMOVE_CREDENTIAL, CREATE/UPDATE/DELETE_SHIFT, CHECK_WEBSITE, UPDATE_NOTE, EXPORT_CSV, EXPORT_EXCEL, EXPORT_SHIFT_PDF, UPDATE_EMAIL_SETTINGS.

shifts

days_of_week is a digit string using MySQL DAYOFWEEK: 1=Sun 2=Mon 3=Tue 4=Wed 5=Thu 6=Fri 7=Sat. E.g. "23456" = MonFri. Queried with LOCATE(DAYOFWEEK(CURDATE()), days_of_week) > 0.

shift_users (junction): (shift_id, user_id) PK, both CASCADE

shift_websites (junction): (shift_id, website_id) PK + sort_order

login_attempts: username, attempted_at, ip_address (indexed)

website_users (junction): (website_id, user_id) PK — for visibility='assigned'


5. Module Details

config.py

  • DB_CONFIG loaded from config.ini at import; placeholders if file missing
  • get_connection() → pool (pool_size=5)
  • initialize_database() → idempotent DDL + ALTER TABLE migrations
  • load_config() / save_config() → config.ini read/write
  • config_exists() → True if host, database, user are set
  • reload_db_config() → re-reads config.ini, resets pool

models.py

Each function opens+closes its own connection. All writes call log_action().

Password helpers:

  • _hash_password(pw) → bcrypt rounds=12
  • _verify_password(pw, stored) → handles bcrypt + legacy SHA-256
  • _needs_rehash(stored) → True for 64-char hex (SHA-256)

Rate-limit constants: MAX_FAILED_ATTEMPTS=5, LOCKOUT_MINUTES=15 Password strength: PW_MIN_LENGTH=8, requires upper + digit + special char

utils/crypto.py

  • PBKDF2-HMAC-SHA256, 100,000 iterations, 32-byte salt from config.ini [crypto]
  • encrypt(plaintext) → "enc:" + base64(Fernet token)
  • decrypt(ciphertext) → plaintext; legacy plaintext (no enc: prefix) passes through unchanged
  • reset_fernet() → force key reload after config.ini replacement

utils/ui_helpers.py

  • ThemeManager singleton — initial="light". toggle(rebuild_callback) flips theme.
  • COLOURS = _ColourProxy(dict) — always delegates to ThemeManager.get(). Import once, always current.
  • THEMES["dark"] and THEMES["light"] each have 18 colour keys + 4 cal_* keys
  • DateEntry — Frame subclass; .get() → "YYYY-MM-DD", .set(str). Calendar popup with prev/next month+year.
  • make_scrollable_frame() — mousewheel scoped to Enter/Leave to prevent stale-widget crashes
  • scrolled_text(parent, height, width) → (frame, tk.Text)

utils/scheduler.py

  • Daemon thread; polls every 60 seconds
  • Sends HTML email once per day when now >= send_time
  • last_sent_date in-memory (resets on restart)
  • start() / stop() called from app.py on login/logout (admin only)

views/login_view.py

  • check_login_allowed(username) called before authenticate()
  • Locked: form disabled; 1-second countdown; re-enables when timer reaches 0
  • Not-yet-locked failed attempt: shows "N attempt(s) remaining before lockout"

views/user_dashboard_view.py

Key internals:

  • _health_cache: {website_id: ("ok"|"slow"|"down", ms)} — daemon threads, HEAD request, 6s timeout
  • Dot colours: green (<=3000ms), amber (>3000ms), red (error)
  • Mousewheel: Enter/Leave scoped; unbind_all in destroy()
  • Keyboard shortcuts: self.bind() stored in _shortcut_ids; unbound in destroy()
  • Notifications: after(60_000) loop; plyer first, fallback to borderless Toplevel toast

views/admin_shifts_view.py

  • _export_pdf(): reportlab A4 document; one section per active shift; user+website tables

views/reports_view.py

  • All date fields use DateEntry (no manual text entry)
  • Chart: matplotlib Agg + FigureCanvasTkAgg; bars green>=100% / amber>=50% / red<50%

6. Authentication & Security

Concern Implementation
Password hashing bcrypt rounds=12; SHA-256 auto-rehashed on next login
Credential encryption Fernet (AES-128-CBC + HMAC) via cryptography library
Login rate limiting 5 attempts → 15-min lockout in DB
Session timeout 30-min idle; 1-min warning; any mouse/key event resets
Password policy 8+ chars, uppercase, digit, special character
Self-service change Requires current password; strength meter; same-as-current guard

7. Key Business Logic

check_type

  • daily: shown every shift day
  • weekly: hidden once checked this week (YEARWEEK ISO); reappears Monday

visibility

  • all: every user in shift sees it
  • assigned: only users in website_users table see it

get_today_checks resolution

  1. Check if user has active shifts today (DAYOFWEEK match)
  2. If yes: union of shift websites filtered by check_type + visibility
  3. If no: all active websites filtered by visibility + check_type (legacy fallback)

Soft deletes

Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first).


8. Configuration Constants (edit in source)

File Constant Default
app.py IDLE_TIMEOUT_MS 1,800,000 (30 min)
app.py IDLE_WARNING_MS 60,000 (1 min)
app.py APP_VERSION "1.0.0"
app.py VERSION_CHECK_ENABLED False
models.py MAX_FAILED_ATTEMPTS 5
models.py LOCKOUT_MINUTES 15
models.py PW_MIN_LENGTH 8
user_dashboard_view.py NOTIFY_MINUTES_BEFORE 15
admin_dashboard_view.py REFRESH_INTERVAL_MS 60,000
utils/crypto.py _ITERATIONS 100,000

9. Critical Gotchas

  1. bind_all("") is NEVER used at module level. Always Enter/Leave scoped. Every view's destroy() calls unbind_all("") and _unbind_shortcuts().

  2. COLOURS is a live proxy — delegates to ThemeManager.get() on every access. Never snapshot it into a local variable at class-creation time.

  3. bcrypt is slow by design (~200-400ms at rounds=12). Expected behaviour.

  4. config.ini stores DB password and SMTP password in plaintext. Website credential passwords are Fernet-encrypted.

  5. get_today_checks GROUP BY includes sc.id to prevent row collisions when a user belongs to multiple shifts sharing the same website.

  6. Weekly site logic uses YEARWEEK(..., 1) (ISO week, Monday start).

  7. The email scheduler last_sent_date is in-memory — resets on restart. A production deployment should persist it to the DB.

  8. All log messages use ASCII only (hyphens not em-dashes/arrows) to prevent cp1252 UnicodeEncodeError on Windows consoles.


10. Dependencies

mysql-connector-python>=8.0.0
bcrypt>=4.0.0
openpyxl>=3.1.0
cryptography>=41.0.0
matplotlib>=3.7.0
plyer>=2.1.0
reportlab>=4.0.0

stdlib used: tkinter, csv, smtplib, urllib.request, configparser, threading, calendar, datetime


11. First-Run Flow

  1. _boot() → config_exists() → False → SettingsView (locked modal)
  2. User enters DB creds → Test Connection → Save & Connect
  3. reload_db_config() → initialize_database() → seeds admin/admin123 if users empty
  4. Login screen shown
  5. ThemeManager(root, initial="light") applied; shell built

12. Deployment Notes

  • Python 3.9+ required. 3.12 tested on Windows.
  • tkinter bundled on Windows/macOS; Linux: apt install python3-tk
  • Create DB first: CREATE DATABASE website_checker CHARACTER SET utf8mb4;
  • MySQL port 3306 must be reachable from client
  • Default admin: username=admin / password=admin123 — change immediately