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:
457
pathvector-admin/lib/BirdConfig.php
Normal file
457
pathvector-admin/lib/BirdConfig.php
Normal file
@@ -0,0 +1,457 @@
|
||||
<?php
|
||||
/**
|
||||
* BirdConfig Class
|
||||
*
|
||||
* Helper class for BIRD 3 configuration parsing and generation
|
||||
*/
|
||||
|
||||
class BirdConfig {
|
||||
|
||||
/**
|
||||
* Parse BIRD protocol status output
|
||||
*
|
||||
* @param string $output
|
||||
* @return array
|
||||
*/
|
||||
public static function parseProtocolStatus(string $output): array {
|
||||
$protocols = [];
|
||||
$lines = explode("\n", trim($output));
|
||||
|
||||
// Skip header lines
|
||||
$dataStarted = false;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip BIRD header
|
||||
if (strpos($line, 'BIRD') === 0 || strpos($line, 'Name') === 0) {
|
||||
$dataStarted = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$dataStarted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse protocol line
|
||||
// Format: Name Proto Table State Since Info
|
||||
$parts = preg_split('/\s+/', $line, 6);
|
||||
|
||||
if (count($parts) >= 5) {
|
||||
$protocols[] = [
|
||||
'name' => $parts[0],
|
||||
'proto' => $parts[1],
|
||||
'table' => $parts[2],
|
||||
'state' => $parts[3],
|
||||
'since' => $parts[4],
|
||||
'info' => $parts[5] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $protocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse BIRD route count output
|
||||
*
|
||||
* @param string $output
|
||||
* @return array
|
||||
*/
|
||||
public static function parseRouteCount(string $output): array {
|
||||
$counts = [
|
||||
'total' => 0,
|
||||
'primary' => 0,
|
||||
'filtered' => 0,
|
||||
];
|
||||
|
||||
// Format: X of Y routes for Z networks
|
||||
if (preg_match('/(\d+)\s+of\s+(\d+)\s+routes\s+for\s+(\d+)\s+networks/', $output, $matches)) {
|
||||
$counts['primary'] = (int) $matches[1];
|
||||
$counts['total'] = (int) $matches[2];
|
||||
$counts['networks'] = (int) $matches[3];
|
||||
}
|
||||
|
||||
// Filtered routes
|
||||
if (preg_match('/(\d+)\s+routes\s+\((\d+)\s+filtered\)/', $output, $matches)) {
|
||||
$counts['total'] = (int) $matches[1];
|
||||
$counts['filtered'] = (int) $matches[2];
|
||||
}
|
||||
|
||||
return $counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse BIRD status output
|
||||
*
|
||||
* @param string $output
|
||||
* @return array
|
||||
*/
|
||||
public static function parseStatus(string $output): array {
|
||||
$status = [
|
||||
'version' => '',
|
||||
'router_id' => '',
|
||||
'hostname' => '',
|
||||
'server_time' => '',
|
||||
'last_reboot' => '',
|
||||
'last_reconfiguration' => '',
|
||||
];
|
||||
|
||||
$lines = explode("\n", trim($output));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
|
||||
if (preg_match('/^BIRD\s+(.+)$/', $line, $matches)) {
|
||||
$status['version'] = trim($matches[1]);
|
||||
} elseif (preg_match('/^Router ID is\s+(.+)$/', $line, $matches)) {
|
||||
$status['router_id'] = trim($matches[1]);
|
||||
} elseif (preg_match('/^Hostname is\s+(.+)$/', $line, $matches)) {
|
||||
$status['hostname'] = trim($matches[1]);
|
||||
} elseif (preg_match('/^Current server time is\s+(.+)$/', $line, $matches)) {
|
||||
$status['server_time'] = trim($matches[1]);
|
||||
} elseif (preg_match('/^Last reboot on\s+(.+)$/', $line, $matches)) {
|
||||
$status['last_reboot'] = trim($matches[1]);
|
||||
} elseif (preg_match('/^Last reconfiguration on\s+(.+)$/', $line, $matches)) {
|
||||
$status['last_reconfiguration'] = trim($matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse detailed protocol info
|
||||
*
|
||||
* @param string $output
|
||||
* @return array
|
||||
*/
|
||||
public static function parseProtocolDetail(string $output): array {
|
||||
$info = [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'state' => '',
|
||||
'table' => '',
|
||||
'neighbor_address' => '',
|
||||
'neighbor_as' => '',
|
||||
'local_as' => '',
|
||||
'neighbor_id' => '',
|
||||
'routes' => [
|
||||
'imported' => 0,
|
||||
'exported' => 0,
|
||||
'preferred' => 0,
|
||||
'filtered' => 0,
|
||||
],
|
||||
'uptime' => '',
|
||||
'hold_timer' => '',
|
||||
'keepalive_timer' => '',
|
||||
'last_error' => '',
|
||||
'channel' => [],
|
||||
];
|
||||
|
||||
$lines = explode("\n", trim($output));
|
||||
$currentChannel = null;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
|
||||
// Protocol name
|
||||
if (preg_match('/^(\S+)\s+BGP\s+/', $line, $matches)) {
|
||||
$info['name'] = $matches[1];
|
||||
}
|
||||
|
||||
// Description
|
||||
if (preg_match('/Description:\s+(.+)$/', $line, $matches)) {
|
||||
$info['description'] = trim($matches[1]);
|
||||
}
|
||||
|
||||
// State
|
||||
if (preg_match('/BGP state:\s+(\S+)/', $line, $matches)) {
|
||||
$info['state'] = $matches[1];
|
||||
}
|
||||
|
||||
// Neighbor address and AS
|
||||
if (preg_match('/Neighbor address:\s+(\S+)/', $line, $matches)) {
|
||||
$info['neighbor_address'] = $matches[1];
|
||||
}
|
||||
|
||||
if (preg_match('/Neighbor AS:\s+(\d+)/', $line, $matches)) {
|
||||
$info['neighbor_as'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
if (preg_match('/Local AS:\s+(\d+)/', $line, $matches)) {
|
||||
$info['local_as'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
if (preg_match('/Neighbor ID:\s+(\S+)/', $line, $matches)) {
|
||||
$info['neighbor_id'] = $matches[1];
|
||||
}
|
||||
|
||||
// Routes
|
||||
if (preg_match('/Routes:\s+(\d+)\s+imported,\s+(\d+)\s+exported,\s+(\d+)\s+preferred/', $line, $matches)) {
|
||||
$info['routes']['imported'] = (int) $matches[1];
|
||||
$info['routes']['exported'] = (int) $matches[2];
|
||||
$info['routes']['preferred'] = (int) $matches[3];
|
||||
}
|
||||
|
||||
if (preg_match('/(\d+)\s+filtered/', $line, $matches)) {
|
||||
$info['routes']['filtered'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
// Hold timer
|
||||
if (preg_match('/Hold timer:\s+(\S+)/', $line, $matches)) {
|
||||
$info['hold_timer'] = $matches[1];
|
||||
}
|
||||
|
||||
// Keepalive timer
|
||||
if (preg_match('/Keepalive timer:\s+(\S+)/', $line, $matches)) {
|
||||
$info['keepalive_timer'] = $matches[1];
|
||||
}
|
||||
|
||||
// Last error
|
||||
if (preg_match('/Last error:\s+(.+)$/', $line, $matches)) {
|
||||
$info['last_error'] = trim($matches[1]);
|
||||
}
|
||||
|
||||
// Channel info
|
||||
if (preg_match('/Channel (ipv[46])/', $line, $matches)) {
|
||||
$currentChannel = $matches[1];
|
||||
$info['channel'][$currentChannel] = [];
|
||||
}
|
||||
|
||||
if ($currentChannel && preg_match('/State:\s+(\S+)/', $line, $matches)) {
|
||||
$info['channel'][$currentChannel]['state'] = $matches[1];
|
||||
}
|
||||
|
||||
if ($currentChannel && preg_match('/Table:\s+(\S+)/', $line, $matches)) {
|
||||
$info['channel'][$currentChannel]['table'] = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state color class for UI
|
||||
*
|
||||
* @param string $state
|
||||
* @return string
|
||||
*/
|
||||
public static function getStateColorClass(string $state): string {
|
||||
return match (strtolower($state)) {
|
||||
'established' => 'color-fg-success',
|
||||
'up' => 'color-fg-success',
|
||||
'start', 'connect', 'active', 'opensent', 'openconfirm' => 'color-fg-attention',
|
||||
'idle' => 'color-fg-muted',
|
||||
'down' => 'color-fg-danger',
|
||||
default => 'color-fg-default',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state label for UI
|
||||
*
|
||||
* @param string $state
|
||||
* @return string
|
||||
*/
|
||||
public static function getStateLabel(string $state): string {
|
||||
return match (strtolower($state)) {
|
||||
'established' => 'Label--success',
|
||||
'up' => 'Label--success',
|
||||
'start', 'connect', 'active', 'opensent', 'openconfirm' => 'Label--attention',
|
||||
'idle' => 'Label--secondary',
|
||||
'down' => 'Label--danger',
|
||||
default => 'Label--primary',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate basic BIRD 3 configuration
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
public static function generateBasicConfig(array $config): string {
|
||||
$bird = "# BIRD 3 Configuration\n";
|
||||
$bird .= "# Generated by Pathvector Admin\n\n";
|
||||
|
||||
// Router ID
|
||||
if (!empty($config['router_id'])) {
|
||||
$bird .= "router id {$config['router_id']};\n\n";
|
||||
}
|
||||
|
||||
// Log
|
||||
$bird .= "log syslog all;\n\n";
|
||||
|
||||
// Debug
|
||||
$bird .= "debug protocols all;\n\n";
|
||||
|
||||
// Timeformat
|
||||
$bird .= "timeformat protocol iso long;\n\n";
|
||||
|
||||
// Protocol device
|
||||
$bird .= "protocol device {\n";
|
||||
$bird .= " scan time 10;\n";
|
||||
$bird .= "}\n\n";
|
||||
|
||||
// Protocol direct
|
||||
$bird .= "protocol direct {\n";
|
||||
$bird .= " ipv4;\n";
|
||||
$bird .= " ipv6;\n";
|
||||
$bird .= "}\n\n";
|
||||
|
||||
// Protocol kernel
|
||||
$bird .= "protocol kernel {\n";
|
||||
$bird .= " ipv4 {\n";
|
||||
$bird .= " export all;\n";
|
||||
$bird .= " import all;\n";
|
||||
$bird .= " };\n";
|
||||
$bird .= "}\n\n";
|
||||
|
||||
$bird .= "protocol kernel {\n";
|
||||
$bird .= " ipv6 {\n";
|
||||
$bird .= " export all;\n";
|
||||
$bird .= " import all;\n";
|
||||
$bird .= " };\n";
|
||||
$bird .= "}\n\n";
|
||||
|
||||
return $bird;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate BIRD config syntax (basic)
|
||||
*
|
||||
* @param string $config
|
||||
* @return array
|
||||
*/
|
||||
public static function validateSyntax(string $config): array {
|
||||
$errors = [];
|
||||
$warnings = [];
|
||||
|
||||
// Check for common issues
|
||||
$lines = explode("\n", $config);
|
||||
$braceCount = 0;
|
||||
$inString = false;
|
||||
|
||||
foreach ($lines as $lineNum => $line) {
|
||||
$lineNum++; // 1-indexed
|
||||
|
||||
// Skip comments
|
||||
if (preg_match('/^\s*#/', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count braces
|
||||
for ($i = 0; $i < strlen($line); $i++) {
|
||||
$char = $line[$i];
|
||||
|
||||
if ($char === '"' && ($i === 0 || $line[$i - 1] !== '\\')) {
|
||||
$inString = !$inString;
|
||||
}
|
||||
|
||||
if (!$inString) {
|
||||
if ($char === '{') {
|
||||
$braceCount++;
|
||||
} elseif ($char === '}') {
|
||||
$braceCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for missing semicolons (basic check)
|
||||
$trimmed = trim($line);
|
||||
if (!empty($trimmed) &&
|
||||
!preg_match('/[{};#]$/', $trimmed) &&
|
||||
!preg_match('/^\s*(protocol|filter|function|table|if|else|for|case|switch)/', $trimmed)) {
|
||||
$warnings[] = "Line $lineNum: Possible missing semicolon";
|
||||
}
|
||||
}
|
||||
|
||||
if ($braceCount !== 0) {
|
||||
$errors[] = "Unbalanced braces: " . ($braceCount > 0 ? "missing $braceCount closing braces" : "extra " . abs($braceCount) . " closing braces");
|
||||
}
|
||||
|
||||
return [
|
||||
'valid' => empty($errors),
|
||||
'errors' => $errors,
|
||||
'warnings' => $warnings,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format BIRD configuration
|
||||
*
|
||||
* @param string $config
|
||||
* @return string
|
||||
*/
|
||||
public static function formatConfig(string $config): string {
|
||||
$lines = explode("\n", $config);
|
||||
$output = [];
|
||||
$indent = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$trimmed = trim($line);
|
||||
|
||||
// Decrease indent for closing braces
|
||||
if (preg_match('/^}/', $trimmed)) {
|
||||
$indent = max(0, $indent - 1);
|
||||
}
|
||||
|
||||
// Add line with proper indentation
|
||||
if (!empty($trimmed)) {
|
||||
$output[] = str_repeat("\t", $indent) . $trimmed;
|
||||
} else {
|
||||
$output[] = '';
|
||||
}
|
||||
|
||||
// Increase indent for opening braces
|
||||
if (preg_match('/{\s*$/', $trimmed)) {
|
||||
$indent++;
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of BIRD 3 protocol types
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getProtocolTypes(): array {
|
||||
return [
|
||||
'bgp' => 'Border Gateway Protocol',
|
||||
'ospf' => 'Open Shortest Path First',
|
||||
'rip' => 'Routing Information Protocol',
|
||||
'babel' => 'Babel Routing Protocol',
|
||||
'static' => 'Static Routes',
|
||||
'kernel' => 'Kernel Route Table',
|
||||
'device' => 'Device Protocol',
|
||||
'direct' => 'Direct Interfaces',
|
||||
'pipe' => 'Table Pipe',
|
||||
'bfd' => 'Bidirectional Forwarding Detection',
|
||||
'rpki' => 'Resource Public Key Infrastructure',
|
||||
'mrt' => 'MRT Table Dump',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get BGP session states
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getBgpStates(): array {
|
||||
return [
|
||||
'Idle' => 'Not running',
|
||||
'Connect' => 'Connecting to peer',
|
||||
'Active' => 'Trying to connect',
|
||||
'OpenSent' => 'Open message sent',
|
||||
'OpenConfirm' => 'Waiting for keepalive',
|
||||
'Established' => 'Session established',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user