Logging in
dashboard/login.php checks the submitted username/password against the users table with PHP's password_verify() against a bcrypt hash stored in password_hash. There is no password-reset-by-email flow - resetting a password means editing the row directly, via the Users page below or SQL.
A default account is seeded by database/schema.sql:
username: admin
password: TruPath@2026
dashboard/login.php and this guide - treat it as a known value, not a secret, until it's rotated.Session mechanics
All session handling lives in includes/dashboard_session.php:
| Function | Purpose |
|---|---|
dashboard_start_session() | Starts the PHP session if one isn't active yet. Called internally by the functions below. |
dashboard_current_user(): ?array | Returns the logged-in user's session data (id, username, full_name, role), or null if nobody is logged in. |
dashboard_require_login(): array | Redirects to login and exits if nobody is logged in; otherwise returns the current user. Every dashboard page calls this first. |
dashboard_require_role(string $role): array | Calls dashboard_require_login(), then responds 403 and exits if the user's role doesn't match. Only dashboard/users.php uses this today (gated to 'admin') - the only page with role-based access control. |
dashboard_login(array $user): void | Called by login.php after a successful password_verify(). Regenerates the session id first, to prevent session fixation. |
dashboard_logout(): void | Clears and destroys the session. dashboard/logout.php calls this then redirects to login. |
Dashboard pages
All pages share a common header/nav/footer via dashboard/_layout.php, and a common list+modal CRUD pattern (POST action=create|update|delete handled at the top of the same file, ?edit=<id>/?new=1 opening an add/edit dialog, a DataTables-enhanced list). dashboard/pathogens.php is the clearest example of this pattern.
| Page | Purpose |
|---|---|
dashboard/index.php | Overview - record counts and data-library metadata. |
dashboard/reports.php | Browse/filter previously generated reports (status, type, source, date range, and on this instance a Sample filter - see below), download their PDFs. |
dashboard/pathogens.php | Manage the pathogens table. |
dashboard/resistance_genes.php | Manage the resistance_genes table. |
dashboard/antimicrobials.php | Manage the antimicrobials table. |
dashboard/recommendations.php | Manage per-organism antimicrobial recommendation rankings. |
dashboard/users.php admin only | Manage dashboard accounts - see below. |
This product line vs. TruPath's UTI (tpl10) product
This instance is cloned from tpl10's PHP/MySQL architecture (dashboard, API, database-driven reference data) but its clinical engine and report design are ported from the approved TruWomen's Health PCR standalone generator, not from tpl10's UTI/MAST product. Differences relevant to dashboard admins:
- No MAST/culture-plate workflow. TruPath's UTI product has a preliminary-PCR / MAST-pending / final-PCR+MAST report lifecycle (Evolve culture plates apply only to UTI organisms). This product has a single report mode -
api/generate-report.phptakes nomodefield, anddashboard/reports.php's reports are alwaysCOMPLETE, neverWAITING_MAST. - Qualitative PCR loads, not numeric copy counts. Detected organisms are reported as
High/Moderate/Low(orDetected, for the qualitative-only STI-panel organisms on the Plus product) - there is no numeric clinical-significance threshold. A pathogen is clinically significant unless its load isLow. - Pathogens and Resistance Genes Assessed panel always shows the full current panel (every row in
dashboard/pathogens.php, every row indashboard/resistance_genes.php) - there is no separate "Visible in Assessed Panel" flag to toggle per row for this product. - Resistance-gene IDs matter. The clinical engine's gene-to-drug-class blocking rules (
app/src/ClinicalEngine.php'sblockedByGenes()) key off the exact gene ID string -TetM,blaTEM,mefA,ermBfor the 4 genes this product ships with. A new resistance gene added indashboard/resistance_genes.phpwill display correctly everywhere but won't participate in drug-blocking unlessblockedByGenes()is extended to recognize its ID. - Alternative Antimicrobials can be submitted directly. The API's
case.alternatives[]field lets a caller specify up to 3 drugs directly (matching the Report Generator page's manual override); if omitted, the server ranks alternatives itself the same way the page's "Reload from Master Mapping" button does. - Sample reports. The Report Generator's "Display SAMPLE - NOT FOR CLINICAL USE banner" checkbox (
case.showSampleBanner) is tracked: checked reports are flaggedis_samplein thereportstable and shown as a red "Sample" badge with a dedicated filter ondashboard/reports.php- the generated report/PDF itself is unaffected, only its entry in the Report Dashboard.
Users (add/remove dashboard accounts)
dashboard/users.php is an admin-role-only page for managing who can log into the dashboard:
- Add - username, full name, role (
adminorstaff), and a password (minimum 8 characters, hashed withpassword_hash($password, PASSWORD_DEFAULT)before storage - never stored or logged in plaintext). - Edit - same fields; leave the password field blank to keep the existing password unchanged.
- Delete - removes the account. You cannot delete the account you're currently logged in as (hidden on your own row, and rejected server-side if attempted directly).
admin (can access the Users page) vs. everything else (cannot). No per-page/per-action granularity beyond that single check.What the Admin Dashboard does NOT do
- It does not provide an audit trail of who changed what reference data or when.
- It does not send password-reset emails or support any self-service account-recovery flow - a locked-out account has to be fixed by another
adminuser via the Users page, or directly in the database. - It does not rate-limit or lock out repeated failed login attempts.
- It does not manage the report-generation API's
X-API-Key- that's a separate config value covered in the API docs, unrelated to anyusersrow.