- source_type (auto vs manual) added to dedup hash so automated
tickets never collide with manually created ones
- OSD-specific subtype (osd_down_N) so each OSD gets its own ticket
- Description refreshed on every automated update (current sensor data)
- Comments on worsening condition only fire on meaningful changes
- ASCII art descriptions wrapped in fenced code blocks in comments
- Reopen comment also uses fenced code block
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Include source_type (auto vs manual) in dedup hash so automated
tickets never collide with manually created ones. This was causing
hwmonDaemon to hijack manual task tickets that shared the same
cluster/category/environment tags.
- Include specific OSD ID in hash subtype (osd_down_N) so each OSD
failure gets its own ticket instead of all colliding to osd_down.
- Wrap hwmonDaemon report descriptions in fenced code blocks in
comments so ASCII art box-drawing renders correctly instead of
collapsing into a paragraph blob.
- Refresh ticket description on every automated update so the ticket
body shows current sensor data, not stale values from first report.
- Only post a worsening-condition comment when title or priority
actually changed (not just a description refresh).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use random_int(100000000-999999999) so IDs are always 9 digits without
a leading zero, matching the behaviour of TicketModel::createTicket().
The old sprintf('%09d', mt_rand(1, ...)) could produce IDs like
000123456 which broke PHP array key lookups elsewhere.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Title changes (e.g. rising Power_On_Hours counter) were firing a Matrix
ping every hour. Notifications now only trigger when priority escalates
to a higher severity level.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two changes to the external ticket API:
1. Serial-based dedup: generateTicketHash() now uses the `serial` field
from the hwmonDaemon payload as the stable drive identifier instead of
extracting /dev/sdX from the title. Device path is kept as a fallback
for payloads without a serial field (backwards compatible).
Hash key renamed from `device` to `drive` to reflect this.
2. Active-ticket updates: when a duplicate is detected and the ticket is
still open, the API now compares the incoming title and priority against
the existing ticket. If the title changed or priority escalated (lower
number), the ticket is updated and a comment is added explaining what
changed. Previously the API silently returned "Duplicate ticket" with
no update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of crashing (PHP 8.2 TypeError) or silently failing on duplicate hash,
the API now:
- Checks for any existing ticket with the same hash (no 24h limit)
- If open/pending/in-progress: returns Duplicate ticket with existing ID
- If closed: reopens the ticket, posts a recurrence comment, returns action=reopened
- If new: creates the ticket as before
- Wraps INSERT in try/catch for mysqli_sql_exception to handle race conditions
gracefully when multiple nodes POST simultaneously
Also improves the hash function:
- Ceph issues now include a subtype (bluestore_slow, clock_skew, osd_down, etc.)
so different Ceph warnings get distinct tickets instead of colliding
- LXC storage issues include the container ID so each container gets its own ticket
- Fixed potential null-subject issue in preg_match for missing titles
- Added early input validation (400 + JSON error) before any processing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
generateTicketHash() passed $data['title'] to preg_match() before any
input validation. In PHP 8.2, preg_match() with null subject throws
TypeError, causing HTTP 500 with empty body. hwmonDaemon saw this as
"Expecting value: line 1 column 1 (char 0)" and failed to create tickets
on all nodes.
Moved input validation before the hash call: missing or empty title now
returns HTTP 400 with proper JSON error instead of crashing. Also removed
the redundant late URL-encoded fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add helpers/NotificationHelper.php: shared Matrix webhook sender
that reads MATRIX_WEBHOOK_URL and MATRIX_NOTIFY_USERS from config
- Remove sendDiscordWebhook() from TicketController; call
NotificationHelper::sendTicketNotification() instead
- Replace 60-line Discord embed block in create_ticket_api.php
with a single NotificationHelper call
- config/config.php: DISCORD_WEBHOOK_URL → MATRIX_WEBHOOK_URL +
new MATRIX_NOTIFY_USERS key (comma-separated Matrix user IDs)
- .env.example: updated env var names and comments
Payload sent to hookshot includes notify_users array so the
JS transform can build proper @mention links for each user.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
create_ticket_api.php was not including config/config.php, so
$GLOBALS['config'] was empty when UrlHelper::ticketUrl() checked
for APP_DOMAIN, causing it to fall back to localhost.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Standardized embed format across both ticket creation paths
- Added consistent priority colors (P1-P5) with distinct hex values
- Added priority labels (e.g., "P1 - Critical" instead of just "1")
- Added Source field showing hostname extracted from ticket title
- Added Status field to both webhook formats
- Added footer distinguishing "Automated Alert" vs "Manual Entry"
- Added timestamp to API endpoint webhooks
- Added error logging for failed webhook calls
- Added timeout (10s) to API endpoint curl calls
- Added null check for webhook URL in API endpoint
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bug fixes:
- Fix ticket ID extraction using URLSearchParams instead of split()
- Add error handling for query result in get_users.php
- Make Discord webhook URLs dynamic (use HTTP_HOST)
Code cleanup:
- Remove debug console.log statements from dashboard.js and ticket.js
- Add getTicketIdFromUrl() helper function to both JS files
Documentation:
- Update Claude.md: fix web server (nginx not Apache), add new notes
- Update README.md: add keyboard shortcuts, update setup instructions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update generateTicketHash() to exclude hostname from hash for
cluster-wide Ceph issues, enabling proper deduplication across
all nodes in the cluster.
Cluster-wide issues detected by:
- [cluster-wide] tag in title
- HEALTH_ERR or HEALTH_WARN in title
- "cluster usage" in title
This prevents all nodes from creating duplicate tickets for the
same cluster-wide issue (e.g., Ceph HEALTH_WARN).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Problem: When SMART errors evolved on the same drive, new tickets were created
instead of updating the existing ticket. This happened because the hash was
based on specific error values (e.g., "Reallocated_Sector_Ct: 8") instead of
just the issue category.
Root Cause:
- Old hash included specific SMART attribute names and values
- When errors changed (8 → 16 reallocated sectors, or new errors appeared),
the hash changed, allowing duplicate tickets
- Only matched "Warning" attributes, missing "Critical" and "Error X occurred"
- Only matched /dev/sd[a-z], missing NVMe devices
Solution:
- Hash now based on: hostname + device + issue_category (e.g., "smart")
- Does NOT include specific error values or attribute names
- Supports both /dev/sdX and /dev/nvmeXnY devices
- Detects issue categories: smart, storage, memory, cpu, network
Result:
✅ Same drive, errors evolve → Same hash → Updates existing ticket
✅ Different device → Different hash → New ticket
✅ Drive replaced → Different device → New ticket
✅ NVMe devices now supported
Example:
Before:
- "Warning Reallocated: 8" → hash abc123
- "Warning Reallocated: 16" → hash xyz789 (NEW TICKET - bad!)
After:
- "Warning Reallocated: 8" → hash abc123
- "Warning Reallocated: 16" → hash abc123 (SAME TICKET - good!)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Updated parse_ini_file to use INI_SCANNER_TYPED
- Added quote stripping for all .env value parsing
- Fixes database connection and Discord webhook issues when values are quoted
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>