511 lines
21 KiB
PHP
Executable File
511 lines
21 KiB
PHP
Executable File
|
|
<?php
|
|
/**
|
|
* SERVER SUPERVISOR
|
|
* Auto-detect burned IPs, idle servers, auto-terminate to save costs
|
|
*/
|
|
header('Content-Type: application/json');
|
|
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123", [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
|
|
|
$pdo->exec("
|
|
CREATE TABLE IF NOT EXISTS admin.server_monitor (
|
|
id SERIAL PRIMARY KEY,
|
|
server_id INTEGER,
|
|
ip_address VARCHAR(50),
|
|
provider VARCHAR(50),
|
|
cloud_instance_id VARCHAR(255),
|
|
status VARCHAR(50) DEFAULT 'active',
|
|
last_output TIMESTAMP,
|
|
emails_last_hour INTEGER DEFAULT 0,
|
|
emails_last_check INTEGER DEFAULT 0,
|
|
bounce_rate FLOAT DEFAULT 0,
|
|
blacklist_count INTEGER DEFAULT 0,
|
|
blacklisted_on TEXT,
|
|
idle_minutes INTEGER DEFAULT 0,
|
|
cost_per_hour FLOAT DEFAULT 0.05,
|
|
total_cost FLOAT DEFAULT 0,
|
|
auto_action VARCHAR(50),
|
|
action_reason TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
terminated_at TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS admin.supervisor_rules (
|
|
id SERIAL PRIMARY KEY,
|
|
rule_name VARCHAR(100) UNIQUE,
|
|
condition_type VARCHAR(50),
|
|
threshold_value FLOAT,
|
|
action VARCHAR(50),
|
|
is_active BOOLEAN DEFAULT true,
|
|
priority INTEGER DEFAULT 5
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS admin.supervisor_logs (
|
|
id SERIAL PRIMARY KEY,
|
|
server_id INTEGER,
|
|
event_type VARCHAR(100),
|
|
details TEXT,
|
|
action_taken VARCHAR(100),
|
|
cost_saved FLOAT DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS admin.blacklist_checks (
|
|
id SERIAL PRIMARY KEY,
|
|
ip_address VARCHAR(50),
|
|
rbl_name VARCHAR(100),
|
|
listed BOOLEAN DEFAULT false,
|
|
checked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
");
|
|
|
|
class ServerSupervisor {
|
|
private $pdo;
|
|
private $rbls = [
|
|
'zen.spamhaus.org',
|
|
'bl.spamcop.net',
|
|
'b.barracudacentral.org',
|
|
'dnsbl.sorbs.net',
|
|
'spam.dnsbl.sorbs.net',
|
|
'cbl.abuseat.org',
|
|
'dnsbl-1.uceprotect.net',
|
|
'pbl.spamhaus.org',
|
|
'sbl.spamhaus.org',
|
|
'xbl.spamhaus.org'
|
|
];
|
|
|
|
private $huaweiApi = 'http://localhost:5821/api/huawei.php';
|
|
private $scalewayApi = 'http://localhost:5821/api/scaleway.php';
|
|
|
|
public function __construct($pdo) {
|
|
$this->pdo = $pdo;
|
|
$this->initRules();
|
|
}
|
|
|
|
private function initRules() {
|
|
$rules = [
|
|
['idle_30min', 'idle_minutes', 30, 'warn', true, 1],
|
|
['idle_60min', 'idle_minutes', 60, 'stop', true, 2],
|
|
['idle_120min', 'idle_minutes', 120, 'terminate', true, 3],
|
|
['blacklist_1', 'blacklist_count', 1, 'warn', true, 4],
|
|
['blacklist_3', 'blacklist_count', 3, 'stop', true, 5],
|
|
['blacklist_5', 'blacklist_count', 5, 'terminate', true, 6],
|
|
['bounce_10pct', 'bounce_rate', 10, 'warn', true, 7],
|
|
['bounce_25pct', 'bounce_rate', 25, 'stop', true, 8],
|
|
['bounce_50pct', 'bounce_rate', 50, 'terminate', true, 9],
|
|
['no_output_15min', 'no_output_minutes', 15, 'warn', true, 10],
|
|
['no_output_30min', 'no_output_minutes', 30, 'stop', true, 11],
|
|
['cost_limit_10', 'total_cost', 10, 'warn', true, 12],
|
|
['cost_limit_50', 'total_cost', 50, 'terminate', true, 13]
|
|
];
|
|
|
|
foreach ($rules as $r) {
|
|
$this->pdo->prepare("INSERT INTO admin.supervisor_rules (rule_name, condition_type, threshold_value, action, is_active, priority) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (rule_name) DO NOTHING")
|
|
->execute($r);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// MONITORING
|
|
// ============================================
|
|
|
|
public function checkAllServers() {
|
|
$servers = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE status != 'terminated'")->fetchAll(PDO::FETCH_ASSOC);
|
|
$results = [];
|
|
|
|
foreach ($servers as $server) {
|
|
$results[] = $this->checkServer($server['id']);
|
|
}
|
|
|
|
return ['checked' => count($servers), 'results' => $results];
|
|
}
|
|
|
|
public function checkServer($monitorId) {
|
|
$server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC);
|
|
if (!$server) return ['error' => 'Server not found'];
|
|
|
|
$issues = [];
|
|
$actions = [];
|
|
|
|
// 1. Check PMTA output
|
|
$pmtaStatus = $this->checkPMTAOutput($server['ip_address']);
|
|
if ($pmtaStatus['idle']) {
|
|
$idleMinutes = $pmtaStatus['idle_minutes'];
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET idle_minutes = $idleMinutes, emails_last_hour = {$pmtaStatus['emails_hour']} WHERE id = $monitorId");
|
|
$issues[] = "Idle for {$idleMinutes} minutes";
|
|
} else {
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET idle_minutes = 0, last_output = NOW(), emails_last_hour = {$pmtaStatus['emails_hour']} WHERE id = $monitorId");
|
|
}
|
|
|
|
// 2. Check blacklists
|
|
$blacklistResult = $this->checkBlacklists($server['ip_address']);
|
|
if ($blacklistResult['listed_count'] > 0) {
|
|
$listedOn = implode(',', $blacklistResult['listed_on']);
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET blacklist_count = {$blacklistResult['listed_count']}, blacklisted_on = '$listedOn' WHERE id = $monitorId");
|
|
$issues[] = "Blacklisted on {$blacklistResult['listed_count']} RBLs: $listedOn";
|
|
}
|
|
|
|
// 3. Check bounce rate
|
|
$bounceRate = $this->getBounceRate($server['ip_address']);
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET bounce_rate = $bounceRate WHERE id = $monitorId");
|
|
if ($bounceRate > 10) {
|
|
$issues[] = "High bounce rate: {$bounceRate}%";
|
|
}
|
|
|
|
// 4. Update cost
|
|
$this->updateCost($monitorId, $server['cost_per_hour']);
|
|
|
|
// 5. Apply rules
|
|
$server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC);
|
|
$action = $this->applyRules($server);
|
|
|
|
if ($action) {
|
|
$actions[] = $action;
|
|
$this->executeAction($monitorId, $action['action'], $action['reason']);
|
|
}
|
|
|
|
return [
|
|
'server_id' => $monitorId,
|
|
'ip' => $server['ip_address'],
|
|
'status' => $server['status'],
|
|
'issues' => $issues,
|
|
'actions' => $actions,
|
|
'metrics' => [
|
|
'idle_minutes' => $server['idle_minutes'],
|
|
'blacklist_count' => $server['blacklist_count'],
|
|
'bounce_rate' => $bounceRate,
|
|
'emails_last_hour' => $pmtaStatus['emails_hour'],
|
|
'total_cost' => $server['total_cost']
|
|
]
|
|
];
|
|
}
|
|
|
|
private function checkPMTAOutput($ip) {
|
|
// Try to connect to PMTA web interface
|
|
$ch = curl_init("http://$ip:8080/status");
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 5,
|
|
CURLOPT_USERPWD => 'admin:admin'
|
|
]);
|
|
$response = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($code != 200) {
|
|
return ['idle' => true, 'idle_minutes' => 999, 'emails_hour' => 0, 'error' => 'PMTA not responding'];
|
|
}
|
|
|
|
// Parse PMTA status
|
|
preg_match('/delivered:\s*(\d+)/i', $response, $delivered);
|
|
preg_match('/queued:\s*(\d+)/i', $response, $queued);
|
|
|
|
$emailsHour = intval($delivered[1] ?? 0);
|
|
$queuedCount = intval($queued[1] ?? 0);
|
|
|
|
// If no deliveries and no queue, server is idle
|
|
$isIdle = ($emailsHour == 0 && $queuedCount == 0);
|
|
|
|
return [
|
|
'idle' => $isIdle,
|
|
'idle_minutes' => $isIdle ? 15 : 0, // Will accumulate over checks
|
|
'emails_hour' => $emailsHour,
|
|
'queued' => $queuedCount
|
|
];
|
|
}
|
|
|
|
public function checkBlacklists($ip) {
|
|
$reversed = implode('.', array_reverse(explode('.', $ip)));
|
|
$listedOn = [];
|
|
|
|
foreach ($this->rbls as $rbl) {
|
|
$lookup = $reversed . '.' . $rbl;
|
|
$result = @dns_get_record($lookup, DNS_A);
|
|
|
|
$listed = !empty($result);
|
|
|
|
// Log check
|
|
$this->pdo->prepare("INSERT INTO admin.blacklist_checks (ip_address, rbl_name, listed) VALUES (?, ?, ?)")
|
|
->execute([$ip, $rbl, $listed]);
|
|
|
|
if ($listed) {
|
|
$listedOn[] = $rbl;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'ip' => $ip,
|
|
'listed_count' => count($listedOn),
|
|
'listed_on' => $listedOn,
|
|
'clean' => empty($listedOn)
|
|
];
|
|
}
|
|
|
|
private function getBounceRate($ip) {
|
|
// Get bounce rate from tracking data
|
|
$result = $this->pdo->query("SELECT
|
|
COUNT(*) FILTER (WHERE bounce_type = 'hard') as hard_bounces,
|
|
COUNT(*) as total
|
|
FROM admin.bounces
|
|
WHERE received_at > NOW() - INTERVAL '1 hour'")->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($result['total'] > 0) {
|
|
return round(($result['hard_bounces'] / $result['total']) * 100, 2);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private function updateCost($monitorId, $costPerHour) {
|
|
// Add hourly cost (called every check, typically every 5 min = 1/12 of hour)
|
|
$costIncrement = $costPerHour / 12;
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET total_cost = total_cost + $costIncrement WHERE id = $monitorId");
|
|
}
|
|
|
|
// ============================================
|
|
// RULES ENGINE
|
|
// ============================================
|
|
|
|
private function applyRules($server) {
|
|
$rules = $this->pdo->query("SELECT * FROM admin.supervisor_rules WHERE is_active = true ORDER BY priority ASC")->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($rules as $rule) {
|
|
$triggered = false;
|
|
$currentValue = 0;
|
|
|
|
switch ($rule['condition_type']) {
|
|
case 'idle_minutes':
|
|
$currentValue = $server['idle_minutes'];
|
|
$triggered = $currentValue >= $rule['threshold_value'];
|
|
break;
|
|
case 'blacklist_count':
|
|
$currentValue = $server['blacklist_count'];
|
|
$triggered = $currentValue >= $rule['threshold_value'];
|
|
break;
|
|
case 'bounce_rate':
|
|
$currentValue = $server['bounce_rate'];
|
|
$triggered = $currentValue >= $rule['threshold_value'];
|
|
break;
|
|
case 'total_cost':
|
|
$currentValue = $server['total_cost'];
|
|
$triggered = $currentValue >= $rule['threshold_value'];
|
|
break;
|
|
case 'no_output_minutes':
|
|
$lastOutput = strtotime($server['last_output'] ?? 'now');
|
|
$currentValue = (time() - $lastOutput) / 60;
|
|
$triggered = $currentValue >= $rule['threshold_value'];
|
|
break;
|
|
}
|
|
|
|
if ($triggered) {
|
|
return [
|
|
'rule' => $rule['rule_name'],
|
|
'action' => $rule['action'],
|
|
'reason' => "{$rule['condition_type']} = $currentValue (threshold: {$rule['threshold_value']})"
|
|
];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// ============================================
|
|
// ACTIONS
|
|
// ============================================
|
|
|
|
private function executeAction($monitorId, $action, $reason) {
|
|
$server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC);
|
|
|
|
switch ($action) {
|
|
case 'warn':
|
|
$this->logEvent($monitorId, 'warning', $reason, 'alert_sent');
|
|
$this->sendAlert("⚠️ Server Warning", "Server {$server['ip_address']}: $reason");
|
|
break;
|
|
|
|
case 'stop':
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET status = 'stopped', auto_action = 'stop', action_reason = '$reason' WHERE id = $monitorId");
|
|
$this->stopServer($server);
|
|
$this->logEvent($monitorId, 'stop', $reason, 'server_stopped');
|
|
$this->sendAlert("🛑 Server Stopped", "Server {$server['ip_address']} stopped: $reason");
|
|
break;
|
|
|
|
case 'terminate':
|
|
$costSaved = $this->calculateCostSaved($server);
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET status = 'terminated', auto_action = 'terminate', action_reason = '$reason', terminated_at = NOW() WHERE id = $monitorId");
|
|
$this->terminateServer($server);
|
|
$this->logEvent($monitorId, 'terminate', $reason, 'server_terminated', $costSaved);
|
|
$this->sendAlert("🔴 Server Terminated", "Server {$server['ip_address']} terminated: $reason\nCost saved: \${$costSaved}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private function stopServer($server) {
|
|
$provider = strtolower($server['provider']);
|
|
$instanceId = $server['cloud_instance_id'];
|
|
|
|
if ($provider == 'huawei') {
|
|
$this->callApi($this->huaweiApi, [
|
|
'action' => 'server_action',
|
|
'server_id' => $instanceId,
|
|
'server_action' => 'stop'
|
|
]);
|
|
} elseif ($provider == 'scaleway') {
|
|
$this->callApi($this->scalewayApi, [
|
|
'action' => 'server_action',
|
|
'server_id' => $instanceId,
|
|
'server_action' => 'poweroff'
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function terminateServer($server) {
|
|
$provider = strtolower($server['provider']);
|
|
$instanceId = $server['cloud_instance_id'];
|
|
|
|
if ($provider == 'huawei') {
|
|
$this->callApi($this->huaweiApi, [
|
|
'action' => 'delete_server',
|
|
'server_id' => $instanceId
|
|
]);
|
|
} elseif ($provider == 'scaleway') {
|
|
$this->callApi($this->scalewayApi, [
|
|
'action' => 'delete_server',
|
|
'server_id' => $instanceId
|
|
]);
|
|
}
|
|
|
|
// Also update auto_servers table
|
|
$this->pdo->exec("UPDATE admin.auto_servers SET status = 'terminated' WHERE ip_address = '{$server['ip_address']}'");
|
|
}
|
|
|
|
private function callApi($url, $data) {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => http_build_query($data),
|
|
CURLOPT_TIMEOUT => 30
|
|
]);
|
|
$response = curl_exec($ch);
|
|
curl_close($ch);
|
|
return json_decode($response, true);
|
|
}
|
|
|
|
private function calculateCostSaved($server) {
|
|
// Estimate cost saved by terminating (remaining hours in typical campaign)
|
|
$hoursRemaining = 24; // Assume would run 24 more hours
|
|
return round($server['cost_per_hour'] * $hoursRemaining, 2);
|
|
}
|
|
|
|
private function logEvent($serverId, $eventType, $details, $actionTaken, $costSaved = 0) {
|
|
$this->pdo->prepare("INSERT INTO admin.supervisor_logs (server_id, event_type, details, action_taken, cost_saved) VALUES (?, ?, ?, ?, ?)")
|
|
->execute([$serverId, $eventType, $details, $actionTaken, $costSaved]);
|
|
}
|
|
|
|
private function sendAlert($title, $message) {
|
|
// Send to Telegram
|
|
$config = $this->pdo->query("SELECT * FROM admin.telegram_config WHERE is_active = true LIMIT 1")->fetch(PDO::FETCH_ASSOC);
|
|
if ($config) {
|
|
$ch = curl_init("https://api.telegram.org/bot{$config['bot_token']}/sendMessage");
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => [
|
|
'chat_id' => $config['chat_id'],
|
|
'text' => "<b>$title</b>\n\n$message",
|
|
'parse_mode' => 'HTML'
|
|
]
|
|
]);
|
|
curl_exec($ch);
|
|
curl_close($ch);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// REGISTRATION & MANAGEMENT
|
|
// ============================================
|
|
|
|
public function registerServer($data) {
|
|
$this->pdo->prepare("INSERT INTO admin.server_monitor (server_id, ip_address, provider, cloud_instance_id, cost_per_hour) VALUES (?, ?, ?, ?, ?)")
|
|
->execute([
|
|
$data['server_id'] ?? null,
|
|
$data['ip_address'],
|
|
$data['provider'] ?? 'huawei',
|
|
$data['cloud_instance_id'] ?? '',
|
|
$data['cost_per_hour'] ?? 0.05
|
|
]);
|
|
return ['success' => true, 'monitor_id' => $this->pdo->lastInsertId()];
|
|
}
|
|
|
|
public function forceTerminate($monitorId) {
|
|
$server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC);
|
|
if ($server) {
|
|
$this->terminateServer($server);
|
|
$this->pdo->exec("UPDATE admin.server_monitor SET status = 'terminated', auto_action = 'manual_terminate', terminated_at = NOW() WHERE id = $monitorId");
|
|
return ['success' => true, 'message' => 'Server terminated'];
|
|
}
|
|
return ['success' => false, 'error' => 'Server not found'];
|
|
}
|
|
|
|
public function getStats() {
|
|
return [
|
|
'active_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE status = 'active'")->fetchColumn(),
|
|
'stopped_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE status = 'stopped'")->fetchColumn(),
|
|
'terminated_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE status = 'terminated'")->fetchColumn(),
|
|
'total_cost_today' => $this->pdo->query("SELECT COALESCE(SUM(total_cost), 0) FROM admin.server_monitor WHERE DATE(created_at) = CURRENT_DATE")->fetchColumn(),
|
|
'cost_saved_today' => $this->pdo->query("SELECT COALESCE(SUM(cost_saved), 0) FROM admin.supervisor_logs WHERE DATE(created_at) = CURRENT_DATE")->fetchColumn(),
|
|
'blacklisted_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE blacklist_count > 0 AND status = 'active'")->fetchColumn(),
|
|
'idle_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE idle_minutes > 30 AND status = 'active'")->fetchColumn(),
|
|
'by_provider' => $this->pdo->query("SELECT provider, COUNT(*) as count, SUM(total_cost) as cost FROM admin.server_monitor GROUP BY provider")->fetchAll(PDO::FETCH_ASSOC)
|
|
];
|
|
}
|
|
|
|
public function getRecentActions() {
|
|
return $this->pdo->query("SELECT sl.*, sm.ip_address, sm.provider FROM admin.supervisor_logs sl LEFT JOIN admin.server_monitor sm ON sl.server_id = sm.id ORDER BY sl.created_at DESC LIMIT 50")->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
}
|
|
|
|
$supervisor = new ServerSupervisor($pdo);
|
|
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
|
|
|
switch ($action) {
|
|
case 'check_all':
|
|
echo json_encode($supervisor->checkAllServers());
|
|
break;
|
|
case 'check':
|
|
echo json_encode($supervisor->checkServer($_GET['monitor_id'] ?? $_POST['monitor_id']));
|
|
break;
|
|
case 'check_blacklist':
|
|
echo json_encode($supervisor->checkBlacklists($_GET['ip'] ?? $_POST['ip']));
|
|
break;
|
|
case 'register':
|
|
echo json_encode($supervisor->registerServer($_POST));
|
|
break;
|
|
case 'terminate':
|
|
echo json_encode($supervisor->forceTerminate($_POST['monitor_id']));
|
|
break;
|
|
case 'servers':
|
|
echo json_encode(['servers' => $pdo->query("SELECT * FROM admin.server_monitor ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC)]);
|
|
break;
|
|
case 'rules':
|
|
echo json_encode(['rules' => $pdo->query("SELECT * FROM admin.supervisor_rules ORDER BY priority")->fetchAll(PDO::FETCH_ASSOC)]);
|
|
break;
|
|
case 'update_rule':
|
|
$pdo->prepare("UPDATE admin.supervisor_rules SET threshold_value = ?, is_active = ? WHERE id = ?")
|
|
->execute([$_POST['threshold'], $_POST['is_active'] ?? true, $_POST['rule_id']]);
|
|
echo json_encode(['success' => true]);
|
|
break;
|
|
case 'logs':
|
|
echo json_encode(['logs' => $supervisor->getRecentActions()]);
|
|
break;
|
|
case 'stats':
|
|
echo json_encode($supervisor->getStats());
|
|
break;
|
|
default:
|
|
echo json_encode([
|
|
'name' => 'Server Supervisor',
|
|
'description' => 'Auto-detect burned IPs, idle servers, auto-terminate to save costs',
|
|
'actions' => ['check_all','check','check_blacklist','register','terminate','servers','rules','update_rule','logs','stats']
|
|
]);
|
|
}
|
|
|