Redesign as VM webshop with NetBox virtualization integration
- Split single-page into 3 pages: - index.php: product catalog with VM size cards - create.php: order form (name, domain, description) - requests.php: request history with pagination - Rewrite NetBox integration to create Virtual Machines (/virtualization/virtual-machines/) instead of DCIM devices - Add netbox_integration/netbox_api.php: NetBoxClient with createVirtualMachine(), findCluster(), findTag(), getList() - Generate new sleek product-card images (420x280) with aligned spec boxes (vcpus/ram/disk) - Add webshop CSS: product cards, nav links, hero section, order layout, responsive grid - Update redirects: send.php -> requests.php, cancel.php -> requests.php - Update E2E test for new page structure - Add .gitignore, deploy-local.sh, setup_database.sql to repo
This commit is contained in:
345
send.php
Normal file → Executable file
345
send.php
Normal file → Executable file
@@ -1,249 +1,114 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Alex VM Project</title>
|
||||
<meta http-equiv="refresh" content="20;url=index.php">
|
||||
<style>
|
||||
.logout {
|
||||
text-align: right;
|
||||
margin: 10px 20px;
|
||||
}
|
||||
.logout a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
.logout a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
// Enable error reporting
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||
echo "Error: Unauthorized access.";
|
||||
exit;
|
||||
require __DIR__ . '/config.php';
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo 'Method not allowed.';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!verify_csrf($_POST['csrf_token'] ?? null)) {
|
||||
http_response_code(419);
|
||||
echo 'Invalid CSRF token.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$config = require __DIR__ . '/sizes.php';
|
||||
$sizeMapping = $config['sizes'];
|
||||
$allowedDomains = $config['domains'];
|
||||
|
||||
$vmName = trim((string) ($_POST['VM_Name'] ?? ''));
|
||||
$size = trim((string) ($_POST['size'] ?? ''));
|
||||
$domain = trim((string) ($_POST['domain'] ?? ''));
|
||||
$description = trim((string) ($_POST['description'] ?? ''));
|
||||
|
||||
$errors = [];
|
||||
if (!preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]{1,63}$/', $vmName)) {
|
||||
$errors[] = 'VM name must be 2-64 characters and use letters, numbers, dots, dashes, or underscores.';
|
||||
}
|
||||
if (!isset($sizeMapping[$size])) {
|
||||
$errors[] = 'Invalid VM size.';
|
||||
}
|
||||
if (!in_array($domain, $allowedDomains, true)) {
|
||||
$errors[] = 'Invalid domain.';
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
header('Location: /create.php?size=' . urlencode($size) . '&error=' . urlencode(implode(' ', $errors)));
|
||||
exit;
|
||||
}
|
||||
|
||||
$resources = $sizeMapping[$size];
|
||||
try {
|
||||
$stmt = db()->prepare(
|
||||
'INSERT INTO vm_requests
|
||||
(vm_name, domain_name, size, vcpus, ram_gb, disk_gb, requested_by, status, description)
|
||||
VALUES
|
||||
(:vm_name, :domain_name, :size, :vcpus, :ram_gb, :disk_gb, :requested_by, :status, :description)'
|
||||
);
|
||||
$stmt->execute([
|
||||
'vm_name' => $vmName,
|
||||
'domain_name' => $domain,
|
||||
'size' => $size,
|
||||
'vcpus' => $resources['vcpus'],
|
||||
'ram_gb' => $resources['ram_gb'],
|
||||
'disk_gb' => $resources['disk_gb'],
|
||||
'requested_by' => $_SESSION['user'] ?? 'unknown',
|
||||
'status' => 'queued',
|
||||
'description' => $description,
|
||||
]);
|
||||
|
||||
// Get the inserted request ID
|
||||
$requestId = db()->lastInsertId();
|
||||
|
||||
// Create NetBox Virtual Machine for this request
|
||||
try {
|
||||
$netboxClient = getNetBoxClient();
|
||||
|
||||
// Find the first cluster (proxmox-cluster)
|
||||
$cluster = $netboxClient->findCluster('proxmox-cluster');
|
||||
if (!$cluster) {
|
||||
throw new Exception('No NetBox cluster found.');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo "Error: Invalid request method.";
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
|
||||
echo "Error: Invalid CSRF token.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// NetBox API configuration
|
||||
$netboxApiUrl = 'http://192.168.1.144:80/api/virtualization/virtual-machines/';
|
||||
$apiToken = 'fb82b28af1fcc5f180cc8f3b9d747e8fccb987d0';
|
||||
|
||||
// Validate and sanitize user input
|
||||
$VM_Name = isset($_POST['VM_Name']) ? htmlspecialchars(trim($_POST['VM_Name'])) : null;
|
||||
$Size = isset($_POST['size']) ? htmlspecialchars(trim($_POST['size'])) : null;
|
||||
$Domain = isset($_POST['domain']) ? htmlspecialchars(trim($_POST['domain'])) : null;
|
||||
|
||||
// Map size to vCPUs and RAM
|
||||
$sizeMapping = [
|
||||
'small' => ['vcpus' => 2, 'ram' => 4],
|
||||
'medium' => ['vcpus' => 4, 'ram' => 8],
|
||||
'big' => ['vcpus' => 8, 'ram' => 16]
|
||||
];
|
||||
|
||||
$vCPUs = isset($sizeMapping[$Size]) ? $sizeMapping[$Size]['vcpus'] : null;
|
||||
$Ram_Value = isset($sizeMapping[$Size]) ? $sizeMapping[$Size]['ram'] * 1024 : null; // Convert GB to MB
|
||||
|
||||
// Set HDD value to always be 50GB
|
||||
$HDD_Value = 50 * 1024; // Convert GB to MB
|
||||
|
||||
// Check for required fields
|
||||
if (!$VM_Name || $vCPUs === null || $Ram_Value === null || !$Domain) {
|
||||
echo "Error: Missing or invalid input data.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Function to check if a VM name already exists in NetBox
|
||||
function checkIfVmExists($vmName, $netboxApiUrl, $apiToken) {
|
||||
$vmsApiUrl = rtrim($netboxApiUrl, '/') . '/?name=' . urlencode($vmName);
|
||||
|
||||
// Query NetBox for the VM name
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $vmsApiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Token ' . $apiToken
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$vms = json_decode($response, true);
|
||||
if (!empty($vms['results'])) {
|
||||
return true; // VM with the same name exists
|
||||
}
|
||||
} else {
|
||||
echo "Error: Failed to check VM name. HTTP Code: $httpCode<br>";
|
||||
echo "Response: $response<br>";
|
||||
}
|
||||
|
||||
return false; // VM does not exist
|
||||
}
|
||||
|
||||
// Check if the VM name already exists
|
||||
if (checkIfVmExists($VM_Name, $netboxApiUrl, $apiToken)) {
|
||||
echo "Error: A VM with the name '$VM_Name' already exists in NetBox.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Function to get or create a tag in NetBox
|
||||
function getOrCreateTag($tagName, $netboxApiUrl, $apiToken) {
|
||||
$tagsApiUrl = 'http://192.168.1.144:80/api/extras/tags/';
|
||||
|
||||
// Check if the tag exists
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $tagsApiUrl . '?name=' . urlencode($tagName));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Token ' . $apiToken
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$tags = json_decode($response, true);
|
||||
if (!empty($tags['results'])) {
|
||||
return $tags['results'][0]['id']; // Return the ID of the existing tag
|
||||
}
|
||||
}
|
||||
|
||||
// If the tag doesn't exist, create it
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $tagsApiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['name' => $tagName]));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Token ' . $apiToken
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 201) {
|
||||
$tag = json_decode($response, true);
|
||||
return $tag['id']; // Return the ID of the newly created tag
|
||||
}
|
||||
|
||||
return null; // Return null if the tag couldn't be created
|
||||
}
|
||||
|
||||
// Get or create the domain tag
|
||||
$domainTagId = getOrCreateTag($Domain, $netboxApiUrl, $apiToken);
|
||||
if (!$domainTagId) {
|
||||
echo "Error: Failed to retrieve or create the domain tag.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Function to create a virtual disk in NetBox
|
||||
function createDisk($vmId, $diskName, $diskSize, $netboxApiUrl, $apiToken) {
|
||||
$disksApiUrl = 'http://192.168.1.144:80/api/virtualization/virtual-disks/';
|
||||
|
||||
// Prepare disk data
|
||||
$diskData = [
|
||||
'virtual_machine' => $vmId,
|
||||
'name' => $diskName,
|
||||
'size' => $diskSize // Size in MB
|
||||
];
|
||||
|
||||
// Initialize cURL
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $disksApiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($diskData));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Token ' . $apiToken
|
||||
]);
|
||||
|
||||
// Execute cURL request
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 201) {
|
||||
echo "Disk '$diskName' created successfully.<br>";
|
||||
} else {
|
||||
echo "Failed to create disk '$diskName'. HTTP Code: $httpCode<br>";
|
||||
echo "Response: $response<br>";
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare data for NetBox API to create the VM
|
||||
$data = [
|
||||
'name' => $VM_Name,
|
||||
'vcpus' => $vCPUs,
|
||||
'memory' => $Ram_Value,
|
||||
'status' => 'active', // Adjust as needed
|
||||
'cluster' => 1, // Replace with the appropriate cluster ID
|
||||
'tags' => [$domainTagId] // Include the domain tag ID
|
||||
];
|
||||
|
||||
// Initialize cURL to create the VM
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $netboxApiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Token ' . $apiToken
|
||||
$vm = $netboxClient->createVirtualMachine([
|
||||
'name' => $vmName,
|
||||
'cluster_id' => $cluster['id'],
|
||||
'vcpus' => $resources['vcpus'],
|
||||
'ram_gb' => $resources['ram_gb'],
|
||||
'disk_gb' => $resources['disk_gb'],
|
||||
'domain' => $domain,
|
||||
'description' => $description ?: ('Requested by ' . ($_SESSION['user'] ?? 'unknown')),
|
||||
]);
|
||||
|
||||
// Execute cURL request to create the VM
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$netboxVmId = $vm['id'] ?? null;
|
||||
|
||||
if ($response === false) {
|
||||
echo 'cURL Error: ' . curl_error($ch);
|
||||
} else {
|
||||
if ($httpCode === 201) {
|
||||
echo "VM created successfully in NetBox.<br>";
|
||||
$vm = json_decode($response, true);
|
||||
$vmId = $vm['id']; // Get the VM ID from the response
|
||||
// Update the request with NetBox VM ID
|
||||
$stmt = db()->prepare(
|
||||
'UPDATE vm_requests SET status = :status, netbox_device_id = :netbox_vm_id WHERE id = :id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'status' => 'deployed_to_netbox',
|
||||
'netbox_vm_id' => $netboxVmId,
|
||||
'id' => $requestId,
|
||||
]);
|
||||
|
||||
// Create OS Disk
|
||||
createDisk($vmId, 'osdisk', 60 * 1024, $netboxApiUrl, $apiToken);
|
||||
} catch (Exception $e) {
|
||||
error_log("NetBox integration failed: " . $e->getMessage());
|
||||
// Request stays in 'queued' — admin can retry later
|
||||
}
|
||||
|
||||
} catch (PDOException $exception) {
|
||||
if ($exception->getCode() !== '23000') {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// Create Data Disk
|
||||
createDisk($vmId, 'datadisk', 50 * 1024, $netboxApiUrl, $apiToken);
|
||||
} else {
|
||||
echo "Failed to create VM in NetBox. HTTP Code: $httpCode<br>";
|
||||
echo "Response: $response<br>";
|
||||
}
|
||||
}
|
||||
header('Location: /create.php?size=' . urlencode($size) . '&error=' . urlencode('A VM request with that name already exists.'));
|
||||
exit;
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
?>
|
||||
<div class="header">
|
||||
<h1>Processing Your VM Configuration...</h1>
|
||||
<p>You will be redirected to the front page in 20 seconds.</p>
|
||||
<div class="logout">
|
||||
<a href="logout.php">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
header('Location: /requests.php?created=1');
|
||||
exit;
|
||||
|
||||
Reference in New Issue
Block a user