2024-11-30 19:48:01 -05:00
|
|
|
<?php
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
|
2024-12-02 21:21:10 -05:00
|
|
|
error_reporting(E_ALL);
|
|
|
|
|
ini_set('display_errors', 1);
|
|
|
|
|
file_put_contents('debug.log', print_r($_POST, true) . "\n" . file_get_contents('php://input') . "\n", FILE_APPEND);
|
|
|
|
|
|
2024-11-30 19:48:01 -05:00
|
|
|
// Load environment variables with error check
|
|
|
|
|
$envFile = __DIR__ . '/.env';
|
|
|
|
|
if (!file_exists($envFile)) {
|
|
|
|
|
echo json_encode([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'error' => 'Configuration file not found'
|
|
|
|
|
]);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 16:52:35 -05:00
|
|
|
$envVars = parse_ini_file($envFile, false, INI_SCANNER_TYPED);
|
2024-11-30 19:48:01 -05:00
|
|
|
if (!$envVars) {
|
|
|
|
|
echo json_encode([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'error' => 'Invalid configuration file'
|
|
|
|
|
]);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 16:52:35 -05:00
|
|
|
// Strip quotes from values if present (parse_ini_file may include them)
|
|
|
|
|
foreach ($envVars as $key => $value) {
|
|
|
|
|
if (is_string($value)) {
|
|
|
|
|
if ((substr($value, 0, 1) === '"' && substr($value, -1) === '"') ||
|
|
|
|
|
(substr($value, 0, 1) === "'" && substr($value, -1) === "'")) {
|
|
|
|
|
$envVars[$key] = substr($value, 1, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 19:48:01 -05:00
|
|
|
// Database connection with detailed error handling
|
|
|
|
|
$conn = new mysqli(
|
2024-12-02 21:21:10 -05:00
|
|
|
$envVars['DB_HOST'],
|
|
|
|
|
$envVars['DB_USER'],
|
|
|
|
|
$envVars['DB_PASS'],
|
|
|
|
|
$envVars['DB_NAME']
|
2024-11-30 19:48:01 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($conn->connect_error) {
|
|
|
|
|
echo json_encode([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'error' => 'Database connection failed: ' . $conn->connect_error
|
|
|
|
|
]);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
// Authenticate via API key
|
|
|
|
|
require_once __DIR__ . '/middleware/ApiKeyAuth.php';
|
|
|
|
|
require_once __DIR__ . '/models/AuditLogModel.php';
|
|
|
|
|
|
|
|
|
|
$apiKeyAuth = new ApiKeyAuth($conn);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$systemUser = $apiKeyAuth->authenticate();
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
// Authentication failed - ApiKeyAuth already sent the response
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$userId = $systemUser['user_id'];
|
|
|
|
|
file_put_contents('debug.log', "Authenticated as system user ID: $userId\n", FILE_APPEND);
|
|
|
|
|
|
2025-02-27 21:37:38 -05:00
|
|
|
// Create tickets table with hash column if not exists
|
|
|
|
|
$createTableSQL = "CREATE TABLE IF NOT EXISTS tickets (
|
|
|
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
ticket_id VARCHAR(9) NOT NULL,
|
|
|
|
|
title VARCHAR(255) NOT NULL,
|
|
|
|
|
hash VARCHAR(64) NOT NULL,
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
UNIQUE KEY unique_hash (hash)
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
$conn->query($createTableSQL);
|
|
|
|
|
|
2025-02-27 22:12:28 -05:00
|
|
|
// Parse input regardless of content-type header
|
|
|
|
|
$rawInput = file_get_contents('php://input');
|
|
|
|
|
$data = json_decode($rawInput, true);
|
|
|
|
|
|
2025-02-27 21:37:38 -05:00
|
|
|
// Generate hash from stable components
|
|
|
|
|
function generateTicketHash($data) {
|
Fix duplicate ticket creation for evolving SMART errors
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>
2026-01-07 19:27:13 -05:00
|
|
|
// Extract device name if present (matches /dev/sdX, /dev/nvmeXnY patterns)
|
|
|
|
|
preg_match('/\/dev\/(sd[a-z]|nvme\d+n\d+)/', $data['title'], $deviceMatches);
|
2025-03-03 18:23:44 -05:00
|
|
|
$isDriveTicket = !empty($deviceMatches);
|
Fix duplicate ticket creation for evolving SMART errors
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>
2026-01-07 19:27:13 -05:00
|
|
|
|
2025-03-03 18:23:44 -05:00
|
|
|
// Extract hostname from title [hostname][tags]...
|
|
|
|
|
preg_match('/\[([\w\d-]+)\]/', $data['title'], $hostMatches);
|
|
|
|
|
$hostname = $hostMatches[1] ?? '';
|
|
|
|
|
|
Fix duplicate ticket creation for evolving SMART errors
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>
2026-01-07 19:27:13 -05:00
|
|
|
// Detect issue category (not specific attribute values)
|
|
|
|
|
$issueCategory = '';
|
|
|
|
|
if (stripos($data['title'], 'SMART issues') !== false) {
|
|
|
|
|
$issueCategory = 'smart';
|
|
|
|
|
} elseif (stripos($data['title'], 'LXC') !== false || stripos($data['title'], 'storage usage') !== false) {
|
|
|
|
|
$issueCategory = 'storage';
|
|
|
|
|
} elseif (stripos($data['title'], 'memory') !== false) {
|
|
|
|
|
$issueCategory = 'memory';
|
|
|
|
|
} elseif (stripos($data['title'], 'cpu') !== false) {
|
|
|
|
|
$issueCategory = 'cpu';
|
|
|
|
|
} elseif (stripos($data['title'], 'network') !== false) {
|
|
|
|
|
$issueCategory = 'network';
|
|
|
|
|
}
|
2025-03-03 18:23:44 -05:00
|
|
|
|
2025-05-15 08:33:13 -04:00
|
|
|
// Build stable components with only static data
|
2025-02-27 21:37:38 -05:00
|
|
|
$stableComponents = [
|
2025-03-03 18:23:44 -05:00
|
|
|
'hostname' => $hostname,
|
Fix duplicate ticket creation for evolving SMART errors
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>
2026-01-07 19:27:13 -05:00
|
|
|
'issue_category' => $issueCategory, // Generic category, not specific errors
|
2025-05-15 08:33:13 -04:00
|
|
|
'environment_tags' => array_filter(
|
|
|
|
|
explode('][', $data['title']),
|
|
|
|
|
fn($tag) => in_array($tag, ['production', 'development', 'staging', 'single-node'])
|
|
|
|
|
)
|
2025-02-27 21:37:38 -05:00
|
|
|
];
|
2025-03-03 18:23:44 -05:00
|
|
|
|
|
|
|
|
// Only include device info for drive-specific tickets
|
|
|
|
|
if ($isDriveTicket) {
|
|
|
|
|
$stableComponents['device'] = $deviceMatches[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort arrays for consistent hashing
|
2025-05-15 08:33:13 -04:00
|
|
|
sort($stableComponents['environment_tags']);
|
Fix duplicate ticket creation for evolving SMART errors
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>
2026-01-07 19:27:13 -05:00
|
|
|
|
2025-02-27 21:37:38 -05:00
|
|
|
return hash('sha256', json_encode($stableComponents, JSON_UNESCAPED_SLASHES));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for duplicate tickets
|
|
|
|
|
$ticketHash = generateTicketHash($data);
|
|
|
|
|
$checkDuplicateSQL = "SELECT ticket_id FROM tickets WHERE hash = ? AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)";
|
|
|
|
|
$checkStmt = $conn->prepare($checkDuplicateSQL);
|
|
|
|
|
$checkStmt->bind_param("s", $ticketHash);
|
|
|
|
|
$checkStmt->execute();
|
|
|
|
|
$result = $checkStmt->get_result();
|
|
|
|
|
|
|
|
|
|
if ($result->num_rows > 0) {
|
|
|
|
|
$existingTicket = $result->fetch_assoc();
|
|
|
|
|
echo json_encode([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'error' => 'Duplicate ticket',
|
|
|
|
|
'existing_ticket_id' => $existingTicket['ticket_id']
|
|
|
|
|
]);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 21:21:10 -05:00
|
|
|
// Force JSON content type for all incoming requests
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
|
|
|
|
|
if (!$data) {
|
|
|
|
|
// Try parsing as URL-encoded data
|
|
|
|
|
parse_str($rawInput, $data);
|
|
|
|
|
}
|
2024-11-30 19:48:01 -05:00
|
|
|
|
|
|
|
|
// Generate ticket ID (9-digit format with leading zeros)
|
|
|
|
|
$ticket_id = sprintf('%09d', mt_rand(1, 999999999));
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
// Prepare insert query with created_by field
|
|
|
|
|
$sql = "INSERT INTO tickets (ticket_id, title, description, status, priority, category, type, hash, created_by)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
2024-11-30 19:48:01 -05:00
|
|
|
|
|
|
|
|
$stmt = $conn->prepare($sql);
|
|
|
|
|
// First, store all values in variables
|
|
|
|
|
$title = $data['title'];
|
|
|
|
|
$description = $data['description'];
|
|
|
|
|
$status = $data['status'] ?? 'Open';
|
|
|
|
|
$priority = $data['priority'] ?? '4';
|
|
|
|
|
$category = $data['category'] ?? 'General';
|
|
|
|
|
$type = $data['type'] ?? 'Issue';
|
|
|
|
|
|
|
|
|
|
// Then use the variables in bind_param
|
|
|
|
|
$stmt->bind_param(
|
2026-01-01 15:40:32 -05:00
|
|
|
"ssssssssi",
|
2024-11-30 19:48:01 -05:00
|
|
|
$ticket_id,
|
|
|
|
|
$title,
|
|
|
|
|
$description,
|
|
|
|
|
$status,
|
|
|
|
|
$priority,
|
|
|
|
|
$category,
|
2025-02-27 21:37:38 -05:00
|
|
|
$type,
|
2026-01-01 15:40:32 -05:00
|
|
|
$ticketHash,
|
|
|
|
|
$userId
|
2024-11-30 19:48:01 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($stmt->execute()) {
|
2026-01-01 15:40:32 -05:00
|
|
|
// Log ticket creation to audit log
|
|
|
|
|
$auditLog = new AuditLogModel($conn);
|
|
|
|
|
$auditLog->logTicketCreate($userId, $ticket_id, [
|
|
|
|
|
'title' => $title,
|
|
|
|
|
'priority' => $priority,
|
|
|
|
|
'category' => $category,
|
|
|
|
|
'type' => $type
|
|
|
|
|
]);
|
|
|
|
|
|
2024-11-30 19:48:01 -05:00
|
|
|
echo json_encode([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'ticket_id' => $ticket_id,
|
|
|
|
|
'message' => 'Ticket created successfully'
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
echo json_encode([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'error' => $conn->error
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stmt->close();
|
|
|
|
|
$conn->close();
|
2024-12-02 21:21:10 -05:00
|
|
|
|
|
|
|
|
// Discord webhook
|
|
|
|
|
$discord_webhook_url = $envVars['DISCORD_WEBHOOK_URL'];
|
|
|
|
|
|
|
|
|
|
// Map priorities to Discord colors (decimal format)
|
|
|
|
|
$priorityColors = [
|
|
|
|
|
"1" => 16736589, // --priority-1: #ff4d4d
|
|
|
|
|
"2" => 16753958, // --priority-2: #ffa726
|
|
|
|
|
"3" => 4363509, // --priority-3: #42a5f5
|
|
|
|
|
"4" => 6736490 // --priority-4: #66bb6a
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$discord_data = [
|
|
|
|
|
"content" => "",
|
|
|
|
|
"embeds" => [[
|
|
|
|
|
"title" => "New Ticket Created: #" . $ticket_id,
|
|
|
|
|
"description" => $title,
|
2025-11-29 12:52:27 -05:00
|
|
|
"url" => "http://t.lotusguild.org/ticket/" . $ticket_id,
|
2024-12-02 21:21:10 -05:00
|
|
|
"color" => $priorityColors[$priority],
|
|
|
|
|
"fields" => [
|
|
|
|
|
["name" => "Priority", "value" => $priority, "inline" => true],
|
|
|
|
|
["name" => "Category", "value" => $category, "inline" => true],
|
|
|
|
|
["name" => "Type", "value" => $type, "inline" => true]
|
|
|
|
|
]
|
|
|
|
|
]]
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$ch = curl_init($discord_webhook_url);
|
|
|
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
|
|
|
|
curl_setopt($ch, CURLOPT_POST, 1);
|
|
|
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($discord_data));
|
|
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
|
curl_exec($ch);
|
|
|
|
|
curl_close($ch);
|