04/22 user dashboard view, no credentials popup automatically
This commit is contained in:
@@ -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" = Mon–Fri. 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.
|
||||
|
||||
Reference in New Issue
Block a user