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:
446
pathvector-admin/lib/ASN.php
Normal file
446
pathvector-admin/lib/ASN.php
Normal file
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
/**
|
||||
* ASN Class
|
||||
*
|
||||
* Manages ASN (Autonomous System Number) configurations for multi-ASN deployments
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/FlatFileDB.php';
|
||||
require_once __DIR__ . '/Logger.php';
|
||||
require_once __DIR__ . '/Validator.php';
|
||||
|
||||
class ASN {
|
||||
private FlatFileDB $db;
|
||||
private Logger $logger;
|
||||
private Validator $validator;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $dataFile Path to ASNs JSON file
|
||||
* @param Logger $logger Logger instance
|
||||
*/
|
||||
public function __construct(string $dataFile, Logger $logger) {
|
||||
$this->db = new FlatFileDB($dataFile);
|
||||
$this->logger = $logger;
|
||||
$this->validator = new Validator();
|
||||
|
||||
// Initialize structure
|
||||
if (!$this->db->exists('asns')) {
|
||||
$this->db->set('asns', []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all ASNs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAll(): array {
|
||||
return $this->db->get('asns') ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ASN by ID
|
||||
*
|
||||
* @param string $id ASN number as string key
|
||||
* @return array|null
|
||||
*/
|
||||
public function get(string $id): ?array {
|
||||
$asns = $this->db->get('asns') ?? [];
|
||||
return $asns[$id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new ASN
|
||||
*
|
||||
* @param array $data ASN data
|
||||
* @return array ['success' => bool, 'message' => string, 'errors' => array]
|
||||
*/
|
||||
public function create(array $data): array {
|
||||
$this->validator->clear();
|
||||
|
||||
// Validate ASN number
|
||||
if (!$this->validator->validateAsn($data['asn'] ?? null, 'asn')) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Validation failed',
|
||||
'errors' => $this->validator->getErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
$asns = $this->db->get('asns') ?? [];
|
||||
$id = (string) $data['asn'];
|
||||
|
||||
// Check if ASN already exists
|
||||
if (isset($asns[$id])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "ASN $id already exists",
|
||||
'errors' => ['asn' => 'ASN already exists'],
|
||||
];
|
||||
}
|
||||
|
||||
// Build ASN entry
|
||||
$asnData = [
|
||||
'asn' => (int) $data['asn'],
|
||||
'name' => $data['name'] ?? "AS{$data['asn']}",
|
||||
'description' => $data['description'] ?? '',
|
||||
'pathvector_defaults' => $this->buildPathvectorDefaults($data['pathvector_defaults'] ?? []),
|
||||
'templates' => $data['templates'] ?? [],
|
||||
'contacts' => $data['contacts'] ?? [],
|
||||
'metadata' => $data['metadata'] ?? [],
|
||||
'created_at' => date('c'),
|
||||
'updated_at' => date('c'),
|
||||
];
|
||||
|
||||
$asns[$id] = $asnData;
|
||||
|
||||
if ($this->db->set('asns', $asns)) {
|
||||
$this->logger->success('asn', "Created ASN: $id ({$asnData['name']})");
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "ASN $id created successfully",
|
||||
'data' => $asnData,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Failed to save ASN',
|
||||
'errors' => ['database' => 'Failed to save ASN'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ASN
|
||||
*
|
||||
* @param string $id ASN ID
|
||||
* @param array $data Updated data
|
||||
* @return array
|
||||
*/
|
||||
public function update(string $id, array $data): array {
|
||||
$asns = $this->db->get('asns') ?? [];
|
||||
|
||||
if (!isset($asns[$id])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "ASN $id not found",
|
||||
'errors' => ['asn' => 'ASN not found'],
|
||||
];
|
||||
}
|
||||
|
||||
// Merge with existing data
|
||||
$asnData = $asns[$id];
|
||||
|
||||
if (isset($data['name'])) {
|
||||
$asnData['name'] = $data['name'];
|
||||
}
|
||||
|
||||
if (isset($data['description'])) {
|
||||
$asnData['description'] = $data['description'];
|
||||
}
|
||||
|
||||
if (isset($data['pathvector_defaults'])) {
|
||||
$asnData['pathvector_defaults'] = $this->buildPathvectorDefaults(
|
||||
array_merge($asnData['pathvector_defaults'], $data['pathvector_defaults'])
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($data['templates'])) {
|
||||
$asnData['templates'] = $data['templates'];
|
||||
}
|
||||
|
||||
if (isset($data['contacts'])) {
|
||||
$asnData['contacts'] = $data['contacts'];
|
||||
}
|
||||
|
||||
if (isset($data['metadata'])) {
|
||||
$asnData['metadata'] = array_merge($asnData['metadata'] ?? [], $data['metadata']);
|
||||
}
|
||||
|
||||
$asnData['updated_at'] = date('c');
|
||||
$asns[$id] = $asnData;
|
||||
|
||||
if ($this->db->set('asns', $asns)) {
|
||||
$this->logger->info('asn', "Updated ASN: $id");
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "ASN $id updated successfully",
|
||||
'data' => $asnData,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Failed to update ASN',
|
||||
'errors' => ['database' => 'Failed to save ASN'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete ASN
|
||||
*
|
||||
* @param string $id ASN ID
|
||||
* @return array
|
||||
*/
|
||||
public function delete(string $id): array {
|
||||
$asns = $this->db->get('asns') ?? [];
|
||||
|
||||
if (!isset($asns[$id])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "ASN $id not found",
|
||||
];
|
||||
}
|
||||
|
||||
$asnName = $asns[$id]['name'];
|
||||
unset($asns[$id]);
|
||||
|
||||
if ($this->db->set('asns', $asns)) {
|
||||
$this->logger->warning('asn', "Deleted ASN: $id ($asnName)");
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "ASN $id deleted successfully",
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Failed to delete ASN',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Pathvector defaults structure
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
private function buildPathvectorDefaults(array $data): array {
|
||||
return [
|
||||
// Global config
|
||||
'router_id' => $data['router_id'] ?? '',
|
||||
'source4' => $data['source4'] ?? '',
|
||||
'source6' => $data['source6'] ?? '',
|
||||
'prefixes' => $data['prefixes'] ?? [],
|
||||
'hostname' => $data['hostname'] ?? '',
|
||||
|
||||
// BIRD/Pathvector paths
|
||||
'bird_directory' => $data['bird_directory'] ?? '/etc/bird',
|
||||
'bird_binary' => $data['bird_binary'] ?? '/usr/sbin/bird',
|
||||
'bird_socket' => $data['bird_socket'] ?? '/var/run/bird.ctl',
|
||||
'cache_directory' => $data['cache_directory'] ?? '/var/cache/pathvector',
|
||||
|
||||
// PeeringDB settings
|
||||
'peeringdb_query_timeout' => $data['peeringdb_query_timeout'] ?? 10,
|
||||
'peeringdb_api_key' => $data['peeringdb_api_key'] ?? '',
|
||||
'peeringdb_cache' => $data['peeringdb_cache'] ?? true,
|
||||
'peeringdb_url' => $data['peeringdb_url'] ?? 'https://peeringdb.com/api/',
|
||||
|
||||
// IRR settings
|
||||
'irr_server' => $data['irr_server'] ?? 'rr.ntt.net',
|
||||
'irr_query_timeout' => $data['irr_query_timeout'] ?? 30,
|
||||
'bgpq_args' => $data['bgpq_args'] ?? '',
|
||||
|
||||
// RTR/RPKI settings
|
||||
'rtr_server' => $data['rtr_server'] ?? '',
|
||||
'rpki_enable' => $data['rpki_enable'] ?? true,
|
||||
|
||||
// Filtering defaults
|
||||
'keep_filtered' => $data['keep_filtered'] ?? false,
|
||||
'merge_paths' => $data['merge_paths'] ?? false,
|
||||
'default_route' => $data['default_route'] ?? false,
|
||||
'accept_default' => $data['accept_default'] ?? false,
|
||||
|
||||
// Communities
|
||||
'origin_communities' => $data['origin_communities'] ?? [],
|
||||
'local_communities' => $data['local_communities'] ?? [],
|
||||
'add_on_import' => $data['add_on_import'] ?? [],
|
||||
'add_on_export' => $data['add_on_export'] ?? [],
|
||||
|
||||
// Blocklist
|
||||
'blocklist' => $data['blocklist'] ?? [],
|
||||
'blocklist_urls' => $data['blocklist_urls'] ?? [],
|
||||
'blocklist_files' => $data['blocklist_files'] ?? [],
|
||||
|
||||
// Bogons
|
||||
'bogons4' => $data['bogons4'] ?? [],
|
||||
'bogons6' => $data['bogons6'] ?? [],
|
||||
'bogon_asns' => $data['bogon_asns'] ?? [],
|
||||
'blackhole_bogon_asns' => $data['blackhole_bogon_asns'] ?? false,
|
||||
|
||||
// Transit ASNs
|
||||
'transit_asns' => $data['transit_asns'] ?? [],
|
||||
|
||||
// Operation modes
|
||||
'no_announce' => $data['no_announce'] ?? false,
|
||||
'no_accept' => $data['no_accept'] ?? false,
|
||||
'stun' => $data['stun'] ?? false,
|
||||
|
||||
// Global config
|
||||
'global_config' => $data['global_config'] ?? '',
|
||||
|
||||
// Web UI
|
||||
'web_ui_file' => $data['web_ui_file'] ?? '',
|
||||
|
||||
// Log
|
||||
'log_file' => $data['log_file'] ?? 'syslog',
|
||||
|
||||
// Keepalived
|
||||
'keepalived_config' => $data['keepalived_config'] ?? '/etc/keepalived.conf',
|
||||
|
||||
// Authorized providers (ASPA)
|
||||
'authorized_providers' => $data['authorized_providers'] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Count ASNs
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int {
|
||||
$asns = $this->db->get('asns') ?? [];
|
||||
return count($asns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search ASNs
|
||||
*
|
||||
* @param string $query
|
||||
* @return array
|
||||
*/
|
||||
public function search(string $query): array {
|
||||
$asns = $this->db->get('asns') ?? [];
|
||||
$query = strtolower($query);
|
||||
|
||||
return array_filter($asns, function($asn) use ($query) {
|
||||
return strpos(strtolower((string) $asn['asn']), $query) !== false ||
|
||||
strpos(strtolower($asn['name']), $query) !== false ||
|
||||
strpos(strtolower($asn['description'] ?? ''), $query) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export ASN to YAML format for Pathvector
|
||||
*
|
||||
* @param string $id
|
||||
* @return string|null
|
||||
*/
|
||||
public function exportYaml(string $id): ?string {
|
||||
$asn = $this->get($id);
|
||||
|
||||
if (!$asn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = [];
|
||||
$config['asn'] = $asn['asn'];
|
||||
|
||||
$defaults = $asn['pathvector_defaults'];
|
||||
|
||||
if (!empty($defaults['router_id'])) {
|
||||
$config['router-id'] = $defaults['router_id'];
|
||||
}
|
||||
|
||||
if (!empty($defaults['source4'])) {
|
||||
$config['source4'] = $defaults['source4'];
|
||||
}
|
||||
|
||||
if (!empty($defaults['source6'])) {
|
||||
$config['source6'] = $defaults['source6'];
|
||||
}
|
||||
|
||||
if (!empty($defaults['prefixes'])) {
|
||||
$config['prefixes'] = $defaults['prefixes'];
|
||||
}
|
||||
|
||||
if (!empty($defaults['hostname'])) {
|
||||
$config['hostname'] = $defaults['hostname'];
|
||||
}
|
||||
|
||||
// Add other non-empty defaults
|
||||
foreach ($defaults as $key => $value) {
|
||||
if ($value !== '' && $value !== [] && $value !== null && !isset($config[$key])) {
|
||||
$yamlKey = str_replace('_', '-', $key);
|
||||
$config[$yamlKey] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->arrayToYaml($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array to YAML string
|
||||
*
|
||||
* @param array $data
|
||||
* @param int $indent
|
||||
* @return string
|
||||
*/
|
||||
private function arrayToYaml(array $data, int $indent = 0): string {
|
||||
$yaml = '';
|
||||
$prefix = str_repeat(' ', $indent);
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
if (empty($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if it's a sequential array
|
||||
if (array_keys($value) === range(0, count($value) - 1)) {
|
||||
$yaml .= "$prefix$key:\n";
|
||||
foreach ($value as $item) {
|
||||
if (is_array($item)) {
|
||||
$yaml .= "$prefix -\n" . $this->arrayToYaml($item, $indent + 2);
|
||||
} else {
|
||||
$yaml .= "$prefix - " . $this->formatYamlValue($item) . "\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$yaml .= "$prefix$key:\n" . $this->arrayToYaml($value, $indent + 1);
|
||||
}
|
||||
} else {
|
||||
if ($value === '' || $value === null) {
|
||||
continue;
|
||||
}
|
||||
$yaml .= "$prefix$key: " . $this->formatYamlValue($value) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $yaml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format value for YAML output
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
private function formatYamlValue($value): string {
|
||||
if (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
if (is_string($value) && (strpos($value, ':') !== false || strpos($value, '#') !== false)) {
|
||||
return '"' . addslashes($value) . '"';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup ASN data
|
||||
*
|
||||
* @param string $backupDir
|
||||
* @return string|false
|
||||
*/
|
||||
public function backup(string $backupDir): string|false {
|
||||
return $this->db->backup($backupDir);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user