feat: Add admin navigation, fix modals, clickable stats, update docs
- Add admin dropdown menu in dashboard header with links to all admin pages - Fix template modal: larger size (800px), responsive grid, type/priority dropdowns - Fix recurring tickets modal: add Type and Assign To fields, larger size - Make dashboard stat cards clickable for quick filtering - Fix user-activity query (remove is_active requirement) - Add table existence check in ticket_dependencies API - Fix table overflow on dashboard - Update Claude.md and README.md with current project status - Remove migrations directory (all migrations completed) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
771
Claude.md
771
Claude.md
@@ -2,20 +2,34 @@
|
|||||||
|
|
||||||
## Project Status (January 2026)
|
## Project Status (January 2026)
|
||||||
|
|
||||||
**Current Phase**: All 5 core features implemented and deployed. Ready for ANSI Art redesign.
|
**Current Phase**: All core features implemented. ANSI Art terminal redesign complete. System is production-ready.
|
||||||
|
|
||||||
**Recent Completion**:
|
**Completed Features**:
|
||||||
- ✅ Activity Timeline (Feature 1)
|
- ✅ Activity Timeline (Feature 1)
|
||||||
- ✅ Ticket Assignment (Feature 2)
|
- ✅ Ticket Assignment (Feature 2)
|
||||||
- ✅ Status Transitions with Workflows (Feature 3)
|
- ✅ Status Transitions with Workflows (Feature 3)
|
||||||
- ✅ Ticket Templates (Feature 4)
|
- ✅ Ticket Templates (Feature 4)
|
||||||
- ✅ Bulk Actions - Admin Only (Feature 5)
|
- ✅ Bulk Actions - Admin Only (Feature 5)
|
||||||
|
- ✅ ANSI Art Terminal Redesign
|
||||||
|
- ✅ File Attachments
|
||||||
|
- ✅ Ticket Dependencies
|
||||||
|
- ✅ @Mentions in Comments
|
||||||
|
- ✅ Recurring Tickets
|
||||||
|
- ✅ Custom Fields
|
||||||
|
- ✅ Advanced Search with Saved Filters
|
||||||
|
- ✅ Export to CSV/JSON
|
||||||
|
- ✅ Admin Pages (Templates, Workflow, Recurring, Custom Fields, User Activity, Audit Log)
|
||||||
|
|
||||||
**Next Priority**: 🎨 ANSI Art Redesign (major visual overhaul)
|
**Recent Updates** (January 2026):
|
||||||
|
- Added admin dropdown navigation in dashboard header
|
||||||
|
- Fixed template/recurring ticket modals (larger size, type/assignee fields)
|
||||||
|
- Made dashboard stat cards clickable for quick filtering
|
||||||
|
- Fixed table overflow on dashboard
|
||||||
|
- Improved error handling for ticket dependencies API
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Tinker Tickets is a feature-rich, self-hosted ticket management system built for managing data center infrastructure issues. It features SSO integration with Authelia/LLDAP, workflow management, Discord notifications, and a comprehensive web interface.
|
Tinker Tickets is a feature-rich, self-hosted ticket management system built for managing data center infrastructure issues. It features SSO integration with Authelia/LLDAP, workflow management, Discord notifications, and a retro terminal-style web interface.
|
||||||
|
|
||||||
**Tech Stack:**
|
**Tech Stack:**
|
||||||
- Backend: PHP 7.4+ with MySQLi
|
- Backend: PHP 7.4+ with MySQLi
|
||||||
@@ -40,57 +54,100 @@ Controllers → Models → Database
|
|||||||
Views
|
Views
|
||||||
```
|
```
|
||||||
|
|
||||||
### Project Structure (Updated)
|
### Project Structure
|
||||||
```
|
```
|
||||||
/tinker_tickets/
|
/tinker_tickets/
|
||||||
├── api/ # API endpoints
|
├── api/ # API endpoints
|
||||||
│ ├── add_comment.php # POST: Add comment
|
│ ├── add_comment.php # POST: Add comment
|
||||||
│ ├── assign_ticket.php # POST: Assign ticket to user (NEW)
|
│ ├── assign_ticket.php # POST: Assign ticket to user
|
||||||
│ ├── bulk_operation.php # POST: Bulk operations - admin only (NEW)
|
│ ├── bulk_operation.php # POST: Bulk operations - admin only
|
||||||
│ ├── get_template.php # GET: Fetch ticket template (NEW)
|
│ ├── check_duplicates.php # GET: Check for duplicate tickets
|
||||||
│ ├── get_users.php # GET: Get user list (NEW)
|
│ ├── delete_attachment.php # POST/DELETE: Delete attachment
|
||||||
│ └── update_ticket.php # POST: Update ticket (workflow validation)
|
│ ├── export_tickets.php # GET: Export tickets to CSV/JSON
|
||||||
|
│ ├── get_template.php # GET: Fetch ticket template
|
||||||
|
│ ├── get_users.php # GET: Get user list
|
||||||
|
│ ├── manage_recurring.php # CRUD: Recurring tickets (admin)
|
||||||
|
│ ├── manage_templates.php # CRUD: Templates (admin)
|
||||||
|
│ ├── manage_workflows.php # CRUD: Workflow rules (admin)
|
||||||
|
│ ├── ticket_dependencies.php # GET/POST/DELETE: Ticket dependencies
|
||||||
|
│ ├── update_ticket.php # POST: Update ticket (workflow validation)
|
||||||
|
│ └── upload_attachment.php # GET/POST: List or upload attachments
|
||||||
├── assets/
|
├── assets/
|
||||||
│ ├── css/
|
│ ├── css/
|
||||||
│ │ ├── dashboard.css # Shared + dashboard + bulk actions
|
│ │ ├── dashboard.css # Dashboard + terminal styling
|
||||||
│ │ └── ticket.css # Ticket + timeline + dark mode fixes
|
│ │ └── ticket.css # Ticket view styling
|
||||||
│ ├── js/
|
│ ├── js/
|
||||||
│ │ ├── dashboard.js # Dashboard + hamburger + bulk actions + templates
|
│ │ ├── advanced-search.js # Advanced search modal
|
||||||
│ │ └── ticket.js # Ticket + comments + status updates + assignment
|
│ │ ├── ascii-banner.js # ASCII art banner
|
||||||
|
│ │ ├── dashboard.js # Dashboard + bulk actions + templates
|
||||||
|
│ │ ├── keyboard-shortcuts.js # Keyboard shortcuts
|
||||||
|
│ │ ├── markdown.js # Markdown rendering
|
||||||
|
│ │ ├── settings.js # User preferences
|
||||||
|
│ │ ├── ticket.js # Ticket + comments + assignment
|
||||||
|
│ │ └── toast.js # Toast notifications
|
||||||
│ └── images/
|
│ └── images/
|
||||||
│ └── favicon.png
|
│ └── favicon.png
|
||||||
├── config/
|
├── config/
|
||||||
│ └── config.php # Config + .env loading
|
│ └── config.php # Config + .env loading
|
||||||
├── controllers/ # MVC Controllers
|
├── controllers/
|
||||||
│ ├── DashboardController.php # Dashboard with assigned_to column
|
│ ├── DashboardController.php # Dashboard with stats + filters
|
||||||
│ └── TicketController.php # Ticket CRUD + timeline + templates
|
│ └── TicketController.php # Ticket CRUD + timeline + templates
|
||||||
├── models/ # Data models
|
├── cron/
|
||||||
|
│ └── create_recurring_tickets.php # Process recurring ticket schedules
|
||||||
|
├── helpers/
|
||||||
|
│ └── ResponseHelper.php # Standardized JSON responses
|
||||||
|
├── middleware/
|
||||||
|
│ ├── AuthMiddleware.php # Authelia SSO integration
|
||||||
|
│ ├── CsrfMiddleware.php # CSRF protection
|
||||||
|
│ ├── RateLimitMiddleware.php # API rate limiting
|
||||||
|
│ └── SecurityHeadersMiddleware.php # Security headers
|
||||||
|
├── models/
|
||||||
│ ├── AuditLogModel.php # Audit logging + timeline
|
│ ├── AuditLogModel.php # Audit logging + timeline
|
||||||
│ ├── BulkOperationsModel.php # Bulk operations tracking (NEW)
|
│ ├── BulkOperationsModel.php # Bulk operations tracking
|
||||||
│ ├── CommentModel.php # Comment data access
|
│ ├── CommentModel.php # Comment data access
|
||||||
│ ├── TemplateModel.php # Ticket templates (NEW)
|
│ ├── CustomFieldModel.php # Custom field definitions/values
|
||||||
|
│ ├── DependencyModel.php # Ticket dependencies
|
||||||
|
│ ├── RecurringTicketModel.php # Recurring ticket schedules
|
||||||
|
│ ├── StatsModel.php # Dashboard statistics
|
||||||
|
│ ├── TemplateModel.php # Ticket templates
|
||||||
│ ├── TicketModel.php # Ticket CRUD + assignment
|
│ ├── TicketModel.php # Ticket CRUD + assignment
|
||||||
│ ├── UserModel.php # User management (NEW)
|
│ ├── UserModel.php # User management
|
||||||
│ └── WorkflowModel.php # Status transition workflows (NEW)
|
│ ├── UserPreferencesModel.php # User preferences
|
||||||
├── views/ # PHP templates
|
│ └── WorkflowModel.php # Status transition workflows
|
||||||
|
├── scripts/
|
||||||
|
│ └── cleanup_orphan_uploads.php # Clean orphaned uploads
|
||||||
|
├── uploads/ # File attachment storage
|
||||||
|
├── views/
|
||||||
|
│ ├── admin/
|
||||||
|
│ │ ├── AuditLogView.php # Audit log browser
|
||||||
|
│ │ ├── CustomFieldsView.php # Custom field management
|
||||||
|
│ │ ├── RecurringTicketsView.php # Recurring ticket management
|
||||||
|
│ │ ├── TemplatesView.php # Template management
|
||||||
|
│ │ ├── UserActivityView.php # User activity report
|
||||||
|
│ │ └── WorkflowDesignerView.php # Workflow transition designer
|
||||||
│ ├── CreateTicketView.php # Ticket creation with templates
|
│ ├── CreateTicketView.php # Ticket creation with templates
|
||||||
│ ├── DashboardView.php # Dashboard with bulk actions + assigned column
|
│ ├── DashboardView.php # Dashboard with stats + bulk actions
|
||||||
│ └── TicketView.php # Ticket view with timeline + assignment
|
│ └── TicketView.php # Ticket view with timeline + assignment
|
||||||
├── migrations/ # Database migrations
|
├── .env # Environment variables (GITIGNORED)
|
||||||
│ ├── 001_initial_schema.sql
|
├── Claude.md # This file
|
||||||
│ ├── 007_add_ticket_assignment.sql # Ticket assignment
|
├── README.md # User documentation
|
||||||
│ ├── 008_add_status_workflows.sql # Workflow rules
|
└── index.php # Main router
|
||||||
│ ├── 009_add_ticket_templates.sql # Ticket templates
|
|
||||||
│ ├── 010_add_bulk_operations.sql # Bulk operations
|
|
||||||
│ └── 011_remove_view_tracking.sql # Remove view audit logs
|
|
||||||
├── .env # Environment variables (GITIGNORED)
|
|
||||||
├── Claude.md # This file
|
|
||||||
├── README.md # User documentation
|
|
||||||
├── index.php # Dashboard entry point
|
|
||||||
└── ticket.php # Ticket view/create router
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Database Schema (Updated)
|
## Admin Pages
|
||||||
|
|
||||||
|
All admin pages are accessible via the **Admin dropdown** in the dashboard header (for admin users only).
|
||||||
|
|
||||||
|
| Route | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `/admin/templates` | Create and edit ticket templates |
|
||||||
|
| `/admin/workflow` | Visual workflow transition designer |
|
||||||
|
| `/admin/recurring-tickets` | Manage recurring ticket schedules |
|
||||||
|
| `/admin/custom-fields` | Define custom fields per category |
|
||||||
|
| `/admin/user-activity` | View per-user activity statistics |
|
||||||
|
| `/admin/audit-log` | Browse all audit log entries |
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
**Database**: `ticketing_system` at 10.10.10.50
|
**Database**: `ticketing_system` at 10.10.10.50
|
||||||
**User**: `tinkertickets`
|
**User**: `tinkertickets`
|
||||||
@@ -98,443 +155,63 @@ Controllers → Models → Database
|
|||||||
|
|
||||||
### Core Tables
|
### Core Tables
|
||||||
|
|
||||||
#### `tickets` Table (Updated)
|
- `tickets` - Core ticket data with assignment
|
||||||
```sql
|
- `ticket_comments` - Markdown-supported comments
|
||||||
CREATE TABLE tickets (
|
- `ticket_attachments` - File attachment metadata
|
||||||
ticket_id INT AUTO_INCREMENT PRIMARY KEY,
|
- `ticket_dependencies` - Ticket relationships (blocks/blocked_by/relates_to/duplicates)
|
||||||
title VARCHAR(255) NOT NULL,
|
- `users` - User accounts synced from LLDAP
|
||||||
description TEXT,
|
- `user_preferences` - User settings and preferences
|
||||||
status VARCHAR(50) DEFAULT 'Open',
|
- `audit_log` - Complete audit trail
|
||||||
priority INT DEFAULT 4,
|
- `status_transitions` - Workflow configuration
|
||||||
category VARCHAR(50) DEFAULT 'General',
|
- `ticket_templates` - Reusable ticket templates
|
||||||
type VARCHAR(50) DEFAULT 'Issue',
|
- `recurring_tickets` - Scheduled ticket definitions
|
||||||
created_by INT, -- User who created
|
- `custom_field_definitions` - Custom field schemas
|
||||||
updated_by INT, -- User who last updated
|
- `custom_field_values` - Custom field data per ticket
|
||||||
assigned_to INT, -- User assigned to (NEW)
|
- `saved_filters` - User-saved dashboard filters
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
- `bulk_operations` - Bulk operation tracking
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (created_by) REFERENCES users(user_id),
|
|
||||||
FOREIGN KEY (updated_by) REFERENCES users(user_id),
|
|
||||||
FOREIGN KEY (assigned_to) REFERENCES users(user_id) ON DELETE SET NULL,
|
|
||||||
INDEX idx_status (status),
|
|
||||||
INDEX idx_assigned_to (assigned_to)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `users` Table (SSO Integration)
|
## API Endpoints
|
||||||
```sql
|
|
||||||
CREATE TABLE users (
|
|
||||||
user_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
username VARCHAR(100) UNIQUE NOT NULL,
|
|
||||||
display_name VARCHAR(255),
|
|
||||||
email VARCHAR(255),
|
|
||||||
is_admin BOOLEAN DEFAULT FALSE,
|
|
||||||
last_login TIMESTAMP NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `comments` Table
|
|
||||||
```sql
|
|
||||||
CREATE TABLE comments (
|
|
||||||
comment_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
ticket_id INT NOT NULL,
|
|
||||||
user_id INT,
|
|
||||||
comment_text TEXT NOT NULL,
|
|
||||||
markdown_enabled BOOLEAN DEFAULT FALSE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (ticket_id) REFERENCES tickets(ticket_id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
|
||||||
INDEX idx_ticket_id (ticket_id)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `audit_log` Table (Activity Timeline)
|
|
||||||
```sql
|
|
||||||
CREATE TABLE audit_log (
|
|
||||||
log_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
user_id INT,
|
|
||||||
action_type VARCHAR(50) NOT NULL, -- 'create', 'update', 'comment', 'assign', etc.
|
|
||||||
entity_type VARCHAR(50) NOT NULL, -- 'ticket', 'comment'
|
|
||||||
entity_id INT NOT NULL, -- ticket_id or comment_id
|
|
||||||
details JSON, -- JSON details of what changed
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
|
||||||
INDEX idx_entity (entity_type, entity_id),
|
|
||||||
INDEX idx_user (user_id),
|
|
||||||
INDEX idx_action (action_type)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `status_transitions` Table (Workflow Rules)
|
|
||||||
```sql
|
|
||||||
CREATE TABLE status_transitions (
|
|
||||||
transition_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
from_status VARCHAR(50) NOT NULL,
|
|
||||||
to_status VARCHAR(50) NOT NULL,
|
|
||||||
requires_comment BOOLEAN DEFAULT FALSE, -- Transition requires comment
|
|
||||||
requires_admin BOOLEAN DEFAULT FALSE, -- Transition requires admin
|
|
||||||
is_active BOOLEAN DEFAULT TRUE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE KEY unique_transition (from_status, to_status),
|
|
||||||
INDEX idx_from_status (from_status)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
```
|
|
||||||
|
|
||||||
Default transitions:
|
|
||||||
```sql
|
|
||||||
-- Open → Pending → In Progress → Closed
|
|
||||||
-- In Progress → Closed, Pending
|
|
||||||
-- Closed → X
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `ticket_templates` Table
|
|
||||||
```sql
|
|
||||||
CREATE TABLE ticket_templates (
|
|
||||||
template_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
template_name VARCHAR(100) NOT NULL,
|
|
||||||
title_template VARCHAR(255) NOT NULL,
|
|
||||||
description_template TEXT NOT NULL,
|
|
||||||
category VARCHAR(50),
|
|
||||||
type VARCHAR(50),
|
|
||||||
default_priority INT DEFAULT 4,
|
|
||||||
created_by INT,
|
|
||||||
is_active BOOLEAN DEFAULT TRUE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (created_by) REFERENCES users(user_id),
|
|
||||||
INDEX idx_template_name (template_name)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
```
|
|
||||||
|
|
||||||
Default templates: Hardware Failure, Software Installation, Network Issue, Maintenance Request
|
|
||||||
|
|
||||||
#### `bulk_operations` Table
|
|
||||||
```sql
|
|
||||||
CREATE TABLE bulk_operations (
|
|
||||||
operation_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
operation_type VARCHAR(50) NOT NULL, -- 'bulk_close', 'bulk_assign', 'bulk_priority'
|
|
||||||
ticket_ids TEXT NOT NULL, -- Comma-separated ticket IDs
|
|
||||||
performed_by INT NOT NULL,
|
|
||||||
parameters JSON, -- Operation parameters
|
|
||||||
status VARCHAR(20) DEFAULT 'pending',
|
|
||||||
total_tickets INT,
|
|
||||||
processed_tickets INT DEFAULT 0,
|
|
||||||
failed_tickets INT DEFAULT 0,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
completed_at TIMESTAMP NULL,
|
|
||||||
FOREIGN KEY (performed_by) REFERENCES users(user_id),
|
|
||||||
INDEX idx_performed_by (performed_by),
|
|
||||||
INDEX idx_created_at (created_at)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Endpoints (Updated)
|
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
All API endpoints check: `$_SESSION['user']['user_id']` for authentication.
|
All API endpoints check: `$_SESSION['user']['user_id']` for authentication.
|
||||||
Admin-only endpoints check: `$_SESSION['user']['is_admin']`.
|
Admin-only endpoints check: `$_SESSION['user']['is_admin']`.
|
||||||
|
|
||||||
### POST `/api/update_ticket.php`
|
### Core Endpoints
|
||||||
Updates ticket with workflow validation.
|
|
||||||
|
|
||||||
**Request:**
|
| Endpoint | Method | Description |
|
||||||
```json
|
|----------|--------|-------------|
|
||||||
{
|
| `/api/update_ticket.php` | POST | Update ticket with workflow validation |
|
||||||
"ticket_id": 123,
|
| `/api/assign_ticket.php` | POST | Assign ticket to user |
|
||||||
"status": "In Progress", // Validated against workflow rules
|
| `/api/add_comment.php` | POST | Add comment to ticket |
|
||||||
"priority": 2,
|
| `/api/get_template.php` | GET | Fetch ticket template |
|
||||||
"title": "Updated title",
|
| `/api/get_users.php` | GET | Get user list for assignments |
|
||||||
"description": "...",
|
| `/api/bulk_operation.php` | POST | Perform bulk operations (admin) |
|
||||||
"category": "Software",
|
| `/api/ticket_dependencies.php` | GET/POST/DELETE | Manage ticket dependencies |
|
||||||
"type": "Task"
|
| `/api/upload_attachment.php` | GET/POST | List or upload attachments |
|
||||||
}
|
| `/api/delete_attachment.php` | POST/DELETE | Delete attachment |
|
||||||
```
|
| `/api/export_tickets.php` | GET | Export tickets to CSV/JSON |
|
||||||
|
| `/api/check_duplicates.php` | GET | Check for duplicate tickets |
|
||||||
|
|
||||||
**Response:**
|
## Dashboard Features
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"status": "In Progress",
|
|
||||||
"priority": 2,
|
|
||||||
"message": "Ticket updated successfully"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
- **Stats Widgets**: Clickable cards for quick filtering (Open, Critical, Unassigned, Created Today, Closed Today)
|
||||||
- Workflow validation via WorkflowModel
|
- **Admin Dropdown**: Quick access to all admin pages
|
||||||
- Partial updates (only send changed fields)
|
- **Sortable Columns**: Click headers to sort
|
||||||
- User tracking (updated_by)
|
- **Advanced Search**: Date ranges, priority ranges, user filters
|
||||||
- Discord webhook notifications
|
- **Saved Filters**: Save and load custom filter combinations
|
||||||
- Audit logging
|
- **Bulk Actions** (admin): Select multiple tickets for bulk close/assign/priority/status
|
||||||
|
- **Export**: Export selected tickets to CSV or JSON
|
||||||
|
- **Left Sidebar**: Status, Category, Type filters
|
||||||
|
|
||||||
### POST `/api/assign_ticket.php` (NEW)
|
## Terminal UI Design
|
||||||
Assigns ticket to a user.
|
|
||||||
|
|
||||||
**Request:**
|
The application uses a retro terminal aesthetic with:
|
||||||
```json
|
- **Box-drawing characters**: ╔═╗║╚═╝┌─┐│└─┘
|
||||||
{
|
- **Monospace fonts**: Courier New, Consolas, Monaco
|
||||||
"ticket_id": 123,
|
- **Terminal colors**: Green (#00ff41), Amber (#ffb000), Cyan (#00ffff)
|
||||||
"assigned_to": 5 // user_id, or null to unassign
|
- **CRT effects**: Scanlines, subtle flicker
|
||||||
}
|
- **Glow effects**: Text shadows for terminal phosphor look
|
||||||
```
|
- **ASCII art**: Boot sequence, empty states, headers
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET `/api/get_users.php` (NEW)
|
|
||||||
Returns list of all users for assignment dropdowns.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"user_id": 1,
|
|
||||||
"username": "jared",
|
|
||||||
"display_name": "Jared Vititoe",
|
|
||||||
"is_admin": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET `/api/get_template.php?template_id=1` (NEW)
|
|
||||||
Fetches a ticket template.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"template": {
|
|
||||||
"template_id": 1,
|
|
||||||
"template_name": "Hardware Failure",
|
|
||||||
"title_template": "Hardware Failure: [Device Name]",
|
|
||||||
"description_template": "Device: \nIssue: \n...",
|
|
||||||
"category": "Hardware",
|
|
||||||
"type": "Problem",
|
|
||||||
"default_priority": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST `/api/bulk_operation.php` (NEW - ADMIN ONLY)
|
|
||||||
Performs bulk operations on tickets.
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"operation_type": "bulk_close", // or 'bulk_assign', 'bulk_priority'
|
|
||||||
"ticket_ids": [123, 456, 789],
|
|
||||||
"parameters": { // For bulk_assign or bulk_priority
|
|
||||||
"assigned_to": 5, // For bulk_assign
|
|
||||||
"priority": 2 // For bulk_priority
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"operation_id": 42,
|
|
||||||
"processed": 3,
|
|
||||||
"failed": 0,
|
|
||||||
"message": "Bulk operation completed: 3 succeeded, 0 failed"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST `/api/add_comment.php`
|
|
||||||
Adds comment to ticket.
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ticket_id": 123,
|
|
||||||
"comment_text": "Comment content",
|
|
||||||
"markdown_enabled": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"user_name": "Jared Vititoe",
|
|
||||||
"created_at": "Jan 01, 2026 12:00"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Features Implementation
|
|
||||||
|
|
||||||
### Feature 1: Activity Timeline
|
|
||||||
**Location**: Ticket view → Activity tab
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- `AuditLogModel->getTicketTimeline()` - Fetches all events for a ticket
|
|
||||||
- Shows: creates, updates, comments, assignments, status changes
|
|
||||||
- Displays: user, action, timestamp, details
|
|
||||||
- CSS: timeline-content boxes with icons
|
|
||||||
- Dark mode: Fully supported
|
|
||||||
|
|
||||||
**Code**: `views/TicketView.php:258-282`, `models/AuditLogModel.php:getTicketTimeline()`
|
|
||||||
|
|
||||||
### Feature 2: Ticket Assignment
|
|
||||||
**Location**: Ticket view → "Assigned to" dropdown, Dashboard → "Assigned To" column
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- Database: `tickets.assigned_to` column
|
|
||||||
- Models: `TicketModel->assignTicket()`, `TicketModel->unassignTicket()`
|
|
||||||
- API: `api/assign_ticket.php`
|
|
||||||
- Dashboard: Shows assigned user in table
|
|
||||||
- Auto-saves on change
|
|
||||||
- Audit logged
|
|
||||||
|
|
||||||
**Code**: `views/TicketView.php:170-181`, `assets/js/ticket.js:handleAssignmentChange()`
|
|
||||||
|
|
||||||
### Feature 3: Status Transitions with Workflows
|
|
||||||
**Location**: Ticket view → Status dropdown (header)
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- Database: `status_transitions` table defines allowed transitions
|
|
||||||
- Models: `WorkflowModel->isTransitionAllowed()`, `WorkflowModel->getAllowedTransitions()`
|
|
||||||
- Dropdown shows only valid transitions for current status
|
|
||||||
- Server-side validation prevents invalid changes
|
|
||||||
- Can require comments or admin privileges
|
|
||||||
- Removed from hamburger menu (was duplicate)
|
|
||||||
|
|
||||||
**Code**: `models/WorkflowModel.php`, `api/update_ticket.php:130-144`, `views/TicketView.php:185-198`
|
|
||||||
|
|
||||||
### Feature 4: Ticket Templates
|
|
||||||
**Location**: Create ticket page → Template selector
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- Database: `ticket_templates` table
|
|
||||||
- Models: `TemplateModel->getAllTemplates()`, `TemplateModel->getTemplateById()`
|
|
||||||
- API: `api/get_template.php`
|
|
||||||
- JavaScript: `loadTemplate()` in dashboard.js
|
|
||||||
- Auto-fills: title, description, category, type, priority
|
|
||||||
- 4 default templates included
|
|
||||||
|
|
||||||
**Code**: `views/CreateTicketView.php:27-39`, `assets/js/dashboard.js:loadTemplate()`
|
|
||||||
|
|
||||||
### Feature 5: Bulk Actions (Admin Only)
|
|
||||||
**Location**: Dashboard → Checkboxes + Toolbar (admins only)
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- Database: `bulk_operations` table tracks operations
|
|
||||||
- Models: `BulkOperationsModel->processBulkOperation()`
|
|
||||||
- API: `api/bulk_operation.php`
|
|
||||||
- UI: Toolbar appears when tickets selected
|
|
||||||
- Operations: Bulk close, bulk assign, bulk priority
|
|
||||||
- All operations audit logged
|
|
||||||
- Server-side admin validation
|
|
||||||
|
|
||||||
**Code**: `views/DashboardView.php:176-188`, `assets/js/dashboard.js:bulkClose()`, `models/BulkOperationsModel.php`
|
|
||||||
|
|
||||||
## Authentication & SSO Integration
|
|
||||||
|
|
||||||
### Authelia Integration
|
|
||||||
User information passed via HTTP headers:
|
|
||||||
- `Remote-User`: Username
|
|
||||||
- `Remote-Name`: Display name
|
|
||||||
- `Remote-Email`: Email
|
|
||||||
- `Remote-Groups`: Comma-separated groups
|
|
||||||
|
|
||||||
### Session Management
|
|
||||||
```php
|
|
||||||
$_SESSION['user'] = [
|
|
||||||
'user_id' => 123,
|
|
||||||
'username' => 'jared',
|
|
||||||
'display_name' => 'Jared Vititoe',
|
|
||||||
'email' => 'jared@lotusguild.org',
|
|
||||||
'is_admin' => true // true if 'admins' in Remote-Groups
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Admin Privileges
|
|
||||||
- Bulk operations (close, assign, priority)
|
|
||||||
- Future: Admin-only transitions
|
|
||||||
|
|
||||||
## Frontend Components (Updated)
|
|
||||||
|
|
||||||
### Dashboard (`DashboardView.php` + `dashboard.js`)
|
|
||||||
|
|
||||||
**Features**:
|
|
||||||
- Sortable columns including new "Assigned To" column
|
|
||||||
- Search (title, description, ticket_id, category, type)
|
|
||||||
- Status filtering (default: Open + In Progress)
|
|
||||||
- Pagination (configurable)
|
|
||||||
- Dark mode toggle
|
|
||||||
- **Bulk Actions Toolbar** (admin only):
|
|
||||||
- Checkboxes on each ticket
|
|
||||||
- "Select All" checkbox
|
|
||||||
- Bulk close, assign, priority buttons
|
|
||||||
- Shows count of selected tickets
|
|
||||||
|
|
||||||
**Hamburger Menu**:
|
|
||||||
- Category/Type filtering
|
|
||||||
- Apply/Clear filters
|
|
||||||
- No status field (removed)
|
|
||||||
|
|
||||||
### Ticket View (`TicketView.php` + `ticket.js`)
|
|
||||||
|
|
||||||
**Features**:
|
|
||||||
- **Tabbed Interface**: Description, Comments, Activity
|
|
||||||
- **Activity Timeline**: Complete audit trail with icons
|
|
||||||
- **Assignment Dropdown**: Assign to users
|
|
||||||
- **Status Dropdown**: Workflow-validated status changes (header)
|
|
||||||
- **Hamburger Menu**: Priority, Category, Type editing
|
|
||||||
- **Edit Button**: Title and description editing
|
|
||||||
- **Markdown Comments**: With live preview
|
|
||||||
- **Dark Mode**: Comprehensive support
|
|
||||||
|
|
||||||
**Visual Indicators**:
|
|
||||||
- Priority colors (P1=Red, P2=Orange, P3=Blue, P4=Green, P5=Gray)
|
|
||||||
- Status badges (Open=Green, In Progress=Yellow, Closed=Red)
|
|
||||||
- Priority border colors on ticket container
|
|
||||||
|
|
||||||
### Create Ticket (`CreateTicketView.php`)
|
|
||||||
|
|
||||||
**Features**:
|
|
||||||
- **Template Selector**: Quick-fill from templates
|
|
||||||
- Standard fields: title, description, status, priority, category, type
|
|
||||||
- Form validation
|
|
||||||
- Discord webhook on creation
|
|
||||||
|
|
||||||
## Dark Mode (Fixed)
|
|
||||||
|
|
||||||
### Comprehensive Dark Mode CSS
|
|
||||||
**Files**: `assets/css/ticket.css`, `assets/css/dashboard.css`
|
|
||||||
|
|
||||||
**Colors**:
|
|
||||||
```css
|
|
||||||
body.dark-mode {
|
|
||||||
--bg-primary: #1a202c; /* Main background */
|
|
||||||
--bg-secondary: #2d3748; /* Cards, inputs */
|
|
||||||
--bg-tertiary: #4a5568; /* Hover states */
|
|
||||||
--text-primary: #e2e8f0; /* Main text */
|
|
||||||
--text-secondary: #cbd5e0; /* Secondary text */
|
|
||||||
--text-muted: #a0aec0; /* Muted text */
|
|
||||||
--border-color: #4a5568; /* Borders */
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Fixed Elements**:
|
|
||||||
- Timeline boxes (background + text)
|
|
||||||
- Bulk actions toolbar
|
|
||||||
- Tables and table rows
|
|
||||||
- Input fields and textareas
|
|
||||||
- Dropdowns and selects
|
|
||||||
- Comment boxes
|
|
||||||
- Modal dialogs
|
|
||||||
- All text elements
|
|
||||||
|
|
||||||
**Important**: Used `!important` flags to override any conflicting styles.
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -549,27 +226,6 @@ DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
|
|||||||
|
|
||||||
**CRITICAL**: `.env` is gitignored! Never commit this file.
|
**CRITICAL**: `.env` is gitignored! Never commit this file.
|
||||||
|
|
||||||
### Apache Configuration
|
|
||||||
**Virtual Host**: Apache serving from `/root/code/tinker_tickets`
|
|
||||||
|
|
||||||
```apache
|
|
||||||
<VirtualHost *:80>
|
|
||||||
ServerName t.lotusguild.org
|
|
||||||
DocumentRoot /root/code/tinker_tickets
|
|
||||||
|
|
||||||
<Directory /root/code/tinker_tickets>
|
|
||||||
Options -Indexes +FollowSymLinks
|
|
||||||
AllowOverride All
|
|
||||||
Require all granted
|
|
||||||
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteBase /
|
|
||||||
RewriteRule ^ticket/([0-9]+)$ ticket.php?id=$1 [L,QSA]
|
|
||||||
RewriteRule ^ticket/create$ ticket.php?action=create [L,QSA]
|
|
||||||
</Directory>
|
|
||||||
</VirtualHost>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Git Auto-Deploy
|
### Git Auto-Deploy
|
||||||
@@ -579,17 +235,6 @@ DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
|
|||||||
1. Push to `main` branch
|
1. Push to `main` branch
|
||||||
2. Auto-deploys to `/root/code/tinker_tickets` on 10.10.10.45
|
2. Auto-deploys to `/root/code/tinker_tickets` on 10.10.10.45
|
||||||
3. `.env` is preserved
|
3. `.env` is preserved
|
||||||
4. Migrations must be run manually
|
|
||||||
|
|
||||||
### Running Migrations
|
|
||||||
```bash
|
|
||||||
cd /root/code/tinker_tickets/migrations
|
|
||||||
mysql -h 10.10.10.50 -u tinkertickets -p'pass' ticketing_system < 007_add_ticket_assignment.sql
|
|
||||||
mysql -h 10.10.10.50 -u tinkertickets -p'pass' ticketing_system < 008_add_status_workflows.sql
|
|
||||||
mysql -h 10.10.10.50 -u tinkertickets -p'pass' ticketing_system < 009_add_ticket_templates.sql
|
|
||||||
mysql -h 10.10.10.50 -u tinkertickets -p'pass' ticketing_system < 010_add_bulk_operations.sql
|
|
||||||
mysql -h 10.10.10.50 -u tinkertickets -p'pass' ticketing_system < 011_remove_view_tracking.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Guidelines
|
## Development Guidelines
|
||||||
|
|
||||||
@@ -601,123 +246,45 @@ mysql -h 10.10.10.50 -u tinkertickets -p'pass' ticketing_system < 011_remove_vie
|
|||||||
|
|
||||||
### Error Handling
|
### Error Handling
|
||||||
- APIs return JSON with `{success: bool, error: string}`
|
- APIs return JSON with `{success: bool, error: string}`
|
||||||
- Debug logging to `/tmp/api_debug.log` (update_ticket.php)
|
- Debug logging to `/tmp/api_debug.log`
|
||||||
- User-friendly error messages
|
- User-friendly error messages
|
||||||
|
|
||||||
### Adding New Features
|
### Adding New Features
|
||||||
1. **Database**: Create migration in `migrations/`
|
1. **Model**: Add methods to relevant Model class
|
||||||
2. **Model**: Add methods to relevant Model class
|
2. **API**: Create API endpoint in `api/` (with auth check)
|
||||||
3. **API**: Create API endpoint in `api/` (with auth check)
|
3. **Controller**: Update controller to load data
|
||||||
4. **Controller**: Update controller to load data
|
4. **View**: Add UI elements
|
||||||
5. **View**: Add UI elements
|
5. **JavaScript**: Add interactivity
|
||||||
6. **JavaScript**: Add interactivity
|
6. **CSS**: Style with terminal aesthetic
|
||||||
7. **CSS**: Style for light + dark mode
|
7. **Test**: Test thoroughly before pushing
|
||||||
8. **Test**: Test thoroughly before pushing
|
|
||||||
|
|
||||||
## ANSI Art Redesign (Next Priority)
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
Transform Tinker Tickets into a retro terminal aesthetic using ANSI art and ASCII characters.
|
|
||||||
|
|
||||||
### Design Concept
|
|
||||||
- **Terminal-style borders**: Use box-drawing characters (┌─┐│└─┘)
|
|
||||||
- **Monospace fonts**: Courier New, Consolas, Monaco
|
|
||||||
- **ASCII art headers**: Stylized "TINKER TICKETS" banner
|
|
||||||
- **Retro color palette**: Green terminal, amber terminal, or custom
|
|
||||||
- **Template objects**: Reusable border/box components
|
|
||||||
|
|
||||||
### Implementation Approach
|
|
||||||
1. **CSS Variables**: Define ANSI color palette
|
|
||||||
2. **Border Components**: Create CSS classes for boxes with ASCII borders
|
|
||||||
3. **Typography**: Monospace fonts throughout
|
|
||||||
4. **Icons**: Replace emoji with ASCII art
|
|
||||||
5. **Dashboard**: Terminal-style table with borders
|
|
||||||
6. **Tickets**: Box-drawing characters for sections
|
|
||||||
7. **Forms**: Terminal-style input boxes
|
|
||||||
|
|
||||||
### Reference Colors (Classic Terminal)
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
--ansi-black: #000000;
|
|
||||||
--ansi-green: #00ff00;
|
|
||||||
--ansi-amber: #ffb000;
|
|
||||||
--ansi-blue: #0000ff;
|
|
||||||
--ansi-cyan: #00ffff;
|
|
||||||
--ansi-white: #ffffff;
|
|
||||||
--ansi-bg: #000000;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example Box Template
|
|
||||||
```
|
|
||||||
┌─────────────────────────────┐
|
|
||||||
│ TICKET #123 │
|
|
||||||
├─────────────────────────────┤
|
|
||||||
│ Title: Hardware Failure │
|
|
||||||
│ Status: [OPEN] │
|
|
||||||
│ Priority: P1 (CRITICAL) │
|
|
||||||
└─────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Debugging
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
```bash
|
|
||||||
# API debug logs
|
|
||||||
tail -f /tmp/api_debug.log
|
|
||||||
|
|
||||||
# Database connection
|
|
||||||
mysql -h 10.10.10.50 -u tinkertickets -p ticketing_system
|
|
||||||
|
|
||||||
# JavaScript console
|
|
||||||
# Open browser DevTools (F12) → Console tab
|
|
||||||
|
|
||||||
# Check dark mode
|
|
||||||
# localStorage.getItem('theme')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Known Behaviors
|
|
||||||
- Ticket viewing no longer tracked (011 migration removes view logs)
|
|
||||||
- Status can only be changed via header dropdown (removed from hamburger)
|
|
||||||
- Bulk actions only visible to admins
|
|
||||||
- Templates are optional when creating tickets
|
|
||||||
- Workflow validation prevents invalid status transitions
|
|
||||||
|
|
||||||
## Important Notes for AI Assistants
|
## Important Notes for AI Assistants
|
||||||
|
|
||||||
1. **All 5 features are complete and deployed**
|
1. **All features are complete and deployed**
|
||||||
2. **Dark mode is fixed** with comprehensive CSS
|
2. **Terminal UI redesign is complete** - maintain the aesthetic
|
||||||
3. **Next priority is ANSI Art redesign** (major visual overhaul)
|
3. **Database at 10.10.10.50**, can't access directly from dev machine
|
||||||
4. **Database at 10.10.10.50**, can't access directly from dev machine
|
4. **Auto-deploy is active**, test carefully before pushing
|
||||||
5. **Auto-deploy is active**, test carefully before pushing
|
5. **Session format**: `$_SESSION['user']['user_id']` (not `$_SESSION['user_id']`)
|
||||||
6. **Session format**: `$_SESSION['user']['user_id']` (not `$_SESSION['user_id']`)
|
6. **API auth**: Check `$_SESSION['user']['user_id']` exists
|
||||||
7. **API auth**: Check `$_SESSION['user']['user_id']` exists
|
7. **Admin check**: `$_SESSION['user']['is_admin'] ?? false`
|
||||||
8. **Admin check**: `$_SESSION['user']['is_admin'] ?? false`
|
8. **Config path**: `config/config.php` (not `config/db.php`)
|
||||||
9. **Config path**: `config/config.php` (not `config/db.php`)
|
9. **Comments table**: `ticket_comments` (not `comments`)
|
||||||
10. **Migrations**: Must be run manually on database server
|
10. **CSRF**: Required for POST/DELETE requests via `X-CSRF-Token` header
|
||||||
|
|
||||||
## File Reference Quick Guide
|
## File Reference Quick Guide
|
||||||
|
|
||||||
| File | Purpose | Key Functions |
|
| File | Purpose |
|
||||||
|------|---------|---------------|
|
|------|---------|
|
||||||
| `index.php` | Dashboard router | Database connection, routing |
|
| `index.php` | Main router for all routes |
|
||||||
| `ticket.php` | Ticket router | View/create ticket routing |
|
| `api/update_ticket.php` | Ticket updates with workflow validation |
|
||||||
| `api/update_ticket.php` | Update API | Workflow validation, partial updates |
|
| `api/ticket_dependencies.php` | Manage ticket dependencies |
|
||||||
| `api/assign_ticket.php` | Assignment API | Assign/unassign tickets |
|
| `models/TicketModel.php` | Ticket CRUD, assignment, filtering |
|
||||||
| `api/bulk_operation.php` | Bulk ops API | Admin bulk operations |
|
| `models/WorkflowModel.php` | Status transition validation |
|
||||||
| `api/get_template.php` | Template API | Fetch ticket templates |
|
| `models/DependencyModel.php` | Ticket dependency management |
|
||||||
| `api/get_users.php` | Users API | Get user list |
|
| `controllers/DashboardController.php` | Dashboard logic, stats, filters |
|
||||||
| `models/TicketModel.php` | Ticket data | CRUD, assignment, filtering |
|
| `assets/js/dashboard.js` | Dashboard UI, bulk actions, templates |
|
||||||
| `models/WorkflowModel.php` | Workflow rules | Status transition validation |
|
| `assets/js/ticket.js` | Ticket UI, comments, assignment |
|
||||||
| `models/AuditLogModel.php` | Audit logging | Timeline, activity tracking |
|
| `assets/css/dashboard.css` | Terminal styling, layout, components |
|
||||||
| `models/TemplateModel.php` | Templates | Template CRUD |
|
|
||||||
| `models/BulkOperationsModel.php` | Bulk ops | Process bulk operations |
|
|
||||||
| `controllers/DashboardController.php` | Dashboard logic | Pagination, filters, assigned column |
|
|
||||||
| `controllers/TicketController.php` | Ticket logic | CRUD, timeline, templates |
|
|
||||||
| `assets/js/dashboard.js` | Dashboard UI | Filters, bulk actions, templates |
|
|
||||||
| `assets/js/ticket.js` | Ticket UI | Status updates, assignment, comments |
|
|
||||||
| `assets/css/dashboard.css` | Dashboard styles | Layout, table, bulk toolbar, dark mode |
|
|
||||||
| `assets/css/ticket.css` | Ticket styles | Timeline, ticket view, dark mode |
|
|
||||||
|
|
||||||
## Repository & Contact
|
## Repository & Contact
|
||||||
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,12 +1,14 @@
|
|||||||
# Tinker Tickets
|
# Tinker Tickets
|
||||||
|
|
||||||
A feature-rich PHP-based ticketing system designed for tracking and managing data center infrastructure issues with enterprise-grade workflow management.
|
A feature-rich PHP-based ticketing system designed for tracking and managing data center infrastructure issues with enterprise-grade workflow management and a retro terminal aesthetic.
|
||||||
|
|
||||||
## Core Features
|
## Core Features
|
||||||
|
|
||||||
### Dashboard & Ticket Management
|
### Dashboard & Ticket Management
|
||||||
- **Smart Dashboard**: Sortable columns, advanced filtering by status/priority/category/type
|
- **Smart Dashboard**: Sortable columns, advanced filtering by status/priority/category/type
|
||||||
|
- **Stats Widgets**: Clickable cards for quick filtering (Open, Critical, Unassigned, Today's tickets)
|
||||||
- **Full-Text Search**: Search across tickets, descriptions, and metadata
|
- **Full-Text Search**: Search across tickets, descriptions, and metadata
|
||||||
|
- **Advanced Search**: Date ranges, priority ranges, user filters with saved filter support
|
||||||
- **Ticket Assignment**: Assign tickets to specific users with quick-assign from dashboard
|
- **Ticket Assignment**: Assign tickets to specific users with quick-assign from dashboard
|
||||||
- **Priority Tracking**: P1 (Critical) to P5 (Minimal Impact) with color-coded indicators
|
- **Priority Tracking**: P1 (Critical) to P5 (Minimal Impact) with color-coded indicators
|
||||||
- **Custom Categories**: Hardware, Software, Network, Security, General
|
- **Custom Categories**: Hardware, Software, Network, Security, General
|
||||||
@@ -57,14 +59,16 @@ A feature-rich PHP-based ticketing system designed for tracking and managing dat
|
|||||||
- **Bulk Status**: Change status for multiple tickets
|
- **Bulk Status**: Change status for multiple tickets
|
||||||
|
|
||||||
### Admin Pages
|
### Admin Pages
|
||||||
|
Access all admin pages via the **Admin dropdown** in the dashboard header.
|
||||||
|
|
||||||
| Route | Description |
|
| Route | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
|
| `/admin/templates` | Create and edit ticket templates |
|
||||||
|
| `/admin/workflow` | Visual workflow transition designer |
|
||||||
| `/admin/recurring-tickets` | Manage recurring ticket schedules |
|
| `/admin/recurring-tickets` | Manage recurring ticket schedules |
|
||||||
| `/admin/custom-fields` | Define custom fields per category |
|
| `/admin/custom-fields` | Define custom fields per category |
|
||||||
| `/admin/workflow` | Visual workflow transition designer |
|
|
||||||
| `/admin/templates` | Create and edit ticket templates |
|
|
||||||
| `/admin/audit-log` | Browse all audit log entries |
|
|
||||||
| `/admin/user-activity` | View per-user activity statistics |
|
| `/admin/user-activity` | View per-user activity statistics |
|
||||||
|
| `/admin/audit-log` | Browse all audit log entries |
|
||||||
|
|
||||||
### Notifications
|
### Notifications
|
||||||
- **Discord Integration**: Webhook notifications for ticket creation and updates
|
- **Discord Integration**: Webhook notifications for ticket creation and updates
|
||||||
@@ -86,9 +90,10 @@ A feature-rich PHP-based ticketing system designed for tracking and managing dat
|
|||||||
- **Architecture**: MVC pattern with models, views, controllers
|
- **Architecture**: MVC pattern with models, views, controllers
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
- **HTML5/CSS3**: Semantic markup with modern CSS
|
- **HTML5/CSS3**: Semantic markup with retro terminal styling
|
||||||
- **JavaScript**: Vanilla JS with Fetch API for AJAX
|
- **JavaScript**: Vanilla JS with Fetch API for AJAX
|
||||||
- **Markdown**: Custom markdown parser with toolbar
|
- **Markdown**: Custom markdown parser with toolbar
|
||||||
|
- **Terminal UI**: Box-drawing characters, monospace fonts, CRT effects
|
||||||
|
|
||||||
### Database Tables
|
### Database Tables
|
||||||
| Table | Purpose |
|
| Table | Purpose |
|
||||||
@@ -137,7 +142,7 @@ DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
|
|||||||
|
|
||||||
### 2. Database Setup
|
### 2. Database Setup
|
||||||
|
|
||||||
The database schema is managed through migrations. For a fresh install, run all migrations in order using the migration runner.
|
The database schema should already be configured. For a fresh install, contact the system administrator for database setup instructions.
|
||||||
|
|
||||||
### 3. Cron Jobs
|
### 3. Cron Jobs
|
||||||
|
|
||||||
@@ -188,7 +193,6 @@ tinker_tickets/
|
|||||||
├── uploads/ # File upload storage
|
├── uploads/ # File upload storage
|
||||||
├── views/ # View templates
|
├── views/ # View templates
|
||||||
│ └── admin/ # Admin panel views
|
│ └── admin/ # Admin panel views
|
||||||
├── migrations/ # Database migrations
|
|
||||||
├── index.php # Main router
|
├── index.php # Main router
|
||||||
└── .env # Environment configuration
|
└── .env # Environment configuration
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Ticket Dependencies API
|
* Ticket Dependencies API
|
||||||
*
|
|
||||||
* GET: Get dependencies for a ticket
|
|
||||||
* POST: Add a new dependency
|
|
||||||
* DELETE: Remove a dependency
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Capture ALL errors for debugging
|
// Immediately set JSON header and start output buffering
|
||||||
|
ob_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Register shutdown function to catch fatal errors
|
||||||
|
register_shutdown_function(function() {
|
||||||
|
$error = error_get_last();
|
||||||
|
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
||||||
|
ob_end_clean();
|
||||||
|
http_response_code(500);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'Fatal: ' . $error['message'],
|
||||||
|
'file' => basename($error['file']),
|
||||||
|
'line' => $error['line']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ini_set('display_errors', 0);
|
ini_set('display_errors', 0);
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
// Custom error handler to return JSON errors
|
// Custom error handler
|
||||||
set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
||||||
|
ob_end_clean();
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
@@ -26,6 +42,7 @@ set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
|||||||
|
|
||||||
// Custom exception handler
|
// Custom exception handler
|
||||||
set_exception_handler(function($e) {
|
set_exception_handler(function($e) {
|
||||||
|
ob_end_clean();
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
@@ -81,6 +98,12 @@ if ($conn->connect_error) {
|
|||||||
ResponseHelper::serverError('Database connection failed');
|
ResponseHelper::serverError('Database connection failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if ticket_dependencies table exists
|
||||||
|
$tableCheck = $conn->query("SHOW TABLES LIKE 'ticket_dependencies'");
|
||||||
|
if ($tableCheck->num_rows === 0) {
|
||||||
|
ResponseHelper::serverError('Ticket dependencies feature not available. The ticket_dependencies table does not exist. Please run the migration.');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$dependencyModel = new DependencyModel($conn);
|
$dependencyModel = new DependencyModel($conn);
|
||||||
$auditLog = new AuditLogModel($conn);
|
$auditLog = new AuditLogModel($conn);
|
||||||
|
|||||||
@@ -3167,3 +3167,79 @@ tr:hover .quick-actions {
|
|||||||
background: rgba(0, 255, 65, 0.1);
|
background: rgba(0, 255, 65, 0.1);
|
||||||
color: var(--terminal-amber);
|
color: var(--terminal-amber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== ADMIN DROPDOWN ===== */
|
||||||
|
.admin-dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-dropdown .admin-badge {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 2px solid var(--priority-1);
|
||||||
|
min-width: 180px;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 0 15px rgba(255, 77, 77, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-dropdown-content.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-dropdown-content a {
|
||||||
|
display: block;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
color: var(--terminal-green);
|
||||||
|
text-decoration: none;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-bottom: 1px solid var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-dropdown-content a:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-dropdown-content a:hover {
|
||||||
|
background: rgba(255, 77, 77, 0.1);
|
||||||
|
color: var(--priority-1);
|
||||||
|
text-shadow: var(--glow-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CLICKABLE STAT CARDS ===== */
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== TABLE OVERFLOW FIX ===== */
|
||||||
|
.ascii-content {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduce column widths for better fit */
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make title column wrap if needed */
|
||||||
|
table td:nth-child(4) {
|
||||||
|
max-width: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|||||||
@@ -224,7 +224,6 @@ switch (true) {
|
|||||||
(SELECT COUNT(*) FROM tickets t WHERE t.assigned_to = u.user_id AND DATE(t.created_at) BETWEEN ? AND ?) as tickets_assigned,
|
(SELECT COUNT(*) FROM tickets t WHERE t.assigned_to = u.user_id AND DATE(t.created_at) BETWEEN ? AND ?) as tickets_assigned,
|
||||||
(SELECT MAX(al.created_at) FROM audit_log al WHERE al.user_id = u.user_id) as last_activity
|
(SELECT MAX(al.created_at) FROM audit_log al WHERE al.user_id = u.user_id) as last_activity
|
||||||
FROM users u
|
FROM users u
|
||||||
WHERE u.is_active = 1
|
|
||||||
ORDER BY tickets_created DESC, tickets_resolved DESC";
|
ORDER BY tickets_created DESC, tickets_resolved DESC";
|
||||||
|
|
||||||
$stmt = $conn->prepare($sql);
|
$stmt = $conn->prepare($sql);
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Database Migration Runner
|
|
||||||
* Executes all migration files in order
|
|
||||||
*/
|
|
||||||
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
|
|
||||||
// Load environment variables
|
|
||||||
$envFile = dirname(__DIR__) . '/.env';
|
|
||||||
if (!file_exists($envFile)) {
|
|
||||||
die("Error: .env file not found at $envFile\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
$envVars = parse_ini_file($envFile);
|
|
||||||
if (!$envVars) {
|
|
||||||
die("Error: Could not parse .env file\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to database
|
|
||||||
$conn = new mysqli(
|
|
||||||
$envVars['DB_HOST'],
|
|
||||||
$envVars['DB_USER'],
|
|
||||||
$envVars['DB_PASS'],
|
|
||||||
$envVars['DB_NAME']
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($conn->connect_error) {
|
|
||||||
die("Database connection failed: " . $conn->connect_error . "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Connected to database: {$envVars['DB_NAME']}\n\n";
|
|
||||||
|
|
||||||
// Get all migration files
|
|
||||||
$migrationFiles = glob(__DIR__ . '/*.sql');
|
|
||||||
sort($migrationFiles);
|
|
||||||
|
|
||||||
// Filter out rollback script
|
|
||||||
$migrationFiles = array_filter($migrationFiles, function($file) {
|
|
||||||
return !strpos($file, 'rollback');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (empty($migrationFiles)) {
|
|
||||||
echo "No migration files found.\n";
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Found " . count($migrationFiles) . " migration(s):\n";
|
|
||||||
foreach ($migrationFiles as $file) {
|
|
||||||
echo " - " . basename($file) . "\n";
|
|
||||||
}
|
|
||||||
echo "\n";
|
|
||||||
|
|
||||||
// Execute each migration
|
|
||||||
$successCount = 0;
|
|
||||||
$errorCount = 0;
|
|
||||||
|
|
||||||
foreach ($migrationFiles as $file) {
|
|
||||||
$filename = basename($file);
|
|
||||||
echo "Executing: $filename... ";
|
|
||||||
|
|
||||||
$sql = file_get_contents($file);
|
|
||||||
|
|
||||||
// Split SQL into individual statements
|
|
||||||
// This handles multi-statement migrations
|
|
||||||
if ($conn->multi_query($sql)) {
|
|
||||||
do {
|
|
||||||
// Store first result set
|
|
||||||
if ($result = $conn->store_result()) {
|
|
||||||
$result->free();
|
|
||||||
}
|
|
||||||
// Check for errors
|
|
||||||
if ($conn->errno) {
|
|
||||||
echo "FAILED\n";
|
|
||||||
echo " Error: " . $conn->error . "\n";
|
|
||||||
$errorCount++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while ($conn->more_results() && $conn->next_result());
|
|
||||||
|
|
||||||
// If we got through all results without error
|
|
||||||
if (!$conn->errno) {
|
|
||||||
echo "OK\n";
|
|
||||||
$successCount++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo "FAILED\n";
|
|
||||||
echo " Error: " . $conn->error . "\n";
|
|
||||||
$errorCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "\n";
|
|
||||||
echo "Migration Summary:\n";
|
|
||||||
echo " Success: $successCount\n";
|
|
||||||
echo " Errors: $errorCount\n";
|
|
||||||
|
|
||||||
if ($errorCount > 0) {
|
|
||||||
echo "\nSome migrations failed. Please review errors above.\n";
|
|
||||||
exit(1);
|
|
||||||
} else {
|
|
||||||
echo "\nAll migrations completed successfully!\n";
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
$conn->close();
|
|
||||||
@@ -79,7 +79,17 @@
|
|||||||
<?php if (isset($GLOBALS['currentUser'])): ?>
|
<?php if (isset($GLOBALS['currentUser'])): ?>
|
||||||
<span class="user-name">👤 <?php echo htmlspecialchars($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username']); ?></span>
|
<span class="user-name">👤 <?php echo htmlspecialchars($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username']); ?></span>
|
||||||
<?php if ($GLOBALS['currentUser']['is_admin']): ?>
|
<?php if ($GLOBALS['currentUser']['is_admin']): ?>
|
||||||
<span class="admin-badge">Admin</span>
|
<div class="admin-dropdown">
|
||||||
|
<button class="admin-badge" onclick="toggleAdminMenu(event)">Admin ▼</button>
|
||||||
|
<div class="admin-dropdown-content" id="adminDropdown">
|
||||||
|
<a href="/admin/templates">📋 Templates</a>
|
||||||
|
<a href="/admin/workflow">🔄 Workflow</a>
|
||||||
|
<a href="/admin/recurring-tickets">🔁 Recurring Tickets</a>
|
||||||
|
<a href="/admin/custom-fields">📝 Custom Fields</a>
|
||||||
|
<a href="/admin/user-activity">👥 User Activity</a>
|
||||||
|
<a href="/admin/audit-log">📜 Audit Log</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<button class="settings-icon" title="Settings (Alt+S)" onclick="openSettingsModal()">⚙</button>
|
<button class="settings-icon" title="Settings (Alt+S)" onclick="openSettingsModal()">⚙</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -667,5 +677,47 @@
|
|||||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js"></script>
|
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js"></script>
|
||||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/keyboard-shortcuts.js"></script>
|
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/keyboard-shortcuts.js"></script>
|
||||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/advanced-search.js"></script>
|
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/advanced-search.js"></script>
|
||||||
|
<script>
|
||||||
|
// Admin dropdown toggle
|
||||||
|
function toggleAdminMenu(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const dropdown = document.getElementById('adminDropdown');
|
||||||
|
dropdown.classList.toggle('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close admin dropdown when clicking outside
|
||||||
|
document.addEventListener('click', function(event) {
|
||||||
|
const dropdown = document.getElementById('adminDropdown');
|
||||||
|
if (dropdown && !event.target.closest('.admin-dropdown')) {
|
||||||
|
dropdown.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stat card click handlers for filtering
|
||||||
|
document.querySelectorAll('.stat-card').forEach(card => {
|
||||||
|
card.style.cursor = 'pointer';
|
||||||
|
card.addEventListener('click', function() {
|
||||||
|
const classList = this.classList;
|
||||||
|
let url = '/?';
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
if (classList.contains('stat-open')) {
|
||||||
|
url += 'status=Open';
|
||||||
|
} else if (classList.contains('stat-critical')) {
|
||||||
|
url += 'status=Open,Pending,In+Progress&priority_max=1';
|
||||||
|
} else if (classList.contains('stat-unassigned')) {
|
||||||
|
url += 'status=Open,Pending,In+Progress&assigned_to=unassigned';
|
||||||
|
} else if (classList.contains('stat-today')) {
|
||||||
|
url += 'status=Open,Pending,In+Progress&created_from=' + today + '&created_to=' + today;
|
||||||
|
} else if (classList.contains('stat-resolved')) {
|
||||||
|
url += 'status=Closed&updated_from=' + today + '&updated_to=' + today;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url !== '/?') {
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
|
|
||||||
<!-- Create/Edit Modal -->
|
<!-- Create/Edit Modal -->
|
||||||
<div class="settings-modal" id="recurringModal" style="display: none;">
|
<div class="settings-modal" id="recurringModal" style="display: none;">
|
||||||
<div class="settings-content" style="max-width: 600px;">
|
<div class="settings-content" style="max-width: 800px; width: 90%;">
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<h3 id="modalTitle">Create Recurring Ticket</h3>
|
<h3 id="modalTitle">Create Recurring Ticket</h3>
|
||||||
<button class="close-settings" onclick="closeModal()">×</button>
|
<button class="close-settings" onclick="closeModal()">×</button>
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<label for="description_template">Description Template</label>
|
<label for="description_template">Description Template</label>
|
||||||
<textarea id="description_template" name="description_template" rows="4" style="width: 100%;"></textarea>
|
<textarea id="description_template" name="description_template" rows="8" style="width: 100%; min-height: 150px;"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<label for="schedule_type">Schedule Type *</label>
|
<label for="schedule_type">Schedule Type *</label>
|
||||||
@@ -140,25 +140,45 @@
|
|||||||
<label for="schedule_time">Schedule Time *</label>
|
<label for="schedule_time">Schedule Time *</label>
|
||||||
<input type="time" id="schedule_time" name="schedule_time" value="09:00" required>
|
<input type="time" id="schedule_time" name="schedule_time" value="09:00" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem;">
|
||||||
<label for="category">Category</label>
|
<div class="setting-row">
|
||||||
<select id="category" name="category">
|
<label for="category">Category</label>
|
||||||
<option value="General">General</option>
|
<select id="category" name="category" style="width: 100%;">
|
||||||
<option value="Hardware">Hardware</option>
|
<option value="General">General</option>
|
||||||
<option value="Software">Software</option>
|
<option value="Hardware">Hardware</option>
|
||||||
<option value="Network">Network</option>
|
<option value="Software">Software</option>
|
||||||
<option value="Security">Security</option>
|
<option value="Network">Network</option>
|
||||||
</select>
|
<option value="Security">Security</option>
|
||||||
</div>
|
</select>
|
||||||
<div class="setting-row">
|
</div>
|
||||||
<label for="priority">Priority</label>
|
<div class="setting-row">
|
||||||
<select id="priority" name="priority">
|
<label for="type">Type</label>
|
||||||
<option value="1">P1 - Critical</option>
|
<select id="type" name="type" style="width: 100%;">
|
||||||
<option value="2">P2 - High</option>
|
<option value="Issue">Issue</option>
|
||||||
<option value="3">P3 - Medium</option>
|
<option value="Maintenance">Maintenance</option>
|
||||||
<option value="4" selected>P4 - Low</option>
|
<option value="Install">Install</option>
|
||||||
<option value="5">P5 - Lowest</option>
|
<option value="Task">Task</option>
|
||||||
</select>
|
<option value="Upgrade">Upgrade</option>
|
||||||
|
<option value="Problem">Problem</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<label for="priority">Priority</label>
|
||||||
|
<select id="priority" name="priority" style="width: 100%;">
|
||||||
|
<option value="1">P1 - Critical</option>
|
||||||
|
<option value="2">P2 - High</option>
|
||||||
|
<option value="3">P3 - Medium</option>
|
||||||
|
<option value="4" selected>P4 - Low</option>
|
||||||
|
<option value="5">P5 - Lowest</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<label for="assigned_to">Assign To</label>
|
||||||
|
<select id="assigned_to" name="assigned_to" style="width: 100%;">
|
||||||
|
<option value="">Unassigned</option>
|
||||||
|
<!-- Populated by JavaScript -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-footer">
|
<div class="settings-footer">
|
||||||
@@ -269,15 +289,35 @@
|
|||||||
document.getElementById('schedule_day').value = rt.schedule_day || '';
|
document.getElementById('schedule_day').value = rt.schedule_day || '';
|
||||||
document.getElementById('schedule_time').value = rt.schedule_time ? rt.schedule_time.substring(0, 5) : '09:00';
|
document.getElementById('schedule_time').value = rt.schedule_time ? rt.schedule_time.substring(0, 5) : '09:00';
|
||||||
document.getElementById('category').value = rt.category || 'General';
|
document.getElementById('category').value = rt.category || 'General';
|
||||||
|
document.getElementById('type').value = rt.type || 'Issue';
|
||||||
document.getElementById('priority').value = rt.priority || 4;
|
document.getElementById('priority').value = rt.priority || 4;
|
||||||
|
document.getElementById('assigned_to').value = rt.assigned_to || '';
|
||||||
document.getElementById('modalTitle').textContent = 'Edit Recurring Ticket';
|
document.getElementById('modalTitle').textContent = 'Edit Recurring Ticket';
|
||||||
document.getElementById('recurringModal').style.display = 'flex';
|
document.getElementById('recurringModal').style.display = 'flex';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load users for assignee dropdown
|
||||||
|
function loadUsers() {
|
||||||
|
fetch('/api/get_users.php')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.users) {
|
||||||
|
const select = document.getElementById('assigned_to');
|
||||||
|
data.users.forEach(user => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = user.user_id;
|
||||||
|
option.textContent = user.display_name || user.username;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
updateScheduleOptions();
|
updateScheduleOptions();
|
||||||
|
loadUsers();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
|
|
||||||
<!-- Create/Edit Modal -->
|
<!-- Create/Edit Modal -->
|
||||||
<div class="settings-modal" id="templateModal" style="display: none;">
|
<div class="settings-modal" id="templateModal" style="display: none;">
|
||||||
<div class="settings-content" style="max-width: 600px;">
|
<div class="settings-content" style="max-width: 800px; width: 90%;">
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<h3 id="modalTitle">Create Template</h3>
|
<h3 id="modalTitle">Create Template</h3>
|
||||||
<button class="close-settings" onclick="closeModal()">×</button>
|
<button class="close-settings" onclick="closeModal()">×</button>
|
||||||
@@ -111,12 +111,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<label for="description_template">Description Template</label>
|
<label for="description_template">Description Template</label>
|
||||||
<textarea id="description_template" name="description_template" rows="6" style="width: 100%;" placeholder="Pre-filled description content"></textarea>
|
<textarea id="description_template" name="description_template" rows="10" style="width: 100%; min-height: 200px;" placeholder="Pre-filled description content"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem;">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<label for="category">Category</label>
|
<label for="category">Category</label>
|
||||||
<select id="category" name="category">
|
<select id="category" name="category" style="width: 100%;">
|
||||||
<option value="">Any</option>
|
<option value="">Any</option>
|
||||||
<option value="General">General</option>
|
<option value="General">General</option>
|
||||||
<option value="Hardware">Hardware</option>
|
<option value="Hardware">Hardware</option>
|
||||||
@@ -127,18 +127,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<label for="type">Type</label>
|
<label for="type">Type</label>
|
||||||
<select id="type" name="type">
|
<select id="type" name="type" style="width: 100%;">
|
||||||
<option value="">Any</option>
|
<option value="">Any</option>
|
||||||
<option value="Maintenance">Maintenance</option>
|
<option value="Maintenance">Maintenance</option>
|
||||||
<option value="Install">Install</option>
|
<option value="Install">Install</option>
|
||||||
<option value="Task">Task</option>
|
<option value="Task">Task</option>
|
||||||
<option value="Upgrade">Upgrade</option>
|
<option value="Upgrade">Upgrade</option>
|
||||||
<option value="Issue">Issue</option>
|
<option value="Issue">Issue</option>
|
||||||
|
<option value="Problem">Problem</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<label for="priority">Priority</label>
|
<label for="priority">Priority</label>
|
||||||
<select id="priority" name="priority">
|
<select id="priority" name="priority" style="width: 100%;">
|
||||||
<option value="1">P1</option>
|
<option value="1">P1</option>
|
||||||
<option value="2">P2</option>
|
<option value="2">P2</option>
|
||||||
<option value="3">P3</option>
|
<option value="3">P3</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user