Add nodes management and initial setup pages

- Implemented nodes management functionality in `nodes.php` including create, update, and delete actions.
- Added form validation and error handling for node operations.
- Created a new setup page in `setup.php` for initial administrator account creation.
- Included user feedback messages for successful operations and errors.
- Designed user interface for both nodes management and setup processes.
This commit is contained in:
2025-12-14 01:33:12 -05:00
parent cf0ec74888
commit d8b76233c0
19 changed files with 8800 additions and 0 deletions

View File

@@ -0,0 +1,314 @@
<?php
/**
* Dashboard Page
* Main overview with statistics and quick actions
*/
// Initialize managers
$asnManager = new ASN($db);
$nodeManager = new Node($db);
$peerManager = new Peer($db);
$templateManager = new Template($db);
$hostManager = new Host($db);
// Get statistics
$asns = $asnManager->getAll();
$nodes = $nodeManager->getAll();
$peers = $peerManager->getAll();
$templates = $templateManager->getAll();
$hosts = $hostManager->getAll();
// Count by status
$peersByStatus = [];
foreach ($peers as $peer) {
$status = $peer['enabled'] ?? true ? 'enabled' : 'disabled';
$peersByStatus[$status] = ($peersByStatus[$status] ?? 0) + 1;
}
// Recent activity
$recentLogs = $logger->search([], 10);
?>
<div class="page-header">
<div class="d-flex flex-items-center flex-justify-between">
<div>
<h1 class="h2 mb-1">Dashboard</h1>
<p class="color-fg-muted mb-0">Overview of your BGP configuration</p>
</div>
<div>
<?php if (hasPermission('execute_commands')): ?>
<a href="?page=execute" class="btn btn-primary">
<svg class="octicon mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"/></svg>
Execute
</a>
<?php endif; ?>
</div>
</div>
</div>
<div class="page-content">
<!-- Statistics Cards -->
<div class="d-flex flex-wrap" style="gap: 16px; margin-bottom: 24px;">
<div class="stat-card flex-1" style="min-width: 200px;">
<div class="d-flex flex-items-center">
<div class="mr-3">
<svg class="octicon color-fg-muted" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32"><path fill-rule="evenodd" d="M1.5 14.25c0 .138.112.25.25.25H4v-1.25a.75.75 0 01.75-.75h2.5a.75.75 0 01.75.75v1.25h2.25a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25h-8.5a.25.25 0 00-.25.25v12.5zM1.75 16A1.75 1.75 0 010 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 00.25-.25V8.285a.25.25 0 00-.111-.208l-1.055-.703a.75.75 0 11.832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0114.25 16h-3.5a.75.75 0 01-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 01-.75-.75V14h-1v1.25a.75.75 0 01-.75.75h-3z"/></svg>
</div>
<div>
<div class="stat-value"><?= count($asns) ?></div>
<div class="stat-label">ASNs</div>
</div>
</div>
</div>
<div class="stat-card flex-1" style="min-width: 200px;">
<div class="d-flex flex-items-center">
<div class="mr-3">
<svg class="octicon color-fg-muted" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32"><path fill-rule="evenodd" d="M1.75 1A1.75 1.75 0 000 2.75v4c0 .372.116.717.314 1a1.742 1.742 0 00-.314 1v4c0 .966.784 1.75 1.75 1.75h12.5A1.75 1.75 0 0016 12.75v-4c0-.372-.116-.717-.314-1 .198-.283.314-.628.314-1v-4A1.75 1.75 0 0014.25 1H1.75z"/></svg>
</div>
<div>
<div class="stat-value"><?= count($nodes) ?></div>
<div class="stat-label">Nodes</div>
</div>
</div>
</div>
<div class="stat-card flex-1" style="min-width: 200px;">
<div class="d-flex flex-items-center">
<div class="mr-3">
<svg class="octicon color-fg-muted" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32"><path fill-rule="evenodd" d="M11.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122V6A2.5 2.5 0 0110 8.5H6a1 1 0 00-1 1v1.128a2.251 2.251 0 11-1.5 0V5.372a2.25 2.25 0 111.5 0v1.836A2.492 2.492 0 016 7h4a1 1 0 001-1v-.628A2.25 2.25 0 019.5 3.25z"/></svg>
</div>
<div>
<div class="stat-value"><?= count($peers) ?></div>
<div class="stat-label">Peers</div>
</div>
</div>
</div>
<div class="stat-card flex-1" style="min-width: 200px;">
<div class="d-flex flex-items-center">
<div class="mr-3">
<svg class="octicon color-fg-muted" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32"><path fill-rule="evenodd" d="M4 1.75C4 .784 4.784 0 5.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0114.25 15h-9a.75.75 0 010-1.5h9a.25.25 0 00.25-.25V6h-2.75A1.75 1.75 0 0110 4.25V1.5H5.75a.25.25 0 00-.25.25v2.5a.75.75 0 01-1.5 0v-2.5z"/></svg>
</div>
<div>
<div class="stat-value"><?= count($templates) ?></div>
<div class="stat-label">Templates</div>
</div>
</div>
</div>
<div class="stat-card flex-1" style="min-width: 200px;">
<div class="d-flex flex-items-center">
<div class="mr-3">
<svg class="octicon color-fg-muted" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32"><path fill-rule="evenodd" d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0114.25 15H1.75A1.75 1.75 0 010 13.25V2.75z"/></svg>
</div>
<div>
<div class="stat-value"><?= count($hosts) ?></div>
<div class="stat-label">Hosts</div>
</div>
</div>
</div>
</div>
<div class="d-flex flex-wrap" style="gap: 24px;">
<!-- Quick Actions -->
<div class="flex-1" style="min-width: 300px;">
<div class="Box">
<div class="Box-header">
<h3 class="Box-title">Quick Actions</h3>
</div>
<div class="Box-body">
<div class="d-flex flex-column" style="gap: 8px;">
<?php if (hasPermission('create_peers')): ?>
<a href="?page=asns&action=create" class="btn btn-outline btn-block text-left">
<svg class="octicon mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 110 1.5H8.5v4.25a.75.75 0 11-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>
Add New ASN
</a>
<a href="?page=nodes&action=create" class="btn btn-outline btn-block text-left">
<svg class="octicon mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 110 1.5H8.5v4.25a.75.75 0 11-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>
Add New Node
</a>
<a href="?page=peers&action=create" class="btn btn-outline btn-block text-left">
<svg class="octicon mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 110 1.5H8.5v4.25a.75.75 0 11-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>
Add New Peer
</a>
<?php endif; ?>
<a href="?page=config" class="btn btn-outline btn-block text-left">
<svg class="octicon mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.679 7.932c.412-.621 1.242-1.75 2.366-2.717C5.175 4.242 6.527 3.5 8 3.5c1.473 0 2.824.742 3.955 1.715 1.124.967 1.954 2.096 2.366 2.717a.119.119 0 010 .136c-.412.621-1.242 1.75-2.366 2.717C10.825 11.758 9.473 12.5 8 12.5c-1.473 0-2.824-.742-3.955-1.715C2.92 9.818 2.09 8.69 1.679 8.068a.119.119 0 010-.136zM8 2c-1.981 0-3.67.992-4.933 2.078C1.797 5.169.88 6.423.43 7.1a1.619 1.619 0 000 1.798c.45.678 1.367 1.932 2.637 3.024C4.329 13.008 6.019 14 8 14c1.981 0 3.67-.992 4.933-2.078 1.27-1.091 2.187-2.345 2.637-3.023a1.619 1.619 0 000-1.798c-.45-.678-1.367-1.932-2.637-3.023C11.671 2.992 9.981 2 8 2zm0 8a2 2 0 100-4 2 2 0 000 4z"/></svg>
Preview Configuration
</a>
<?php if (hasPermission('execute_commands')): ?>
<a href="?page=execute&action=validate" class="btn btn-outline btn-block text-left">
<svg class="octicon mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg>
Validate Configuration
</a>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Recent ASNs -->
<div class="flex-1" style="min-width: 300px;">
<div class="Box">
<div class="Box-header d-flex flex-items-center flex-justify-between">
<h3 class="Box-title">ASNs</h3>
<a href="?page=asns" class="Link--primary text-small">View all</a>
</div>
<?php if (empty($asns)): ?>
<div class="Box-body color-fg-muted">
No ASNs configured yet.
</div>
<?php else: ?>
<ul class="list-style-none">
<?php foreach (array_slice($asns, 0, 5) as $asn): ?>
<li class="Box-row d-flex flex-items-center">
<span class="Label Label--secondary mr-2">AS<?= e($asn['asn']) ?></span>
<span class="flex-1"><?= e($asn['name']) ?></span>
<a href="?page=asns&action=edit&id=<?= e($asn['id']) ?>" class="Link--secondary text-small">Edit</a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
</div>
<div class="d-flex flex-wrap mt-4" style="gap: 24px;">
<!-- Recent Nodes -->
<div class="flex-1" style="min-width: 300px;">
<div class="Box">
<div class="Box-header d-flex flex-items-center flex-justify-between">
<h3 class="Box-title">Nodes</h3>
<a href="?page=nodes" class="Link--primary text-small">View all</a>
</div>
<?php if (empty($nodes)): ?>
<div class="Box-body color-fg-muted">
No nodes configured yet.
</div>
<?php else: ?>
<ul class="list-style-none">
<?php foreach (array_slice($nodes, 0, 5) as $node): ?>
<?php
$nodeAsn = $asnManager->get($node['asn_id']);
$nodePeers = array_filter($peers, fn($p) => $p['node_id'] === $node['id']);
?>
<li class="Box-row d-flex flex-items-center">
<div class="flex-1">
<div class="text-bold"><?= e($node['name']) ?></div>
<div class="text-small color-fg-muted">
<?= $nodeAsn ? 'AS' . e($nodeAsn['asn']) : 'Unknown ASN' ?> •
<?= count($nodePeers) ?> peers
</div>
</div>
<a href="?page=nodes&action=edit&id=<?= e($node['id']) ?>" class="Link--secondary text-small">Edit</a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
<!-- Recent Activity -->
<div class="flex-1" style="min-width: 300px;">
<div class="Box">
<div class="Box-header d-flex flex-items-center flex-justify-between">
<h3 class="Box-title">Recent Activity</h3>
<a href="?page=logs" class="Link--primary text-small">View all</a>
</div>
<?php if (empty($recentLogs)): ?>
<div class="Box-body color-fg-muted">
No recent activity.
</div>
<?php else: ?>
<ul class="list-style-none">
<?php foreach (array_slice($recentLogs, 0, 5) as $log): ?>
<li class="Box-row">
<div class="d-flex flex-items-start">
<span class="status-dot <?= $log['level'] === 'error' ? 'danger' : ($log['level'] === 'warning' ? 'warning' : 'success') ?> mt-1"></span>
<div class="flex-1 min-width-0">
<div class="text-small text-truncate"><?= e($log['message']) ?></div>
<div class="text-small color-fg-muted">
<?= e($log['category']) ?> • <?= formatDate($log['timestamp']) ?>
</div>
</div>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
</div>
<!-- Peers Summary -->
<div class="mt-4">
<div class="Box">
<div class="Box-header d-flex flex-items-center flex-justify-between">
<h3 class="Box-title">Recent Peers</h3>
<a href="?page=peers" class="Link--primary text-small">View all</a>
</div>
<?php if (empty($peers)): ?>
<div class="Box-body color-fg-muted">
No peers configured yet.
</div>
<?php else: ?>
<div class="overflow-auto">
<table class="data-table">
<thead>
<tr>
<th>Peer Name</th>
<th>ASN</th>
<th>Neighbor</th>
<th>Node</th>
<th>Template</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach (array_slice($peers, 0, 10) as $peer): ?>
<?php
$peerNode = $nodeManager->get($peer['node_id']);
$peerAsn = $peerNode ? $asnManager->get($peerNode['asn_id']) : null;
?>
<tr>
<td class="text-bold"><?= e($peer['name']) ?></td>
<td><?= isset($peer['config']['asn']) ? 'AS' . e($peer['config']['asn']) : '-' ?></td>
<td>
<?php if (!empty($peer['config']['neighbors'])): ?>
<?= e(implode(', ', array_slice($peer['config']['neighbors'], 0, 2))) ?>
<?php if (count($peer['config']['neighbors']) > 2): ?>
<span class="color-fg-muted">+<?= count($peer['config']['neighbors']) - 2 ?></span>
<?php endif; ?>
<?php else: ?>
-
<?php endif; ?>
</td>
<td><?= $peerNode ? e($peerNode['name']) : '-' ?></td>
<td>
<?php if (!empty($peer['config']['template'])): ?>
<span class="Label Label--secondary"><?= e($peer['config']['template']) ?></span>
<?php else: ?>
-
<?php endif; ?>
</td>
<td>
<?php if ($peer['enabled'] ?? true): ?>
<span class="Label Label--success">Enabled</span>
<?php else: ?>
<span class="Label Label--secondary">Disabled</span>
<?php endif; ?>
</td>
<td>
<a href="?page=peers&action=edit&id=<?= e($peer['id']) ?>" class="btn btn-sm">Edit</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>