Jun 29 - Update app name, and documents
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
# ============================================================
|
||||
# Website Checker — Environment Variables
|
||||
# Bid Checker — Environment Variables
|
||||
# Copy this file to .env and fill in your values.
|
||||
# NEVER commit .env to version control.
|
||||
# ============================================================
|
||||
|
||||
@@ -6,7 +6,7 @@ This file gives Claude (or any AI assistant) the context needed to continue deve
|
||||
|
||||
## What This Project Is
|
||||
|
||||
**Website Checker Web** is a Flask web application that is a direct port of a Tkinter desktop application. Both apps share the **same MySQL database** — this is the single most important constraint. Any schema change, query, or encryption logic must remain compatible with what the desktop app writes and reads.
|
||||
**Bid Checker Web** is a Flask web application that is a direct port of a Tkinter desktop application. Both apps share the **same MySQL database** — this is the single most important constraint. Any schema change, query, or encryption logic must remain compatible with what the desktop app writes and reads.
|
||||
|
||||
The app helps a small team at LT Services Inc. (Falls Church, VA) track government procurement websites across scheduled shifts, manage bid/opportunity follow-up, and run AI-powered solicitation document analysis.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Website Checker — Production Deployment Guide
|
||||
# Bid Checker — Production Deployment Guide
|
||||
### Ubuntu Server · Nginx · Gunicorn · MySQL · systemd
|
||||
|
||||
---
|
||||
@@ -202,7 +202,7 @@ Paste the following:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Website Checker — Gunicorn Application Server
|
||||
Description=Bid Checker — Gunicorn Application Server
|
||||
After=network.target mysql.service
|
||||
Requires=mysql.service
|
||||
|
||||
@@ -496,4 +496,4 @@ Create `/etc/logrotate.d/webchecker`:
|
||||
|
||||
---
|
||||
|
||||
*Generated for Website Checker Web — Flask/MySQL/Nginx/Gunicorn/Ubuntu deployment.*
|
||||
*Generated for Bid Checker Web — Flask/MySQL/Nginx/Gunicorn/Ubuntu deployment.*
|
||||
|
||||
@@ -1,245 +1,140 @@
|
||||
# Website Checker — Web Application
|
||||
# Bid Checker
|
||||
|
||||
A full-stack web application for monitoring government procurement websites across team shifts. Converted from a Tkinter desktop application, sharing the same MySQL database.
|
||||
**Bid Checker** is a web-based tool for LT Services Inc. that helps your team stay on top of government procurement websites, track bid opportunities, and analyze solicitation documents with AI — all in one place.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
## What It Does
|
||||
|
||||
Website Checker helps teams track which government websites need to be checked during each shift, manage bid/procurement opportunities, and run AI-powered document analysis on solicitation files. Administrators manage users, websites, shifts, and view reports; regular users work through their daily or weekly checklists.
|
||||
Bid Checker has three main jobs:
|
||||
|
||||
1. **Website Checklist** — Each team member sees a daily or weekly list of government procurement websites assigned to their shift. They visit each site, check for new opportunities, and mark it done. The app tracks who checked what and when.
|
||||
|
||||
2. **Bid Tracker** — When the team finds a promising opportunity, they add it as a bid. The tracker shows status, due dates, notes, and a full update history so nothing falls through the cracks.
|
||||
|
||||
3. **AI Document Analysis** — Upload a solicitation document (PDF, Word, Excel, or text) and the AI extracts the key details: solicitation number, scope of work, set-aside, pre-proposal conference, due date, point of contact, and driving distance from the office. If evaluation criteria are configured, the AI also recommends whether to Pursue, Pass, or flag as Unclear.
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
## Getting Started
|
||||
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Language | Python 3.10+ |
|
||||
| Web Framework | Flask 3.0 |
|
||||
| Database | MySQL 8.x (shared with desktop app) |
|
||||
| Application Server | Gunicorn |
|
||||
| Reverse Proxy | Nginx |
|
||||
| Process Manager | systemd |
|
||||
| Credential Encryption | Fernet (PBKDF2-HMAC-SHA256, 100k iterations) |
|
||||
| Password Hashing | bcrypt (12 rounds) |
|
||||
| AI Analysis | Groq REST API (llama-3.3-70b-versatile) |
|
||||
| Markdown Rendering | marked.js (CDN, client-side) |
|
||||
| Frontend | Vanilla JS + DM Sans/DM Mono (Google Fonts) |
|
||||
| OS | Ubuntu 22.04 / 24.04 LTS |
|
||||
### Signing In
|
||||
|
||||
Go to your team's Bid Checker URL and sign in with your username and password.
|
||||
|
||||
If your admin has enabled self-registration, you'll see a **Create Account** link on the sign-in page. Fill in your username, name, email, and a password (minimum 8 characters), and your account will be created immediately.
|
||||
|
||||
If you forget your password, click **Forgot password?** on the sign-in page and a reset link will be sent to your email.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## For Regular Users
|
||||
|
||||
### All Users
|
||||
- **Shift Checklist** — Sites grouped by Daily / Weekly frequency, collapsible groups, live progress bar, bulk-check, search filter, server-side health dot per site
|
||||
- **Site Credentials** — View stored credentials (password masked, copy-to-clipboard)
|
||||
- **Per-site Notes** — Add or update notes per check, displayed in a dedicated row under the site card
|
||||
- **AI Document Analysis** — Upload PDF / DOCX / XLSX / TXT solicitation files; AI extracts solicitation number, scope of work, due dates, driving distance from office, and evaluates against active criteria. All documents are analyzed as one combined source. Output rendered as formatted markdown.
|
||||
- **AI History** — Filter past analyses by verdict (PURSUE / PASS / UNCLEAR / No verdict), view criteria snapshot used, delete own records
|
||||
- **Bid Tracker** — Split-pane view of all tracked opportunities; post updates, filter by status, search by title / source / solicitation number, urgency badges for bids due within 7 days / overdue, load-more pagination, export current view to CSV
|
||||
- **My Profile** — Update own full name and email address
|
||||
- **Password Reset** — Self-service reset via emailed link (1-hour token); "Forgot password?" on login page
|
||||
- **Relative Timestamps** — Activity log timestamps shown as "2 hours ago" / "3 days ago"
|
||||
- **Mobile Sidebar** — Hamburger toggle collapses sidebar on small screens
|
||||
### Your Daily Checklist
|
||||
|
||||
### Admins Only
|
||||
- **Dashboard** — KPI cards (users, active today, total sites, open bids, bids due this week, AI analyses past 30 days); per-user completion progress; missed-shift warning
|
||||
- **User Management** — Create, edit, activate/deactivate users; role assignment; send password reset link directly to any user with an email address
|
||||
- **Website Management** — CRUD for monitored sites with credentials, check type (daily/weekly), user assignment; inline search and type filter
|
||||
- **Shift Management** — Define shifts with days-of-week, time windows, assigned users and sites; weekly calendar view with today's column highlighted
|
||||
- **Reports** — Shift detail, unchecked sites, and summary reports with date/user/site filters; CSV export
|
||||
- **Logs** — Activity log (user actions) and application log (system events) with search and purge
|
||||
- **Settings** — SMTP email configuration (host, port, security, username, password, from address, recipients); Groq AI API key / model selection; one-click **Test** button for both SMTP and Groq to verify config without saving
|
||||
- **AI Criteria Management** — Create, edit, reorder, and deactivate evaluation criteria used by AI analysis
|
||||
- **Bid Email Reminders** — One-click digest email to all admin addresses listing bids due within 7 days
|
||||
- **Missed Shift Alerting** — Warning card on the admin dashboard showing any user/shift pair with 0 sites checked today
|
||||
After signing in you land on **My Shift — Website Checklist**. This is your to-do list for the day.
|
||||
|
||||
- **Daily websites** reset every morning — check them fresh each day.
|
||||
- **Weekly websites** stay on your list until you check them once that week, then show as completed for the rest of the week.
|
||||
- The progress bar at the top shows how many sites you've completed.
|
||||
- Use the search box to find a specific site quickly.
|
||||
- Click a site name to open it in a new tab.
|
||||
- Click **✔ Check** to mark a site as done. You can add a note about what you found.
|
||||
- Use **Select All Unchecked** + **Mark Selected Checked** to complete multiple sites at once.
|
||||
- The colored dot next to each site shows whether the site is reachable right now (click to refresh): green = fast, orange = slow, red = unreachable.
|
||||
- Click **↻ Refresh** to reload your list at any time.
|
||||
|
||||
### Bid Tracker
|
||||
|
||||
Go to **Bid Tracker** in the sidebar to see all open bid opportunities.
|
||||
|
||||
- The left panel lists all bids. Use the status tabs (Open / Submitted / Won / Lost / No-bid) to filter.
|
||||
- Click any bid to open its detail panel on the right.
|
||||
- Use the **+ New Bid** button to log a new opportunity.
|
||||
- Inside a bid you can post updates, set a due date, and track status changes.
|
||||
- Bids approaching their deadline show a **Due Soon** badge; overdue bids show an **Overdue** badge.
|
||||
- Use **⬇ CSV** to export the current list to a spreadsheet.
|
||||
|
||||
### AI Document Analysis
|
||||
|
||||
Go to **AI Analysis** in the sidebar.
|
||||
|
||||
1. Click **Choose Files** and upload one or more solicitation documents (PDF, Word, Excel, TXT, or CSV). You can upload several files from the same solicitation and the AI will treat them as one combined package.
|
||||
2. Click **🔍 Analyze with AI**.
|
||||
3. The AI extracts all the key fields and writes a detailed summary. If your admin has configured evaluation criteria, you'll also see a **PURSUE / PASS / UNCLEAR** recommendation banner.
|
||||
4. Use **📋 Copy** or **💾 Save as TXT** to keep a copy of the result.
|
||||
5. Every analysis is saved to **Analysis History** at the bottom of the page. Click any row to re-open a past result. The history is shared — you can see analyses run by other team members.
|
||||
|
||||
### Your Profile
|
||||
|
||||
Click your name in the sidebar to open **My Profile**. You can update your display name and email address here. Your username and role are managed by your admin.
|
||||
|
||||
To change your password, use **Change Password** in the sidebar.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
## For Administrators
|
||||
|
||||
```
|
||||
webchecker_web/
|
||||
├── app.py # Flask application factory, blueprint registration
|
||||
├── config.py # DB connection pool, schema DDL, get/set settings
|
||||
├── models.py # All data-access functions (no ORM)
|
||||
├── wsgi.py # Gunicorn entry point
|
||||
├── requirements.txt
|
||||
├── .env.example # Environment variable template
|
||||
├── CLAUDE.md # AI developer context
|
||||
├── DEPLOY.md # Full production deployment guide
|
||||
│
|
||||
├── routes/
|
||||
│ ├── auth.py # Login, logout, change password, profile, password reset
|
||||
│ ├── admin_dashboard.py # /admin/
|
||||
│ ├── admin_users.py # /admin/users/
|
||||
│ ├── admin_websites.py # /admin/websites/
|
||||
│ ├── admin_shifts.py # /admin/shifts/
|
||||
│ ├── admin_logs.py # /admin/logs/
|
||||
│ ├── admin_reports.py # /admin/reports/
|
||||
│ ├── admin_settings.py # /admin/settings/ (incl. test-email, test-groq)
|
||||
│ ├── user_dashboard.py # /dashboard/ (incl. server-side health check)
|
||||
│ ├── ai_summary.py # /ai-summary/ (incl. history delete)
|
||||
│ └── bid_tracker.py # /bids/ (incl. CSV export, email reminders)
|
||||
│
|
||||
├── templates/
|
||||
│ ├── base.html # Sidebar layout, flash messages, nav, mobile toggle
|
||||
│ ├── login.html # Standalone (no base.html)
|
||||
│ ├── forgot_password.html # Standalone password-reset request
|
||||
│ ├── reset_password.html # Standalone new-password form
|
||||
│ ├── profile.html # User profile editor
|
||||
│ ├── change_password.html
|
||||
│ ├── ai_summary.html
|
||||
│ ├── bid_tracker.html
|
||||
│ ├── admin/
|
||||
│ │ ├── dashboard.html
|
||||
│ │ ├── users.html
|
||||
│ │ ├── websites.html
|
||||
│ │ ├── shifts.html
|
||||
│ │ ├── logs.html
|
||||
│ │ ├── reports.html
|
||||
│ │ └── settings.html
|
||||
│ └── user/
|
||||
│ └── dashboard.html
|
||||
│
|
||||
├── static/
|
||||
│ ├── css/style.css # Full light-theme design system
|
||||
│ └── js/app.js # Modal helpers, tabs, timeAgo(), mobile sidebar, session timeout
|
||||
│
|
||||
└── utils/
|
||||
├── crypto.py # Fernet encryption (DB-compatible with desktop app)
|
||||
├── decorators.py # @login_required, @admin_required
|
||||
└── email.py # SMTP helper: send_email(to, subject, body)
|
||||
```
|
||||
Admins have access to additional pages in the sidebar under the **Admin** section.
|
||||
|
||||
### Dashboard
|
||||
|
||||
The admin dashboard shows a live summary of today's activity:
|
||||
- How many sites each user has checked, with a progress bar per person
|
||||
- A **Missed Shifts** alert for users whose shift has started but who haven't checked anything yet
|
||||
- KPI cards: open bids, bids due this week, and AI analyses run in the last 30 days
|
||||
- A **📧 Remind Incomplete** button that immediately emails any staff members who are within 40 minutes of their shift end and still have unchecked sites
|
||||
|
||||
### User Management
|
||||
|
||||
Add, edit, deactivate, or delete user accounts. You can also:
|
||||
- Assign roles: **User** (checklist + bid tracker + AI analysis) or **Admin** (full access)
|
||||
- Send a password reset link directly from the user list using the **✉ Reset** button
|
||||
- Enable or disable public self-registration under **Settings → User Registration**
|
||||
|
||||
### Website Management
|
||||
|
||||
Add and manage the procurement websites your team monitors. Each site has:
|
||||
- A name and URL
|
||||
- A check type: **Daily** (checked every day) or **Weekly** (checked once per week)
|
||||
- Optional login credentials (stored encrypted — only visible when a user clicks the key icon)
|
||||
- Visibility: All users, or restricted to specific users only
|
||||
|
||||
### Shift Management
|
||||
|
||||
Shifts define *who checks what websites, and when*. Each shift has:
|
||||
- A name and optional note
|
||||
- Days of the week it runs (e.g. Mon–Fri)
|
||||
- Start and end times
|
||||
- Assigned users
|
||||
- Assigned websites — shown grouped as **Daily** and **Weekly** for easy selection
|
||||
|
||||
When a new user registers via the sign-up form, they are automatically placed into the **Default (Mon–Fri)** shift (Mon–Fri, 9:00 AM–5:30 PM) with all daily websites assigned.
|
||||
|
||||
Automatic reminder emails fire every 5 minutes via a scheduled task. If a user is in a shift ending within 40 minutes and still has unchecked sites, they receive one email per shift per day listing exactly which sites remain.
|
||||
|
||||
### Evaluation Criteria (AI Analysis)
|
||||
|
||||
On the AI Analysis page, admins can manage **Evaluation Criteria** — the standards the AI uses when recommending Pursue / Pass / Unclear. Each criterion has a title and a description. Inactive criteria are preserved but not included in the analysis.
|
||||
|
||||
### Reports & Logs
|
||||
|
||||
- **Reports** — A summary of recent shift activity, check completion rates, and bid status over time.
|
||||
- **Logs** — A full audit trail: every login, site check, bid update, user change, and admin action is recorded with the user name and timestamp.
|
||||
|
||||
### Settings
|
||||
|
||||
Configure email (SMTP for reminders and password resets) and AI (Groq API key and model) from the **Settings** page. Use the **Test** buttons to verify your configuration before saving.
|
||||
|
||||
---
|
||||
|
||||
## URL Routes
|
||||
## Helpful Tips
|
||||
|
||||
| Prefix | Description | Access |
|
||||
|---|---|---|
|
||||
| `/` | Root redirect (role-based) | Any |
|
||||
| `/login` | Login page | Public |
|
||||
| `/logout` | Session clear + redirect | Authenticated |
|
||||
| `/ping` | Session keepalive (returns 204) | Authenticated |
|
||||
| `/profile` | Edit own full name and email | Authenticated |
|
||||
| `/change-password` | Change own password | Authenticated |
|
||||
| `/forgot-password` | Request password reset email | Public |
|
||||
| `/reset-password/<token>` | Set new password via token | Public |
|
||||
| `/admin/dashboard` | Admin dashboard with KPIs | Admin |
|
||||
| `/admin/users/` | User CRUD + send reset link | Admin |
|
||||
| `/admin/websites/` | Website CRUD + credentials | Admin |
|
||||
| `/admin/shifts/` | Shift CRUD + calendar view | Admin |
|
||||
| `/admin/logs/` | Activity & app logs | Admin |
|
||||
| `/admin/reports/` | Shift reports + CSV export | Admin |
|
||||
| `/admin/settings/` | Email + Groq AI settings + connection tests | Admin |
|
||||
| `/dashboard/` | User shift checklist | User |
|
||||
| `/dashboard/health/<id>` | Server-side site health check | User |
|
||||
| `/ai-summary/` | AI document analysis + history | Any |
|
||||
| `/ai-summary/history/<id>` | Analysis detail (JSON) | Any |
|
||||
| `/ai-summary/history/<id>/delete` | Delete an analysis record | Any |
|
||||
| `/bids/` | Bid / opportunity tracker | Any |
|
||||
| `/bids/export.csv` | Export bid list as CSV | Any |
|
||||
| `/bids/remind` | Send bid deadline reminder email | Admin |
|
||||
- The app signs you out after **30 minutes of inactivity**. A warning appears 2 minutes before your session expires with a "Stay signed in" option.
|
||||
- On a phone or small screen, tap the **☰** button at the top left to open the navigation menu.
|
||||
- Shift reminder emails are sent automatically — if your shift ends within 40 minutes and you still have unchecked sites, you'll get an email listing what's left.
|
||||
- Admins can delete individual AI analysis records from the detail modal, or select multiple records in the history table and delete them in bulk.
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Copy `.env.example` to `.env` and fill in all values:
|
||||
|
||||
```dotenv
|
||||
SECRET_KEY= # Flask session secret — generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_NAME=webchecker
|
||||
DB_USER=webchecker_user
|
||||
DB_PASSWORD=
|
||||
|
||||
GROQ_API_KEY= # Optional — can also be set via Admin → Settings
|
||||
GROQ_MODEL=llama-3.3-70b-versatile # Optional fallback; overridden by DB setting
|
||||
|
||||
# SMTP email — optional fallback; values are stored in and overridden by Admin → Settings
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM=
|
||||
|
||||
FLASK_ENV=production
|
||||
```
|
||||
|
||||
> **Important:** `SECRET_KEY` must remain stable across restarts — changing it invalidates all active sessions and CSRF tokens across Gunicorn workers.
|
||||
|
||||
---
|
||||
|
||||
## Database Compatibility
|
||||
|
||||
This web application shares the MySQL database with the desktop application. Key compatibility notes:
|
||||
|
||||
- **Encryption** — `utils/crypto.py` uses the exact same key derivation as the desktop (`APP_SECRET = b"WebsiteChecker-v1-CredentialKey"`, 100,000 PBKDF2 iterations, base64 salt, `enc:` prefix). Credentials are interchangeable between apps.
|
||||
- **`activity_log`** — The live database column is `logged_at` (desktop schema). Queries use `al.logged_at AS created_at` for template compatibility.
|
||||
- **`app_settings`** — On startup, `initialize_database()` seeds any settings keys that are blank in the DB from the corresponding `.env` variables, using `ON DUPLICATE KEY UPDATE … IF(value = '', …)` so admin-saved values are never overwritten.
|
||||
- **`password_reset_tokens`** — Web-only table; created by the app's migration-safe DDL on first startup. Tokens expire after 1 hour and are consumed on use. Expired tokens are purged on every successful login.
|
||||
- **`login_attempts.ip_address`** — The only additive column the web app creates. Added via `ALTER TABLE … ADD COLUMN IF NOT EXISTS` on startup.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Run locally
|
||||
|
||||
```bash
|
||||
# 1. Clone and enter the project
|
||||
cd webchecker_web
|
||||
|
||||
# 2. Create and activate a virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# 3. Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 4. Configure environment
|
||||
cp .env.example .env
|
||||
# Edit .env with your local MySQL credentials
|
||||
|
||||
# 5. Run the development server
|
||||
python app.py
|
||||
# Open http://localhost:5000
|
||||
```
|
||||
|
||||
### Deploy changes to production
|
||||
|
||||
```bash
|
||||
# After copying Python files to the server:
|
||||
sudo systemctl reload webchecker
|
||||
|
||||
# Templates and static files take effect immediately (no reload needed)
|
||||
|
||||
# Check for errors after reload:
|
||||
sudo journalctl -u webchecker -n 30 --no-pager
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
- Passwords are hashed with bcrypt (12 rounds); legacy SHA-256 hashes from the desktop app are automatically rehashed on next login.
|
||||
- Sessions expire after **30 minutes** of inactivity. A client-side warning fires 5 minutes before expiry with a keep-alive ping endpoint.
|
||||
- Login is rate-limited: 5 failed attempts locks the account for 15 minutes.
|
||||
- Credentials stored in `website_credentials` are Fernet-encrypted at rest.
|
||||
- File uploads for AI analysis are validated against magic bytes (PDF `%PDF`, DOCX/XLSX `PK\x03\x04`, DOC/XLS OLE header) and UTF-8 decodability for text files — rejecting disguised uploads without extra dependencies.
|
||||
- Upload size is capped at 20 MB (`MAX_CONTENT_LENGTH`).
|
||||
- HTTP security headers (`X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`) are set on every response.
|
||||
- CSRF protection (Flask-WTF) on all non-GET requests.
|
||||
- Session fixation prevention: `session.clear()` is called immediately before setting the session on successful login.
|
||||
- Password reset tokens are single-use, expire after 1 hour, and are purged on each login.
|
||||
- Admins cannot demote or deactivate the last remaining active admin.
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
See [`DEPLOY.md`](DEPLOY.md) for the full step-by-step guide covering MySQL setup, Gunicorn systemd service, Nginx reverse proxy, Certbot SSL, firewall rules, log rotation, and database backup.
|
||||
*Bid Checker — Built for LT Services Inc., Falls Church, VA*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
app.py — Flask application factory.
|
||||
Web conversion of the desktop Website Checker application.
|
||||
Web conversion of the desktop Bid Checker application.
|
||||
Stack: Python 3.11+, Flask, MySQL (mysql-connector-python), Gunicorn, Nginx.
|
||||
"""
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ _formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s - %(message
|
||||
_stream_handler = logging.StreamHandler(stream=sys.stdout)
|
||||
_stream_handler.setFormatter(_formatter)
|
||||
|
||||
APP_TITLE = "Website Checker"
|
||||
APP_TITLE = "Bid Checker"
|
||||
APP_VERSION = "1.0.0"
|
||||
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ def test_email():
|
||||
try:
|
||||
send_email(
|
||||
to,
|
||||
"Website Checker — SMTP Test",
|
||||
"This is a test email from Website Checker. Your SMTP configuration is working correctly.",
|
||||
"Bid Checker — SMTP Test",
|
||||
"This is a test email from Bid Checker. Your SMTP configuration is working correctly.",
|
||||
)
|
||||
log_action(admin["id"], "TEST_EMAIL", "app_settings", None,
|
||||
f"SMTP test email sent to {to}.")
|
||||
|
||||
@@ -155,7 +155,7 @@ def do_send_incomplete_reminders(triggered_by_user_id=None) -> dict:
|
||||
f"website(s) left to check:\n\n"
|
||||
f"{site_lines}\n\n"
|
||||
f"Please complete your checks before the shift ends.\n"
|
||||
f"Log in to Website Checker to mark them done."
|
||||
f"Log in to Bid Checker to mark them done."
|
||||
)
|
||||
try:
|
||||
send_email(
|
||||
|
||||
@@ -106,12 +106,12 @@ def send_reset(user_id):
|
||||
reset_url = url_for("auth.reset_password", token=token, _external=True)
|
||||
body = (
|
||||
f"Hi {user.get('full_name') or user['username']},\n\n"
|
||||
f"An administrator has sent you a password reset link for your Website Checker account.\n"
|
||||
f"An administrator has sent you a password reset link for your Bid Checker account.\n"
|
||||
f"Click the link below to set a new password (valid for 1 hour):\n\n"
|
||||
f"{reset_url}\n\n"
|
||||
f"If you did not expect this, please contact your administrator.\n"
|
||||
)
|
||||
send_email(user["email"], "Website Checker — Password Reset", body)
|
||||
send_email(user["email"], "Bid Checker — Password Reset", body)
|
||||
log_action(admin["id"], "SEND_RESET_LINK", "users", user_id,
|
||||
f"Password reset link sent to '{user['username']}' ({user['email']}).")
|
||||
logger.info(f"Reset email sent to user_id={user_id} by admin_id={admin['id']}.")
|
||||
|
||||
+2
-2
@@ -191,12 +191,12 @@ def forgot_password():
|
||||
reset_url = url_for("auth.reset_password", token=token, _external=True)
|
||||
body = (
|
||||
f"Hi {user.get('full_name') or user['username']},\n\n"
|
||||
f"A password reset was requested for your Website Checker account.\n"
|
||||
f"A password reset was requested for your Bid Checker account.\n"
|
||||
f"Click the link below to set a new password (valid for 1 hour):\n\n"
|
||||
f"{reset_url}\n\n"
|
||||
f"If you did not request this, you can safely ignore this email.\n"
|
||||
)
|
||||
send_email(user["email"], "Website Checker — Password Reset", body)
|
||||
send_email(user["email"], "Bid Checker — Password Reset", body)
|
||||
except Exception as e:
|
||||
logger.error(f"Password reset email error for '{email}': {e}")
|
||||
|
||||
|
||||
@@ -269,11 +269,11 @@ def send_reminders():
|
||||
if b.get("solicitation_number"):
|
||||
line += f" — Sol#: {b['solicitation_number']}"
|
||||
lines.append(line)
|
||||
lines.append("\nLog in to Website Checker for full details.")
|
||||
lines.append("\nLog in to Bid Checker for full details.")
|
||||
body = "\n".join(lines)
|
||||
|
||||
try:
|
||||
send_email(recipients, "Website Checker — Upcoming Bid Deadlines", body)
|
||||
send_email(recipients, "Bid Checker — Upcoming Bid Deadlines", body)
|
||||
log_action(user["id"], "BID_REMIND", "bid_tracker", None,
|
||||
f"Bid reminder email sent to {len(recipients)} admin(s), {len(bids)} bid(s) listed.")
|
||||
flash(f"Reminder digest sent to {len(recipients)} recipient(s).", "success")
|
||||
|
||||
@@ -109,7 +109,7 @@ def health_check(website_id):
|
||||
try:
|
||||
t0 = time.time()
|
||||
resp = _http_req.head(url, timeout=6, allow_redirects=True,
|
||||
headers={"User-Agent": "WebsiteChecker/1.0 (health-check)"})
|
||||
headers={"User-Agent": "BidChecker/1.0 (health-check)"})
|
||||
ms = int((time.time() - t0) * 1000)
|
||||
ok = resp.status_code < 400
|
||||
return jsonify({"status": "ok" if ok else "error", "ms": ms})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* ============================================================
|
||||
Website Checker — style.css (Light Theme v2)
|
||||
Bid Checker — style.css (Light Theme v2)
|
||||
Aesthetic : Modern SaaS — warm white, slate sidebar, airy
|
||||
Fonts : DM Sans (UI) + DM Mono (data)
|
||||
============================================================ */
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* ============================================================
|
||||
Website Checker — app.js
|
||||
Bid Checker — app.js
|
||||
Global utilities: modals, tabs, auto-dismiss alerts, helpers
|
||||
============================================================ */
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Dashboard — Website Checker{% endblock %}
|
||||
{% block title %}Dashboard — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Dashboard</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Logs — Website Checker{% endblock %}
|
||||
{% block title %}Logs — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Logs</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Reports — Website Checker{% endblock %}
|
||||
{% block title %}Reports — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Reports</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Settings — Website Checker{% endblock %}
|
||||
{% block title %}Settings — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header"><h1 class="page-title">Application Settings</h1></div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Shifts — Website Checker{% endblock %}
|
||||
{% block title %}Shifts — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Shift Management</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Users — Website Checker{% endblock %}
|
||||
{% block title %}Users — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">User Management</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Websites — Website Checker{% endblock %}
|
||||
{% block title %}Websites — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Website Link Management</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}AI Summary — Website Checker{% endblock %}
|
||||
{% block title %}AI Summary — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="page-header">
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{% block title %}Website Checker{% endblock %}</title>
|
||||
<title>{% block title %}Bid Checker{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body class="layout">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Bid Tracker — Website Checker{% endblock %}
|
||||
{% block title %}Bid Tracker — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="page-header">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Change Password — Website Checker{% endblock %}
|
||||
{% block title %}Change Password — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header"><h1 class="page-title">🔑 Change Password</h1></div>
|
||||
<div class="card" style="max-width:480px">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Forgot Password — Website Checker</title>
|
||||
<title>Forgot Password — Bid Checker</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body class="login-wrap">
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="login-logo">
|
||||
<span class="brand-icon">🌐</span>
|
||||
<h1>Website Checker</h1>
|
||||
<h1>Bid Checker</h1>
|
||||
<p>Password Reset</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login — Website Checker</title>
|
||||
<title>Login — Bid Checker</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body class="login-wrap">
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="login-logo">
|
||||
<span class="brand-icon">🌐</span>
|
||||
<h1>Website Checker</h1>
|
||||
<h1>Bid Checker</h1>
|
||||
<p>Shift Monitoring Tool</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}My Profile — Website Checker{% endblock %}
|
||||
{% block title %}My Profile — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">My Profile</h1>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Create Account — Website Checker</title>
|
||||
<title>Create Account — Bid Checker</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body class="login-wrap">
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="login-logo">
|
||||
<span class="brand-icon">🌐</span>
|
||||
<h1>Website Checker</h1>
|
||||
<h1>Bid Checker</h1>
|
||||
<p>Create Account</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set New Password — Website Checker</title>
|
||||
<title>Set New Password — Bid Checker</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body class="login-wrap">
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="login-logo">
|
||||
<span class="brand-icon">🌐</span>
|
||||
<h1>Website Checker</h1>
|
||||
<h1>Bid Checker</h1>
|
||||
<p>Set New Password</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}My Shifts — Website Checker{% endblock %}
|
||||
{% block title %}My Shifts — Bid Checker{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<!-- ── Page header ─────────────────────────────────────────── -->
|
||||
|
||||
Reference in New Issue
Block a user