# Tinker Tickets - Project Documentation for AI Assistants ## Project Overview Tinker Tickets is a lightweight, self-hosted ticket management system built for managing data center infrastructure issues. It features automatic ticket creation via hardware monitoring integration, Discord notifications, and a clean web interface. **Tech Stack:** - Backend: PHP 8+ with MySQLi - Frontend: Vanilla JavaScript, CSS3 - Database: MariaDB (on separate LXC: 10.10.10.???) - Web Server: Nginx on production (10.10.10.45) - External Libraries: marked.js (Markdown rendering) **Production Environment:** - **Primary URL**: https://t.lotusguild.org - **Beta URL**: https://beta.t.lotusguild.org (React port - in development) - **Web Server**: Nginx at 10.10.10.45 (`/var/www/html/tinkertickets`) - **Database**: MariaDB on separate LXC (`ticketing_system` database) ## Architecture ### Project Structure ``` /tinker_tickets/ ├── api/ # API endpoints (standalone PHP files) │ ├── add_comment.php # POST: Add comment to ticket │ └── update_ticket.php # POST: Update ticket fields (partial updates) ├── assets/ # Static assets │ ├── css/ │ │ ├── dashboard.css # Shared + dashboard styles │ │ └── ticket.css # Ticket page styles │ ├── js/ │ │ ├── dashboard.js # Dashboard + hamburger menu │ │ └── ticket.js # Ticket interactions │ └── images/ │ └── favicon.png ├── config/ │ └── config.php # Config + .env loading ├── controllers/ # MVC Controllers │ ├── CommentController.php # Comment operations │ ├── DashboardController.php # Dashboard/listing │ └── TicketController.php # Ticket CRUD + webhooks ├── models/ # Data models │ ├── CommentModel.php # Comment data access │ └── TicketModel.php # Ticket data access ├── views/ # PHP templates │ ├── CreateTicketView.php # Ticket creation form │ ├── DashboardView.php # Main listing page │ └── TicketView.php # Single ticket view ├── .env # Environment variables (GITIGNORED) ├── .gitignore ├── create_ticket_api.php # External API for hwmonDaemon ├── deploy.sh # Legacy manual deploy (not used) ├── index.php # Main entry point + router └── README.md ``` ### Routing System (`index.php`) Simple switch-based router: - `/` → Dashboard - `/ticket/{id}` → View ticket - `/ticket/create` → Create ticket form - `/api/update_ticket.php` → Update ticket (AJAX) - `/api/add_comment.php` → Add comment (AJAX) **Important Notes:** - API routes handle their own database connections - Page routes receive connection from index.php - Legacy routes redirect to new URLs ## Database Schema **Database Name:** `ticketing_system` (NOT `tinkertickets`) ### `tickets` Table ```sql CREATE TABLE tickets ( ticket_id VARCHAR(9) PRIMARY KEY, -- 9-digit format with leading zeros title VARCHAR(255) NOT NULL, description TEXT, status VARCHAR(50) DEFAULT 'Open', -- 'Open', 'Closed', 'In Progress', 'Pending' priority INT DEFAULT 4, -- 1=Critical, 2=High, 3=Medium, 4=Low, 5=Lowest category VARCHAR(50) DEFAULT 'General', -- Hardware, Software, Network, Security, General type VARCHAR(50) DEFAULT 'Issue', -- Maintenance, Install, Task, Upgrade, Issue hash VARCHAR(64), -- For duplicate detection (hwmonDaemon) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB; ``` ### `ticket_comments` Table ```sql CREATE TABLE ticket_comments ( comment_id INT AUTO_INCREMENT PRIMARY KEY, ticket_id VARCHAR(10), user_name VARCHAR(50), comment_text TEXT, markdown_enabled TINYINT(1) DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (ticket_id) REFERENCES tickets(ticket_id) ) ENGINE=InnoDB; ``` **Key Points:** - Ticket IDs are VARCHAR (9-digit format with leading zeros) - Status ENUM-like validation in application layer - Hash field used for duplicate detection by hwmonDaemon - Comments support optional Markdown rendering ## API Endpoints ### POST `/api/update_ticket.php` Updates ticket fields (supports partial updates). **Request:** ```json { "ticket_id": 123456789, "status": "In Progress", // Optional "priority": 2, // Optional (1-5) "title": "Updated title", // Optional "description": "...", // Optional "category": "Software", // Optional "type": "Task" // Optional } ``` **Response:** ```json { "success": true, "status": "In Progress", "priority": 2, "message": "Ticket updated successfully" } ``` **Features:** - Merges updates with existing ticket data (partial updates) - Validates status against allowed values - Validates priority range (1-5) - Sends Discord webhook on changes - Debug logging to `/tmp/api_debug.log` **Discord Webhook:** - Triggered on ticket updates - Shows field changes (old → new) - Color-coded by priority - Links to ticket URL - Only sends if changes detected ### POST `/api/add_comment.php` Adds a comment to a ticket. **Request:** ```json { "ticket_id": "123456789", "comment_text": "Comment content", "markdown_enabled": true, // Optional, default false "user_name": "User" // Optional, default "User" } ``` **Response:** ```json { "success": true, "user_name": "User", "created_at": "Jan 01, 2026 12:00", "markdown_enabled": 1, "comment_text": "Comment content" } ``` ### POST `/create_ticket_api.php` **EXTERNAL API** used by hwmonDaemon for automated ticket creation. **Request:** ```json { "title": "[hostname][auto][hardware]Issue[single-node][production][maintenance]", "description": "Detailed hardware issue...", "priority": "2", "category": "Hardware", "type": "Problem" } ``` **Response:** ```json { "success": true, "ticket_id": "123456789", "message": "Ticket created successfully" } ``` **OR** (if duplicate): ```json { "success": false, "error": "Duplicate ticket", "existing_ticket_id": "987654321" } ``` **Special Features:** - Duplicate detection via SHA-256 hashing - Hash based on: hostname, SMART attributes, environment tags, device - 24-hour duplicate window - Sends Discord webhook notification - Auto-creates tickets table if not exists ## Frontend Components ### Dashboard (`views/DashboardView.php` + `assets/js/dashboard.js`) **Features:** - Pagination (default 15, configurable via settings) - Search (title, description, ticket_id, category, type) - Status filtering (Open, In Progress, Closed) - Category/Type filtering via hamburger menu - Column sorting (click headers) - Theme toggle (light/dark, persisted to localStorage) - Settings modal (rows per page) **Default Behavior:** - Shows Open + In Progress tickets (Closed hidden) - Use `?show_all=1` to see all tickets - Use `?status=Open,Closed` for specific statuses **Hamburger Menu:** - Left sidebar with filters - Multi-select checkboxes - Apply/Clear filter buttons ### Ticket View (`views/TicketView.php` + `assets/js/ticket.js`) **Features:** - Tabbed interface (Description, Comments) - Inline editing via Edit button - Real-time status/priority indicators - Markdown support for comments - Live markdown preview toggle **Hamburger Menu (Ticket Page):** - Quick edit: Status, Priority, Category, Type - Click value → dropdown → save/cancel - Updates main page elements dynamically - Changes ticket border color based on priority **Visual Indicators:** Priority Colors: - P1 (Critical): Red `#ff4d4d` - P2 (High): Orange `#ffa726` - P3 (Medium): Blue `#42a5f5` - P4 (Low): Green `#66bb6a` - P5 (Lowest): Gray `#9e9e9e` Status Colors: - Open: Green `#28a745` - In Progress: Yellow `#ffc107` - Closed: Red `#dc3545` ### Create Ticket (`views/CreateTicketView.php`) **Form Fields:** - Title (required) - Description (required, textarea) - Status (dropdown, default: Open) - Priority (dropdown, default: P4) - Category (dropdown, default: General) - Type (dropdown, default: Issue) **On Submit:** - Server-side validation - Discord webhook notification - Redirect to new ticket ## Configuration ### Environment Variables (`.env`) ```ini DB_HOST= DB_USER= DB_PASS= DB_NAME=ticketing_system DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... ``` **CRITICAL:** `.env` is gitignored! Never commit this file. ### Config (`config/config.php`) ```php $GLOBALS['config'] = [ 'DB_HOST' => $envVars['DB_HOST'], 'DB_USER' => $envVars['DB_USER'], 'DB_PASS' => $envVars['DB_PASS'], 'DB_NAME' => $envVars['DB_NAME'], 'BASE_URL' => '', // Empty (serving from root) 'ASSETS_URL' => '/assets', 'API_URL' => '/api' ]; ``` ## Deployment System ### Auto-Deploy Pipeline **Gitea → Webhook → Production Server** 1. **Push to `main` branch** on Gitea (code.lotusguild.org) 2. **Gitea sends webhook** to `http://10.10.10.45:9000/hooks/tinker-deploy` 3. **Webhook service** validates signature and triggers deploy script 4. **Deploy script** pulls code, preserves `.env`, sets permissions ### Webhook Configuration **Service:** `/etc/systemd/system/webhook.service` ```ini [Unit] Description=Webhook Listener for Auto Deploy After=network.target [Service] ExecStart=/usr/bin/webhook -hooks /etc/webhook/hooks.json -port 9000 Restart=always User=root ``` **Hooks:** `/etc/webhook/hooks.json` ```json { "id": "tinker-deploy", "execute-command": "/usr/local/bin/tinker_deploy.sh", "command-working-directory": "/var/www/html/tinkertickets", "response-message": "Deploying tinker_tickets...", "trigger-rule": { "match": { "type": "payload-hash-sha256", "secret": "...", "parameter": { "source": "header", "name": "X-Gitea-Signature" } } } } ``` **Deploy Script:** `/usr/local/bin/tinker_deploy.sh` ```bash #!/bin/bash set -e WEBROOT="/var/www/html/tinkertickets" # Backup .env if [ -f "$WEBROOT/.env" ]; then cp "$WEBROOT/.env" /tmp/.env.backup fi # Pull latest code if [ ! -d "$WEBROOT/.git" ]; then rm -rf "$WEBROOT" git clone https://code.lotusguild.org/LotusGuild/tinker_tickets.git "$WEBROOT" else cd "$WEBROOT" git fetch --all git reset --hard origin/main fi # Restore .env if [ -f /tmp/.env.backup ]; then mv /tmp/.env.backup "$WEBROOT/.env" fi # Set permissions chown -R www-data:www-data "$WEBROOT" ``` **IMPORTANT NOTES:** - **Test thoroughly before pushing to main!** - Low traffic (single user), so testing in production is acceptable - But avoid breaking changes - `.env` is preserved across deployments - Database changes require manual migration ### Nginx Configuration **Site Config:** `/etc/nginx/sites-enabled/tinker_prod` ```nginx server { listen 80; server_name t.lotusguild.org; root /var/www/html/tinkertickets; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php-fpm.sock; } location ~ /\.env { deny all; } } ``` **Key Points:** - Clean URLs via `try_files` - PHP-FPM via Unix socket - `.env` explicitly denied ## Hardware Monitoring Integration ### hwmonDaemon Overview The `hwmonDaemon` runs on all Proxmox VE servers as a systemd timer (hourly). It monitors: - SMART drive health - Disk usage - Memory (including ECC errors) - CPU usage - Network connectivity (Management + Ceph networks) - System logs for drive errors **When issues are detected**, it automatically creates tickets via `/create_ticket_api.php`. ### Daemon Configuration **Service:** `/etc/systemd/system/hwmon.service` - Executes Python script from Gitea URL (self-updating) - Runs as root for hardware access - Auto-restarts on failure **Timer:** `/etc/systemd/system/hwmon.timer` - Runs hourly - 5-minute randomized delay **API Endpoint:** ```python TICKET_API_URL = 'http://10.10.10.45/create_ticket_api.php' ``` ### Ticket Title Format hwmonDaemon creates tickets with structured titles: ``` [hostname][auto][hardware]Issue Description[single-node][production][maintenance] ``` **Components:** - `[hostname]`: Server name - `[auto]`: Automated creation - `[hardware]`: Issue category - Issue Description: e.g., "Drive /dev/sda has SMART issues: Reallocated_Sector_Ct" - `[single-node]`: Scope - `[production]`: Environment - `[maintenance]`: Ticket type ### Duplicate Detection Tickets are hashed based on: - Hostname - SMART attribute types (not values) - Environment tags - Device path (for drive issues) **Hash Window:** 24 hours This prevents duplicate tickets for the same issue on the same host. ## Development Guidelines ### Code Style - Tabs for indentation in PHP - Parameterized queries (prepared statements) - Output escaping with `htmlspecialchars()` - Error logging to `/tmp/api_debug.log` ### Security Practices - **SQL Injection**: All queries use prepared statements - **XSS**: HTML output escaped - **CSRF**: Not implemented (single-user system) - **Environment Variables**: `.env` gitignored - **File Permissions**: `www-data:www-data` ownership ### Error Handling - API endpoints use output buffering - Errors returned as JSON with `success: false` - Debug logging in `/tmp/api_debug.log` - Display errors disabled in production ### JavaScript Patterns - Vanilla JavaScript (no framework) - `DOMContentLoaded` for initialization - `fetch()` for AJAX - `window.ticketData` for ticket pages - CSS class toggling for state changes ## Common Tasks ### Adding a New Ticket Field 1. **Database:** Add column to `tickets` table on MariaDB server 2. **Model:** Update `TicketModel.php`: - `getTicketById()` - `updateTicket()` - `createTicket()` 3. **API:** Update `update_ticket.php`: - Add to validation - Add to merge logic (line 73-81) 4. **Views:** - Add field to `TicketView.php` - Add field to `CreateTicketView.php` 5. **JavaScript:** Add to hamburger menu in `dashboard.js` (if editable) 6. **CSS:** Add styling if needed ### Modifying Status/Priority Values 1. **API:** Update validation in `update_ticket.php`:172 - Status: Line 102-108 - Priority: Line 93-98 2. **Views:** Update dropdowns: - `TicketView.php:410-414` (status) - `TicketView.php:426-432` (priority) - `CreateTicketView.php` 3. **JavaScript:** Update hamburger options in `dashboard.js` 4. **CSS:** Add color classes to `dashboard.css` and `ticket.css` ### Changing Discord Notifications Edit `update_ticket.php` → `sendDiscordWebhook()` (lines 135-219): - Change embed structure - Modify color mapping - Add/remove fields - Update ticket URL Also check `TicketController.php` → `sendDiscordWebhook()` (lines 128-207) for ticket creation webhooks. ### Updating Pagination Defaults 1. **Controller:** `DashboardController.php:16` (default: 15) 2. **JavaScript:** `dashboard.js:128-133` (settings modal options) 3. **Cookie:** Stored as `ticketsPerPage` ## Known Behaviors & Quirks ### Ticket ID Generation - Format: 9-digit random number with leading zeros - Generation: `sprintf('%09d', mt_rand(1, 999999999))` - Stored as VARCHAR - **Collision possible** (no uniqueness check beyond DB constraint) ### Status Filtering - **Default:** Shows Open + In Progress (hides Closed) - `?show_all=1` → All statuses - `?status=Open,Closed` → Specific statuses - No status param → Default behavior ### Markdown Rendering - Client-side only (marked.js from CDN) - Toggle must be enabled before preview works - **XSS Risk:** Consider adding DOMPurify ### CSS Class Naming - Status: `status-Open`, `status-In-Progress`, `status-Closed` - Spaces replaced with hyphens - Priority: `priority-1` through `priority-5` ### Theme Persistence - Stored: `localStorage['theme']` → `light` or `dark` - Applied via `data-theme` attribute on `` - CSS variables change based on theme ## Debugging ### API Issues ```bash tail -f /tmp/api_debug.log ``` ### JavaScript Issues - Browser console (F12) - Check Network tab for API responses - Look for `console.log()` statements ### Database Issues ```bash # Connect to MariaDB server ssh root@ mysql ticketing_system ``` ### Deployment Issues ```bash # On production server (10.10.10.45) journalctl -u webhook.service -f systemctl status webhook.service # Manual deploy cd /var/www/html/tinkertickets git pull chown -R www-data:www-data . ``` ### hwmonDaemon Issues ```bash # On Proxmox server journalctl -u hwmon.service -f systemctl status hwmon.timer # Manual test python3 /path/to/hwmonDaemon.py --dry-run ``` ## Important Notes for AI Assistants 1. **Always read existing code** before suggesting changes 2. **Test carefully** - auto-deploy to production is enabled 3. **Database changes** require manual migration (no auto-rollback) 4. **Preserve security** (prepared statements, escaping, `.env` protection) 5. **Consider auto-deploy** when making changes 6. **Single-user system** - authentication/authorization not implemented 7. **hwmonDaemon integration** - test with `create_ticket_api.php` 8. **Duplicate detection** - understand hashing for automated tickets 9. **Discord webhooks** - changes trigger notifications 10. **MariaDB on separate server** - can't access directly from this machine ## Future Considerations ### Potential Improvements - User authentication/authorization - CSRF protection - File attachments - Email notifications - Advanced search/filters - Ticket assignment - Activity/audit log - API rate limiting - Database migrations system - Unit tests - DOMPurify for Markdown XSS protection ### Performance Optimizations - Database indexes - Query caching - Lazy load comments - Minify/bundle assets ## Related Systems ### React Beta Site - **URL:** https://beta.t.lotusguild.org - **Branch:** `react_test` - **Status:** Early development (brother's project) - **Deploy:** Separate webhook + script (`tinker_react_deploy.sh`) - **Location:** `/var/www/html/tinkertickets-react` ## File Reference Quick Guide | File | Purpose | Key Functions | |------|---------|---------------| | `index.php` | Router | URL routing, DB connection | | `create_ticket_api.php` | hwmonDaemon API | Duplicate detection, auto-tickets | | `api/update_ticket.php` | Update API | Partial updates, Discord webhooks | | `api/add_comment.php` | Comment API | Markdown-enabled comments | | `models/TicketModel.php` | Ticket data layer | CRUD, filtering, sorting | | `models/CommentModel.php` | Comment data layer | Get/add comments | | `controllers/DashboardController.php` | Dashboard logic | Pagination, filters | | `controllers/TicketController.php` | Ticket logic | CRUD, webhooks | | `assets/js/dashboard.js` | Dashboard UI | Filters, sorting, hamburger | | `assets/js/ticket.js` | Ticket UI | Edit mode, comments, markdown | | `assets/css/dashboard.css` | Shared styles | Layout, table, theme | | `assets/css/ticket.css` | Ticket styles | Ticket-specific components | ## Contact & Repository - **Gitea:** https://code.lotusguild.org/LotusGuild/tinker_tickets - **Production:** https://t.lotusguild.org - **Beta:** https://beta.t.lotusguild.org This is a personal project for infrastructure management. For issues, use the Gitea repository.