Really-amin's picture
Upload 295 files
d6d843f verified
#!/usr/bin/env node
/**
* FAILOVER CHAIN MANAGER
* Builds redundancy chains and manages automatic failover for API resources
*/
const fs = require('fs');
class FailoverManager {
constructor(reportPath = './api-monitor-report.json') {
this.reportPath = reportPath;
this.report = null;
this.failoverChains = {};
}
// Load monitoring report
loadReport() {
try {
const data = fs.readFileSync(this.reportPath, 'utf8');
this.report = JSON.parse(data);
return true;
} catch (error) {
console.error('Failed to load report:', error.message);
return false;
}
}
// Build failover chains for each data type
buildFailoverChains() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('β•‘ FAILOVER CHAIN BUILDER β•‘');
console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n');
const chains = {
ethereumPrice: this.buildPriceChain('ethereum'),
bitcoinPrice: this.buildPriceChain('bitcoin'),
ethereumExplorer: this.buildExplorerChain('ethereum'),
bscExplorer: this.buildExplorerChain('bsc'),
tronExplorer: this.buildExplorerChain('tron'),
rpcEthereum: this.buildRPCChain('ethereum'),
rpcBSC: this.buildRPCChain('bsc'),
newsFeeds: this.buildNewsChain(),
sentiment: this.buildSentimentChain()
};
this.failoverChains = chains;
// Display chains
for (const [chainName, chain] of Object.entries(chains)) {
this.displayChain(chainName, chain);
}
return chains;
}
// Build price data failover chain
buildPriceChain(coin) {
const chain = [];
// Get market data resources
const marketResources = this.report?.categories?.marketData || [];
// Sort by status and tier
const sorted = marketResources
.filter(r => ['ONLINE', 'DEGRADED'].includes(r.status))
.sort((a, b) => {
// Prioritize by tier first
if (a.tier !== b.tier) return a.tier - b.tier;
// Then by status
const statusPriority = { ONLINE: 1, DEGRADED: 2, SLOW: 3 };
return statusPriority[a.status] - statusPriority[b.status];
});
for (const resource of sorted) {
chain.push({
name: resource.name,
url: resource.url,
status: resource.status,
tier: resource.tier,
responseTime: resource.lastCheck?.responseTime
});
}
return chain;
}
// Build explorer failover chain
buildExplorerChain(blockchain) {
const chain = [];
const explorerResources = this.report?.categories?.blockchainExplorers || [];
const filtered = explorerResources
.filter(r => {
const name = r.name.toLowerCase();
return (blockchain === 'ethereum' && name.includes('eth')) ||
(blockchain === 'bsc' && name.includes('bsc')) ||
(blockchain === 'tron' && name.includes('tron'));
})
.filter(r => ['ONLINE', 'DEGRADED'].includes(r.status))
.sort((a, b) => a.tier - b.tier);
for (const resource of filtered) {
chain.push({
name: resource.name,
url: resource.url,
status: resource.status,
tier: resource.tier,
responseTime: resource.lastCheck?.responseTime
});
}
return chain;
}
// Build RPC node failover chain
buildRPCChain(network) {
const chain = [];
const rpcResources = this.report?.categories?.rpcNodes || [];
const filtered = rpcResources
.filter(r => {
const name = r.name.toLowerCase();
return name.includes(network.toLowerCase());
})
.filter(r => ['ONLINE', 'DEGRADED'].includes(r.status))
.sort((a, b) => {
if (a.tier !== b.tier) return a.tier - b.tier;
return (a.lastCheck?.responseTime || 999999) - (b.lastCheck?.responseTime || 999999);
});
for (const resource of filtered) {
chain.push({
name: resource.name,
url: resource.url,
status: resource.status,
tier: resource.tier,
responseTime: resource.lastCheck?.responseTime
});
}
return chain;
}
// Build news feed failover chain
buildNewsChain() {
const chain = [];
const newsResources = this.report?.categories?.newsAndSentiment || [];
const filtered = newsResources
.filter(r => ['ONLINE', 'DEGRADED'].includes(r.status))
.sort((a, b) => a.tier - b.tier);
for (const resource of filtered) {
chain.push({
name: resource.name,
url: resource.url,
status: resource.status,
tier: resource.tier,
responseTime: resource.lastCheck?.responseTime
});
}
return chain;
}
// Build sentiment data failover chain
buildSentimentChain() {
const chain = [];
const newsResources = this.report?.categories?.newsAndSentiment || [];
const filtered = newsResources
.filter(r => r.name.toLowerCase().includes('fear') ||
r.name.toLowerCase().includes('greed') ||
r.name.toLowerCase().includes('sentiment'))
.filter(r => ['ONLINE', 'DEGRADED'].includes(r.status));
for (const resource of filtered) {
chain.push({
name: resource.name,
url: resource.url,
status: resource.status,
tier: resource.tier,
responseTime: resource.lastCheck?.responseTime
});
}
return chain;
}
// Display failover chain
displayChain(chainName, chain) {
console.log(`\nπŸ“Š ${chainName.toUpperCase()} Failover Chain:`);
console.log('─'.repeat(60));
if (chain.length === 0) {
console.log(' ⚠️ No available resources');
return;
}
chain.forEach((resource, index) => {
const arrow = index === 0 ? '🎯' : ' ↓';
const priority = index === 0 ? '[PRIMARY]' : index === 1 ? '[BACKUP]' : `[BACKUP-${index}]`;
const tierBadge = `[TIER-${resource.tier}]`;
const rt = resource.responseTime ? `${resource.responseTime}ms` : 'N/A';
console.log(` ${arrow} ${priority.padEnd(12)} ${resource.name.padEnd(25)} ${resource.status.padEnd(10)} ${rt.padStart(8)} ${tierBadge}`);
});
}
// Generate failover configuration file
exportFailoverConfig(filename = 'failover-config.json') {
const config = {
generatedAt: new Date().toISOString(),
chains: this.failoverChains,
usage: {
description: 'Automatic failover configuration for API resources',
example: `
// Example usage in your application:
const failoverConfig = require('./failover-config.json');
async function fetchWithFailover(chainName, fetchFunction) {
const chain = failoverConfig.chains[chainName];
for (const resource of chain) {
try {
const result = await fetchFunction(resource.url);
return result;
} catch (error) {
console.log(\`Failed \${resource.name}, trying next...\`);
continue;
}
}
throw new Error('All resources in chain failed');
}
// Use it:
const data = await fetchWithFailover('ethereumPrice', async (url) => {
const response = await fetch(url + '/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
return response.json();
});
`
}
};
fs.writeFileSync(filename, JSON.stringify(config, null, 2));
console.log(`\nβœ“ Failover configuration exported to ${filename}`);
}
// Identify categories with single point of failure
identifySinglePointsOfFailure() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('β•‘ SINGLE POINT OF FAILURE ANALYSIS β•‘');
console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n');
const spofs = [];
for (const [chainName, chain] of Object.entries(this.failoverChains)) {
const onlineCount = chain.filter(r => r.status === 'ONLINE').length;
if (onlineCount === 0) {
spofs.push({
chain: chainName,
severity: 'CRITICAL',
message: 'Zero available resources'
});
} else if (onlineCount === 1) {
spofs.push({
chain: chainName,
severity: 'HIGH',
message: 'Only one resource available (SPOF)'
});
} else if (onlineCount === 2) {
spofs.push({
chain: chainName,
severity: 'MEDIUM',
message: 'Only two resources available'
});
}
}
if (spofs.length === 0) {
console.log(' βœ“ No single points of failure detected\n');
} else {
for (const spof of spofs) {
const icon = spof.severity === 'CRITICAL' ? 'πŸ”΄' :
spof.severity === 'HIGH' ? '🟠' : '🟑';
console.log(` ${icon} [${spof.severity}] ${spof.chain}: ${spof.message}`);
}
console.log();
}
return spofs;
}
// Generate redundancy report
generateRedundancyReport() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('β•‘ REDUNDANCY ANALYSIS REPORT β•‘');
console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n');
const categories = this.report?.categories || {};
for (const [category, resources] of Object.entries(categories)) {
const total = resources.length;
const online = resources.filter(r => r.status === 'ONLINE').length;
const degraded = resources.filter(r => r.status === 'DEGRADED').length;
const offline = resources.filter(r => r.status === 'OFFLINE').length;
let indicator = 'βœ“';
if (online === 0) indicator = 'βœ—';
else if (online === 1) indicator = '⚠';
else if (online >= 3) indicator = 'βœ“βœ“';
console.log(` ${indicator} ${category.padEnd(25)} Online: ${online}/${total} Degraded: ${degraded} Offline: ${offline}`);
}
}
}
// ═══════════════════════════════════════════════════════════════
// MAIN EXECUTION
// ═══════════════════════════════════════════════════════════════
async function main() {
const manager = new FailoverManager();
if (!manager.loadReport()) {
console.error('\nβœ— Please run the monitor first: node api-monitor.js');
process.exit(1);
}
// Build failover chains
manager.buildFailoverChains();
// Export configuration
manager.exportFailoverConfig();
// Identify SPOFs
manager.identifySinglePointsOfFailure();
// Generate redundancy report
manager.generateRedundancyReport();
console.log('\nβœ“ Failover analysis complete\n');
}
if (require.main === module) {
main().catch(console.error);
}
module.exports = FailoverManager;