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:
152
requests.php
Normal file
152
requests.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
require __DIR__ . '/config.php';
|
||||
require_login();
|
||||
|
||||
/* ---- Pagination ---- */
|
||||
$page = max(1, (int) ($_GET['page'] ?? 1));
|
||||
$perPage = 12;
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$totalStmt = db()->query('SELECT COUNT(*) FROM vm_requests');
|
||||
$total = (int) $totalStmt->fetchColumn();
|
||||
$totalPages = max(1, (int) ceil($total / $perPage));
|
||||
if ($page > $totalPages) $page = $totalPages;
|
||||
|
||||
$stmt = db()->prepare(
|
||||
'SELECT id, vm_name, domain_name, size, vcpus, ram_gb, disk_gb, status, description, created_at, netbox_device_id
|
||||
FROM vm_requests
|
||||
ORDER BY created_at DESC
|
||||
LIMIT :limit OFFSET :offset'
|
||||
);
|
||||
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$recentRequests = $stmt->fetchAll();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>My Requests</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="shop-page">
|
||||
<nav class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Infrastructure Portal</p>
|
||||
<a href="/index.php" style="text-decoration:none;color:inherit;">
|
||||
<h1>VM Shop</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="/index.php" class="nav-link">Shop</a>
|
||||
<a href="/requests.php" class="nav-link active">My Requests</a>
|
||||
<a class="link-button" href="/logout.php">← Logout</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<?php if (!empty($_GET['cancelled'])): ?>
|
||||
<div class="success-message">✓ VM request cancelled.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="panel requests-panel">
|
||||
<div class="requests-header">
|
||||
<h2 style="margin:0">My VM Requests</h2>
|
||||
<a href="/index.php" class="btn btn-primary btn-sm">+ New Request</a>
|
||||
</div>
|
||||
|
||||
<?php if (!$recentRequests): ?>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">🚀</div>
|
||||
<p class="muted">No VM requests yet.<br><a href="/index.php">Order your first VM →</a></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Domain</th>
|
||||
<th>Specs</th>
|
||||
<th>Status</th>
|
||||
<th>When</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($recentRequests as $request): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?= h($request['vm_name']) ?></strong>
|
||||
<?php if (!empty($request['description'])): ?>
|
||||
<div class="muted" style="font-size:0.75rem;margin-top:2px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="<?= h($request['description']) ?>">
|
||||
<?= h($request['description']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= h($request['domain_name']) ?></td>
|
||||
<td>
|
||||
<span class="badge badge-<?= h($request['size']) ?>" style="font-size:0.7rem;padding:2px 8px;">
|
||||
<?= h(ucfirst($request['size'])) ?>
|
||||
</span>
|
||||
<span class="muted" style="font-size:0.78rem;margin-left:6px;">
|
||||
<?= h((string) $request['vcpus']) ?>c / <?= h((string) $request['ram_gb']) ?>G
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-<?= h($request['status']) ?>">
|
||||
<span class="badge-dot"></span>
|
||||
<?= h(ucfirst(str_replace('_', ' ', $request['status']))) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="td-timestamp">
|
||||
<?= h(date('M j, Y', strtotime($request['created_at']))) ?>
|
||||
<br>
|
||||
<span style="opacity:0.6"><?= h(date('g:i A', strtotime($request['created_at']))) ?></span>
|
||||
</td>
|
||||
<td class="td-actions">
|
||||
<?php if ($request['status'] === 'queued'): ?>
|
||||
<form action="/cancel.php" method="post" style="display:inline" onsubmit="return confirm('Cancel this request?');">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="id" value="<?= (int) $request['id'] ?>">
|
||||
<button type="submit" class="cancel-btn" title="Cancel request">Cancel</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if ($request['netbox_device_id']): ?>
|
||||
<tr style="background:var(--success-bg)">
|
||||
<td colspan="6" style="font-size:0.78rem;color:var(--success-text);padding-top:4px;padding-bottom:8px;">
|
||||
✅ NetBox Device #<?= h((string) $request['netbox_device_id']) ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<nav class="pagination" aria-label="Requests pagination">
|
||||
<?php if ($page > 1): ?>
|
||||
<a class="pagination-link" href="/requests.php?page=<?= $page - 1 ?>">← Prev</a>
|
||||
<?php else: ?>
|
||||
<span class="pagination-link disabled">← Prev</span>
|
||||
<?php endif; ?>
|
||||
<span class="pagination-info">Page <?= $page ?> of <?= $totalPages ?> (<?= $total ?> total)</span>
|
||||
<?php if ($page < $totalPages): ?>
|
||||
<a class="pagination-link" href="/requests.php?page=<?= $page + 1 ?>">Next →</a>
|
||||
<?php else: ?>
|
||||
<span class="pagination-link disabled">Next →</span>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user