19 KiB
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_systemdatabase)
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
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
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:
{
"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:
{
"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:
{
"ticket_id": "123456789",
"comment_text": "Comment content",
"markdown_enabled": true, // Optional, default false
"user_name": "User" // Optional, default "User"
}
Response:
{
"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:
{
"title": "[hostname][auto][hardware]Issue[single-node][production][maintenance]",
"description": "Detailed hardware issue...",
"priority": "2",
"category": "Hardware",
"type": "Problem"
}
Response:
{
"success": true,
"ticket_id": "123456789",
"message": "Ticket created successfully"
}
OR (if duplicate):
{
"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=1to see all tickets - Use
?status=Open,Closedfor 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)
DB_HOST=<mariadb-ip>
DB_USER=<username>
DB_PASS=<password>
DB_NAME=ticketing_system
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
CRITICAL: .env is gitignored! Never commit this file.
Config (config/config.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
- Push to
mainbranch on Gitea (code.lotusguild.org) - Gitea sends webhook to
http://10.10.10.45:9000/hooks/tinker-deploy - Webhook service validates signature and triggers deploy script
- Deploy script pulls code, preserves
.env, sets permissions
Webhook Configuration
Service: /etc/systemd/system/webhook.service
[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
{
"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
#!/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
.envis preserved across deployments- Database changes require manual migration
Nginx Configuration
Site Config: /etc/nginx/sites-enabled/tinker_prod
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
.envexplicitly 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:
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:
.envgitignored - File Permissions:
www-data:www-dataownership
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)
DOMContentLoadedfor initializationfetch()for AJAXwindow.ticketDatafor ticket pages- CSS class toggling for state changes
Common Tasks
Adding a New Ticket Field
- Database: Add column to
ticketstable on MariaDB server - Model: Update
TicketModel.php:getTicketById()updateTicket()createTicket()
- API: Update
update_ticket.php:- Add to validation
- Add to merge logic (line 73-81)
- Views:
- Add field to
TicketView.php - Add field to
CreateTicketView.php
- Add field to
- JavaScript: Add to hamburger menu in
dashboard.js(if editable) - CSS: Add styling if needed
Modifying Status/Priority Values
- API: Update validation in
update_ticket.php:172- Status: Line 102-108
- Priority: Line 93-98
- Views: Update dropdowns:
TicketView.php:410-414(status)TicketView.php:426-432(priority)CreateTicketView.php
- JavaScript: Update hamburger options in
dashboard.js - CSS: Add color classes to
dashboard.cssandticket.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
- Controller:
DashboardController.php:16(default: 15) - JavaScript:
dashboard.js:128-133(settings modal options) - 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-1throughpriority-5
Theme Persistence
- Stored:
localStorage['theme']→lightordark - Applied via
data-themeattribute on<html> - CSS variables change based on theme
Debugging
API Issues
tail -f /tmp/api_debug.log
JavaScript Issues
- Browser console (F12)
- Check Network tab for API responses
- Look for
console.log()statements
Database Issues
# Connect to MariaDB server
ssh root@<mariadb-ip>
mysql ticketing_system
Deployment Issues
# 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
# 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
- Always read existing code before suggesting changes
- Test carefully - auto-deploy to production is enabled
- Database changes require manual migration (no auto-rollback)
- Preserve security (prepared statements, escaping,
.envprotection) - Consider auto-deploy when making changes
- Single-user system - authentication/authorization not implemented
- hwmonDaemon integration - test with
create_ticket_api.php - Duplicate detection - understand hashing for automated tickets
- Discord webhooks - changes trigger notifications
- 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.