04/22 user dashboard view, no credentials popup automatically

This commit is contained in:
2026-04-22 18:38:57 -04:00
parent b7e2a164c7
commit eab4207e1f
10 changed files with 479 additions and 62 deletions
+132 -18
View File
@@ -29,7 +29,8 @@ website_checker/
├── requirements.txt
├── CLAUDE.md This file
├── utils/
│ ├── crypto.py Fernet credential encryption
│ ├── config_crypto.py Windows DPAPI encryption for config.ini credentials
│ ├── crypto.py Fernet credential encryption (website passwords)
│ ├── export.py CSV + Excel export
│ ├── scheduler.py Daily email report background daemon
│ └── ui_helpers.py ThemeManager, COLOURS proxy, DateEntry, widgets
@@ -38,7 +39,8 @@ website_checker/
├── 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
├── admin_websites_view.py Website CRUD + visibility + credentials (collapsible)
├── ai_summary_view.py AI document analysis via Groq API (NEW)
├── 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
@@ -52,26 +54,31 @@ website_checker/
## 3. Runtime Files
### `config.ini`
Created on first launch. Contains [database], [email], and [crypto] sections.
Created on first launch. Contains [database], [email], [crypto], and [groq] sections.
**Sensitive fields are DPAPI-encrypted** (see §6 Security). Encrypted values have a `dpapi:` prefix.
```ini
[database]
host=your-mysql-host
port=3306
database=website_checker
user=your-db-user
password=your-db-password
user=dpapi:<base64-blob> ; encrypted
password=dpapi:<base64-blob> ; encrypted
[email]
enabled=false
smtp_host=smtp.example.com
smtp_port=587
smtp_user=sender@example.com
smtp_password=secret
smtp_password=dpapi:<base64-blob> ; encrypted
use_tls=true
recipients=admin@example.com
send_time=18:00
[groq]
api_key=dpapi:<base64-blob> ; encrypted
model=llama-3.3-70b-versatile
[crypto]
salt=<base64 32-byte salt — auto-generated>
```
@@ -145,10 +152,19 @@ E.g. "23456" = MonFri. Queried with `LOCATE(DAYOFWEEK(CURDATE()), days_of_wee
- 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
- load_config() / save_config() → decrypt/encrypt DPAPI fields transparently
- migrate_plaintext_config() → one-time migration; encrypts any plain-text creds on startup
- config_exists() → True if host, database, user are set
- reload_db_config() → re-reads config.ini, resets pool
### utils/config_crypto.py *(NEW)*
Windows DPAPI-based encryption for config.ini sensitive values.
- encrypt_value(plaintext) → "dpapi:<base64>" string
- decrypt_value(stored) → plaintext; plain-text pass-through for legacy values
- is_encrypted(value) → True if value starts with "dpapi:"
- Tied to the current Windows user account — blob is unreadable on any other machine/account
- Graceful fallback: if pywin32 not available, values stored/returned as plain text
### models.py
Each function opens+closes its own connection. All writes call log_action().
@@ -177,13 +193,37 @@ Password strength: PW_MIN_LENGTH=8, requires upper + digit + special char
### utils/scheduler.py
- Daemon thread; polls every 60 seconds
- Sends HTML email once per day when now >= send_time
- smtp_password is DPAPI-encrypted on save (encrypt_value) / decrypted on load (decrypt_value)
- 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/ai_summary_view.py *(NEW)*
AI-powered document analysis panel. Accessible from the sidebar for both admin and user roles.
Key internals:
- Supported file types: .txt, .md, .csv, .pdf, .docx, .doc, .xlsx, .xls
- Groq API key and model selection are **visible only to admins** (user role sees neither)
- Regular users use the stored API key transparently
- Background thread for AI calls (prevents UI freeze)
- Extraction prompt focused on procurement/solicitation fields:
Solicitation Number/Type, Set-Aside, Description, Work Site, Pre-Proposal Conference,
POC, Square Footage, Driving Distance from office (2815 Hartland Rd, Falls Church VA),
Last Day for Questions, Due Date
- Overall summary covers: Scope of Work, Contract Period, Proposal Submission Requirements,
Key Deadlines & Action Items
- Groq API key stored DPAPI-encrypted in config.ini [groq] section
- max_tokens=4096; temperature=0.2; MAX_CHARS_PER_FILE=14,000
.doc reading (legacy binary Word format) — 3-tier fallback:
1. win32com (Word COM automation — requires MS Word installed)
2. docx2txt (pure Python)
3. Raw ASCII scrape from binary
### views/admin_websites_view.py
- Website CRUD dialog now has a **collapsible credentials section** (hidden by default)
- "🔑 Show Credentials" / "🔒 Hide Credentials" toggle button
- Auto-expands when editing a site that already has saved credentials
- " Add Credential" auto-expands the section if collapsed
### views/user_dashboard_view.py
Key internals:
@@ -192,6 +232,15 @@ Key internals:
- 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
- **Credentials popup no longer opens automatically on link click**
- "🔑 Credentials" button appears on each site card only if the site has saved credentials
- CredentialsPopup: 📋 copy button for username (2s flash); 📋 copy button for password
(2s flash + clipboard auto-cleared after 15s for security); 👁 toggle to reveal password
### 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/admin_shifts_view.py
- _export_pdf(): reportlab A4 document; one section per active shift; user+website tables
@@ -207,11 +256,19 @@ Key internals:
| Concern | Implementation |
|---|---|
| Password hashing | bcrypt rounds=12; SHA-256 auto-rehashed on next login |
| Credential encryption | Fernet (AES-128-CBC + HMAC) via cryptography library |
| Website credential encryption | Fernet (AES-128-CBC + HMAC) via cryptography library |
| Config credential protection | Windows DPAPI (CryptProtectData) — user-account-scoped |
| Encrypted fields | DB user, DB password, SMTP password, Groq API key |
| 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 |
| Clipboard security | Password copy auto-clears clipboard after 15 seconds |
### DPAPI Migration
`migrate_plaintext_config()` is called automatically on every startup (in `_init_db`).
It is a no-op if all sensitive fields are already encrypted (dpapi: prefix present).
On first run after this feature was added, it encrypts any existing plain-text values in-place.
---
@@ -249,6 +306,8 @@ Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first
| user_dashboard_view.py | NOTIFY_MINUTES_BEFORE | 15 |
| admin_dashboard_view.py | REFRESH_INTERVAL_MS | 60,000 |
| utils/crypto.py | _ITERATIONS | 100,000 |
| ai_summary_view.py | MAX_CHARS_PER_FILE | 14,000 |
| ai_summary_view.py | _OFFICE_ADDRESS | "2815 Hartland Road, Falls Church, VA 22043, USA" |
---
@@ -262,8 +321,9 @@ Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first
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.
4. config.ini sensitive fields are DPAPI-encrypted (dpapi: prefix).
Website credential passwords are Fernet-encrypted (enc: prefix).
Non-sensitive fields (host, port, db name, smtp_host, recipients) remain plain text.
5. get_today_checks GROUP BY includes sc.id to prevent row collisions when a user
belongs to multiple shifts sharing the same website.
@@ -276,6 +336,13 @@ Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first
8. All log messages use ASCII only (hyphens not em-dashes/arrows) to prevent
cp1252 UnicodeEncodeError on Windows consoles.
9. DPAPI encrypted blobs are tied to the Windows user account that created them.
If config.ini is copied to a different machine or user account, credentials
cannot be decrypted. Users must re-enter credentials via the Settings dialog.
10. The AI summary view fetches credentials from the DB at card render time (not lazily).
Avoid having hundreds of sites with credentials as this adds DB round-trips per render.
---
## 10. Dependencies
@@ -288,9 +355,15 @@ cryptography>=41.0.0
matplotlib>=3.7.0
plyer>=2.1.0
reportlab>=4.0.0
groq>=1.0.0
pypdf>=3.0.0
python-docx>=1.0.0
pywin32>=306
docx2txt>=0.8
```
stdlib used: tkinter, csv, smtplib, urllib.request, configparser, threading, calendar, datetime
stdlib used: tkinter, csv, smtplib, urllib.request, configparser, threading, calendar,
datetime, base64, re, tempfile
---
@@ -299,15 +372,56 @@ stdlib used: tkinter, csv, smtplib, urllib.request, configparser, threading, cal
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
4. migrate_plaintext_config() → encrypts any plain-text credentials in config.ini
5. Login screen shown
6. ThemeManager(root, initial="light") applied; shell built
---
## 12. Deployment Notes
## 12. Sidebar Navigation
### Admin role
Dashboard · Websites · Users · Shifts · Reports · Activity Log · 🤖 AI Summary · Settings · Sign Out
### User role
My Shift · 🤖 AI Summary · Change Password · Sign Out
---
## 13. 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
- pywin32 required for DPAPI encryption (Windows only). Install: pip install pywin32
- Microsoft Word recommended for .doc file support in AI Summary (falls back to docx2txt)
- Groq API key required for AI Summary feature — free at https://console.groq.com
---
## 14. AI Summary Feature — Setup & Prompt Details
### Setup (Admin)
1. Obtain a free Groq API key at https://console.groq.com
2. Navigate to **🤖 AI Summary** in the sidebar
3. Enter the API key and select a model
4. Click **💾 Save Settings** — key is stored DPAPI-encrypted in config.ini
### Supported Models
- llama-3.3-70b-versatile (default, recommended)
- llama-3.1-8b-instant
- gemma2-9b-it
- mixtral-8x7b-32768
### Prompt Strategy
The prompt instructs the AI to extract 12 labeled fields per document:
Solicitation Number, Type, Set-Aside, Description/Scope, Work Site, Pre-Proposal Conference,
POC, Square Footage, **Driving Distance from 2815 Hartland Rd Falls Church VA** (calculated by AI),
Last Day for Questions, Due Date, Other requirements.
Then produces an Overall Summary with 4 sections:
A. Scope of Work · B. Contract Period · C. Proposal Submission Requirements · D. Key Deadlines
The driving distance/time is an AI estimate using major highways. Actual times may vary with traffic.