Add schema baseline, fix cron/retention, restore cleanup, correct docs
Lint / PHP (phpcs PSR-12) (push) Successful in 26s
Lint / JS (eslint) (push) Successful in 12s
Lint / PHP requirements (version + extensions) (push) Successful in 21s
Security / PHP Security (semgrep) (push) Successful in 1m11s
Lint / Deploy (push) Successful in 2s
Lint / Notify on failure (push) Has been skipped
Lint / PHP (phpcs PSR-12) (pull_request) Successful in 47s
Lint / JS (eslint) (pull_request) Successful in 12s
Lint / PHP requirements (version + extensions) (pull_request) Successful in 59s
Security / PHP Security (semgrep) (pull_request) Successful in 1m6s
Lint / Deploy (pull_request) Has been skipped
Lint / Notify on failure (pull_request) Has been skipped
Lint / PHP (phpcs PSR-12) (push) Successful in 26s
Lint / JS (eslint) (push) Successful in 12s
Lint / PHP requirements (version + extensions) (push) Successful in 21s
Security / PHP Security (semgrep) (push) Successful in 1m11s
Lint / Deploy (push) Successful in 2s
Lint / Notify on failure (push) Has been skipped
Lint / PHP (phpcs PSR-12) (pull_request) Successful in 47s
Lint / JS (eslint) (pull_request) Successful in 12s
Lint / PHP requirements (version + extensions) (pull_request) Successful in 59s
Security / PHP Security (semgrep) (pull_request) Successful in 1m6s
Lint / Deploy (pull_request) Has been skipped
Lint / Notify on failure (pull_request) Has been skipped
- migrations/000_baseline.sql: full schema baseline captured from prod (validated on a throwaway DB: 17 tables/17 FKs), so the schema is reproducible for fresh installs / disaster recovery - create_recurring_tickets cron: send the Matrix ticket-created notification and invalidate the stats cache like the other create paths - create_ticket_api.php + TicketController::create: invalidate the stats cache on create/escalate/reopen so dashboard counts aren't stale - scripts/cleanup_orphan_uploads.php: restored, made safe (24h mtime grace, 9-digit-dir only, skips avatars/symlinks, matches the unique filename column, --dry-run) - cron/cleanup_audit_log.php: enforce the configured audit-log retention (deleteOldLogs was implemented but never called) - README: correct CSRF-rotation, hwmon dedup (no 24h window), SLA (no P3), stats-cache callers, and the project structure/endpoint listing - .env.example: document TRUSTED_PROXIES fail-open risk and .env quoting Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -73,7 +73,7 @@ The following features are intentionally **not planned** for this system:
|
||||
- **Duplicate Detection**: Similarity check on ticket title surfaces potential duplicates with one-click linking
|
||||
- **Activity Timeline**: Full `lt-timeline` audit trail — color-coded by event type (status, comment, assign, attach)
|
||||
- **Watcher Avatars**: Avatar group shows who is watching a ticket; tooltip lists all names
|
||||
- **SLA Timer**: P1/P2 tickets display a live elapsed-time banner with progress bar (P1 = 8 h, P2 = 24 h, P3 = 72 h)
|
||||
- **SLA Timer**: P1/P2 tickets display a live elapsed-time banner with progress bar (P1 = 8 h, P2 = 24 h). Lower priorities (P3–P5) have no SLA banner.
|
||||
- **Priority Alert Banner**: P1 shows a sticky error banner; P2 shows a warning banner — dismissible per session
|
||||
|
||||
### Ticket Templates
|
||||
@@ -121,7 +121,7 @@ The following features are intentionally **not planned** for this system:
|
||||
- **Powered by audit_log**: No extra table — notifications are derived from existing audit trail
|
||||
|
||||
### Matrix Notifications (hookshot)
|
||||
- **Ticket Created**: Fires when any ticket is created (manual or via API)
|
||||
- **Ticket Created**: Fires when a ticket is created via the manual form, the external API (hwmonDaemon), or the recurring-ticket cron. (Cloned tickets do not fire this event.)
|
||||
- **Status Changed**: Fires on every status transition
|
||||
- **@Mentions**: Mentioned users receive a direct Matrix notification
|
||||
- **Assignment**: Optional — set `MATRIX_NOTIFY_ASSIGNMENTS=1` to enable
|
||||
@@ -150,7 +150,7 @@ The following features are intentionally **not planned** for this system:
|
||||
| `?` | Show keyboard shortcuts help |
|
||||
|
||||
### Security Features
|
||||
- **CSRF Protection**: Token-based protection with constant-time comparison; token rotated after each write
|
||||
- **CSRF Protection**: Token-based protection with constant-time comparison. `bootstrap.php` rotates the token on a successful write and returns the current token in every response (including on rejection); the client (`lt.api`) resyncs from that value. Rejected requests do not rotate the token.
|
||||
- **Rate Limiting**: Session-based AND IP-based rate limiting to prevent abuse
|
||||
- **Security Headers**: CSP with nonces (no unsafe-inline), X-Frame-Options, X-Content-Type-Options
|
||||
- **SQL Injection Prevention**: All queries use prepared statements with parameter binding
|
||||
@@ -179,9 +179,9 @@ Content-Type: application/json
|
||||
|
||||
**Key behaviours:**
|
||||
- Authenticated via `Authorization: Bearer` header — API key stored in `/etc/hwmonDaemon/.env`
|
||||
- **Deduplication**: Generates a SHA-256 hash from the issue category, hostname, and device; rejects duplicate tickets within 24 hours
|
||||
- **Deduplication**: Generates a SHA-256 hash from the issue category, hostname, and device (no time window). A repeat alert matching an existing **open** ticket updates its title/description and escalates the priority if the condition worsened; if the matching ticket was already **closed**, it is reopened instead of creating a new one
|
||||
- Cluster-wide issues (Ceph health, etc.) deduplicate across all nodes (hostname excluded from hash)
|
||||
- Matrix notification sent automatically after ticket creation
|
||||
- Matrix notification sent automatically on ticket creation, priority escalation, and reopen
|
||||
- API key must be generated at `/admin/api-keys`; the key goes in hwmonDaemon's `/etc/hwmonDaemon/.env` as `TICKET_API_KEY`
|
||||
|
||||
## Technical Architecture
|
||||
@@ -240,6 +240,11 @@ Content-Type: application/json
|
||||
- `tickets`: `ticket_id` (unique), `status`, `priority`, `created_at`, `created_by`, `assigned_to`, `visibility`
|
||||
- `audit_log`: `user_id`, `action_type`, `entity_type`, `created_at`
|
||||
|
||||
### Database Schema / Migrations
|
||||
|
||||
- `migrations/000_baseline.sql` is the full schema baseline for the whole database. It is written to be safe to re-run (idempotent) and is the source of truth for a fresh install.
|
||||
- `php migrations/migrate.php` applies any pending migration files in `migrations/` in order, tracking applied files in the `migrations` table. Use `--status` to list state and `--dry-run` to preview without executing.
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
@@ -248,6 +253,7 @@ Content-Type: application/json
|
||||
| `/api/update_ticket.php` | POST | Update ticket with workflow validation |
|
||||
| `/api/assign_ticket.php` | POST | Assign ticket to user |
|
||||
| `/api/add_comment.php` | POST | Add comment to ticket |
|
||||
| `/api/get_comments.php` | GET | Fetch paginated comments for a ticket |
|
||||
| `/api/clone_ticket.php` | POST | Clone an existing ticket |
|
||||
| `/api/get_template.php` | GET | Fetch ticket template |
|
||||
| `/api/get_users.php` | GET | Get user list for assignments |
|
||||
@@ -292,6 +298,7 @@ tinker_tickets/
|
||||
│ ├── download_attachment.php # GET: Download with visibility check
|
||||
│ ├── export_tickets.php # GET: Export tickets to CSV/JSON
|
||||
│ ├── generate_api_key.php # POST: Generate API key (admin)
|
||||
│ ├── get_comments.php # GET: Fetch paginated ticket comments
|
||||
│ ├── get_template.php # GET: Fetch ticket template
|
||||
│ ├── get_users.php # GET: Get user list
|
||||
│ ├── health.php # GET: Health check endpoint
|
||||
@@ -329,14 +336,20 @@ tinker_tickets/
|
||||
├── config/
|
||||
│ └── config.php # Config + .env loading
|
||||
├── controllers/
|
||||
│ ├── CommentController.php # Comment create/edit/delete + notifications
|
||||
│ ├── DashboardController.php # Dashboard with stats + filters
|
||||
│ └── TicketController.php # Ticket CRUD + timeline + visibility
|
||||
├── cron/
|
||||
│ ├── cleanup_audit_log.php # Delete audit_log rows past retention (daily)
|
||||
│ ├── cleanup_ratelimit.php # Purge expired rate-limit files (every few min)
|
||||
│ └── create_recurring_tickets.php # Process recurring ticket schedules
|
||||
├── helpers/
|
||||
│ ├── CacheHelper.php # File-based cache (stats, avatars)
|
||||
│ ├── Database.php # Centralized mysqli connection
|
||||
│ ├── ErrorHandler.php # Global error/exception handler
|
||||
│ ├── NotificationHelper.php # Matrix hookshot webhook events
|
||||
│ ├── OutputHelper.php # Safe HTML output helpers
|
||||
│ ├── ResponseHelper.php # JSON API response helpers
|
||||
│ ├── SynapseHelper.php # Resolves usernames → Matrix IDs via Synapse admin API
|
||||
│ └── UrlHelper.php # Canonical ticket URLs using APP_DOMAIN
|
||||
├── middleware/
|
||||
@@ -347,6 +360,7 @@ tinker_tickets/
|
||||
│ └── SecurityHeadersMiddleware.php # CSP headers with per-request nonce generation
|
||||
├── models/
|
||||
│ ├── ApiKeyModel.php # API key generation/validation
|
||||
│ ├── AttachmentModel.php # Ticket file attachment metadata
|
||||
│ ├── AuditLogModel.php # Audit logging + timeline
|
||||
│ ├── BulkOperationsModel.php # Bulk operations tracking
|
||||
│ ├── CommentModel.php # Comment data access
|
||||
@@ -360,11 +374,12 @@ tinker_tickets/
|
||||
│ ├── UserModel.php # User management + groups
|
||||
│ ├── UserPreferencesModel.php # User preferences
|
||||
│ └── WorkflowModel.php # Status transition workflows
|
||||
├── migrations/
|
||||
│ ├── 000_baseline.sql # Full schema baseline (safe to re-run)
|
||||
│ └── migrate.php # CLI migration runner (tracks applied migrations)
|
||||
├── scripts/
|
||||
│ ├── add_closed_at_column.php # Migration: add closed_at column to tickets
|
||||
│ ├── add_comment_updated_at.php # Migration: add updated_at column to ticket_comments
|
||||
│ ├── cleanup_orphan_uploads.php # Clean orphaned uploads (run manually or via cron)
|
||||
│ └── create_dependencies_table.php # Create ticket_dependencies table
|
||||
│ ├── check_requirements.php # Verify PHP extensions/config prerequisites
|
||||
│ └── cleanup_orphan_uploads.php # Delete orphaned upload files past grace period (cron)
|
||||
├── uploads/ # File attachment storage
|
||||
│ └── avatars/ # lldap avatar disk cache
|
||||
├── views/
|
||||
@@ -455,13 +470,20 @@ AVATAR_CACHE_TTL=3600
|
||||
|
||||
### 2. Cron Jobs
|
||||
|
||||
Add to crontab for recurring tickets and optional cleanup:
|
||||
Add to crontab for recurring tickets and maintenance cleanup:
|
||||
```bash
|
||||
# Run every hour to create scheduled recurring tickets
|
||||
0 * * * * php /path/to/tinkertickets/cron/create_recurring_tickets.php
|
||||
|
||||
# Optional: clean up orphaned uploads weekly
|
||||
0 3 * * 0 php /path/to/tinkertickets/scripts/cleanup_orphan_uploads.php
|
||||
# Purge expired rate-limit files (every 5 minutes)
|
||||
*/5 * * * * php /path/to/tinkertickets/cron/cleanup_ratelimit.php
|
||||
|
||||
# Delete audit_log rows older than AUDIT_LOG_RETENTION_DAYS (daily)
|
||||
30 3 * * * php /path/to/tinkertickets/cron/cleanup_audit_log.php
|
||||
|
||||
# Delete orphaned upload files with no attachment row, past a 24h grace period (daily).
|
||||
# Add --dry-run to preview without deleting.
|
||||
0 4 * * * php /path/to/tinkertickets/scripts/cleanup_orphan_uploads.php
|
||||
```
|
||||
|
||||
### 3. File Uploads
|
||||
@@ -502,7 +524,7 @@ Key conventions and gotchas for working with this codebase:
|
||||
3. **Admin check**: `$_SESSION['user']['is_admin'] ?? false`
|
||||
4. **Config path**: `config/config.php` (not `config/db.php`)
|
||||
5. **Comments table**: `ticket_comments` (not `comments`)
|
||||
6. **CSRF**: Required for all POST/DELETE requests via `X-CSRF-Token` header; bootstrap.php rotates token and returns it in `csrf_token` field of all `apiRespond()` responses
|
||||
6. **CSRF**: Required for all POST/DELETE requests via `X-CSRF-Token` header. `bootstrap.php` rotates the token only on a successful write and returns the current token in the `csrf_token` field of every `apiRespond()` response (including rejections), so the client can resync. A rejected request keeps the existing token.
|
||||
7. **Cache busting**: `ASSET_VERSION` is auto-computed from asset file mtimes; override with `ASSET_VERSION=` in `.env`
|
||||
8. **Ticket linking**: Use `#123456789` in markdown-enabled comments
|
||||
9. **User groups**: Stored in `users.groups` as comma-separated values
|
||||
@@ -520,8 +542,8 @@ Key conventions and gotchas for working with this codebase:
|
||||
21. **Confirm dialogs**: Never use browser `confirm()`. Use `showConfirmModal(title, message, type, onConfirm)` (defined in `utils.js`, available on all pages). Types: `'warning'` | `'error'` | `'info'`.
|
||||
22. **`utils.js` on all pages**: `utils.js` is loaded by all views (including admin). It provides `escapeHtml()`, `getTicketIdFromUrl()`, and `showConfirmModal()`.
|
||||
23. **No `toast.js`**: `toast.js` is deprecated and no longer loaded by any view. Use `lt.toast.success/error/warning/info()` directly from `base.js`.
|
||||
24. **Stats cache**: `StatsModel` caches stats for 60 s. Any API that modifies ticket state must call `(new StatsModel($conn))->invalidateCache()` after changes (bulk_operation, assign_ticket, update_ticket, clone_ticket all do this).
|
||||
25. **External API (`create_ticket_api.php`)**: Uses `ApiKeyAuth` (Bearer token), not session auth. Served directly by the web server from the document root — not through the index.php router. Includes deduplication logic to prevent duplicate hw-alert tickets within 24 h.
|
||||
24. **Stats cache**: `StatsModel` caches stats for 60 s. Any path that modifies ticket state must call `(new StatsModel($conn))->invalidateCache()` after the change. Callers: `TicketController::create` (manual create), `create_ticket_api.php` (external API create/escalate/reopen), `cron/create_recurring_tickets.php`, `bulk_operation`, `assign_ticket`, `update_ticket`, and `clone_ticket`.
|
||||
25. **External API (`create_ticket_api.php`)**: Uses `ApiKeyAuth` (Bearer token), not session auth. Served directly by the web server from the document root — not through the index.php router. Includes deduplication logic (SHA-256 hash, no time window) that updates/escalates an existing open duplicate or reopens a closed one rather than creating a new ticket.
|
||||
|
||||
## File Reference
|
||||
|
||||
@@ -557,7 +579,7 @@ Key conventions and gotchas for working with this codebase:
|
||||
|---------|---------------|
|
||||
| SQL Injection | All queries use prepared statements with parameter binding |
|
||||
| XSS Prevention | HTML escaped in markdown parser; CSP with per-request nonces |
|
||||
| CSRF Protection | Token-based with constant-time comparison (`hash_equals`); rotated on each write |
|
||||
| CSRF Protection | Token-based with constant-time comparison (`hash_equals`); rotated on successful writes, current token returned in every response (including rejections) for the client to resync — rejected requests do not rotate |
|
||||
| Session Security | Fixation prevention, secure cookies, session timeout |
|
||||
| Rate Limiting | Session-based + IP-based (file storage) |
|
||||
| File Security | Path traversal prevention, MIME type validation, uploads `.htaccess` blocks execution |
|
||||
|
||||
Reference in New Issue
Block a user