logfin expand
This commit is contained in:
parent
48afc9d770
commit
6b5951eb83
10 changed files with 597 additions and 40 deletions
43
Panel/agent_event_receiver.php
Normal file
43
Panel/agent_event_receiver.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once("includes/functions.php");
|
||||||
|
require_once("includes/helpers.php");
|
||||||
|
define("CONFIG_FILE", "includes/config.inc.php");
|
||||||
|
require_once CONFIG_FILE;
|
||||||
|
require_once("includes/agent_event_log.php");
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
gsp_agent_event_json_response(405, array('ok' => false, 'error' => 'method_not_allowed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = createDatabaseConnection($db_type, $db_host, $db_user, $db_pass, $db_name, $table_prefix, isset($db_port) ? $db_port : NULL);
|
||||||
|
if (!$db instanceof OGPDatabase) {
|
||||||
|
gsp_agent_event_fail(503, 'database_unavailable', 'Could not connect to database.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw_body = file_get_contents('php://input');
|
||||||
|
if ($raw_body === false || strlen($raw_body) > 65536) {
|
||||||
|
gsp_agent_event_fail(400, 'invalid_body', 'Invalid or oversized request body.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$remote = gsp_agent_event_authenticate($db, $raw_body);
|
||||||
|
$event = json_decode($raw_body, true);
|
||||||
|
if (!is_array($event)) {
|
||||||
|
gsp_agent_event_fail(400, 'invalid_json', 'Request body is not valid JSON.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$header_remote_id = (int)gsp_agent_event_header('X-GSP-Agent-Id');
|
||||||
|
$event_remote_id = isset($event['remote_server_id']) && is_numeric($event['remote_server_id']) ? (int)$event['remote_server_id'] : $header_remote_id;
|
||||||
|
if ($event_remote_id !== $header_remote_id) {
|
||||||
|
gsp_agent_event_fail(403, 'remote_id_mismatch', 'Payload remote server does not match authenticated agent.');
|
||||||
|
}
|
||||||
|
$event['remote_server_id'] = $header_remote_id;
|
||||||
|
$event['source'] = 'agent';
|
||||||
|
|
||||||
|
$errors = gsp_agent_event_validate($event);
|
||||||
|
if ($errors) {
|
||||||
|
gsp_agent_event_fail(400, 'validation_failed', implode('; ', $errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = gsp_agent_event_insert($db, $event, $remote);
|
||||||
|
gsp_agent_event_json_response(200, $result);
|
||||||
192
Panel/includes/agent_event_log.php
Normal file
192
Panel/includes/agent_event_log.php
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function gsp_agent_event_allowed_types() {
|
||||||
|
return array(
|
||||||
|
'server_process_stopped',
|
||||||
|
'server_process_started',
|
||||||
|
'server_crash_detected',
|
||||||
|
'server_unresponsive',
|
||||||
|
'automatic_restart_started',
|
||||||
|
'automatic_restart_succeeded',
|
||||||
|
'automatic_restart_failed',
|
||||||
|
'scheduled_restart_started',
|
||||||
|
'scheduled_restart_succeeded',
|
||||||
|
'scheduled_restart_failed',
|
||||||
|
'server_stop_confirmed',
|
||||||
|
'server_start_confirmed',
|
||||||
|
'server_port_missing',
|
||||||
|
'server_port_restored',
|
||||||
|
'server_process_found_without_port',
|
||||||
|
'server_port_found_without_managed_process',
|
||||||
|
'restart_attempt_limit_reached',
|
||||||
|
'agent_event_delivery_failed',
|
||||||
|
'agent_event_queue_replayed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_allowed_severities() {
|
||||||
|
return array('info', 'notice', 'warning', 'error', 'critical', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_header($name) {
|
||||||
|
$key = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
|
||||||
|
return isset($_SERVER[$key]) ? $_SERVER[$key] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_json_response($status, $payload) {
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('Content-Type: application/json; charset=UTF-8', true, $status);
|
||||||
|
}
|
||||||
|
echo json_encode($payload);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_fail($status, $code, $message) {
|
||||||
|
error_log("GSP agent event rejected: $code $message");
|
||||||
|
gsp_agent_event_json_response($status, array('ok' => false, 'error' => $code));
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_text($value, $max = 255) {
|
||||||
|
$value = is_scalar($value) ? (string)$value : '';
|
||||||
|
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $value);
|
||||||
|
return substr($value, 0, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_validate($event) {
|
||||||
|
$errors = array();
|
||||||
|
if (!is_array($event)) {
|
||||||
|
return array('payload must be an object');
|
||||||
|
}
|
||||||
|
if (empty($event['event_uuid']) || !preg_match('/^[A-Za-z0-9._:-]{16,64}$/', (string)$event['event_uuid'])) {
|
||||||
|
$errors[] = 'invalid event_uuid';
|
||||||
|
}
|
||||||
|
if (empty($event['timestamp_utc'])) {
|
||||||
|
$errors[] = 'missing timestamp_utc';
|
||||||
|
}
|
||||||
|
if (empty($event['event_type']) || !in_array($event['event_type'], gsp_agent_event_allowed_types(), true)) {
|
||||||
|
$errors[] = 'invalid event_type';
|
||||||
|
}
|
||||||
|
if (empty($event['severity']) || !in_array($event['severity'], gsp_agent_event_allowed_severities(), true)) {
|
||||||
|
$errors[] = 'invalid severity';
|
||||||
|
}
|
||||||
|
if (isset($event['remote_server_id']) && $event['remote_server_id'] !== '' && !is_numeric($event['remote_server_id'])) {
|
||||||
|
$errors[] = 'invalid remote_server_id';
|
||||||
|
}
|
||||||
|
if (isset($event['home_id']) && $event['home_id'] !== '' && !is_numeric($event['home_id'])) {
|
||||||
|
$errors[] = 'invalid home_id';
|
||||||
|
}
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_authenticate($db, $raw_body) {
|
||||||
|
$remote_server_id = gsp_agent_event_header('X-GSP-Agent-Id');
|
||||||
|
$timestamp = gsp_agent_event_header('X-GSP-Agent-Timestamp');
|
||||||
|
$signature = gsp_agent_event_header('X-GSP-Agent-Signature');
|
||||||
|
if (!is_numeric($remote_server_id) || !preg_match('/^\d{10}$/', $timestamp)) {
|
||||||
|
gsp_agent_event_fail(401, 'auth_headers_missing', 'Missing or invalid agent authentication headers.');
|
||||||
|
}
|
||||||
|
if (abs(time() - (int)$timestamp) > 300) {
|
||||||
|
gsp_agent_event_fail(401, 'stale_timestamp', 'Agent event timestamp outside allowed request window.');
|
||||||
|
}
|
||||||
|
$remote = $db->getRemoteServer((int)$remote_server_id);
|
||||||
|
if (!$remote || empty($remote['encryption_key'])) {
|
||||||
|
gsp_agent_event_fail(401, 'unknown_agent', 'Unknown remote server.');
|
||||||
|
}
|
||||||
|
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $raw_body, $remote['encryption_key']);
|
||||||
|
if (!hash_equals($expected, $signature)) {
|
||||||
|
gsp_agent_event_fail(401, 'bad_signature', 'Agent signature verification failed.');
|
||||||
|
}
|
||||||
|
return $remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_home_belongs_to_remote($db, $home_id, $remote_server_id) {
|
||||||
|
if (!is_numeric($home_id) || (int)$home_id <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$home = $db->getGameHome((int)$home_id);
|
||||||
|
if (!$home) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (int)$home['remote_server_id'] === (int)$remote_server_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_format_message($event) {
|
||||||
|
$name = gsp_agent_event_text($event['game_server_name'] ?? $event['server_name'] ?? $event['game_name'] ?? 'server', 120);
|
||||||
|
$home_id = isset($event['home_id']) && $event['home_id'] !== '' ? (int)$event['home_id'] : 0;
|
||||||
|
$port = gsp_agent_event_text($event['game_port'] ?? $event['port'] ?? '', 16);
|
||||||
|
$pid = gsp_agent_event_text($event['detected_process_pid'] ?? $event['tracked_pid'] ?? $event['pid'] ?? '', 32);
|
||||||
|
$attempt = gsp_agent_event_text($event['restart_attempt_number'] ?? '', 16);
|
||||||
|
$base = "Agent event for server '$name'";
|
||||||
|
if ($home_id > 0) {
|
||||||
|
$base .= " (home ID $home_id)";
|
||||||
|
}
|
||||||
|
switch ($event['event_type']) {
|
||||||
|
case 'server_crash_detected':
|
||||||
|
return "$base: unexpected stop detected. PID/process/session or required port disappeared.";
|
||||||
|
case 'server_process_stopped':
|
||||||
|
return "$base: server process stopped.";
|
||||||
|
case 'server_process_started':
|
||||||
|
case 'server_start_confirmed':
|
||||||
|
return "$base: server start confirmed" . ($pid !== '' ? " with PID $pid" : "") . ($port !== '' ? " and port $port listening" : "") . ".";
|
||||||
|
case 'server_stop_confirmed':
|
||||||
|
return "$base: server stop confirmed. Process/session and required port are no longer active.";
|
||||||
|
case 'server_unresponsive':
|
||||||
|
return "$base: process/session exists but required game port did not become ready.";
|
||||||
|
case 'automatic_restart_started':
|
||||||
|
return "$base: automatic restart attempt" . ($attempt !== '' ? " $attempt" : "") . " started.";
|
||||||
|
case 'automatic_restart_succeeded':
|
||||||
|
return "$base: automatic restart succeeded" . ($pid !== '' ? " with PID $pid" : "") . ($port !== '' ? " and port $port listening" : "") . ".";
|
||||||
|
case 'automatic_restart_failed':
|
||||||
|
return "$base: automatic restart failed.";
|
||||||
|
case 'scheduled_restart_started':
|
||||||
|
return "$base: scheduled restart started.";
|
||||||
|
case 'scheduled_restart_succeeded':
|
||||||
|
return "$base: scheduled restart completed successfully.";
|
||||||
|
case 'scheduled_restart_failed':
|
||||||
|
return "$base: scheduled restart failed.";
|
||||||
|
case 'server_port_missing':
|
||||||
|
case 'server_process_found_without_port':
|
||||||
|
return "$base: process/session is active but expected port $port is not listening.";
|
||||||
|
case 'server_port_restored':
|
||||||
|
return "$base: expected port $port is listening again.";
|
||||||
|
case 'server_port_found_without_managed_process':
|
||||||
|
return "$base: expected port $port is listening but no managed process/session was found.";
|
||||||
|
case 'restart_attempt_limit_reached':
|
||||||
|
return "$base: restart attempt limit reached.";
|
||||||
|
case 'agent_event_delivery_failed':
|
||||||
|
return "Agent event delivery failed on " . gsp_agent_event_text($event['agent_hostname'] ?? 'agent', 120) . ".";
|
||||||
|
case 'agent_event_queue_replayed':
|
||||||
|
return "Agent event queue replayed by " . gsp_agent_event_text($event['agent_hostname'] ?? 'agent', 120) . ".";
|
||||||
|
}
|
||||||
|
return $base . ': ' . gsp_agent_event_text($event['message'] ?? $event['event_type'], 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_agent_event_insert($db, $event, $remote) {
|
||||||
|
$remote_server_id = (int)$remote['remote_server_id'];
|
||||||
|
$home_id = isset($event['home_id']) && is_numeric($event['home_id']) ? (int)$event['home_id'] : null;
|
||||||
|
if (!gsp_agent_event_home_belongs_to_remote($db, $home_id, $remote_server_id)) {
|
||||||
|
gsp_agent_event_fail(403, 'home_remote_mismatch', 'Home does not belong to reporting remote server.');
|
||||||
|
}
|
||||||
|
if ($db->loggerEventExists($event['event_uuid'])) {
|
||||||
|
return array('ok' => true, 'duplicate' => true);
|
||||||
|
}
|
||||||
|
$agent_label = gsp_agent_event_text($event['agent_hostname'] ?? $remote['remote_server_name'] ?? $remote['agent_ip'] ?? 'agent', 120);
|
||||||
|
$actor = trim(gsp_agent_event_text(($event['agent_os'] ?? 'Agent') . ': ' . $agent_label, 120));
|
||||||
|
$metadata = $event;
|
||||||
|
unset($metadata['password'], $metadata['steam_password'], $metadata['encryption_key'], $metadata['database_password']);
|
||||||
|
$db->loggerEx(gsp_agent_event_format_message($event), array(
|
||||||
|
'user_id' => 0,
|
||||||
|
'ip' => $agent_label,
|
||||||
|
'source_type' => 'agent',
|
||||||
|
'category' => 'server_lifecycle',
|
||||||
|
'event_type' => gsp_agent_event_text($event['event_type'], 80),
|
||||||
|
'severity' => gsp_agent_event_text($event['severity'], 20),
|
||||||
|
'remote_server_id' => $remote_server_id,
|
||||||
|
'home_id' => $home_id,
|
||||||
|
'event_uuid' => gsp_agent_event_text($event['event_uuid'], 64),
|
||||||
|
'correlation_id' => gsp_agent_event_text($event['correlation_id'] ?? '', 64),
|
||||||
|
'actor' => $actor,
|
||||||
|
'metadata_json' => json_encode($metadata)
|
||||||
|
));
|
||||||
|
return array('ok' => true, 'duplicate' => false);
|
||||||
|
}
|
||||||
|
|
@ -3406,26 +3406,147 @@ class OGPDatabaseMySQL extends OGPDatabase
|
||||||
public function logger($message){
|
public function logger($message){
|
||||||
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 0;
|
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 0;
|
||||||
$client_ip = getClientIPAddress();
|
$client_ip = getClientIPAddress();
|
||||||
$message = $this->realEscapeSingle($message);
|
$this->loggerEx($message, array(
|
||||||
$this->query("INSERT INTO OGP_DB_PREFIXlogger (date, user_id, ip, message) VALUE (FROM_UNIXTIME(UNIX_TIMESTAMP(), '%d-%m-%Y %H:%i:%s'), $user_id, '$client_ip', '$message');");
|
'user_id' => $user_id,
|
||||||
|
'ip' => $client_ip,
|
||||||
|
'source_type' => 'user',
|
||||||
|
'category' => 'panel_action',
|
||||||
|
'severity' => 'info'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_logger_count($search_field) {
|
private function loggerColumnExists($column) {
|
||||||
|
$column = $this->realEscapeSingle($column);
|
||||||
|
$result = $this->resultQuery("SHOW COLUMNS FROM `".$this->table_prefix."logger` LIKE '$column'");
|
||||||
|
return is_array($result) && count($result) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ensureLoggerExtendedSchema() {
|
||||||
|
if (!$this->link) return false;
|
||||||
|
$this->query("ALTER TABLE `".$this->table_prefix."logger` MODIFY `ip` varchar(255) NOT NULL");
|
||||||
|
$this->query("ALTER TABLE `".$this->table_prefix."logger` MODIFY `message` varchar(1000) NOT NULL");
|
||||||
|
$columns = array(
|
||||||
|
'source_type' => "varchar(20) NOT NULL DEFAULT 'user'",
|
||||||
|
'category' => "varchar(40) NOT NULL DEFAULT 'panel_action'",
|
||||||
|
'event_type' => "varchar(80) DEFAULT NULL",
|
||||||
|
'severity' => "varchar(20) NOT NULL DEFAULT 'info'",
|
||||||
|
'remote_server_id' => "int(11) DEFAULT NULL",
|
||||||
|
'home_id' => "int(11) DEFAULT NULL",
|
||||||
|
'event_uuid' => "varchar(64) DEFAULT NULL",
|
||||||
|
'correlation_id' => "varchar(64) DEFAULT NULL",
|
||||||
|
'actor' => "varchar(120) DEFAULT NULL",
|
||||||
|
'metadata_json' => "text DEFAULT NULL"
|
||||||
|
);
|
||||||
|
foreach ($columns as $column => $definition) {
|
||||||
|
if (!$this->loggerColumnExists($column)) {
|
||||||
|
$this->query("ALTER TABLE `".$this->table_prefix."logger` ADD `$column` $definition");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$indexes = $this->resultQuery("SHOW INDEX FROM `".$this->table_prefix."logger`");
|
||||||
|
$index_names = array();
|
||||||
|
foreach ((array)$indexes as $index) {
|
||||||
|
$index_names[$index['Key_name']] = true;
|
||||||
|
}
|
||||||
|
if (!isset($index_names['idx_logger_event_uuid'])) {
|
||||||
|
$this->query("ALTER TABLE `".$this->table_prefix."logger` ADD INDEX `idx_logger_event_uuid` (`event_uuid`)");
|
||||||
|
}
|
||||||
|
if (!isset($index_names['idx_logger_source_category'])) {
|
||||||
|
$this->query("ALTER TABLE `".$this->table_prefix."logger` ADD INDEX `idx_logger_source_category` (`source_type`,`category`,`severity`)");
|
||||||
|
}
|
||||||
|
if (!isset($index_names['idx_logger_home'])) {
|
||||||
|
$this->query("ALTER TABLE `".$this->table_prefix."logger` ADD INDEX `idx_logger_home` (`home_id`)");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loggerEx($message, $options = array()){
|
||||||
|
$this->ensureLoggerExtendedSchema();
|
||||||
|
$user_id = isset($options['user_id']) ? (int)$options['user_id'] : (isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0);
|
||||||
|
$ip = isset($options['ip']) ? $options['ip'] : getClientIPAddress();
|
||||||
|
$source_type = isset($options['source_type']) ? $options['source_type'] : 'user';
|
||||||
|
$category = isset($options['category']) ? $options['category'] : 'panel_action';
|
||||||
|
$event_type = isset($options['event_type']) ? $options['event_type'] : null;
|
||||||
|
$severity = isset($options['severity']) ? $options['severity'] : 'info';
|
||||||
|
$remote_server_id = isset($options['remote_server_id']) && is_numeric($options['remote_server_id']) ? (int)$options['remote_server_id'] : 'NULL';
|
||||||
|
$home_id = isset($options['home_id']) && is_numeric($options['home_id']) ? (int)$options['home_id'] : 'NULL';
|
||||||
|
$event_uuid = isset($options['event_uuid']) ? $options['event_uuid'] : null;
|
||||||
|
$correlation_id = isset($options['correlation_id']) ? $options['correlation_id'] : null;
|
||||||
|
$actor = isset($options['actor']) ? $options['actor'] : null;
|
||||||
|
$metadata_json = isset($options['metadata_json']) ? $options['metadata_json'] : null;
|
||||||
|
|
||||||
|
$message = substr((string)$message, 0, 1000);
|
||||||
|
$ip = substr((string)$ip, 0, 255);
|
||||||
|
$source_type = substr((string)$source_type, 0, 20);
|
||||||
|
$category = substr((string)$category, 0, 40);
|
||||||
|
$event_type_sql = $event_type === null ? 'NULL' : "'".$this->realEscapeSingle(substr((string)$event_type, 0, 80))."'";
|
||||||
|
$severity = substr((string)$severity, 0, 20);
|
||||||
|
$event_uuid_sql = $event_uuid === null ? 'NULL' : "'".$this->realEscapeSingle(substr((string)$event_uuid, 0, 64))."'";
|
||||||
|
$correlation_id_sql = $correlation_id === null ? 'NULL' : "'".$this->realEscapeSingle(substr((string)$correlation_id, 0, 64))."'";
|
||||||
|
$actor_sql = $actor === null ? 'NULL' : "'".$this->realEscapeSingle(substr((string)$actor, 0, 120))."'";
|
||||||
|
$metadata_sql = $metadata_json === null ? 'NULL' : "'".$this->realEscapeSingle($metadata_json)."'";
|
||||||
|
$message = $this->realEscapeSingle($message);
|
||||||
|
$ip = $this->realEscapeSingle($ip);
|
||||||
|
$source_type = $this->realEscapeSingle($source_type);
|
||||||
|
$category = $this->realEscapeSingle($category);
|
||||||
|
$severity = $this->realEscapeSingle($severity);
|
||||||
|
|
||||||
|
$this->query("INSERT INTO `".$this->table_prefix."logger`
|
||||||
|
(date, user_id, ip, message, source_type, category, event_type, severity, remote_server_id, home_id, event_uuid, correlation_id, actor, metadata_json)
|
||||||
|
VALUE (FROM_UNIXTIME(UNIX_TIMESTAMP(), '%d-%m-%Y %H:%i:%s'), $user_id, '$ip', '$message', '$source_type', '$category', $event_type_sql, '$severity', $remote_server_id, $home_id, $event_uuid_sql, $correlation_id_sql, $actor_sql, $metadata_sql);");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loggerEventExists($event_uuid) {
|
||||||
|
$this->ensureLoggerExtendedSchema();
|
||||||
|
$event_uuid = $this->realEscapeSingle($event_uuid);
|
||||||
|
$result = $this->resultQuery("SELECT log_id FROM `".$this->table_prefix."logger` WHERE event_uuid = '$event_uuid' LIMIT 1");
|
||||||
|
return is_array($result) && count($result) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildLoggerWhere($search_field, $filters = array()) {
|
||||||
|
$this->ensureLoggerExtendedSchema();
|
||||||
|
$where = array();
|
||||||
$search_field = $this->realEscapeSingle($search_field);
|
$search_field = $this->realEscapeSingle($search_field);
|
||||||
|
|
||||||
$sql = "SELECT COUNT(1) AS total FROM ".$this->table_prefix."logger ";
|
|
||||||
|
|
||||||
if (!empty($search_field)) {
|
if (!empty($search_field)) {
|
||||||
$sql .= "WHERE ip = '$search_field' OR message LIKE '%$search_field%'
|
$where[] = "(ip = '$search_field' OR message LIKE '%$search_field%'
|
||||||
OR user_id IN (SELECT user_id FROM `".$this->table_prefix."users` WHERE users_login LIKE '%$search_field%')";
|
OR actor LIKE '%$search_field%'
|
||||||
|
OR event_type LIKE '%$search_field%'
|
||||||
|
OR user_id IN (SELECT user_id FROM `".$this->table_prefix."users` WHERE users_login LIKE '%$search_field%'))";
|
||||||
|
}
|
||||||
|
foreach (array('source_type','category','severity') as $field) {
|
||||||
|
if (isset($filters[$field]) && $filters[$field] !== '' && $filters[$field] !== 'all') {
|
||||||
|
$value = $this->realEscapeSingle($filters[$field]);
|
||||||
|
$where[] = "$field = '$value'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($filters['user_id']) && is_numeric($filters['user_id'])) {
|
||||||
|
$where[] = "user_id = ".(int)$filters['user_id'];
|
||||||
|
}
|
||||||
|
if (isset($filters['remote_server_id']) && is_numeric($filters['remote_server_id'])) {
|
||||||
|
$where[] = "remote_server_id = ".(int)$filters['remote_server_id'];
|
||||||
|
}
|
||||||
|
if (isset($filters['home_id']) && is_numeric($filters['home_id'])) {
|
||||||
|
$where[] = "home_id = ".(int)$filters['home_id'];
|
||||||
|
}
|
||||||
|
if (isset($filters['date_from']) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $filters['date_from'])) {
|
||||||
|
$date = $this->realEscapeSingle($filters['date_from']);
|
||||||
|
$where[] = "STR_TO_DATE(date, '%d-%m-%Y %H:%i:%s') >= '$date 00:00:00'";
|
||||||
|
}
|
||||||
|
if (isset($filters['date_to']) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $filters['date_to'])) {
|
||||||
|
$date = $this->realEscapeSingle($filters['date_to']);
|
||||||
|
$where[] = "STR_TO_DATE(date, '%d-%m-%Y %H:%i:%s') <= '$date 23:59:59'";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return count($where) ? " WHERE ".implode(" AND ", $where) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_logger_count($search_field, $filters = array()) {
|
||||||
|
$sql = "SELECT COUNT(1) AS total FROM ".$this->table_prefix."logger ";
|
||||||
|
$sql .= $this->buildLoggerWhere($search_field, $filters);
|
||||||
return $this->resultQuery($sql);
|
return $this->resultQuery($sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function read_logger($page,$limit, $search_field) {
|
public function read_logger($page,$limit, $search_field, $filters = array()) {
|
||||||
$search_field = $this->realEscapeSingle($search_field);
|
|
||||||
|
|
||||||
$log_id = ($page - 1) * $limit;
|
$log_id = ($page - 1) * $limit;
|
||||||
|
|
||||||
if(!is_numeric($log_id) || !is_numeric($limit)){
|
if(!is_numeric($log_id) || !is_numeric($limit)){
|
||||||
|
|
@ -3433,11 +3554,7 @@ class OGPDatabaseMySQL extends OGPDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "SELECT * FROM ".$this->table_prefix."logger ";
|
$sql = "SELECT * FROM ".$this->table_prefix."logger ";
|
||||||
|
$sql .= $this->buildLoggerWhere($search_field, $filters);
|
||||||
if (!empty($search_field)) {
|
|
||||||
$sql .= "WHERE ip = '$search_field' OR message LIKE '%$search_field%'
|
|
||||||
OR user_id IN (SELECT user_id FROM `".$this->table_prefix."users` WHERE users_login LIKE '%$search_field%') ";
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql .= "ORDER BY log_id DESC LIMIT $log_id, $limit;";
|
$sql .= "ORDER BY log_id DESC LIMIT $log_id, $limit;";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -746,11 +746,11 @@ class OGPRemoteLibrary
|
||||||
|
|
||||||
public function remote_restart_server($home_id,$server_ip,$server_port,
|
public function remote_restart_server($home_id,$server_ip,$server_port,
|
||||||
$control_protocol,$control_password,$control_type,
|
$control_protocol,$control_password,$control_type,
|
||||||
$home_path,$server_exe,$run_dir,$cmd,$cpu,$nice,$preStart, $envVars, $game_key, $console_log = "")
|
$home_path,$server_exe,$run_dir,$cmd,$cpu,$nice,$preStart, $envVars, $game_key, $console_log = "", $restart_reason = "")
|
||||||
{
|
{
|
||||||
$params_array = $this->encrypt_params($home_id,$server_ip,$server_port,
|
$params_array = $this->encrypt_params($home_id,$server_ip,$server_port,
|
||||||
$control_protocol,$control_password,$control_type,
|
$control_protocol,$control_password,$control_type,
|
||||||
$home_path,$server_exe,$run_dir,$cmd,$cpu,$nice,$preStart,$envVars, $game_key, $console_log);
|
$home_path,$server_exe,$run_dir,$cmd,$cpu,$nice,$preStart,$envVars, $game_key, $console_log, $restart_reason);
|
||||||
$this->add_enc_chk($params_array);
|
$this->add_enc_chk($params_array);
|
||||||
$request = xmlrpc_encode_request("restart_server", $params_array);
|
$request = xmlrpc_encode_request("restart_server", $params_array);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -433,6 +433,7 @@ function server_content_restart_home($home_id, $options = array())
|
||||||
}
|
}
|
||||||
sleep($delay);
|
sleep($delay);
|
||||||
}
|
}
|
||||||
|
$restart_reason = (isset($options['triggered_by']) && $options['triggered_by'] === 'scheduler') ? 'scheduled_restart' : 'panel_restart';
|
||||||
|
|
||||||
$remote_retval = $remote->remote_restart_server(
|
$remote_retval = $remote->remote_restart_server(
|
||||||
$home_info['home_id'],
|
$home_info['home_id'],
|
||||||
|
|
@ -450,7 +451,8 @@ function server_content_restart_home($home_id, $options = array())
|
||||||
$preStart,
|
$preStart,
|
||||||
$envVars,
|
$envVars,
|
||||||
$server_xml->game_key,
|
$server_xml->game_key,
|
||||||
(isset($server_xml->console_log) ? $server_xml->console_log : "")
|
(isset($server_xml->console_log) ? $server_xml->console_log : ""),
|
||||||
|
$restart_reason
|
||||||
);
|
);
|
||||||
if ($remote_retval !== 1) {
|
if ($remote_retval !== 1) {
|
||||||
return server_content_result('restart_required', 'Update completed but automatic restart failed.', array(
|
return server_content_result('restart_required', 'Update completed but automatic restart failed.', array(
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,20 @@ $install_queries[1] = array(
|
||||||
`log_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
`log_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
`date` varchar(20) NOT NULL,
|
`date` varchar(20) NOT NULL,
|
||||||
`user_id` int(11) NOT NULL,
|
`user_id` int(11) NOT NULL,
|
||||||
`ip` varchar(15) NOT NULL,
|
`ip` varchar(255) NOT NULL,
|
||||||
`message` varchar(250) NOT NULL
|
`message` varchar(1000) NOT NULL,
|
||||||
|
`source_type` varchar(20) NOT NULL DEFAULT 'user',
|
||||||
|
`category` varchar(40) NOT NULL DEFAULT 'panel_action',
|
||||||
|
`event_type` varchar(80) DEFAULT NULL,
|
||||||
|
`severity` varchar(20) NOT NULL DEFAULT 'info',
|
||||||
|
`remote_server_id` int(11) DEFAULT NULL,
|
||||||
|
`home_id` int(11) DEFAULT NULL,
|
||||||
|
`event_uuid` varchar(64) DEFAULT NULL,
|
||||||
|
`correlation_id` varchar(64) DEFAULT NULL,
|
||||||
|
`actor` varchar(120) DEFAULT NULL,
|
||||||
|
`metadata_json` text DEFAULT NULL,
|
||||||
|
KEY `idx_logger_event_uuid` (`event_uuid`),
|
||||||
|
KEY `idx_logger_source_category` (`source_type`,`category`,`severity`),
|
||||||
|
KEY `idx_logger_home` (`home_id`)
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
|
) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,13 @@ function exec_ogp_module() {
|
||||||
$search_field = (isset($_GET['search']) && !empty($_GET['search'])) ? $_GET['search'] : false;
|
$search_field = (isset($_GET['search']) && !empty($_GET['search'])) ? $_GET['search'] : false;
|
||||||
$p = (isset($_GET['page']) && (int)$_GET['page'] > 0) ? (int)$_GET['page'] : 1;
|
$p = (isset($_GET['page']) && (int)$_GET['page'] > 0) ? (int)$_GET['page'] : 1;
|
||||||
$l = (isset($_GET['limit']) && (int)$_GET['limit'] > 0) ? (int)$_GET['limit'] : 10;
|
$l = (isset($_GET['limit']) && (int)$_GET['limit'] > 0) ? (int)$_GET['limit'] : 10;
|
||||||
|
$filters = array();
|
||||||
|
foreach (array('source_type','category','severity','user_id','remote_server_id','home_id','date_from','date_to') as $filter_key) {
|
||||||
|
if (isset($_GET[$filter_key]) && $_GET[$filter_key] !== '' && $_GET[$filter_key] !== 'all') {
|
||||||
|
$filters[$filter_key] = $_GET[$filter_key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$db->ensureLoggerExtendedSchema();
|
||||||
|
|
||||||
if(hasValue($loggedInUserInfo) && is_array($loggedInUserInfo) && $loggedInUserInfo["users_page_limit"] && !(isset($_GET['limit']) and !empty($_GET['limit']))){
|
if(hasValue($loggedInUserInfo) && is_array($loggedInUserInfo) && $loggedInUserInfo["users_page_limit"] && !(isset($_GET['limit']) and !empty($_GET['limit']))){
|
||||||
$l = $loggedInUserInfo["users_page_limit"];
|
$l = $loggedInUserInfo["users_page_limit"];
|
||||||
|
|
@ -36,14 +43,19 @@ function exec_ogp_module() {
|
||||||
|
|
||||||
echo "<h2>".get_lang('watch_logger')."</h2>";
|
echo "<h2>".get_lang('watch_logger')."</h2>";
|
||||||
|
|
||||||
$logs = $db->read_logger($p, $l, $search_field);
|
$logs = $db->read_logger($p, $l, $search_field, $filters);
|
||||||
|
|
||||||
if (empty($logs) && !empty($search_field)) {
|
if (empty($logs) && (!empty($search_field) || !empty($filters))) {
|
||||||
print_failure(get_lang_f('no_results_found', htmlentities($search_field ?? '', ENT_QUOTES, 'UTF-8')));
|
print_failure(get_lang_f('no_results_found', htmlentities($search_field ?? 'selected filters', ENT_QUOTES, 'UTF-8')));
|
||||||
$view->refresh("?m=administration&p=watch_logger", 5);
|
$view->refresh("?m=administration&p=watch_logger", 5);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$query_params = array_merge(array('m' => 'administration', 'p' => 'watch_logger', 'limit' => $l), $filters);
|
||||||
|
if (!empty($search_field)) {
|
||||||
|
$query_params['search'] = $search_field;
|
||||||
|
}
|
||||||
|
$base_query = http_build_query($query_params);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<!-- Search, Empty Logger, and Paging Options Table -->
|
<!-- Search, Empty Logger, and Paging Options Table -->
|
||||||
|
|
@ -53,7 +65,27 @@ function exec_ogp_module() {
|
||||||
<form action="home.php" method="GET" style="display: inline;">
|
<form action="home.php" method="GET" style="display: inline;">
|
||||||
<input type ="hidden" name="m" value="administration" />
|
<input type ="hidden" name="m" value="administration" />
|
||||||
<input type ="hidden" name="p" value="watch_logger" />
|
<input type ="hidden" name="p" value="watch_logger" />
|
||||||
<input name="search" type="text" id="search" value="<?php if(hasValue($search_field)){ echo $search_field; } ?>" />
|
<input name="search" type="text" id="search" value="<?php if(hasValue($search_field)){ echo htmlentities($search_field, ENT_QUOTES, 'UTF-8'); } ?>" />
|
||||||
|
<select name="source_type">
|
||||||
|
<?php foreach (array('all' => 'All sources', 'user' => 'User', 'admin' => 'Admin', 'agent' => 'Agent', 'scheduler' => 'Scheduler') as $value => $label) { ?>
|
||||||
|
<option value="<?php echo $value; ?>" <?php echo (isset($_GET['source_type']) && $_GET['source_type'] === $value) ? 'selected' : ''; ?>><?php echo $label; ?></option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
<select name="category">
|
||||||
|
<?php foreach (array('all' => 'All categories', 'authentication' => 'Authentication', 'panel_action' => 'Panel actions', 'server_lifecycle' => 'Server lifecycle', 'file_config' => 'File/config changes', 'user_management' => 'User management', 'content_workshop' => 'Content/Workshop', 'errors' => 'Errors') as $value => $label) { ?>
|
||||||
|
<option value="<?php echo $value; ?>" <?php echo (isset($_GET['category']) && $_GET['category'] === $value) ? 'selected' : ''; ?>><?php echo $label; ?></option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
<select name="severity">
|
||||||
|
<?php foreach (array('all' => 'All severities', 'info' => 'Info', 'notice' => 'Notice', 'warning' => 'Warning', 'error' => 'Error', 'critical' => 'Critical', 'success' => 'Success') as $value => $label) { ?>
|
||||||
|
<option value="<?php echo $value; ?>" <?php echo (isset($_GET['severity']) && $_GET['severity'] === $value) ? 'selected' : ''; ?>><?php echo $label; ?></option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
<input name="user_id" type="number" min="0" placeholder="User ID" value="<?php echo htmlentities($_GET['user_id'] ?? '', ENT_QUOTES, 'UTF-8'); ?>" style="width:70px;" />
|
||||||
|
<input name="remote_server_id" type="number" min="0" placeholder="Agent ID" value="<?php echo htmlentities($_GET['remote_server_id'] ?? '', ENT_QUOTES, 'UTF-8'); ?>" style="width:75px;" />
|
||||||
|
<input name="home_id" type="number" min="0" placeholder="Home ID" value="<?php echo htmlentities($_GET['home_id'] ?? '', ENT_QUOTES, 'UTF-8'); ?>" style="width:75px;" />
|
||||||
|
<input name="date_from" type="date" value="<?php echo htmlentities($_GET['date_from'] ?? '', ENT_QUOTES, 'UTF-8'); ?>" />
|
||||||
|
<input name="date_to" type="date" value="<?php echo htmlentities($_GET['date_to'] ?? '', ENT_QUOTES, 'UTF-8'); ?>" />
|
||||||
<input type="submit" value="<?php echo get_lang('search'); ?>" />
|
<input type="submit" value="<?php echo get_lang('search'); ?>" />
|
||||||
</form>
|
</form>
|
||||||
<form method=POST style="display: inline;">
|
<form method=POST style="display: inline;">
|
||||||
|
|
@ -61,7 +93,7 @@ function exec_ogp_module() {
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 50%; vertical-align: middle; text-align: right;">
|
<td style="width: 50%; vertical-align: middle; text-align: right;">
|
||||||
<?php echo print_lang('view'); ?> <a href='?m=administration&p=watch_logger&limit=10'>10</a> / <a href='?m=administration&p=watch_logger&limit=20'>20</a> / <a href='?m=administration&p=watch_logger&limit=50'>50</a> / <a href='?m=administration&p=watch_logger&limit=100'>100</a> <?php echo print_lang('per_page'); ?>
|
<?php echo print_lang('view'); ?> <a href='?<?php echo htmlentities(http_build_query(array_merge($query_params, array('limit' => 10))), ENT_QUOTES, 'UTF-8'); ?>'>10</a> / <a href='?<?php echo htmlentities(http_build_query(array_merge($query_params, array('limit' => 20))), ENT_QUOTES, 'UTF-8'); ?>'>20</a> / <a href='?<?php echo htmlentities(http_build_query(array_merge($query_params, array('limit' => 50))), ENT_QUOTES, 'UTF-8'); ?>'>50</a> / <a href='?<?php echo htmlentities(http_build_query(array_merge($query_params, array('limit' => 100))), ENT_QUOTES, 'UTF-8'); ?>'>100</a> <?php echo print_lang('per_page'); ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -74,6 +106,9 @@ function exec_ogp_module() {
|
||||||
<th class="dateFormat-ddmmyyyy"><?php print_lang('when'); ?></th>
|
<th class="dateFormat-ddmmyyyy"><?php print_lang('when'); ?></th>
|
||||||
<th><?php print_lang('who'); ?></th>
|
<th><?php print_lang('who'); ?></th>
|
||||||
<th><?php print_lang('where'); ?></th>
|
<th><?php print_lang('where'); ?></th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Severity</th>
|
||||||
|
<th>Server</th>
|
||||||
<th><?php print_lang('what'); ?></th>
|
<th><?php print_lang('what'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -100,10 +135,23 @@ function exec_ogp_module() {
|
||||||
foreach ((array)$logs as $log)
|
foreach ((array)$logs as $log)
|
||||||
{
|
{
|
||||||
$user = $db->getUserById($log['user_id']);
|
$user = $db->getUserById($log['user_id']);
|
||||||
$when = $log['date'];
|
$user = is_array($user) ? $user : array();
|
||||||
$who = $user['users_login'];
|
$when = htmlentities($log['date'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||||
$where = $log['ip'];
|
$who = isset($log['actor']) && $log['actor'] !== '' ? $log['actor'] : ($user['users_login'] ?? 'System');
|
||||||
$what = $log['message'];
|
$who = htmlentities($who, ENT_QUOTES, 'UTF-8');
|
||||||
|
$where = htmlentities($log['ip'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||||
|
$what = htmlentities($log['message'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||||
|
$source = htmlentities(strtoupper($log['source_type'] ?? 'USER'), ENT_QUOTES, 'UTF-8');
|
||||||
|
$category = htmlentities($log['category'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||||
|
$severity = htmlentities(strtoupper($log['severity'] ?? 'INFO'), ENT_QUOTES, 'UTF-8');
|
||||||
|
$server_ref = '';
|
||||||
|
if (!empty($log['home_id'])) {
|
||||||
|
$server_ref .= 'Home '.$log['home_id'];
|
||||||
|
}
|
||||||
|
if (!empty($log['remote_server_id'])) {
|
||||||
|
$server_ref .= ($server_ref ? ' / ' : '').'Agent '.$log['remote_server_id'];
|
||||||
|
}
|
||||||
|
$server_ref = htmlentities($server_ref, ENT_QUOTES, 'UTF-8');
|
||||||
$log_id = $log['log_id'];
|
$log_id = $log['log_id'];
|
||||||
// Template
|
// Template
|
||||||
echo "<tr class='maintr'>\n".
|
echo "<tr class='maintr'>\n".
|
||||||
|
|
@ -118,18 +166,30 @@ function exec_ogp_module() {
|
||||||
"<td class='collapsible'>$when</td>\n".
|
"<td class='collapsible'>$when</td>\n".
|
||||||
"<td class='collapsible'>$who</td>\n".
|
"<td class='collapsible'>$who</td>\n".
|
||||||
"<td class='collapsible'>$where</td>\n".
|
"<td class='collapsible'>$where</td>\n".
|
||||||
|
"<td class='collapsible'><span class='note'>$source</span><br />$category</td>\n".
|
||||||
|
"<td class='collapsible'>$severity</td>\n".
|
||||||
|
"<td class='collapsible'>$server_ref</td>\n".
|
||||||
"<td class='collapsible'>$what</td>\n".
|
"<td class='collapsible'>$what</td>\n".
|
||||||
"</tr>\n";
|
"</tr>\n";
|
||||||
|
|
||||||
echo "<tr class='expand-child'>\n".
|
echo "<tr class='expand-child'>\n".
|
||||||
"<td colspan='5' >\n".
|
"<td colspan='8' >\n".
|
||||||
"<table>\n";
|
"<table>\n";
|
||||||
|
|
||||||
$show_values = array( "users_login", "users_lang", "users_role", "users_email", "user_expires");
|
$show_values = array( "users_login", "users_lang", "users_role", "users_email", "user_expires");
|
||||||
foreach ((array)$user as $key => $value)
|
foreach ((array)$user as $key => $value)
|
||||||
{
|
{
|
||||||
if( in_array( $key, $show_values ) )
|
if( in_array( $key, $show_values ) )
|
||||||
echo "<tr><td>".str_replace("_", "", substr($key,5))."</td><td>$value</td></tr>\n";
|
echo "<tr><td>".htmlentities(str_replace("_", "", substr($key,5)), ENT_QUOTES, 'UTF-8')."</td><td>".htmlentities($value, ENT_QUOTES, 'UTF-8')."</td></tr>\n";
|
||||||
|
}
|
||||||
|
if (!empty($log['event_type'])) {
|
||||||
|
echo "<tr><td>event type</td><td>".htmlentities($log['event_type'], ENT_QUOTES, 'UTF-8')."</td></tr>\n";
|
||||||
|
}
|
||||||
|
if (!empty($log['correlation_id'])) {
|
||||||
|
echo "<tr><td>correlation id</td><td>".htmlentities($log['correlation_id'], ENT_QUOTES, 'UTF-8')."</td></tr>\n";
|
||||||
|
}
|
||||||
|
if (!empty($log['metadata_json'])) {
|
||||||
|
echo "<tr><td>details</td><td><pre>".htmlentities($log['metadata_json'], ENT_QUOTES, 'UTF-8')."</pre></td></tr>\n";
|
||||||
}
|
}
|
||||||
echo "</tr>\n".
|
echo "</tr>\n".
|
||||||
"</td>\n".
|
"</td>\n".
|
||||||
|
|
@ -139,13 +199,9 @@ function exec_ogp_module() {
|
||||||
echo "</tbody>\n";
|
echo "</tbody>\n";
|
||||||
echo "<tfoot style='border:1px solid grey;'></tfoot>\n";
|
echo "<tfoot style='border:1px solid grey;'></tfoot>\n";
|
||||||
echo "</table>\n";
|
echo "</table>\n";
|
||||||
$count_logs = $db->get_logger_count($search_field);
|
$count_logs = $db->get_logger_count($search_field, $filters);
|
||||||
|
|
||||||
if (isset($_GET['search']) && !empty($_GET['search'])) {
|
$uri = '?'.$base_query.'&page=';
|
||||||
$uri = '?m=administration&p=watch_logger&search='.$_GET['search'].'&limit='.$l.'&page=';
|
|
||||||
} else {
|
|
||||||
$uri = '?m=administration&p=watch_logger&limit='.$l.'&page=';
|
|
||||||
}
|
|
||||||
echo paginationPages($count_logs[0]['total'], $p, $l, $uri, 3, 'watchLogger');
|
echo paginationPages($count_logs[0]['total'], $p, $l, $uri, 3, 'watchLogger');
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,8 @@ function build_cron_scheduler_command($panelURL, $token, $game_home, $action) {
|
||||||
case "start":
|
case "start":
|
||||||
return "wget -qO- \"{$panelURL}/ogp_api.php?gamemanager/start&token={$token}&ip={$ip}&port={$port}&mod_key={$mod_key}\" --no-check-certificate > /dev/null 2>&1";
|
return "wget -qO- \"{$panelURL}/ogp_api.php?gamemanager/start&token={$token}&ip={$ip}&port={$port}&mod_key={$mod_key}\" --no-check-certificate > /dev/null 2>&1";
|
||||||
case "restart":
|
case "restart":
|
||||||
return "wget -qO- \"{$panelURL}/ogp_api.php?gamemanager/restart&token={$token}&ip={$ip}&port={$port}&mod_key={$mod_key}\" --no-check-certificate > /dev/null 2>&1";
|
$options_json = urlencode(json_encode(array('triggered_by' => 'scheduler')));
|
||||||
|
return "wget -qO- \"{$panelURL}/ogp_api.php?gamemanager/restart&token={$token}&ip={$ip}&port={$port}&mod_key={$mod_key}&options={$options_json}\" --no-check-certificate > /dev/null 2>&1";
|
||||||
case "steam_auto_update":
|
case "steam_auto_update":
|
||||||
return "wget -qO- \"{$panelURL}/ogp_api.php?gamemanager/update&token={$token}&ip={$ip}&port={$port}&mod_key={$mod_key}&type=steam\" --no-check-certificate > /dev/null 2>&1";
|
return "wget -qO- \"{$panelURL}/ogp_api.php?gamemanager/update&token={$token}&ip={$ip}&port={$port}&mod_key={$mod_key}&type=steam\" --no-check-certificate > /dev/null 2>&1";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1099,6 +1099,16 @@ function api_gamemanager()
|
||||||
|
|
||||||
if($request[0] == "restart")
|
if($request[0] == "restart")
|
||||||
{
|
{
|
||||||
|
$options = array();
|
||||||
|
if(isset($_POST['options']))
|
||||||
|
{
|
||||||
|
$decoded_options = json_decode((string)$_POST['options'], true);
|
||||||
|
if(is_array($decoded_options))
|
||||||
|
$options = $decoded_options;
|
||||||
|
}
|
||||||
|
if(isset($_POST['triggered_by']))
|
||||||
|
$options['triggered_by'] = $_POST['triggered_by'];
|
||||||
|
$restart_reason = (isset($options['triggered_by']) && $options['triggered_by'] === 'scheduler') ? 'scheduled_restart' : 'panel_restart';
|
||||||
$start_cmd = get_start_cmd($user_info,$remote,$server_xml,$home_info,$mod_id,$ip,$port,$db);
|
$start_cmd = get_start_cmd($user_info,$remote,$server_xml,$home_info,$mod_id,$ip,$port,$db);
|
||||||
// Do text replacements in cfg file
|
// Do text replacements in cfg file
|
||||||
if( $server_xml->replace_texts )
|
if( $server_xml->replace_texts )
|
||||||
|
|
@ -1149,7 +1159,8 @@ function api_gamemanager()
|
||||||
$preStart,
|
$preStart,
|
||||||
$envVars,
|
$envVars,
|
||||||
$server_xml->game_key,
|
$server_xml->game_key,
|
||||||
(isset( $server_xml->console_log ) ? $server_xml->console_log : ""));
|
(isset( $server_xml->console_log ) ? $server_xml->console_log : ""),
|
||||||
|
$restart_reason);
|
||||||
|
|
||||||
if($remote_retval === -1)
|
if($remote_retval === -1)
|
||||||
return array("status" => '333', "message" => "The server could not be restarted.");
|
return array("status" => '333', "message" => "The server could not be restarted.");
|
||||||
|
|
|
||||||
123
docs/features/AGENT_ACTIVITY_EVENTS.md
Normal file
123
docs/features/AGENT_ACTIVITY_EVENTS.md
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Agent Activity Events
|
||||||
|
|
||||||
|
GSP can record meaningful agent-detected server lifecycle events in the existing Panel activity log. User and administrator actions are still logged by the Panel as before; agents only report operational outcomes that the Panel cannot know by itself.
|
||||||
|
|
||||||
|
## Transport
|
||||||
|
|
||||||
|
Agents send JSON to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Panel/agent_event_receiver.php
|
||||||
|
```
|
||||||
|
|
||||||
|
The endpoint authenticates each request with:
|
||||||
|
|
||||||
|
- `X-GSP-Agent-Id`: the Panel `remote_server_id`
|
||||||
|
- `X-GSP-Agent-Timestamp`: current Unix timestamp
|
||||||
|
- `X-GSP-Agent-Signature`: `sha256=` plus HMAC-SHA256 of `timestamp.body`
|
||||||
|
|
||||||
|
The HMAC key is the existing remote server encryption key. Agents do not receive database credentials and never write directly to the Panel database.
|
||||||
|
|
||||||
|
## Event Types
|
||||||
|
|
||||||
|
Supported event types:
|
||||||
|
|
||||||
|
- `server_process_stopped`
|
||||||
|
- `server_process_started`
|
||||||
|
- `server_crash_detected`
|
||||||
|
- `server_unresponsive`
|
||||||
|
- `automatic_restart_started`
|
||||||
|
- `automatic_restart_succeeded`
|
||||||
|
- `automatic_restart_failed`
|
||||||
|
- `scheduled_restart_started`
|
||||||
|
- `scheduled_restart_succeeded`
|
||||||
|
- `scheduled_restart_failed`
|
||||||
|
- `server_stop_confirmed`
|
||||||
|
- `server_start_confirmed`
|
||||||
|
- `server_port_missing`
|
||||||
|
- `server_port_restored`
|
||||||
|
- `server_process_found_without_port`
|
||||||
|
- `server_port_found_without_managed_process`
|
||||||
|
- `restart_attempt_limit_reached`
|
||||||
|
- `agent_event_delivery_failed`
|
||||||
|
- `agent_event_queue_replayed`
|
||||||
|
|
||||||
|
Severity values are `info`, `notice`, `warning`, `error`, and `critical`. The receiver also accepts `success` for compatibility with existing UI wording.
|
||||||
|
|
||||||
|
## Payload
|
||||||
|
|
||||||
|
Agents include a UUID, UTC timestamp, type, severity, source, OS, hostname, remote server ID, home ID, path, IP, ports, session name, PID, restart reason, expected and actual states, message, technical details, and correlation ID where available.
|
||||||
|
|
||||||
|
Payloads must not contain passwords, Steam credentials, database credentials, encryption keys, full environment dumps, or sensitive command-line arguments.
|
||||||
|
|
||||||
|
## Validation And Deduplication
|
||||||
|
|
||||||
|
The Panel receiver:
|
||||||
|
|
||||||
|
1. Verifies the HMAC signature.
|
||||||
|
2. Rejects stale request timestamps.
|
||||||
|
3. Validates event type and severity.
|
||||||
|
4. Confirms `home_id` belongs to the reporting remote server when provided.
|
||||||
|
5. Truncates public fields to safe lengths.
|
||||||
|
6. Escapes activity-log output.
|
||||||
|
7. Deduplicates by `event_uuid`.
|
||||||
|
|
||||||
|
## Activity Log Schema
|
||||||
|
|
||||||
|
Existing logger rows remain valid. The database helper extends the logger table idempotently with:
|
||||||
|
|
||||||
|
- `source_type`
|
||||||
|
- `category`
|
||||||
|
- `event_type`
|
||||||
|
- `severity`
|
||||||
|
- `remote_server_id`
|
||||||
|
- `home_id`
|
||||||
|
- `event_uuid`
|
||||||
|
- `correlation_id`
|
||||||
|
- `actor`
|
||||||
|
- `metadata_json`
|
||||||
|
|
||||||
|
Fresh installs receive the extended schema from the administration module. Existing installs are migrated lazily when the logger or receiver is used.
|
||||||
|
|
||||||
|
## Filters
|
||||||
|
|
||||||
|
Administration -> Watch Logger supports combined filters for source, category, severity, user ID, agent ID, home ID, date range, and search text. Pagination preserves the active filters.
|
||||||
|
|
||||||
|
## Lifecycle Notes
|
||||||
|
|
||||||
|
The agents use existing process, screen/session, PID metadata, and port validation. They do not use `SERVER_STOPPED` marker files.
|
||||||
|
|
||||||
|
Normal Panel start/stop/restart button clicks remain Panel-side actions. Agents add confirmed operational outcomes, such as stop confirmation after process and port validation, start confirmation after status polling sees the required port, unexpected offline transitions, unresponsive process states, and scheduled restart outcomes.
|
||||||
|
|
||||||
|
External process kills, such as BEC terminating a DayZ Mod server, are detected when validated status polling observes an `ONLINE -> OFFLINE` transition without a stop hint. That transition is reported as `server_crash_detected`.
|
||||||
|
|
||||||
|
## Offline Queue
|
||||||
|
|
||||||
|
Agents append undelivered events to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
events/pending-events.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
Each line is one JSON event. The queue is retried when later events are delivered. The queue is rotated when it exceeds 1 MB. Agent lifecycle work continues even when the Panel is unavailable.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Agent `Cfg/Config.pm` should include:
|
||||||
|
|
||||||
|
```perl
|
||||||
|
agent_event_url => 'https://panel.example.com/agent_event_receiver.php',
|
||||||
|
remote_server_id => '1',
|
||||||
|
```
|
||||||
|
|
||||||
|
If `agent_event_url` is empty, the agent tries to derive the receiver URL from `web_api_url`. The `key` must match the Panel remote server encryption key.
|
||||||
|
|
||||||
|
## Manual Test Plan
|
||||||
|
|
||||||
|
1. Start a server from the Panel and confirm the existing Panel user action still appears.
|
||||||
|
2. Confirm the agent reports `server_start_confirmed` only after validated status shows the process/session and required port ready.
|
||||||
|
3. Stop from the Panel and confirm `server_stop_confirmed`.
|
||||||
|
4. Kill a game process outside the Panel and poll status; confirm `server_crash_detected`.
|
||||||
|
5. Trigger a scheduled restart and confirm scheduled start/success/failure events.
|
||||||
|
6. Disconnect the Panel, trigger an event, reconnect, and confirm queued delivery without duplicates.
|
||||||
|
7. Test Watch Logger filters for agent, lifecycle, warning/error, and home ID.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue