Files
Joseph.Rawlings d8b76233c0 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.
2025-12-14 01:33:12 -05:00

611 lines
26 KiB
PHP

<?php
/**
* ASNs Management Page
*/
$asnManager = new ASN($db);
$validator = new Validator();
$action = $_GET['action'] ?? 'list';
$id = $_GET['id'] ?? null;
$message = '';
$error = '';
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$auth->verifyCsrfToken($_POST['csrf_token'] ?? '')) {
$error = 'Invalid security token. Please try again.';
} else {
$postAction = $_POST['action'] ?? '';
switch ($postAction) {
case 'create':
if (!hasPermission('create_peers')) {
$error = 'You do not have permission to create ASNs.';
break;
}
$asnNumber = trim($_POST['asn'] ?? '');
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
// Validate ASN
if (!$validator->validateASN($asnNumber)) {
$error = 'Invalid ASN number.';
break;
}
if (empty($name)) {
$error = 'Name is required.';
break;
}
// Build defaults from form
$defaults = buildDefaultsFromForm($_POST);
$result = $asnManager->create($asnNumber, $name, $description, $defaults);
if ($result['success']) {
$logger->log('asn', 'Created ASN', ['asn' => $asnNumber, 'name' => $name]);
header('Location: ?page=asns&message=ASN created successfully');
exit;
} else {
$error = $result['message'];
}
break;
case 'update':
if (!hasPermission('edit_peers')) {
$error = 'You do not have permission to edit ASNs.';
break;
}
$id = $_POST['id'] ?? '';
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
if (empty($name)) {
$error = 'Name is required.';
break;
}
$defaults = buildDefaultsFromForm($_POST);
$result = $asnManager->update($id, [
'name' => $name,
'description' => $description,
'defaults' => $defaults,
]);
if ($result['success']) {
$logger->log('asn', 'Updated ASN', ['id' => $id, 'name' => $name]);
header('Location: ?page=asns&message=ASN updated successfully');
exit;
} else {
$error = $result['message'];
}
break;
case 'delete':
if (!hasPermission('delete_peers')) {
$error = 'You do not have permission to delete ASNs.';
break;
}
$id = $_POST['id'] ?? '';
$asn = $asnManager->get($id);
if (!$asn) {
$error = 'ASN not found.';
break;
}
$result = $asnManager->delete($id);
if ($result['success']) {
$logger->log('asn', 'Deleted ASN', ['asn' => $asn['asn'], 'name' => $asn['name']]);
header('Location: ?page=asns&message=ASN deleted successfully');
exit;
} else {
$error = $result['message'];
}
break;
}
}
}
// Get message from query string
if (isset($_GET['message'])) {
$message = $_GET['message'];
}
// Helper function to build defaults from form
function buildDefaultsFromForm(array $post): array {
$defaults = [];
// String fields
$stringFields = [
'router_id', 'source4', 'source6', 'irr_server', 'rtr_server',
'bgpq_path', 'bgpq_args', 'bird_directory', 'bird_socket',
'cache_directory', 'log_file', 'hostname', 'peeringdb_api_key',
'peeringdb_cache_file', 'peeringdb_query_timeout', 'kernel_table',
];
foreach ($stringFields as $field) {
if (isset($post[$field]) && $post[$field] !== '') {
$defaults[$field] = trim($post[$field]);
}
}
// Boolean fields
$booleanFields = [
'keep_filtered', 'merge_paths', 'rpki_filter', 'irr_filter',
'transit_locking', 'graceful_shutdown', 'no_announce',
];
foreach ($booleanFields as $field) {
if (isset($post[$field])) {
$defaults[$field] = $post[$field] === '1';
}
}
// Integer fields
$intFields = [
'default_route_limit4', 'default_route_limit6', 'pref_src4_placeholder',
'pref_src6_placeholder', 'kernel_learn', 'kernel_export',
];
foreach ($intFields as $field) {
if (isset($post[$field]) && $post[$field] !== '') {
$defaults[$field] = (int) $post[$field];
}
}
// Array fields (comma-separated)
$arrayFields = [
'prefixes4' => 'prefixes4',
'prefixes6' => 'prefixes6',
'communities_blackhole' => 'blackhole',
'communities_nopeer' => 'nopeer',
];
foreach ($arrayFields as $postField => $defaultField) {
if (isset($post[$postField]) && $post[$postField] !== '') {
$values = array_filter(array_map('trim', explode(',', $post[$postField])));
if (!empty($values)) {
$defaults[$defaultField] = $values;
}
}
}
return $defaults;
}
// Get ASN for edit
$editAsn = null;
if ($action === 'edit' && $id) {
$editAsn = $asnManager->get($id);
if (!$editAsn) {
$error = 'ASN not found.';
$action = 'list';
}
}
// Get all ASNs for list view
$asns = $asnManager->getAll();
?>
<?php if ($action === 'list'): ?>
<!-- List View -->
<div class="page-header">
<div class="d-flex flex-items-center flex-justify-between">
<div>
<h1 class="h2 mb-1">ASNs</h1>
<p class="color-fg-muted mb-0">Manage your Autonomous System Numbers</p>
</div>
<?php if (hasPermission('create_peers')): ?>
<a href="?page=asns&action=create" 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="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>
New ASN
</a>
<?php endif; ?>
</div>
</div>
<div class="page-content">
<?php if ($message): ?>
<div class="flash flash-success mb-3">
<?= e($message) ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="flash flash-error mb-3">
<?= e($error) ?>
</div>
<?php endif; ?>
<?php if (empty($asns)): ?>
<div class="blankslate">
<svg class="octicon blankslate-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="24" height="24"><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.5z"/></svg>
<h3 class="blankslate-heading">No ASNs configured</h3>
<p>Get started by creating your first ASN.</p>
<?php if (hasPermission('create_peers')): ?>
<a href="?page=asns&action=create" class="btn btn-primary">Create ASN</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="Box">
<div class="overflow-auto">
<table class="data-table">
<thead>
<tr>
<th>ASN</th>
<th>Name</th>
<th>Description</th>
<th>Router ID</th>
<th>Nodes</th>
<th></th>
</tr>
</thead>
<tbody>
<?php
$nodeManager = new Node($db);
foreach ($asns as $asn):
$asnNodes = $nodeManager->getByAsn($asn['id']);
?>
<tr>
<td>
<span class="Label Label--accent">AS<?= e($asn['asn']) ?></span>
</td>
<td class="text-bold"><?= e($asn['name']) ?></td>
<td class="color-fg-muted"><?= e($asn['description'] ?? '-') ?></td>
<td>
<code><?= e($asn['defaults']['router_id'] ?? '-') ?></code>
</td>
<td>
<span class="Counter"><?= count($asnNodes) ?></span>
</td>
<td class="text-right">
<a href="?page=asns&action=edit&id=<?= e($asn['id']) ?>" class="btn btn-sm">Edit</a>
<?php if (hasPermission('delete_peers')): ?>
<form method="POST" class="d-inline" onsubmit="return confirmDelete('Are you sure you want to delete AS<?= e($asn['asn']) ?>?')">
<?= csrfField() ?>
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= e($asn['id']) ?>">
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
<?php elseif ($action === 'create' || $action === 'edit'): ?>
<!-- Create/Edit Form -->
<div class="page-header">
<div class="d-flex flex-items-center">
<a href="?page=asns" class="btn-octicon mr-2">
<svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.78 12.53a.75.75 0 01-1.06 0L2.47 8.28a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 1.06L4.81 7h7.44a.75.75 0 010 1.5H4.81l2.97 2.97a.75.75 0 010 1.06z"/></svg>
</a>
<div>
<h1 class="h2 mb-1"><?= $action === 'edit' ? 'Edit ASN' : 'New ASN' ?></h1>
<p class="color-fg-muted mb-0">
<?= $action === 'edit' ? 'AS' . e($editAsn['asn']) . ' - ' . e($editAsn['name']) : 'Configure a new Autonomous System Number' ?>
</p>
</div>
</div>
</div>
<div class="page-content">
<?php if ($error): ?>
<div class="flash flash-error mb-3">
<?= e($error) ?>
</div>
<?php endif; ?>
<form method="POST" action="">
<?= csrfField() ?>
<input type="hidden" name="action" value="<?= $action === 'edit' ? 'update' : 'create' ?>">
<?php if ($editAsn): ?>
<input type="hidden" name="id" value="<?= e($editAsn['id']) ?>">
<?php endif; ?>
<div class="Box mb-4">
<div class="Box-header">
<h3 class="Box-title">Basic Information</h3>
</div>
<div class="Box-body">
<div class="form-group">
<label class="form-label" for="asn">ASN Number *</label>
<input type="number"
id="asn"
name="asn"
class="form-control"
style="max-width: 200px;"
value="<?= e($editAsn['asn'] ?? '') ?>"
min="1"
max="4294967295"
<?= $editAsn ? 'readonly' : 'required' ?>>
<p class="form-hint">Your Autonomous System Number (e.g., 65530)</p>
</div>
<div class="form-group">
<label class="form-label" for="name">Name *</label>
<input type="text"
id="name"
name="name"
class="form-control form-input-wide"
value="<?= e($editAsn['name'] ?? '') ?>"
required>
<p class="form-hint">A friendly name for this ASN</p>
</div>
<div class="form-group">
<label class="form-label" for="description">Description</label>
<textarea id="description"
name="description"
class="form-control form-input-wide"
rows="2"><?= e($editAsn['description'] ?? '') ?></textarea>
</div>
</div>
</div>
<div class="Box mb-4">
<div class="Box-header">
<h3 class="Box-title">Network Configuration</h3>
</div>
<div class="Box-body">
<div class="d-flex flex-wrap" style="gap: 16px;">
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="router_id">Router ID</label>
<input type="text"
id="router_id"
name="router_id"
class="form-control"
value="<?= e($editAsn['defaults']['router_id'] ?? '') ?>"
placeholder="e.g., 192.0.2.1">
<p class="form-hint">Default router ID for all nodes</p>
</div>
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="source4">Source IPv4</label>
<input type="text"
id="source4"
name="source4"
class="form-control"
value="<?= e($editAsn['defaults']['source4'] ?? '') ?>"
placeholder="e.g., 192.0.2.1">
<p class="form-hint">Default source address for IPv4</p>
</div>
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="source6">Source IPv6</label>
<input type="text"
id="source6"
name="source6"
class="form-control"
value="<?= e($editAsn['defaults']['source6'] ?? '') ?>"
placeholder="e.g., 2001:db8::1">
<p class="form-hint">Default source address for IPv6</p>
</div>
</div>
<div class="form-group">
<label class="form-label" for="prefixes4">IPv4 Prefixes</label>
<input type="text"
id="prefixes4"
name="prefixes4"
class="form-control form-input-wide"
value="<?= e(implode(', ', $editAsn['defaults']['prefixes'] ?? [])) ?>"
placeholder="192.0.2.0/24, 198.51.100.0/24">
<p class="form-hint">Comma-separated list of IPv4 prefixes you originate</p>
</div>
<div class="form-group">
<label class="form-label" for="prefixes6">IPv6 Prefixes</label>
<input type="text"
id="prefixes6"
name="prefixes6"
class="form-control form-input-wide"
value="<?= e(implode(', ', $editAsn['defaults']['prefixes6'] ?? [])) ?>"
placeholder="2001:db8::/32">
<p class="form-hint">Comma-separated list of IPv6 prefixes you originate</p>
</div>
</div>
</div>
<div class="Box mb-4">
<div class="Box-header">
<h3 class="Box-title">Filtering Options</h3>
</div>
<div class="Box-body">
<div class="d-flex flex-wrap" style="gap: 16px;">
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="irr_server">IRR Server</label>
<input type="text"
id="irr_server"
name="irr_server"
class="form-control"
value="<?= e($editAsn['defaults']['irr_server'] ?? 'rr.ntt.net') ?>"
placeholder="rr.ntt.net">
</div>
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="rtr_server">RTR Server (RPKI)</label>
<input type="text"
id="rtr_server"
name="rtr_server"
class="form-control"
value="<?= e($editAsn['defaults']['rtr_server'] ?? '') ?>"
placeholder="rtr.example.com:8282">
</div>
</div>
<div class="d-flex flex-wrap" style="gap: 24px;">
<div class="form-checkbox">
<label>
<input type="checkbox"
name="rpki_filter"
value="1"
<?= ($editAsn['defaults']['rpki_filter'] ?? false) ? 'checked' : '' ?>>
Enable RPKI filtering
</label>
</div>
<div class="form-checkbox">
<label>
<input type="checkbox"
name="irr_filter"
value="1"
<?= ($editAsn['defaults']['irr_filter'] ?? false) ? 'checked' : '' ?>>
Enable IRR filtering
</label>
</div>
<div class="form-checkbox">
<label>
<input type="checkbox"
name="keep_filtered"
value="1"
<?= ($editAsn['defaults']['keep_filtered'] ?? false) ? 'checked' : '' ?>>
Keep filtered routes
</label>
</div>
</div>
<div class="d-flex flex-wrap mt-3" style="gap: 16px;">
<div class="form-group flex-1" style="min-width: 150px;">
<label class="form-label" for="default_route_limit4">Default Route Limit (IPv4)</label>
<input type="number"
id="default_route_limit4"
name="default_route_limit4"
class="form-control"
value="<?= e($editAsn['defaults']['default_route_limit4'] ?? '') ?>"
min="0">
</div>
<div class="form-group flex-1" style="min-width: 150px;">
<label class="form-label" for="default_route_limit6">Default Route Limit (IPv6)</label>
<input type="number"
id="default_route_limit6"
name="default_route_limit6"
class="form-control"
value="<?= e($editAsn['defaults']['default_route_limit6'] ?? '') ?>"
min="0">
</div>
</div>
</div>
</div>
<div class="Box mb-4">
<div class="Box-header">
<h3 class="Box-title">Pathvector Paths</h3>
</div>
<div class="Box-body">
<div class="d-flex flex-wrap" style="gap: 16px;">
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="bird_directory">BIRD Directory</label>
<input type="text"
id="bird_directory"
name="bird_directory"
class="form-control"
value="<?= e($editAsn['defaults']['bird_directory'] ?? '/etc/bird') ?>">
</div>
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="bird_socket">BIRD Socket</label>
<input type="text"
id="bird_socket"
name="bird_socket"
class="form-control"
value="<?= e($editAsn['defaults']['bird_socket'] ?? '/run/bird/bird.ctl') ?>">
</div>
</div>
<div class="d-flex flex-wrap" style="gap: 16px;">
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="cache_directory">Cache Directory</label>
<input type="text"
id="cache_directory"
name="cache_directory"
class="form-control"
value="<?= e($editAsn['defaults']['cache_directory'] ?? '/var/cache/pathvector') ?>">
</div>
<div class="form-group flex-1" style="min-width: 200px;">
<label class="form-label" for="bgpq_path">BGPQ4 Path</label>
<input type="text"
id="bgpq_path"
name="bgpq_path"
class="form-control"
value="<?= e($editAsn['defaults']['bgpq_path'] ?? 'bgpq4') ?>">
</div>
</div>
</div>
</div>
<div class="Box mb-4">
<div class="Box-header">
<h3 class="Box-title">Advanced Options</h3>
</div>
<div class="Box-body">
<div class="d-flex flex-wrap" style="gap: 24px;">
<div class="form-checkbox">
<label>
<input type="checkbox"
name="merge_paths"
value="1"
<?= ($editAsn['defaults']['merge_paths'] ?? false) ? 'checked' : '' ?>>
Enable merge paths (ADD-PATH)
</label>
</div>
<div class="form-checkbox">
<label>
<input type="checkbox"
name="transit_locking"
value="1"
<?= ($editAsn['defaults']['transit_locking'] ?? false) ? 'checked' : '' ?>>
Enable transit locking
</label>
</div>
<div class="form-checkbox">
<label>
<input type="checkbox"
name="graceful_shutdown"
value="1"
<?= ($editAsn['defaults']['graceful_shutdown'] ?? false) ? 'checked' : '' ?>>
Enable graceful shutdown
</label>
</div>
<div class="form-checkbox">
<label>
<input type="checkbox"
name="no_announce"
value="1"
<?= ($editAsn['defaults']['no_announce'] ?? false) ? 'checked' : '' ?>>
No announce (disable route announcements)
</label>
</div>
</div>
</div>
</div>
<div class="d-flex flex-items-center" style="gap: 8px;">
<button type="submit" class="btn btn-primary">
<?= $action === 'edit' ? 'Update ASN' : 'Create ASN' ?>
</button>
<a href="?page=asns" class="btn">Cancel</a>
</div>
</form>
</div>
<?php endif; ?>