If you're downloading this project and find it useful,
⭐ Please consider starring it
🌍 And mentioning freeitsm.co.uk on Reddit, Hacker News, Spiceworks, LinkedIn, or anywhere IT pros hang out
It genuinely helps and means a lot!
A comprehensive web-based IT Service Management (ITSM) platform with 17 integrated modules covering a unified attention dashboard, tickets, tasks with Kanban board, assets, knowledge, change management, calendar, morning checks, reporting, software inventory, dynamic forms, contracts, service status, process mapping, LMS with SCORM course player, and system administration. Includes a Chrome/Edge browser extension for Watchtower dashboard monitoring, analyst account management with password reset, forgot password via email, TOTP multi-factor authentication, and IP-based brute force protection.
- Screenshots
- Quick Start
- Technology Stack
- ITSM Modules
- Directory Structure
- Shared Components
- Module Details
- Browser Extension
- API Reference
- Database
- Security
- Key Workflows
- Development Notes
- File Locations Quick Reference
The fastest way to get FreeITSM running — no PHP, MySQL, or web server setup required.
git clone https://github.com/edmozley/freeitsm.git
cd freeitsm
docker compose up -dThen open http://localhost:8080/setup/ to verify the installation and create your admin account.
Requires Docker Desktop (Windows/Mac) or Docker Engine (Linux).
- Web Server: WAMP, XAMPP, LAMP, or any PHP-capable web server
- PHP: 7.4 or higher (tested up to 8.4)
- Database: MySQL 8.0 or higher (included with WAMP/XAMPP)
- Extensions: PHP PDO, pdo_mysql, curl, openssl, mbstring
- Database credentials file: A
db_config.phpfile stored outside your web root (e.g.C:\wamp64\db_config.php) — see step 2 below. The path is configured inconfig.php.
⚠️ Note for early adopters: If you downloaded FreeITSM before 18 February 2026, the project required Microsoft SQL Server Express and ODBC drivers — sorry about that! The original choice of SQL Server made sense at the time (it was the database I was most familiar with), but it created a painful setup experience: downloading SQL Server Express, installing ODBC drivers, enabling Mixed Mode Authentication, and troubleshooting driver compatibility issues. That's a lot of friction for an open-source project that's supposed to be easy to get running.FreeITSM now runs on MySQL, which comes pre-installed with WAMP, XAMPP, and most web hosting stacks. No extra downloads, no driver headaches. If you've already set up with SQL Server, you'll need to migrate your data to MySQL and update your
db_config.php— but for new installations, it's just clone and go.
Tip: After completing these steps, navigate to
/setup/to verify everything is configured correctly.
-
Clone the repository
git clone https://github.com/edmozley/freeitsm.git cd freeitsm -
Configure database credentials
- Copy
db_config.sample.phpto a secure location outside your web root:C:\wamp64\db_config.php (recommended) - Edit the copied file with your MySQL credentials:
define('DB_SERVER', 'localhost'); define('DB_NAME', 'freeitsm'); define('DB_USERNAME', 'your_username'); define('DB_PASSWORD', 'your_password');
- Update
config.phpline 10 if you chose a different location
- Copy
-
Create the database
- Create a new database named
freeitsmin MySQL - Run
database/freeitsm.sqlto create tables, or use the Setup page's DB Verify to auto-create them
- Create a new database named
-
Set up encryption key (for sensitive settings)
mkdir C:\wamp64\encryption_keys # Generate a random 256-bit key (64 hex characters) php -r "echo bin2hex(random_bytes(32));" > C:\wamp64\encryption_keys\sdtickets.key
-
Configure web server
- Point your web server to the application root
- Ensure PHP extensions are enabled:
pdo_mysql,curl,openssl,mbstring - Restart your web server
-
Verify setup
- Navigate to
http://your-server/setup/to run the setup verification checks - Confirms config files, database connection, PHP extensions, and security settings
- Delete the
/setupfolder once your system is in production
- Navigate to
-
First login
- Navigate to
http://your-server/login.php - A default admin account is created by the SQL script:
- Username:
admin - Password:
freeitsm
- Username:
- Change this password immediately after first login via the account menu
- Navigate to
-
Import demo data (optional)
- Navigate to System → Demo Data to populate modules with realistic sample data
- Import Core first (creates analysts, departments, teams, and end users), then choose which modules to populate
- Includes tickets, assets, knowledge articles, changes, calendar events, morning checks, contracts, service status, software licences, forms, tasks, and process-mapper flowcharts
- Demo analysts use password
demo1234 - Designed for fresh installations only — each module can be imported once
| File | Location | Purpose | Commit to Git? |
|---|---|---|---|
config.php |
Web root | Main config (references external DB config) | ✅ Yes |
db_config.php |
Outside web root | Database credentials | ❌ NO |
db_config.sample.php |
Web root | Template for db_config.php | ✅ Yes |
Before making changes: This README provides essential context about the codebase structure. Read relevant sections before modifying code.
| Component | Technology |
|---|---|
| Backend | PHP 7.4–8.4 |
| Database | MySQL 8.0+ (PDO MySQL) |
| Frontend | Vanilla JavaScript, HTML5, CSS3 (no frameworks) |
| Rich Text Editor | TinyMCE 6+ |
| Email Integration | Microsoft Graph API + Gmail API (OAuth 2.0) |
| Encryption | AES-256-GCM (sensitive data at rest) |
| Web Server | Apache (WAMP/XAMPP/LAMP) or any PHP-capable server |
The platform is organised into 16 modules, accessible from a landing page (index.php) and a shared waffle menu for cross-module navigation.
| Module | Folder | Colour | Description |
|---|---|---|---|
| Watchtower | watchtower/ |
Slate #1e293b |
Unified attention dashboard showing actionable items across all modules |
| Tickets | tickets/ |
Blue #0078d4 |
Outlook-style ticket inbox with email integration, departments, teams, and audit trails |
| Assets | asset-management/ |
Green #107c10 |
IT asset tracking, user assignments, and vCenter VM inventory |
| Knowledge | knowledge/ |
Purple #8764b8 |
Rich-text knowledge base articles with AI chat and vector search |
| Changes | change-management/ |
Teal #00897b |
ITIL change management with risk matrix, audit trail, comments, and post-implementation review |
| Calendar | calendar/ |
Orange #ef6c00 |
Event calendar with categories and scheduling |
| Checks | morning-checks/ |
Cyan #00acc1 |
Daily infrastructure health checks (RAG status) with 30-day trend charts |
| Reporting | reporting/ |
Brown #ca5010 |
System logs, audit trails, and analytics |
| Software | software/ |
Indigo #5c6bc0 |
Software inventory and deployment tracking |
| Forms | forms/ |
Teal #00897b |
Dynamic form builder with sidebar list, tabbed editor (Fields/Preview), filler, and submission reporting |
| Contracts | contracts/ |
Amber #f59e0b |
Supplier and contract lifecycle management with terms, financials, and notice tracking |
| Service Status | service-status/ |
Emerald #10b981 |
Real-time service health dashboard with incident tracking |
| Wiki | system-wiki/ |
Red #c62828 |
Auto-generated codebase documentation browser |
| LMS | lms/ |
Blue #2563eb |
Learning Management System with SCORM 1.1/1.2/2004 course player, learning groups, assignments, deadlines, and progress tracking |
| Processes | process-mapper/ |
Indigo #6366f1 |
Visual flowchart builder with dot-grid canvas, snap-to-grid, connectors, and slide-in detail panel |
| System | system/ |
Blue-grey #546e7a |
Encryption key management, security settings, and module access control |
sdtickets/
├── config.php # Database credentials & global settings
├── index.php # Landing page (module selection grid)
├── login.php # Authentication page
├── logout.php # Logout handlers
├── analyst_logout.php
├── admin_settings.php # Legacy admin panel (tickets settings)
├── check_email.php # Scheduled email import (all mailboxes)
├── oauth_callback.php # Microsoft OAuth 2.0 callback
├── google_oauth_callback.php # Google OAuth 2.0 callback
├── forgot-password.php # Password reset request page
├── reset-password.php # Password reset (with token from email)
│
├── includes/ # Shared PHP components
│ ├── functions.php # Database connection helper
│ ├── waffle-menu.php # Cross-module navigation menu + user account menu
│ ├── encryption.php # AES-256-GCM encryption/decryption
│ ├── totp.php # Pure PHP TOTP (RFC 6238) for MFA
│ ├── gmail.php # Gmail API helper (send, read, refresh tokens)
│ ├── template_email.php # Automated email templates for ticket events
│ └── module-colors.php # Module colour definitions
│
├── assets/ # Static assets
│ ├── css/
│ │ ├── inbox.css # Core layout & shared styles
│ │ ├── knowledge.css # Knowledge base styles
│ │ ├── calendar.css # Calendar widget styles
│ │ ├── rota.css # Staff rota styles
│ │ ├── change-management.css # Change management styles
│ │ └── itsm_calendar.css # ITSM calendar styles
│ ├── js/
│ │ ├── inbox.js # Ticket interface logic
│ │ ├── knowledge.js # Knowledge base logic
│ │ ├── calendar.js # Calendar logic
│ │ ├── rota.js # Staff rota logic
│ │ ├── change-management.js # Change management logic
│ │ ├── change-calendar.js # Change management calendar logic
│ │ ├── itsm_calendar.js # ITSM calendar logic
│ │ ├── qrcode.min.js # Client-side QR code generator (for MFA setup)
│ │ └── tinymce/ # Rich text editor library
│ └── images/
│ └── CompanyLogo.png # Company logo (replace with your own)
│
├── tickets/ # Ticket Management Module
│ ├── index.php # Three-panel inbox interface
│ ├── users.php # User directory & their tickets
│ ├── calendar.php # Ticket scheduling calendar
│ ├── rota.php # Staff rota weekly grid
│ ├── settings/ # Departments, types, origins, mailboxes, analysts, teams, rota shifts
│ ├── includes/ # Module header
│ └── attachments/ # Email attachment storage
│
├── asset-management/ # Asset Management Module
│ ├── index.php # Asset list & user assignments
│ ├── dashboard/
│ │ └── index.php # Per-analyst widget dashboard with Chart.js
│ ├── servers/
│ │ └── index.php # vCenter VM inventory with detail modal
│ ├── settings/
│ │ └── index.php # vCenter connection settings
│ └── includes/
│
├── knowledge/ # Knowledge Base Module
│ ├── index.php # Article list & editor
│ ├── review.php # Article review workflow
│ ├── settings/ # Email, AI, and embedding settings
│ └── includes/
│
├── change-management/ # Change Management Module
│ ├── index.php # Change request list & detail
│ ├── calendar.php # Calendar view of scheduled changes
│ ├── approvals.php # Pending approvals view
│ ├── settings/ # Module settings (field visibility)
│ └── includes/
│
├── calendar/ # Calendar Module
│ ├── index.php # Full calendar view with events
│ ├── settings/ # Event categories
│ └── includes/
│
├── morning-checks/ # Morning Checks Module
│ ├── index.php # Daily check interface with PDF export
│ ├── settings/ # Settings page (check definitions, drag-and-drop reorder)
│ ├── create_tables.sql # Database schema
│ └── includes/
│
├── reporting/ # Reporting Module
│ ├── index.php # Reporting landing page (area selection)
│ ├── logs/
│ │ └── index.php # System logs (logins, email imports)
│ ├── tickets/
│ │ └── index.php # Ticket dashboards (coming soon)
│ └── includes/
│
├── software/ # Software Module
│ ├── index.php # Software inventory dashboard
│ ├── dashboard/
│ │ ├── index.php # Software dashboard (Chart.js widgets with drill-down)
│ │ └── library.php # Widget library management
│ ├── licences/
│ │ └── index.php # Licence management (CRUD, search, CSV export)
│ ├── settings/
│ │ └── index.php # API key management
│ └── includes/
│
├── system/ # System Module
│ ├── index.php # System landing page (area selection)
│ ├── encryption/
│ │ └── index.php # Encryption key management
│ ├── modules/
│ │ └── index.php # Analyst module access control
│ └── includes/
│
├── forms/ # Forms Module
│ ├── index.php # Unified form list + builder (sidebar + editor layout)
│ ├── builder.php # Legacy form designer (deprecated, use index.php)
│ ├── fill.php # Form filler (A4-style with company logo)
│ ├── submissions.php # Submission table, detail modal, CSV export
│ ├── create_tables.sql # Database schema
│ └── includes/
│
├── lms/ # LMS Module
│ ├── index.php # Dashboard with courses, groups, assignments, progress
│ ├── player.php # SCORM player with iframe + API bridge
│ ├── content/ # Uploaded SCORM packages (one folder per course)
│ └── includes/
│
├── process-mapper/ # Process Mapper Module
│ ├── index.php # Visual flowchart editor
│ └── includes/
│
├── network-mapper/ # Network Mapper Module
│ ├── index.php # Diagrams landing page
│ ├── diagram.php # Per-diagram editor shell
│ └── includes/
│
├── setup/ # Setup verification (delete after going live)
│ └── index.php # Diagnostic checks page
│
├── api/ # REST API endpoints (~140 total)
│ ├── tickets/ # ~48 endpoints
│ ├── assets/ # 8 endpoints (inc. vCenter sync)
│ ├── knowledge/ # 16 endpoints (inc. AI chat)
│ ├── change-management/ # 15 endpoints
│ ├── calendar/ # 7 endpoints
│ ├── morning-checks/ # 7 endpoints
│ ├── reporting/ # 2 endpoints
│ ├── software/ # 5 endpoints
│ ├── forms/ # 7 endpoints
│ ├── settings/ # 2 endpoints
│ ├── system/ # 4 endpoints (encryption, module access)
│ ├── myaccount/ # 6 endpoints (password, MFA setup/verify/disable)
│ ├── auth/ # 2 endpoints (password reset request/confirm)
│ ├── lms/ # 9 endpoints (courses, groups, assignments, progress, SCORM data)
│ ├── process-mapper/ # 4 endpoints (list, get, save, delete)
│ ├── network-mapper/ # 7 endpoints (list/get/create/save/delete diagrams + versions)
│ ├── external/ # External API (software inventory)
│ └── watchtower/ # Watchtower API (session + extension endpoints)
│
├── browser-extension/ # Chrome/Edge Watchtower extension (Manifest V3)
│ ├── manifest.json
│ ├── background.js # Service worker (polling + badge)
│ ├── popup.html/js/css # Extension popup UI
│ ├── options.html/js # Settings page
│ └── icons/ # Extension icons
│
└── database/ # SQL schema scripts
├── create_teams_tables.sql
├── create_users_assets_table.sql
└── add_knowledge_embeddings.sql
A cross-module navigation component inspired by Microsoft 365's app launcher. Appears in every module's header, allowing quick switching between all modules. Each module is registered here with its name, path, icon, and colour gradient. Respects $_SESSION['allowed_modules'] to filter visible modules per analyst.
Also contains the user account menu — an initials avatar circle in the top-right of every page. Clicking opens a dropdown with:
- Change Password — modal to update password (validates current password, minimum 8 characters)
- Multi-Factor Authentication — modal to set up or disable TOTP-based MFA (generates QR code for authenticator apps)
- Logout — with confirmation prompt
To add a new module, add an entry to the $modules array and corresponding CSS classes.
Pure PHP implementation of RFC 6238 (TOTP) and RFC 4226 (HOTP) for multi-factor authentication. No external dependencies — uses PHP's built-in hash_hmac() and random_bytes().
- Secret generation: 20 random bytes → Base32 encoded (32-character string)
- Code generation: HMAC-SHA1 with 30-second time steps, dynamic truncation → 6-digit code
- Verification: Checks ±1 time window (90-second tolerance) using
hash_equals()for timing-safe comparison - URI format:
otpauth://totp/FreeITSM:{username}?secret={base32}&issuer=FreeITSM
TOTP secrets are encrypted at rest using AES-256-GCM via encryptValue() before being stored in the analysts.totp_secret column.
AES-256-GCM authenticated encryption for sensitive database values.
- Key file:
C:\wamp64\encryption_keys\sdtickets.key(outside web root) - Format: Encrypted values stored as
ENC:+ base64(IV + auth tag + ciphertext) - Migration: Values without the
ENC:prefix pass through unchanged, allowing gradual rollout - Encrypted settings: Defined in
ENCRYPTED_SETTING_KEYSandENCRYPTED_MAILBOX_COLUMNSconstants
// Encrypt before saving to DB
$encrypted = encryptValue($plaintext);
// Decrypt after reading from DB
$plaintext = decryptValue($encrypted);
// Decrypt all sensitive columns in a mailbox row
$mailbox = decryptMailboxRow($mailbox);Currently encrypted in system_settings:
vcenter_server,vcenter_user,vcenter_passwordknowledge_ai_api_key,knowledge_openai_api_keyintune_tenant_id,intune_client_id,intune_client_secret
Of those, the true secrets (vcenter_password, knowledge_ai_api_key, knowledge_openai_api_key, intune_client_secret) are also listed in MASKED_SETTING_KEYS — api/settings/get_system_settings.php returns them as ****<last4> rather than plaintext, and api/settings/save_system_settings.php treats blank or asterisk-prefixed submissions as "leave unchanged" so the encrypted value is preserved when the user saves the form without re-typing the secret.
Currently encrypted in target_mailboxes:
azure_tenant_id,azure_client_id,azure_client_secretoauth_redirect_uri,imap_server,target_mailbox
Contains connectToDatabase() which returns a PDO MySQL connection using the credentials from db_config.php. Also contains getAnalystAllowedModules() which loads module access permissions for an analyst.
Native multi-language support with a t('namespace.path.to.key') call pattern in both PHP and JavaScript. Phase 1 ships the infrastructure plus a pilot conversion of the Process Mapper module.
Folder structure: lang/<locale>/<namespace>.php returns a nested PHP array of translations. The first dot-separated segment of a t() key maps to the filename; everything after walks the nested array.
lang/
en/common.php → t('common.save') → "Save"
en/process-mapper.php → t('process-mapper.toolbar.process') → "Process"
fr/common.php → t('common.save') → "Enregistrer"
fr/process-mapper.php → t('process-mapper.toolbar.process') → "Étape"
Supported locales (phase 1): en, fr, de, es, pt-BR, nl, it, pl. Adding a language is a code change — add to I18n::SUPPORTED_LOCALES and create the lang/<code>/ folder. Locale codes follow BCP 47 (matches HTML lang attribute), with the hyphen form used for region-subtagged locales (pt-BR).
Fallback is per key, not per file. If lang/de/tickets.php has 80% of keys translated, you get those 80% in German and the missing 20% in English. Files that simply don't exist for a locale are treated as empty. Last-resort behaviour: return the key itself so unfilled strings are visible during development.
Interpolation: t('common.welcome', ['name' => 'Ed']) substitutes {name} in the translation. Unknown placeholders are left as-is.
JS bridge: pages declare which namespaces they need ($translationNamespaces = ['common', 'process-mapper']), and the PHP page emits window.translations = {...} with English fallback already merged in per key. JS calls t('common.save') using the same key form as PHP.
Locale resolution (in priority order): logged-in analyst's interface_language user preference → browser Accept-Language header (best supported match, primary subtag fallback) → 'en'. Selectable in System → Preferences — on change, persists to user_preferences and reloads the page so PHP re-renders.
Security: namespace identifiers are regex-validated to prevent path traversal via the t() argument; locale codes are validated against the supported list before being used as a directory name; JSON encoding of the JS bridge uses JSON_HEX_* flags to prevent script-tag injection from translation values.
Adding translations to a module: include includes/i18n.php, call I18n::initFromSession(), set $translationNamespaces for the JS bridge, set <html lang> to I18n::getLocale(), and replace literal strings in PHP with <?php echo htmlspecialchars(t('namespace.key')); ?> and in JS with t('namespace.key').
Each module has its own includes/header.php that:
- Checks session authentication (redirects to login if not logged in)
- Sets
$current_modulefor waffle menu highlighting - Renders the header bar with the module's colour gradient
- Includes the waffle menu button, nav tabs, and user account avatar menu
Unified attention dashboard — a single pane of glass showing actionable items across all modules.
- Attention cards: Eight module-themed cards showing only items that need attention
- Morning Checks: Completion status, failed/warning checks
- Tickets: Open counts, urgent/high priority, unassigned tickets
- Changes: Upcoming changes, pending approvals, in-progress changes
- Calendar: Today's events, weekly event count
- Service Status: Active incidents, degraded services
- Contracts: Expiring contracts (30/90 day), upcoming notice periods
- Knowledge: Recently published articles, overdue reviews
- Assets: Total count, assets not seen in 7+ days
- Features: Color-coded status dots (green/amber/red), auto-refresh every 5 minutes, click-through to each module
The primary module. Three-panel Outlook-style interface.
- Left panel: Folder tree with a By Department / By Analyst toggle at the top — switch between grouping tickets by department + status (default) or by assigned analyst + status. The choice is persisted per-analyst via
user_preferencesso each analyst keeps their own preferred view across sessions. The Unassigned folder is contextual: in Department view it lists tickets with no department; in Analyst view it lists tickets with no assignee — making it easy to see what needs picking up - Middle panel: Ticket list (searchable)
- Right panel: Reading pane with full email thread
- Features: Create tickets, reply/forward emails, attachments, internal notes, audit trail, team-based filtering, scheduling
- Time tracking: Log time spent on a ticket directly in the reading pane. Inline form (minutes + optional description) appends entries to a list showing who logged what and when, with a running total at the top. Each entry is soft-deletable (only by the analyst who logged it) so deleted rows stay in the audit history. Backed by a dedicated
ticket_time_entriestable (analyst + ticket + minutes + entry datetime + notes +is_activefor soft delete) - SLAs: business-hours-aware Service Level Agreements with per-priority response and resolution targets, configurable business calendars (timezone + weekly hours + holidays), pause statuses, mid-ticket-priority-change behaviour, and admin-controlled enforcement cutoff date (so existing tickets can be grandfathered in on first activation). Compute-on-read engine reads the ticket audit log + the per-priority calendar — no stored counters, no drift. Breach notifications are fully configurable per department: define a default rule plus per-dept overrides, choose any combination of recipients (assignee / department team members / specific analyst / custom email addresses), and pick separate triggers for warning (approaching breach) and breach (actual). A cron worker (
cron/sla_breach_check.php, run every 5 minutes via Windows Task Scheduler or Linux cron — seedocs/sla-cron-setup.md) walks open SLA tickets, fires emails through the ticket's originating mailbox, and de-dupes so each (ticket, target, trigger) fires at most once. Seedocs/sla.mdfor the full design - AI Reply Cleanup: When typing a reply, click the ✨ Cleanup button to have Claude rewrite the rough draft as a properly formatted email — adds a "Dear [Name]," greeting from the requester, fixes grammar, applies the configured tone, and signs off "Kind regards,". The prompt is locked down so it won't invent technical details, fabricate apologies, or pad the content beyond what was written. Streams live into the editor; an Undo link appears for 30 seconds in case Claude butchers the rewrite. Configured under Tickets → Settings → Reply Cleanup with its own Anthropic API key (separate from RFP AI / Knowledge AI) so usage shows up as its own line on the Anthropic billing dashboard. Choose the model (Haiku 4.5 default, Sonnet 4.6, Opus 4.7) and tone (Friendly / Formal / Brief).
- Drag-and-drop triage: Drag any ticket from the list onto a folder to update it. In Department view: drop on a department folder to reassign, drop on a department + status subfolder to update both, drop on Unassigned to clear the department. In Analyst view: drop on an analyst folder to assign that analyst as the owner (sets both
assigned_analyst_idandowner_idso the right-pane Owner field stays in sync), drop on an analyst + status subfolder to update both, drop on Unassigned to clear the analyst. Hovering a collapsed folder during a drag auto-expands it (Outlook-style spring-loaded folders) so any nested status is reachable - Settings: Departments, ticket types, origins, mailboxes (Microsoft 365 + Google Workspace), email templates, analysts, teams
- Mailbox whitelist: Per-mailbox domain and email address whitelisting — non-whitelisted senders are rejected
- Email actions: Configurable per-mailbox actions for rejected emails (delete, move to Deleted Items, mark as read) and imported emails (delete, move to folder) with folder verification
- Email templates: Automated email responses triggered by ticket events (new ticket from email, ticket assigned, ticket closed) with merge codes for ticket reference, requester name, analyst name, and more
- Ask AI: Button in ticket detail view opens a slide-in chat panel that searches the knowledge base for relevant articles using ticket context
- Staff rota: Weekly grid showing analyst shift patterns, WFH/office location, and on-call status with per-day entry management and configurable shift definitions
- Activity log: Searchable, paginated log of imported and rejected emails per mailbox with clickable processing log details
- Dashboard (
dashboard/): Per-analyst customisable dashboard with Chart.js widgets- Widget library with 15 pre-built charts (bar, pie, doughnut, line) covering status, priority, department, type, analyst, origin, first time fix, and time-series
- Time-series widgets with configurable grouping: day, month, or year
- Multi-series: stacked bar charts broken down by status or priority, and created-vs-closed comparison line charts
- Configurable date range filter (last 7/30 days, this month, last 3/6/12 months, this year, all time)
- Department filter: scope any widget to specific departments
- Each analyst picks widgets for their own dashboard
- Status filtering on supported widgets
- Inline editing: cog icon on each widget opens a modal to edit properties without leaving the dashboard
- Drag-and-drop reordering
- Help guide (
help.php): Interactive guide covering inbox navigation, ticket lifecycle, comments and attachments, dashboard widgets, calendar, rota, and settings
IT asset management with vCenter integration.
- Assets tab: Searchable asset list with user assignments (many-to-many)
- Servers tab (
servers/): Virtual machine inventory synced from VMware vCenter REST API- Displays VM name, OS, IP, host, cluster, CPU, memory, disk
- Clickable rows show full detail modal with raw JSON from vCenter
- Stores all API response data in
raw_datacolumn
- Dashboard (
dashboard/): Per-analyst customisable dashboard with Chart.js widgets- Widget library with 13 pre-built charts (bar, pie, doughnut) covering OS, manufacturer, model, memory, GPU, TPM, BitLocker, etc.
- Each analyst picks widgets for their own dashboard
- Status filtering on supported widgets
- Drag-and-drop reordering
- Settings: vCenter server URL, username, and password (encrypted); Microsoft InTune tenant ID, client ID, client secret, and verify-SSL toggle (sensitive values encrypted); Sync button kicks off a Microsoft Graph-based InTune device import as a background worker, showing a progress bar
- InTune integration: Pulls all managed devices from Microsoft InTune (via Microsoft Graph
/deviceManagement/managedDevices) intointune_devicesand links them toassetsby hostname (auto-creates stub assets for unknown hostnames). Asset detail panel shows an extra InTune tab when a device matches - Device Manager (
api/external/device-manager/submit/): Enumerates Windows Device Manager devices (category, name, driver manufacturer, driver version, status) and displays them grouped by class on the asset detail screen - PowerShell inventory agent (
scripts/Invoke-AssetInventory.ps1): Collects hardware, disks, network, GPU, TPM, BitLocker, device manager, and installed software from Windows machines and posts to the system-info and device-manager APIs - System-info API (
api/external/system-info/submit/): External endpoint that ingests asset inventory data, syncs disk and network adapter tables, and processes software inventory - Help guide (
help.php): Interactive guide covering asset overview, detail screen, inventory script deployment, collected data, server management, and dashboard widgets
Rich-text knowledge base with AI integration.
- TinyMCE editor for article creation
- Tag-based organisation and full-text search
- AI chat powered by Anthropic Claude (searches articles via vector similarity)
- OpenAI embeddings for semantic search
- Email sharing capability
- Article review workflow
- Article versioning — save as new version to archive previous content with full version history
- Article archiving with recycle bin (soft delete, restore, configurable auto-purge)
- Help guide (
help.php): Interactive guide covering article creation, review workflow, Ask AI, search and navigation, sharing and export, with scroll-spy sidebar navigation
Change request tracking and approval workflows with ITIL-aligned processes.
- Calendar view with month/week/day views for visualising scheduled changes
- Status-based filtering (Draft, Pending Approval, Approved, In Progress, Completed, Failed, Cancelled)
- Click-through from calendar to change detail view
- Approvals page showing changes pending approval (filter by All, Assigned to me, Requested by me, My CAB reviews)
- CAB (Change Advisory Board) multi-member approval with required/optional reviewers, configurable threshold (all or majority), and auto-status transitions on vote
- CAB review panel in detail view with colour-coded vote cards and inline voting for pending members
- Risk assessment matrix with 5x5 colour-coded grid, auto-calculated risk score and level
- Post-implementation review fields for completed/failed changes
- Activity timeline combining comments and audit trail with inline commenting
- Settings page with configurable form field visibility (show/hide fields per section)
- Help guide (
help.php): Interactive guide covering change types, lifecycle, recording changes, CAB review, risk assessment, and post-implementation review
Event calendar with configurable categories.
- Month, week, and day views
- Drag-and-drop to move events between days in month view
- Category colour coding and filtering
- Events visible in adjacent-month cells for context
- Help guide (
help.php): Interactive guide covering calendar views, event creation, categories, settings configuration, and scheduling tips for IT teams
Daily infrastructure health check recording.
- Define checks with Red/Amber/Green (RAG) status options
- Record daily results per check item
- 30-day trend charts — click any bar to jump to that day's checks
- Settings page with tabbed layout, modal popups for add/edit, and drag-and-drop reordering
- PDF export with selectable text, company logo, and coloured status values
- Raise ticket from a check: amber and red rows show a "+ Raise ticket" button that opens a modal pre-filled with the check name, status, date and notes. The analyst picks priority, assignee, department and type, and a ticket is created with the current analyst as the requester
- Help guide (
help.php): Interactive guide covering daily checks, status types, trend chart, PDF export, and settings configuration
System logs and audit trails.
- Login attempt tracking (success/failure with IP and user agent)
- Email import logs
- System event logs
- Searchable and sortable tables
- Intune Dashboard (
reporting/intune/): Curated dashboard of Microsoft Intune device aggregations — KPI strip (total devices, compliant %, encrypted %, stale 30+ days, recently enrolled) plus eight Chart.js widgets covering compliance breakdown, OS distribution, owner type (corporate vs personal), top manufacturers, top OS versions, last-sync window distribution, 90-day enrolment trend, and encryption-by-OS stacked bar. Single API call returns all aggregates; threshold-based KPI tone (green/red) flags compliance and encryption issues at a glance. Shows the timestamp of the most recent Intune sync job in the toolbar. Drill-down: click any chart slice/bar/point or any KPI card to open a modal listing the matching devices with name, user, OS, compliance pill, encryption, and last sync. Paginated (25 rows per page) with Prev/Next; Export CSV button on the modal footer downloads every matching row (UTF-8 BOM for Excel). - Help guide (
help.php): Interactive guide covering ticket reports, system logs, data interpretation, filters, and reporting best practices
Software inventory tracking across the estate.
- External API endpoint for automated inventory submission
- Per-machine software mapping
- Dashboard (
software/dashboard/): Customisable Chart.js widget dashboard- Widget types: version distribution per application, top installed applications, publisher distribution
- Chart types: bar, pie, doughnut
- Click any chart segment to drill down and see machines/details in a modal
- Cog icon for inline widget editing, drag-and-drop reordering
- Widget library for creating, editing, duplicating, and deleting widgets
- Licences (
software/licences/): Software licence management database- Record licences against applications in the software inventory
- Licence types: Per User, Per Device, Site, Concurrent, Subscription, Other
- Track renewal dates with colour-coded warnings (overdue/approaching/ok)
- Store licence keys, costs, portal URLs, vendor contacts, and notes
- Searchable and sortable table with status badges (Active/Expired/Cancelled)
- CSV export of all licence data
- Help guide (
help.php): Interactive guide covering software inventory, dashboard widgets, licence management, data collection, and settings
System administration and configuration.
- Encryption (
system/encryption/): Guided interface for managing the AES-256-GCM encryption key- Shows key status (configured/missing/invalid) with colour-coded status card
- One-click key generation — writes directly to
c:\wamp64\encryption_keys\sdtickets.key - Instructions on key placement, backup importance, and what data is encrypted
- No regenerate button to prevent accidental key destruction
- Module Access (
system/modules/): Control which modules each analyst can see- Toggle matrix: analysts as rows, modules as columns
- "All Access" toggle per analyst (default state — backward compatible)
- System module cannot be disabled (always accessible)
- Auto-saves on toggle with debounced API calls and toast notifications
- Permissions enforced on homepage cards and waffle menu navigation
- Branding (
system/branding/): Organisation-wide logo and default header/footer text used by Network Mapper diagrams (and future PDF/PNG exporters)- Logo upload supports PNG, JPG, or SVG (2 MB cap, extension + mime whitelist); SVG recommended for crisp print/export; old logos are torn down before the new file is saved
- Six header/footer slots (header left/centre/right, footer left/centre/right) accept free text mixed with template tokens —
{{logo}},{{title}},{{author}},{{version}},{{modified}}— resolved client-side at render time - Sensible defaults preloaded on a fresh install (logo top-left, title top-centre, author + version + modified along the bottom); per-page "Reset to defaults" button
- Stored as seven
system_settingskey/value rows (branding_logo_path,branding_header_left, …,branding_footer_right) so no new table is needed; uploads land insystem/uploads/branding/(gitignored) - Logo path is sanity-checked against disk on every read — a stale DB row pointing at a deleted file surfaces as "no logo" rather than 404ing every diagram open
- Toast Notifications (
assets/js/toast.js): Global notification system used across all modules- Four types: success (green), error (red), warning (amber), info (blue) — each with icon and colour bar
- 9 configurable screen positions via visual grid picker in System Settings → General
- Position preference saved per-browser in localStorage
- Slide-in animations, auto-dismiss after 4 seconds, manual close button
- Demo Data (
system/demo-data/): One-click import of realistic sample data across all modules- Populates tickets, assets, knowledge articles, changes, calendar events, morning checks, contracts, services, software, forms, tasks, process-mapper flowcharts, analysts, and end users
- Process Mapper demo data is auto-laid-out: the JSON omits step coordinates and a server-side layered-DAG layout pass assigns x/y on import
- Designed for fresh installations — makes the system feel alive for evaluation and testing
- Debug Tools (
system/debug-tools/): Library of self-contained diagnostics for troubleshooting failed flows- App-store-style landing page lists each diagnostic with an ID badge (D001, D002…), category tag, "when to run" description, the full list of checks performed, runtime estimate, and side-effect notes
- Each diagnostic is a single PHP file under
api/system/debug-tools/that emits a plain-text section-delimited report — designed so the user can click Run, click Copy, and paste the entire report back to support without back-and-forth - Defensive by design: every section is wrapped so one failure doesn't kill the rest, and each diagnostic carries its own expected-state data so it works even when
db_verify.php,config.php, the import script, or the JSON files are missing or broken - D001 — Demo Core Data Import: 9-section report covering environment (PHP, OS, extensions, limits), config files (
config.php,db_config.php, DB constants), required files,core.jsonparse + record counts, DB connection (server version, charset), per-table schema sanity (expected vs actual columns, row counts, redacted sample row), transactional write probe (sentinel insert per table inside a rolled-back transaction), and a live import attempt that captures the real response + any PHP warnings + post-import row counts. Adding more diagnostics is justDnnn_short_name.php+ a registry entry on the landing page.
Dynamic form builder and submission system with a unified sidebar + editor layout.
- Form List & Builder (
index.php): Sidebar shows searchable list of all forms with quick actions (Fill In, Submissions, Delete). Main area has full-width title/description inputs with tabbed Fields and Preview panels. Click a form in the sidebar to edit it inline. Unsaved changes indicator with browser leave warning. Toast notifications on save/delete. - AI Assist (
builder.php): Toolbar button opens a modal where the analyst describes the form in plain English. A streaming Claude call (claude.ai-style live token output) generates a complete form definition — title, description, field types, labels, required flags and dropdown options — and applies it to the builder. Reuses the Anthropic API key configured for the RFP Builder under Contracts → Settings → RFP AI. Three example prompts on the modal for quick starts. - Filler (
fill.php): A4-style form rendering with company logo (alignment configurable). Required field validation. - Submissions (
submissions.php): Table view of all submissions. Click rows for detail modal. Date range filtering. CSV export with UTF-8 BOM for Excel compatibility. - Settings: Gear icon in sidebar opens settings modal. Configurable logo alignment (left, centre, right) applied to both preview and fill-in views.
- Field types:
text,textarea,checkbox,dropdown - Help Guide (
help.php): Full-page guide with left-pane navigation, scroll-spy, and 7 sections covering the form builder, field types, filling in forms, submissions, CSV export, settings, and quick tips.
Supplier and contract lifecycle management with configurable rich text terms.
- Dashboard (
index.php): Left sidebar with overview stats (contracts, active, expiring, suppliers, contacts), quick links, and universal search across contracts, suppliers, and contacts. Main area shows the full contracts table. - Add/Edit (
edit.php): Sectioned form with contract details, dates, financial, documents, and terms & data protection fields. Below the main form, a "Contract Terms Detail" section displays configurable TinyMCE rich text tabs for detailed contract terms (e.g. Special terms, KPIs, SLAs). - View (
view.php): Read-only detail view with all fields organised into sections. Contract terms content displayed in read-only tabs. Header buttons to Create Task and Add to Calendar open inline modals that prefill sensible defaults from the contract (title, description, due date = notice date or end date, assignee = contract owner) and link the new item back to the contract viacontract_id. Below the contract details, Related Tasks and Related Calendar Events sections list everything currently linked to the contract. - Suppliers (
suppliers/): Supplier register with legal/trading names, registration details, address, type/status, questionnaire tracking, and comments. - Contacts (
contacts/): Supplier contacts with name, job title, email, direct dial, and switchboard fields. - Settings (
settings/): Tabbed management of supplier types, supplier statuses, contract statuses, payment schedules, and contract term tabs. - Help (
help.php): Guided help page with left-pane navigation, scroll-spy, and 7 sections covering contract management, rich text terms, suppliers, contacts, settings configuration, and best practice tips. - RFP Builder (
rfp-builder/): AI-powered procurement requirements builder. Upload departmental feedback documents (one per dept), AI extracts every requirement / pain point / challenge, then a single AI consolidation pass deduplicates across departments, proposes 8-20 RFP categories, assigns priorities, and flags genuine contradictions. Manual editing tools (edit / split / merge / add custom / conflict resolve) refine the consolidated set. Lock to gate Phase 4 generation: introduction + scope + response instructions framing plus per-category sections, all streamed live (claude.ai-style SSE) with live progress trackers, TinyMCE editing, version history with restore, Pass 4 restyle that re-applies the style guide without changing meaning, and a print-friendly preview page for PDF export. Phase 5 supplier scoring with click-to-light 0-5 score boxes (red→green gradient), per-category running averages, full-screen radar chart, and multi-analyst rollup. Cross-supplier compare page with big-number cards, multi-supplier radar overlay, and category winners table marking the leader and gap. Coverage heatmap (category × department), AI activity panel + full filterable audit trail, and an in-app help guide.
Service health dashboard with incident-driven status tracking.
- Dashboard (
index.php): Grid view of all active services showing worst current impact from open incidents. Below, a list of active and recently resolved incidents. Create/edit incidents via modal with multi-service impact selector. - Settings (
settings/): Manage the list of services (name, description, display order, active status). - Help (
help.php): Guided help page with left-pane navigation, scroll-spy, and 6 sections covering the status dashboard, status levels, managing services and incidents, incident history, settings, and communication tips.
End-user portal allowing ticket requesters to register, log in, and interact with the service desk directly.
- Registration & Login: Users register with email, name, and password. If a user already exists in the system (e.g. created via email processing) but has no password, they can claim their account by registering with their email. Separate authentication from the analyst login using
$_SESSION['ss_user_id']. Supports MFA (TOTP) login challenge. - Dashboard (
index.php): Personalised overview showing ticket summary cards (Open, In Progress, On Hold, Total), a recent tickets table, and a live system status panel pulled from the Service Status module. - New Ticket (
new-ticket.php): Submit a new support ticket with mailbox selection, subject, priority, description, and drag-and-drop file attachments. - Ticket Detail (
ticket.php): View full ticket conversation thread and non-internal notes. Internal analyst notes are hidden from the portal. - User Avatar Menu: Initials circle in the header with dropdown for account management, MFA setup, password change, and logout.
- My Account: Users can set a preferred name (e.g. "Ed" instead of "Ed Mozley") and change their password.
- Multi-Factor Authentication: TOTP-based MFA using the same core libraries (
includes/totp.php,includes/encryption.php) as the analyst system. Users can enable/disable MFA from their account menu.
Learning Management System with SCORM course player and progress tracking.
- Course Management: Upload SCORM 1.1, 1.2, and 2004 ZIP packages. Manifest is auto-parsed to detect version and launch URL.
- SCORM Player (
player.php): Full-viewport iframe with dual JavaScript API bridge — exposes bothwindow.API(SCORM 1.x) andwindow.API_1484_11(SCORM 2004) so courses find whichever they look for. - Progress Tracking: Per-user status (not started, incomplete, completed, passed, failed), scores, bookmarks, suspend data, and resume support. All CMI data stored as key/value pairs.
- Learning Groups: Create groups of analysts with many-to-many membership. Assign courses to groups with optional deadlines.
- Admin Dashboard: Four tabs — Courses, Groups, Assignments, Progress. Progress tab shows every assigned analyst's completion status with overdue highlighting. Filterable by course, group, or status.
- Learner Data Viewer: View button per learner showing quiz responses with correct/incorrect badges, objectives, scores, suspend data with syntax-highlighted JSON, and all raw CMI elements.
Visual flowchart builder for documenting processes and workflows.
- Dot-grid canvas with 20px snap-to-grid on all movements
- Shape types: Process (rounded rect), Decision (diamond), Terminal/Start-End (pill), Document (wavy bottom)
- Connectors: Drag from edge handles to draw arrows between steps. Optional text labels on connectors (double-click to edit).
- Multi-select: Ctrl+click to toggle, rubber-band drag to select a region, Ctrl+A to select all
- Arrow key nudge: Move selected steps by one grid unit with cursor keys
- Slide-in detail panel: Click a step to edit label, type, colour, description, and view/edit its connectors
- Save/load: Full persistence with sidebar listing all saved processes
- Autosave: Optional toolbar toggle that debounces a save ~2s after the last edit. Live Word-style status indicator beside the toggle cycles through
✓ Saved/Unsaved/Saving…/⚠ Save failed — retry. Toggle state persists per-analyst viauser_preferences(key:process_mapper_autosave). The manual Save button still works regardless of toggle state. - Groups: Optional labelled coloured rectangles that sit behind steps. Click
Groupin the toolbar to drop one at the canvas centre, drag to move, drag the corner handle to resize, double-click to rename. Groups own their contents via a nullablegroup_idonprocess_steps: drop a step inside a group → step gets the group's id; drag the group → contained steps move with it; resize the group so a step falls outside → that step'sgroup_idclears on mouseup; delete the group → contained steps survive untouched but lose theirgroup_id. When groups overlap, the smallest (by area) wins for ownership — useful when you want to highlight a sub-flow inside a broader group. Persisted in theprocess_groupstable (id, process_id, label, color, color2, x, y, width, height); step membership is captured byprocess_steps.group_idand round-trips throughsave.phpvia agroupIdMap[oldRef -> realId]so newly-created groups in the same save are referenced correctly by their member steps. - Gradient fills on both steps and groups. Detail panel's Colour input grows a
Gradientcheckbox plus a second-colour picker; when on, the shape renders aslinear-gradient(135deg, color, color2)for a subtle diagonal fade. Stored as a separatecolor2column (nullable — null = solid as before). The default second colour seeded into the picker when you tick the box is a 40-unit darker shade of the primary colour so the gradient looks sensible out of the gate, but you can change it to anything. - Swimlanes: Horizontal bands stacked top-to-bottom across the canvas, with a left-edge label header showing the lane name (vertically). Steps gain a nullable
lane_idthat auto-assigns based on where the step ends up after each drag. Drag a lane's header up/down to reorder — all steps in every affected lane reflow to stay anchored to their lane's band. Drag the bottom edge of a lane to resize it — lanes below shift, and steps in them follow. Per-lane gradient fills supported via the samecolor2mechanic as steps and groups. Delete a lane: it disappears, the lanes below shift up to close the gap, and steps that belonged to it have theirlane_idcleared but keep their position so nothing is lost. Persisted in theprocess_lanestable (id, process_id, label, color, color2, display_order, height); lane id flows throughsave.phpas a temp-to-real mapping so newly-created lanes in the same save are referenced correctly by steplane_id. - Export to Mermaid: Toolbar
Exportbutton opens a modal with the current process rendered as Mermaid flowchart markup, ready to paste into any Markdown editor that supports it (GitHub READMEs, GitHub/GitLab wikis, Notion, Confluence, Obsidian, Mermaid Live Editor, etc.). Lanes becomesubgraphblocks withflowchart LRdirection so they stack vertically like horizontal swimlanes. Step shapes map to Mermaid's classic syntax: Process →["label"]rectangle, Decision →{"label"}rhombus, Terminal →(["label"])stadium, Document →[/"label"/]parallelogram. Connectors become-->arrows (with-->|"label"|if they have labels) and Mermaid resolves IDs across subgraph boundaries so cross-lane connectors just work. Hand-placed positions, colours, gradients, and groups don't survive — Mermaid auto-layouts. Copy button uses the Clipboard API with a Ctrl-A/Ctrl-C fallback. Lossy on visuals, faithful on structure.
Visual layer over the CMDB for drawing network and architecture diagrams. Diagrams are not standalone artwork — every node is a binding to a real CMDB object, so the diagram stays in sync with what the rest of the platform knows.
Status (Phase 1 complete — chunks A + B + C + D): working editor end-to-end. Drag a class tile onto the canvas → pick which CMDB object to bind it to → node is placed (snapped to 20px grid), draggable to move, Delete to remove. Planned objects render with dashed border + amber tint matching the CMDB browse styling. Connectors drawable between any two nodes via edge handles, with arrowheads, optional labels, and select/delete. Select a node → detail panel slides in beside the canvas → Add related objects pulls CMDB neighbours (outgoing/incoming relationships + property references) into a modal where you tick which to add; selected objects are placed in a ring around the source and connectors are auto-drawn with
cmdb_relationship_idpopulated so the line traces back to a real CMDB relationship. Autosave, manual save, save-as-new-version, read-only handling for historical versions all in place.
- Versioning from day one: each diagram is a row in a
network_diagramschain linked byparent_diagram_id. The leaf (no children) is the editable "current" version; older nodes are read-only history. Saving as a new version clones the leaf forward and stamps the old leaf as historical. No branching in v1 — a parent can have at most one child, so a chain is strictly linear. The editor enforces this on the frontend (Save and Save-as-new-version disabled on non-leaves, amber read-only banner shown) and the backend (save_diagram.phprefuses to write to a non-leaf;create_version.phprefuses to fork an already-forked parent). A Versions dropdown in the editor toolbar lists every version in the chain (newest first) with author + date + Current / Read-only / Viewing pills — click any to jump straight there. - Page size guide: optional dashed cyan outline rendered on the canvas at the chosen paper size — A4 / A3 / A2 / Letter / Tabloid × portrait or landscape, plus Off (default). Picked via a Page dropdown in the editor toolbar, persisted per-diagram (
network_diagrams.paper_size+paper_orientation), carried forward on save-as-new-version. Lets analysts compose diagrams inside the printable area before sharing/exporting so nothing gets cropped. Outline renders inside the SVG layer with a soft white fill (lets the dot-grid bleed through to distinguish on-page vs off-page) and a corner label naming the chosen paper. Drives the export bounds — see PNG/PDF export below. - PNG / PDF export: two toolbar buttons (
PNG,PDF) snapshot the diagram for sharing or printing. Capture is WYSIWYG with the page outline: if a paper size is set, the export clips exactly to the page rectangle so what you see inside the outline is what comes out; if no paper size is set, it crops to the bounding box of placed nodes plus 40px padding. Branding header/footer, page outline, connectors, node icons, and labels all render in the output exactly as they appear on screen. PNG is exported at 2× the diagram's native resolution (via html2canvas'sscale: 2) so the result is crisp on retina screens and when zoomed during a presentation; PDF uses jsPDF with the actual paper size + orientation (A4/A3/A2/Letter/Tabloid, portrait or landscape) so the file opens at the right physical dimensions for print. Implementation flow: stash the current zoom/scroll/selection, reset zoom to 1× so the rasteriser captures at native resolution, add an.is-exportingclass that hides edit-time chrome (selection rings, edge handles, the empty-state placeholder), callhtml2canvas(elCanvasInner, { x, y, width, height, scale: 2, backgroundColor: '#fff' }), then restore everything. Filename slugifies the diagram title + version label so a "Production Network" v2 exports asproduction-network-v2.png. Libraries vendored locally atassets/js/vendor/(html2canvas 1.4.1 + jsPDF 2.5.2) so the feature works offline and doesn't depend on a CDN at print time. - Header / footer overlay: optional six-slot branding strip rendered along the top and bottom of the page outline (left / centre / right, header + footer). Each slot is free text mixed with template tokens —
{{logo}}(the org-wide company logo uploaded at System → Branding),{{title}},{{author}},{{version}},{{modified}}— resolved client-side at render time. Org-wide defaults configured at System → Branding apply to every diagram by default; a Branding toolbar button opens a modal where any individual diagram can override one or more slots (Word/Google Docs style). Modal placeholders show the org default for each slot so the user sees what they're overriding. Reset clears all overrides and re-inherits the org defaults. Per-diagram overrides live on six nullableVARCHAR(200)columns onnetwork_diagrams(NULL = inherit, '' = explicit blank, anything else = override); carried forward on save-as-new-version. Gated on the page outline being on — without paper bounds the strip has no anchor point. Tokens are HTML-escaped at render; the logo renders as an<img>with max-height 28px so it sits neatly inside the strip. - Landing page (
network-mapper/): grid of cards, one per chain, each showing title, version pill (with chain length when > 1 version), description preview, node + connector counts, author, and updated timestamp. Filter-by-title search. + New diagram modal collects title, description, and an initial version label (defaultv1). - Editor (
network-mapper/diagram.php?id=X): title bar with a version pill that turns amber for historical versions, metadata row (author / created / updated), left-side CMDB class palette (one tile per class with its icon + name + object count, drag onto the canvas to start placing), main canvas with dot-grid background, and an action bar with autosave toggle, status indicator, Save as new version button, and Save button (also bound to Ctrl+S). Autosave mirrors Process Mapper's pattern:markDirty()/ 2s debouncedscheduleAutosave()/ status indicator cycling throughUnsaved/Saving…/✓ Saved/⚠ Save failed — retry/Autosave off, toggle state persisted inuser_preferences(keynetwork_mapper_autosave). Autosave defers mid-drag (same fix as Process Mapper #228 —save()reloads the diagram, which would destroy an in-progress drag).beforeunloadfires the browser's leave-prompt if there are unsaved changes. Save as new version auto-saves first if dirty (sincecreate_version.phpclones from the persisted state), then opens a modal pre-filled with the current diagram's metadata and a suggested incremented label (trailing integer in the version label bumps automatically —v3→v4). - Drag-to-canvas → bind → place (chunk C): drag a class tile onto the canvas, drop opens a CMDB object picker scoped to that class — preloads the class's full object list, type-ahead filters server-side (200ms debounced), arrow keys to navigate + Enter to pick, already-placed objects auto-hide so you can't double-place. After pick, the node renders at the drop coordinates (snapped to the 20px grid, offset so the icon centres on the cursor). Placed nodes show the class icon (from the chunk-B icon library) above a 12px label with the object name (truncated to 2 lines). Planned objects render with a dashed amber border + soft
#fffbebbackground + italic amber label + a small PLANNED pill, matching the CMDB browse/detail styling — turns the canvas into a visual as-is/to-be map. Click selects (cyan border + soft cyan glow), click on empty canvas clears. Drag moves the selected node (snapped to grid),markDirty()only runs if the node actually moved so a click-without-drag selects but doesn't dirty. Delete key removes the selected node and cascades down any incident connectors sosave_diagram.phpdoesn't reject the payload. Save → reload: every successful save re-fetches the diagram so temp ids (negative numbers used for unsaved nodes) resolve into real auto-increment ids — vital before any connector references a node. Current selection is preserved across the reload by matching oncmdb_object_idrather than node id. Read-only versions accept selection but reject drop / drag / delete (the backend would refuse anyway). - Connectors (chunk D part 1): hover or select a node → 4 cyan edge handles appear at the top/right/bottom/left of the icon. Mousedown on a handle starts a connect drag — a dashed cyan temp line tracks the cursor; mouseup on another node creates a connector and selects it. Connectors render as SVG paths with arrowheads (cyan when selected, slate otherwise) in an SVG layer behind the nodes. Endpoint geometry snaps to the side of each icon's bounding box that faces the other node, so lines stay sensible after dragging either end around. Click a connector to select (wider hit-area underneath the visible stroke so they're easy to grab), Delete removes it, double-click opens an inline cyan-bordered text input at the midpoint where you type a label (Enter saves, Esc cancels, blur saves, empty clears) which renders mid-line on a white pill. Self-loops and duplicate connectors (same direction between the same pair) are silently refused. Connectors between two brand-new (unsaved) nodes save in a single round-trip — both
tempIdand the connector'sfrom_node_id/to_node_id(which carry the same negative tempIds) are sent in the same payload, andsave_diagram.php'snodeIdMapresolves the refs server-side. - Detail panel + Add related objects (chunk D part 2): selecting any placed node slides a 320px panel in beside the canvas showing the node's name, class, planned status, a deep-link back to the CMDB object page, and every CMDB property that has a value (lazy-loaded from
get_object.phpon selection, hides empty properties, type-aware rendering — boolean as Yes/No, dropdown as a coloured pill matching the option's colour, object_ref as a pink pill linking into CMDB, dates locale-formatted, URLs auto-linked in text fields). The panel's Add related objects button opens a modal that calls a new endpoint (api/network-mapper/get_related_objects.php) which gathers everything CMDB knows about that object across three buckets — outgoing relationships (this depends on X / hosts X), incoming relationships (X is hosted by this), and property references (other objects that point at this one via anobject_refproperty). Rows render grouped by bucket with the related object's icon + name + class + relationship verb. Objects already on the canvas show a grey "on canvas" badge and a disabled checkbox so they can't be double-placed. Ticking rows enables the Add N objects button; confirm bulk-adds them: deduplicated bycmdb_object_id, new nodes laid out in a ring around the source node (radius scales with count so big pull-ins don't crowd, angles distributed evenly starting at 12 o'clock), with one connector per ticked path. Direction respects relationship kind (outgoing = src→other; incoming + property-ref = other→src), andcmdb_relationship_idis populated for relationship-derived connectors so the line traces back to a real CMDB row. Property-ref connectors leavecmdb_relationship_idNULL but carry the property name as their label. The flow turns "CMDB has a TON of info" into guided graph exploration — you start from one node and pull in neighbours on demand instead of dumping everything onto the canvas at once. Read-only versions show the panel but the Add button is disabled with a tooltip explaining why. - Icon library (~65 icons):
assets/js/network-mapper-icons.jsships awindow.NM_ICONSmap +window.NM_ICON_META(label + category per icon) +window.NM_ICON_CATEGORIES(display order/labels) +nmRenderIcon(key, size, extra)helper. Twelve categories: Compute & Servers (server / server-rack / server-blade / server-tower / mainframe / vm / function / workstation), Databases (database / database-cluster / database-cache), Storage (storage / storage-san / storage-tape / backup), Networking (network / router / switch / firewall / load-balancer / proxy / vpn / gateway / wireless-ap / modem / cdn / dns), Security (shield / lock / key / ids / siem), Cloud (cloud / cloud-private / cloud-public / cloud-hybrid / region), Containers (container / container-pod / kubernetes / registry), Applications & Data (application / service / website / api / microservice / queue / cache / dashboard), Endpoints (laptop / mobile / tablet / iot / printer), Monitoring & Ops (monitor / alert / log), People & Org (person / team / org), Files & Generic (document / folder / globe / mail / calendar / box). Feather-style 24×24 viewBox, stroke 1.8, currentColor so they tint with any container. Unknown keys fall back to box. The full set is also seeded intocmdb_iconsso CMDB Classes settings can pick from the wider library when defining new classes. Multiple variants per concept (3 server styles, 3 database styles, 4 cloud styles, etc.) so users can visually distinguish two objects of the same class on a diagram — e.g. "Production MS SQL" asdatabase-clustervs "Reporting Oracle" asdatabasevia the per-node icon override. - Per-node icon override: any node on a diagram can override its class's default icon with any of the ~65 keys above. UI lives in the node detail panel — Icon row shows the current icon + a Change button → opens the icon picker modal (12 category-grouped sections, type-ahead filter that matches both the icon key and its label). Picking the class's own default icon stores NULL rather than a redundant override, so the data stays clean. Reset button appears next to Change when an override is active.
network_diagram_nodes.icon_overrideis a free-text VARCHAR(100) so any icon key works, not just seeded ones. - Schema (3 tables, all prefixed
network_):network_diagrams(chain root via self-FKparent_diagram_idwithON DELETE SET NULLso deleting a version doesn't cascade through history),network_diagram_nodes(FK tocmdb_objectsfor binding, plus positionx/y,sizeenumsmall/medium/large, and optionalicon_overridefor per-node visual swap),network_diagram_connectors(from/to node ids with optionalcmdb_relationship_idif the line corresponds to a real CMDB relationship, plus free-textlabelandline_style). FKs cascade nodes + connectors on diagram delete; node delete cascades its incident connectors. - APIs (
api/network-mapper/):list_diagrams.php(leaf versions only — filters withWHERE id NOT IN (SELECT parent_diagram_id…)),list_versions.php(root-to-leaf chain ordered oldest first),get_diagram.php(single version hydrated with CMDB object name/class/icon/is_plannedper node),create_diagram.php,create_version.php(clones forward, refuses to fork an already-forked parent),save_diagram.php(transactional with temp→real node id mapping for connector refs; refuses to save non-leaf versions; only updates metadata fields the caller explicitly sends),delete_diagram.php(single version delete — older versions in the chain preserved via theSET NULLparent_diagram_id behaviour),get_related_objects.php(walkscmdb_object_relationshipsoutgoing + incoming andcmdb_object_properties.value_object_idto return all CMDB neighbours of a given object for the Add-related-objects flow — one row per (object, path) so an object reachable via two paths surfaces as two tickable rows).
Kanban-style task management with board and list views for tracking internal work.
- Board view: Three-column Kanban board (To Do, In Progress, Done) with drag-and-drop card movement
- List view: Sortable table with all task fields
- Quick create: Inline task creation from each board column
- Detail panel: Slide-in panel with inline editing, auto-save, and TinyMCE rich text description
- Subtasks: Two-level hierarchy with checkbox toggling and progress display on parent cards
- Linking: Link tasks to tickets or changes via searchable dropdowns
- Team assignment: Assign tasks to analysts and teams, filter board by team/analyst/personal
- Comments: Threaded comments on each task
- Watchtower integration: Overdue and due-today counts shown on attention dashboard
Configuration Management Database — model your IT estate as a graph of typed objects (servers, databases, applications, etc.) with a strict containment hierarchy and a separate user-defined relationships layer. See docs/cmdb.md for the full design and roadmap.
- Browse page (
cmdb/): class sidebar with object counts → table of objects in the selected class with name, parent, child count, last updated. Search box filters by name. + New creates an object and opens it for editing. - Object detail page (
cmdb/object.php?id=X): name editable inline (click and type, save on Enter/blur), class + parent breadcrumb, and three sections — Properties (dynamic form built from the class's property definitions, with type-aware inline editors: text input / number / date picker / Yes-No select / dropdown / object-reference picker with autocomplete), Hierarchy (parent + children list with clickable navigation), Relationships (outgoing and incoming columns side-by-side, each link clickable to navigate, X button to remove, + Add relationship modal with verb picker and global object autocomplete). Object references are rendered as pink pills you can click to drill through. Delete cascades to all descendants (with a clear confirmation showing how many will go). - Settings page with three tabs:
- Classes: define types of things (e.g. Server, Database). Each class has its own auto-generated immutable
class_keyplus an editable display name. Click the property-count badge on any class to manage its properties (label, immutable key, type — text/number/date/boolean/dropdown/object_ref, target class for object references, required flag, dropdown options, display order). Property keys are immutable so renaming a label never breaks references. - Relationship Types: define the verbs that link objects (e.g. depends on ↔ is depended on by). Three defaults seeded on first run.
- AI Integration: provider/key/model + custom-instructions textarea + Test connection button, mirroring the established per-feature Anthropic key pattern (separate from RFP AI / Knowledge AI / Reply Cleanup for granular billing visibility).
- Classes: define types of things (e.g. Server, Database). Each class has its own auto-generated immutable
- AI Suggest Properties (in the Properties manager): two-stage wizard — Claude asks 3-5 clarifying questions about the analyst's specific environment (e.g. for Database: "What kind — SQL Server / Postgres / Mongo?"), then suggests 6-12 tailored properties with rationale. Object-reference suggestions automatically create the missing target class on the fly (e.g. suggesting "Owner → Person" auto-creates a Person class if one doesn't exist).
- Tickets ↔ CMDB linking: tickets get an Affected CMDB Objects section in the reading pane — type to search across every CMDB object, click to link, X to unlink. Each linked object renders as a clickable info card showing class + parent context. The CMDB object detail page reciprocally gets an Activity panel with two buckets — open tickets (live) and recent closed tickets (capped at 20, total count shown) — each rendered as a card with status pill in the lookup colour, priority, assignee, department, and last-updated/closed date. Clicking any ticket card deep-links to that ticket via
?ticket_id=X. The AI summary prompt now also pulls open + closed ticket counts so the synthesis can mention things like "currently has 2 open tickets including a P1 backup failure" — turning the CMDB into a live operational map rather than a static inventory. - Planned objects: nullable boolean
is_plannedflag oncmdb_objectslets you record future-state architecture in the same CMDB as your real estate without forking the data. Toggle in the New Object modal and on the object detail page header. Planned objects render with a dashed border + amber PLANNED pill across every surface that shows the object (browse table, detail header, search picker results). Toggle off when the object goes live — single column flip, no migration. The AI summary prompt is taught to surface planned status near the start of the synthesis and frame the sentence in future tense ("will host…", "is proposed to…") so the reader knows immediately the object isn't yet in service. Designed to support the upcoming Network Mapper module where some diagrammed nodes will represent to-be architecture rather than as-is reality. - Data model (8 tables, all prefixed
cmdb_): icons (curated lookup), classes, class_properties, class_property_options, objects, object_properties, relationship_types, object_relationships. Strict cascade-delete on parent_id (per the design's ontological dependency parent semantics). Cycle prevention on parent assignment.
A Chrome/Edge browser extension that shows your Watchtower dashboard summary in a popup with a badge count for items needing attention.
- Generate an API key: Go to Software > Settings > API Keys, enter a label (e.g. "Chrome extension"), and click Generate. Copy the key.
- Install the extension: Open
chrome://extensions, enable Developer mode, click Load unpacked, and select thebrowser-extension/folder from this repository. - Configure: Click the extension icon, then Settings. Enter your FreeITSM server URL and paste the API key. Click Test to verify, then Save.
- Badge count: Shows the number of items needing attention (urgent tickets, unapproved changes, active incidents, etc.)
- Popup dashboard: Compact 8-module summary matching the full Watchtower view
- Configurable polling: 1 to 30 minute refresh intervals
- Rate limited API: 60 requests per minute per key (configurable in system settings)
- Works in Chrome and Edge (Manifest V3)
All endpoints live under api/ and return JSON. Every endpoint requires an active session ($_SESSION['analyst_id']).
session_start();
require_once '../../config.php';
require_once '../../includes/functions.php';
header('Content-Type: application/json');
if (!isset($_SESSION['analyst_id'])) {
echo json_encode(['success' => false, 'error' => 'Not authenticated']);
exit;
}| Endpoint | Method | Purpose |
|---|---|---|
get_emails.php |
GET | List tickets with latest email (filtered by dept/status) |
get_email_detail.php |
GET | Full email content and ticket info |
create_ticket.php |
POST | Create manual ticket |
delete_ticket.php |
POST | Delete ticket and related records |
assign_ticket.php |
POST | Assign ticket to analyst |
update_ticket_owner.php |
POST | Set ticket owner |
schedule_ticket.php |
POST | Set work_start_datetime |
search_tickets.php |
POST | Search by ticket#, email, or subject |
send_email.php |
POST | Send email via Microsoft Graph API |
get_ticket_attachments.php |
GET | List attachments for a ticket |
get_attachment.php |
GET | Download attachment file |
check_mailbox_email.php |
POST | Import emails for a mailbox |
get_departments.php |
GET | List all departments |
get_my_departments.php |
GET | List analyst's team-filtered departments |
save_department.php |
POST | Create/update department |
get_analysts.php |
GET | List all analysts |
save_analyst.php |
POST | Create/update analyst |
get_teams.php |
GET | List teams |
save_team.php |
POST | Create/update team |
get_mailboxes.php |
GET | List mailbox configurations |
save_mailbox.php |
POST | Create/update mailbox |
get_mailbox_whitelist.php |
GET | Get whitelist entries for a mailbox |
save_mailbox_whitelist.php |
POST | Replace whitelist entries for a mailbox |
get_mailbox_activity.php |
GET | Paginated activity log for a mailbox |
verify_mailbox_folder.php |
POST | Verify a mail folder exists via Graph API |
get_email_templates.php |
GET | List email templates |
save_email_template.php |
POST | Create/update an email template |
delete_email_template.php |
POST | Delete an email template |
get_notes.php |
GET | Get notes for a ticket |
save_note.php |
POST | Add internal note |
get_ticket_audit.php |
GET | Get change history |
get_ticket_counts.php |
GET | Counts by department/status |
get_rota_shifts.php |
GET | List rota shift definitions |
save_rota_shift.php |
POST | Create/update rota shift |
delete_rota_shift.php |
POST | Delete rota shift |
get_rota.php |
GET | Get rota entries for a week |
save_rota_entry.php |
POST | Create/update rota entry |
delete_rota_entry.php |
POST | Delete rota entry |
get_ticket_dashboard.php |
GET | Get analyst's dashboard widgets |
get_ticket_widget_data.php |
GET | Aggregated data for a widget chart |
get_ticket_widget_library.php |
GET | List all widget definitions |
add_ticket_dashboard_widget.php |
POST | Add widget to dashboard |
remove_ticket_dashboard_widget.php |
POST | Remove widget from dashboard |
save_ticket_dashboard_widget.php |
POST | Create/update widget definition |
reorder_ticket_dashboard_widgets.php |
POST | Reorder dashboard widgets |
delete_ticket_dashboard_widget.php |
POST | Soft-delete a widget |
| ...and more |
| Endpoint | Method | Purpose |
|---|---|---|
get_assets.php |
GET | List assets with user counts |
get_asset_users.php |
GET | Users assigned to an asset |
assign_asset_user.php |
POST | Assign user to asset |
unassign_asset_user.php |
POST | Remove user from asset |
get_servers.php |
GET | List VMs and ESXi hosts from servers table |
get_vcenter.php |
POST | Sync VMs from vCenter REST API |
debug_vcenter.php |
GET | Dump raw vCenter API responses |
get_software.php |
GET | Software inventory for a server |
| Endpoint | Method | Purpose |
|---|---|---|
knowledge_articles.php |
GET | List articles (with search) |
knowledge_article.php |
GET | Get single article |
knowledge_save.php |
POST | Create/update article (auto-generates embedding) |
knowledge_delete.php |
POST | Delete article |
knowledge_tags.php |
GET | List available tags |
ai_chat.php |
POST | AI-powered Q&A over knowledge base |
generate_embedding.php |
POST | Generate OpenAI embedding for article |
get_email_settings.php |
GET | Get email & AI settings (keys masked) |
save_email_settings.php |
POST | Save email & AI settings (keys encrypted) |
| ...and more |
| Endpoint | Method | Purpose |
|---|---|---|
get_forms.php |
GET | List all forms with field/submission counts |
get_form.php |
GET | Single form with fields (for builder & filler) |
save_form.php |
POST | Create/update form with fields |
delete_form.php |
POST | Delete form and all submissions |
submit_form.php |
POST | Submit a filled-in form |
ai_generate.php |
POST | Streaming SSE endpoint — generates a form definition from a plain-English description |
get_submissions.php |
GET | Submissions for a form (with field data) |
delete_submission.php |
POST | Delete a submission |
get_settings.php |
GET | Get forms module settings (logo alignment) |
save_settings.php |
POST | Save forms module settings |
| Endpoint | Method | Purpose |
|---|---|---|
get_system_settings.php |
GET | Get all settings (auto-decrypts sensitive keys) |
save_system_settings.php |
POST | Save settings (auto-encrypts sensitive keys) |
| Endpoint | Method | Purpose |
|---|---|---|
change_password.php |
POST | Validate current password, update to new (min 8 chars) |
get_mfa_status.php |
GET | Return { mfa_enabled: bool } for current analyst |
setup_mfa.php |
POST | Generate TOTP secret, return secret + otpauth URI for QR |
verify_mfa.php |
POST | Verify OTP against pending secret, encrypt and enable MFA |
disable_mfa.php |
POST | Verify password and disable MFA for current analyst |
verify_login_otp.php |
POST | Verify OTP during login MFA challenge, complete login |
api/change-management/— 15 endpoints for change CRUD, attachments, calendar, approvals, CAB workflow, and settingsapi/calendar/— 7 endpoints for events and categoriesapi/morning-checks/— 8 endpoints for check definitions, results, charts, and reorderapi/reporting/— 2 endpoints for system logsapi/software/— 5 endpoints for software inventory and licence managementapi/service-status/— 7 endpoints for services CRUD, incident management, and dashboard aggregationapi/watchtower/— 1 endpoint for unified attention dashboard aggregation across all modulesapi/system/— 4 endpoints for encryption status and module access managementapi/external/software-inventory/submit/— External API for automated software inventory collectionapi/external/system-info/submit/— External API for full asset inventory ingestion (hardware, disks, network, software)
PDO MySQL connecting to MySQL 8.0+. Connection handled by includes/functions.php:
$conn = connectToDatabase();
$stmt = $conn->prepare("SELECT * FROM table WHERE id = ?");
$stmt->execute([$id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);MySQL AUTO_INCREMENT for auto-increment. Use $conn->lastInsertId() to retrieve new IDs after INSERT:
$stmt = $conn->prepare("INSERT INTO table (col) VALUES (?)");
$stmt->execute([$value]);
$newId = (int)$conn->lastInsertId();id, username, password_hash, full_name, email, is_active, totp_secret, totp_enabled,
created_datetime, last_modified_datetime, last_login_datetimetotp_secret: AES-256-GCM encrypted TOTP secret (NULL when MFA not set up)totp_enabled: Boolean flag indicating whether MFA is active for this analyst
id, ticket_number (YYYYMMDD-XXXX), subject, status, priority, department_id, ticket_type_id,
ticket_origin_id, assigned_analyst_id, owner_id, requester_name, requester_email,
first_time_fix, it_training_provided, work_start_datetime, created_datetime, updated_datetimeid, ticket_id, exchange_message_id (NOT NULL), from_address, from_name, to_recipients (JSON),
cc_recipients (JSON), received_datetime, subject, body_content, body_type, has_attachments,
importance, is_read, is_initial, direction (Incoming/Outgoing/Manual)setting_key, setting_value, updated_datetimeKey-value store for all configuration. Sensitive values are encrypted with AES-256-GCM.
id, name, target_mailbox*, azure_tenant_id*, azure_client_id*, azure_client_secret*,
oauth_redirect_uri*, oauth_scopes, imap_server*, imap_port, imap_encryption,
email_folder, max_emails_per_check, mark_as_read,
token_data (JSON), is_active, created_datetime, last_checked_datetime* Encrypted at rest with AES-256-GCM
id, mailbox_id, entry_type ('domain'|'email'), entry_value, created_datetimePer-mailbox sender whitelist. If no entries exist, all senders are allowed.
id, mailbox_id, action ('imported'|'rejected'), from_address, from_name,
subject, reason, ticket_id, created_datetimeRecords every email imported or rejected during mailbox processing.
id, name, event_trigger, subject_template, body_template, is_active, display_order,
created_datetime, updated_datetimeAutomated email templates triggered by ticket events (new_ticket_email, ticket_assigned, ticket_closed). Subject and body support merge codes like [ticket_reference], [analyst_name], etc.
id, vm_name, guest_os, ip_address, host, cluster, cpu_count, memory_mb, disk_gb,
power_state, raw_data (LONGTEXT - full vCenter JSON), source, last_syncedid, title, description, is_active, created_by, created_date, modified_dateid, form_id (FK CASCADE), field_type, label, options (JSON), is_required, sort_orderid, form_id (FK), submitted_by (FK analysts), submitted_dateid, submission_id (FK CASCADE), field_id (FK), field_valueid, check_name, is_active, display_orderid, check_id (FK), check_date, status (Green/Amber/Red), notes, analyst_idMany-to-many relationships for team-based access control. Analysts only see tickets in departments linked to their teams. Analysts with no team assignments see everything (admin behaviour).
id, analyst_id (FK analysts ON DELETE CASCADE), module_keyControls which modules an analyst can access. No rows = full access to all modules (backward compatible). When rows exist, analyst only sees those modules on homepage and in waffle menu. The system module is always included and cannot be disabled.
- AES-256-GCM encryption for sensitive settings, mailbox credentials, and TOTP secrets with key stored outside web root
- Bcrypt password hashing (
PASSWORD_DEFAULT) - TOTP multi-factor authentication (RFC 6238) — optional per analyst, enforced at login
- Session-based authentication on all pages and API endpoints
- PDO prepared statements throughout (SQL injection prevention)
- Output encoding with
htmlspecialchars()(XSS prevention) - Client-side escaping via DOM
textContent→innerHTMLpattern - OAuth 2.0 for Microsoft 365 and Google Workspace email integration
- Forgot password flow with secure email reset links (1-hour expiry, single-use tokens)
- IP-based brute force protection: escalating bans for IPs attempting logins against non-existent or locked accounts (configurable thresholds, 24-hour bans)
- Team-based access control for ticket visibility
- Module-level access control per analyst (configurable via System module)
- Audit logging for all ticket changes
- Login attempt logging with IP and user agent
- Credential masking in UI (
****+ last 4 characters) - Password required to disable MFA (prevents unauthorized deactivation)
- Trusted device: users can opt in to skip OTP on trusted browsers for a configurable number of days (cookie-based with SHA-256 hashed tokens stored server-side)
- Password expiry policy: configurable maximum password age (30–365 days) with forced password change on next login when expired
- Account lockout: configurable max failed login attempts before temporary account lock with configurable lockout duration
- Algorithm: Bcrypt via PHP's
password_hash()withPASSWORD_DEFAULT - One-way: Passwords cannot be reversed from the stored hash — authentication uses
password_verify()to re-hash the input and compare - Per-hash salt: Every call to
password_hash()generates a unique random salt, so the same password produces a different hash each time - Cost factor: Bcrypt uses configurable work rounds (currently 2^12 = 4,096 iterations) making brute-force attacks extremely slow
- Default admin account: The SQL script includes a pre-computed hash for the initial
admin/freeitsmaccount. Once the password is changed, a new unique hash is generated. Thedb_verify.phpendpoint also seeds this account at runtime (with a fresh hash) if no analysts exist
- Algorithm: AES-256-GCM (authenticated encryption — provides confidentiality + integrity)
- Key: 256-bit random key stored at
C:\wamp64\encryption_keys\sdtickets.key - Nonce: 96-bit random IV per encryption (same value encrypted twice produces different ciphertext)
- Auth tag: 128-bit — detects any tampering with encrypted data
- Prefix:
ENC:allows coexistence of encrypted and plaintext values during migration
check_email.phpruns (scheduled or manual trigger)- For each active mailbox: refreshes OAuth token, calls Graph API, imports new emails
- Creates/matches tickets based on subject/requester
- Downloads attachments, logs results
The ticketing system handles email correspondence as a flat thread — each email in a ticket is stored and displayed as its own standalone entry, newest first, with no nesting or indentation.
When a user replies to an email, their email client (Gmail, Outlook, etc.) automatically appends the entire previous conversation as a quoted block below their new content. If you simply store the full email body, each reply contains every previous message nested inside it, creating an ever-growing blob of duplicated content. Displaying these naively produces deeply indented, confusing threads with coloured borders and boxes-within-boxes.
The reply flow separates what the recipient sees from what gets saved to the database:
-
Reply editor is empty — when an analyst clicks Reply, TinyMCE opens with a blank editor. No quoted thread, no markers, just a clean box for typing.
-
Server assembles the full email — when the analyst clicks Send, only their typed content is sent to the server. The server (
api/tickets/send_email.php) then:- Fetches all previous emails for the ticket from the database
- Builds a quoted thread (each email as "On [date], [name] wrote:" + blockquote)
- Inserts a visible reply marker: — Please reply above this line —
- Constructs the full email: analyst's reply + marker + quoted thread
- Sends this assembled email to the recipient via Microsoft Graph API
-
Only the analyst's content is saved — the database stores just what the analyst typed, not the full assembled email. This prevents thread duplication in the DB.
The visible text — Please reply above this line — serves as the primary anchor for stripping. It is:
- Plain Unicode text (em dashes + words) that survives every email client's HTML processing
- Wrapped in a
<div>withdata-reply-marker="true"as a secondary signal - Displayed to the recipient as a subtle grey line between the analyst's reply and the quoted thread
When a user replies back, their email contains the full thread (their reply + our marker + quoted history). The stripping functions extract only the user's new content by looking for these anchors in order:
- Our visible marker text —
— Please reply above this line —(primary, works with all email clients) - Our
data-reply-markerdiv — backup if the HTML attribute survives - Legacy SDREF marker —
[*** SDREF:XXX-000-00000 REPLY ABOVE THIS LINE ***]for older emails - Generic blockquote fallback — takes content before the first
<blockquote>tag - Attribution line cleanup — removes trailing "On [date], [name] wrote:" lines that email clients add before quoted blocks
Stripping happens at two points:
- Import time (
check_mailbox_email.php→stripInboundThread()) — cleans the body before saving to DB - Display time (
get_ticket_thread.php→stripQuotedThread()) — safety net for legacy emails already in the DB
The correspondence thread in the reading pane renders emails as a flat list:
- Newest email at the top, oldest at the bottom
- Each email separated by a thin horizontal line
- Direction badge (Received/Sent) with sender name, email address, and timestamp
- CSS overrides (
!important) to kill any inline styles, blockquote indentation, or coloured borders from email HTML that leak through stripping
| File | Role |
|---|---|
assets/js/inbox.js |
Reply/forward modals (empty editor), sendEmail() passes type param, loadCorrespondenceThread() renders flat thread |
api/tickets/send_email.php |
buildFullEmailBody() assembles full email for recipient, saves only analyst content to DB |
api/tickets/get_ticket_thread.php |
stripQuotedThread() strips quoted content at display time |
api/tickets/check_mailbox_email.php |
stripInboundThread() strips quoted content at import time |
assets/css/inbox.css |
Flat thread styles, inline HTML overrides |
MFA is optional and per-analyst. Analysts with MFA disabled log in with just username and password as normal. When an analyst enables MFA, subsequent logins require a second verification step:
- Analyst enters username and password on
login.php - Password verified → MFA pending state stored in session (
mfa_pending_analyst_idetc.) —analyst_idis NOT set yet - Login page renders OTP form (shield icon, 6-digit input, auto-submit)
- Analyst enters code from authenticator app → JS calls
api/myaccount/verify_login_otp.php - Server decrypts stored TOTP secret, verifies code (±1 time window)
- On success:
$_SESSION['analyst_id']is set, pending state cleared, redirected toindex.php
The "Cancel and return to login" link clears the pending state so a different analyst (who may not have MFA) can log in on the same browser.
- Analyst's team memberships stored in session at login
- API endpoints filter by team-accessible departments
- No teams assigned = see everything (admin)
- Settings page stores vCenter credentials (encrypted)
api/assets/get_vcenter.phpauthenticates with vCenter REST API- Fetches hosts, clusters, VMs with guest identity and filesystems
- Builds host/cluster maps by querying VMs per host/cluster
- Stores everything including raw JSON in
serverstable
- Admin designs form in the unified form builder (
index.php) — sidebar lists all forms, editor has tabbed Fields/Preview panels - Users fill in form at
fill.php?id=X(A4-style layout with company logo) - Submissions viewable in table format with CSV export
Every module page follows this structure:
<?php
session_start();
require_once '../config.php';
$current_page = 'module_name';
$path_prefix = '../';
?>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../assets/css/inbox.css">
<!-- Module-specific styles in <style> block -->
</head>
<body>
<?php include 'includes/header.php'; ?>
<div class="main-container module-container">
<!-- Module content -->
</div>
<script>
const API_BASE = '../api/module_name/';
// Module JavaScript
</script>
</body>
</html>- Create module folder with
index.phpandincludes/header.php - Create API folder under
api/ - Register in
includes/waffle-menu.php(add to$modulesarray + CSS colours) - Add card to
index.phplanding page (icon, colour, link) - Create SQL schema file if needed
The emails.exchange_message_id column does NOT allow NULL. Manual tickets must use a placeholder: 'manual-' . time() . '-' . uniqid().
| Need to... | Look in... |
|---|---|
| Add a new module | Module folder + api/ + includes/waffle-menu.php + index.php |
| Change database connection | config.php, includes/functions.php |
| Add an encrypted setting | includes/encryption.php → ENCRYPTED_SETTING_KEYS or ENCRYPTED_MAILBOX_COLUMNS |
| Modify cross-module navigation | includes/waffle-menu.php |
| Change the landing page | index.php |
| Modify ticket inbox | tickets/index.php, assets/js/inbox.js |
| Configure vCenter | asset-management/settings/, api/assets/get_vcenter.php |
| Manage knowledge AI | knowledge/settings/, api/knowledge/ai_chat.php |
| Design forms | forms/index.php, api/forms/save_form.php |
| View form submissions | forms/submissions.php, api/forms/get_submissions.php |
| Manage encryption key | system/encryption/, api/system/check_encryption.php |
| Configure module access | system/modules/, api/system/save_analyst_modules.php |
| Account menu (avatar/password/MFA) | includes/waffle-menu.php → renderHeaderRight(), api/myaccount/ |
| MFA login challenge | login.php, api/myaccount/verify_login_otp.php |
| TOTP implementation | includes/totp.php |
Last updated: February 2026










