diff --git a/INFO b/INFO index ee750f57..4e67d21e 100644 --- a/INFO +++ b/INFO @@ -21,7 +21,7 @@ [info] name = syslog -version = 4.6 +version = 4.11 longname = Syslog Monitoring author = The Cacti Group email = diff --git a/includes/functions.php b/includes/functions.php index f2f0cfdf..28c67fbb 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -338,13 +338,31 @@ function syslog_strip_auto_dates(string $search, mixed $date1, mixed $date2): st return $search; } +/** + * Check a Syslog realm by its Cacti-assigned ID, not a filename mapping. + */ +function syslog_realm_allowed(string $display): bool { + $realms = db_fetch_assoc_prepared( + 'SELECT id FROM plugin_realms WHERE plugin = ? AND display = ?', + ['syslog', $display] + ); + + foreach (is_array($realms) ? $realms : [] as $realm) { + if (is_realm_allowed((int) $realm['id'] + 100)) { + return true; + } + } + + return false; +} + /** * Permission to make saved searches global and to manage other users' global searches. * * @return bool True if the user has admin permission. */ function syslog_saved_search_admin(): bool { - return api_plugin_user_realm_auth('syslog_saved_searches.php'); + return syslog_realm_allowed('Syslog Administration'); } /** @@ -353,7 +371,7 @@ function syslog_saved_search_admin(): bool { * @return bool True if the user has share permission. */ function syslog_saved_search_share(): bool { - return syslog_saved_search_admin() || api_plugin_user_realm_auth('syslog_saved_searches_share.php'); + return syslog_saved_search_admin() || syslog_realm_allowed('Share Saved Templates'); } /** @@ -362,7 +380,7 @@ function syslog_saved_search_share(): bool { * @return bool True if the user has dashboard admin permission. */ function syslog_dashboard_admin(): bool { - return api_plugin_user_realm_auth('syslog_alerts.php'); + return syslog_realm_allowed('Syslog Administration'); } /** @@ -371,7 +389,7 @@ function syslog_dashboard_admin(): bool { * @return bool True if the user has dashboard share permission. */ function syslog_dashboard_share(): bool { - return syslog_dashboard_admin() || api_plugin_user_realm_auth('syslog_dashboards_share.php'); + return syslog_dashboard_admin() || syslog_realm_allowed('Share Dashboards'); } /** @@ -850,7 +868,7 @@ function syslog_allow_edits(): bool { * @return bool True when rule edits are permitted for this user and poller. */ function syslog_allow_rule_edits(): bool { - return syslog_allow_edits() && api_plugin_user_realm_auth('syslog_rule_administrator.php'); + return syslog_allow_edits() && syslog_realm_allowed('Rule Administrator'); } /** @@ -3234,10 +3252,8 @@ function syslog_rule_preview(array $rule, string $rule_type = 'alert', int $rows * @return string A JSON document with the preview result, or an error. */ function syslog_rule_test_action(string $rule_type): string { - $realm_page = $rule_type === 'removal' ? 'syslog_removal.php' : 'syslog_alerts.php'; - - if (!api_plugin_user_realm_auth($realm_page) || !syslog_allow_rule_edits()) { - cacti_log("WARNING: syslog rule preview blocked -- missing realm for '$realm_page'", false, 'SYSLOG'); + if (!syslog_realm_allowed('Rule Viewer') || !syslog_allow_rule_edits()) { + cacti_log('WARNING: syslog rule preview blocked -- missing rule realm', false, 'SYSLOG'); return (string) json_encode(['error' => __('Permission denied.', 'syslog')]); } @@ -6773,14 +6789,12 @@ function syslog_message_rule_links($id, $source, $received, $host = ''): array { } $query = http_build_query(['id' => $id, 'action' => 'newedit', 'type' => '0', 'date' => $received]); - if (syslog_allow_rule_edits()) { + if (syslog_realm_allowed('Rule Viewer') && syslog_allow_rule_edits()) { foreach (['alarm' => 'syslog_alerts.php', 'removal' => 'syslog_removal.php'] as $action => $page) { - if (api_plugin_user_realm_auth($page)) { - $links[$action] = $page . '?' . $query; - } + $links[$action] = $page . '?' . $query; } } - if ($host !== '' && api_plugin_user_realm_auth('syslog_device_rules.php')) { + if ($host !== '' && syslog_allow_rule_edits()) { $links['device'] = 'syslog_device_rules.php?' . http_build_query(['action' => 'edit', 'host' => $host]); } return $links; diff --git a/includes/installer.php b/includes/installer.php index b510dc05..dca7e7a3 100644 --- a/includes/installer.php +++ b/includes/installer.php @@ -9,6 +9,23 @@ /* Install and uninstall adviser UI. */ +/** Confirm an existing plugin upgrade without sending it through Cacti's install action. */ +function syslog_upgrade_advisor(string $installed, string $current): void { + top_header(); + print "'; + html_end_box(); + print ""; + form_hidden_box('mode', 'enable', ''); + form_hidden_box('id', 'syslog', ''); + form_hidden_box('syslog_upgrade_confirm', '1', ''); + syslog_confirm_button('install', 'plugins.php', 1, true); + print '
"; + html_start_box(__('Syslog Upgrade Advisor', 'syslog'), '100%', '', '3', 'center', ''); + print '
' . __('Upgrade Syslog from %s to %s? Existing messages and rules will be preserved. Schema changes may take time.', html_escape($installed), html_escape($current), 'syslog') . '
'; + bottom_footer(); + exit; +} + /** * Display the install/upgrade advisor form. * @@ -268,7 +285,7 @@ function syslog_uninstall_advisor(): void { * * @return void */ -function syslog_confirm_button(string $action, string $cancel_url, int $syslog_exists): void { +function syslog_confirm_button(string $action, string $cancel_url, int $syslog_exists, bool $full_page = false): void { if ($action == 'install') { if ($syslog_exists) { $value = __('Upgrade', 'syslog'); @@ -289,6 +306,10 @@ function syslog_confirm_button(string $action, string $cancel_url, int $syslog_e $(function() { $('#syslog_install').submit(function(event) { event.preventDefault(); + + submitPageUsingPost('plugins.php?mode=enable&id=syslog&syslog_upgrade_confirm=1'); + return; + /* set the URL */ var strURL = $(this).attr('action'); diff --git a/includes/schema.php b/includes/schema.php index 9ab9c562..39027f7d 100644 --- a/includes/schema.php +++ b/includes/schema.php @@ -124,31 +124,104 @@ function syslog_create_device_rule_table(): void { ROW_FORMAT=Dynamic"); } -/** Repair share-grant tables on installs whose plugin version is already current. */ -function syslog_ensure_share_tables(): void { - global $syslogdb_default; +/** Create or update saved-search storage. */ +function syslog_ensure_saved_search_tables(): void { + global $syslog_cnn; + + db_update_table('syslog_saved_searches', [ + 'type' => 'InnoDB', + 'columns' => [ + ['name' => 'id', 'type' => 'int(10)', 'NULL' => false, 'auto_increment' => true], + ['name' => 'hash', 'type' => 'varchar(32)', 'NULL' => false, 'default' => ''], + ['name' => 'name', 'type' => 'varchar(128)', 'NULL' => false, 'default' => ''], + ['name' => 'search', 'type' => 'text', 'NULL' => false], + ['name' => 'removal', 'type' => 'int(10)', 'NULL' => false, 'default' => '1'], + ['name' => 'grouping', 'type' => 'int(10)', 'NULL' => false, 'default' => '0'], + ['name' => 'user', 'type' => 'varchar(32)', 'NULL' => false, 'default' => ''], + ['name' => 'is_global', 'type' => 'char(2)', 'NULL' => false, 'default' => ''], + ['name' => 'date', 'type' => 'int(16)', 'NULL' => false, 'default' => '0'] + ], + 'primary' => ['id'], + 'keys' => [['name' => 'owner', 'columns' => ['user']]] + ], false, true, $syslog_cnn); +} - if (!syslog_db_table_exists('syslog_dashboards_perm', false)) { - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_dashboards_perm` ( - `dashboard_id` int(10) NOT NULL default '0', - `type` varchar(5) NOT NULL default '', - `item_id` int(10) NOT NULL default '0', - PRIMARY KEY (`dashboard_id`, `type`, `item_id`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); - } - - if (!syslog_db_table_exists('syslog_saved_searches_perm', false)) { - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_saved_searches_perm` ( - `search_id` int(10) NOT NULL default '0', - `type` varchar(5) NOT NULL default '', - `item_id` int(10) NOT NULL default '0', - PRIMARY KEY (`search_id`, `type`, `item_id`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); +/** Create or update share-grant tables. */ +function syslog_ensure_share_tables(): void { + global $syslog_cnn; + + foreach (['syslog_dashboards_perm' => 'dashboard_id', 'syslog_saved_searches_perm' => 'search_id'] as $table => $id) { + db_update_table($table, [ + 'type' => 'InnoDB', + 'columns' => [ + ['name' => $id, 'type' => 'int(10)', 'NULL' => false, 'default' => '0'], + ['name' => 'type', 'type' => 'varchar(5)', 'NULL' => false, 'default' => ''], + ['name' => 'item_id', 'type' => 'int(10)', 'NULL' => false, 'default' => '0'] + ], + 'primary' => [$id, 'type', 'item_id'] + ], false, true, $syslog_cnn); } } +/** Create or update dashboard storage. */ +function syslog_ensure_dashboard_tables(): void { + global $syslog_cnn; + + db_update_table('syslog_dashboards', [ + 'type' => 'InnoDB', + 'columns' => [ + ['name' => 'id', 'type' => 'int(10)', 'NULL' => false, 'auto_increment' => true], + ['name' => 'hash', 'type' => 'varchar(32)', 'NULL' => false, 'default' => ''], + ['name' => 'name', 'type' => 'varchar(128)', 'NULL' => false, 'default' => ''], + ['name' => 'user', 'type' => 'varchar(32)', 'NULL' => false, 'default' => ''], + ['name' => 'is_global', 'type' => 'char(2)', 'NULL' => false, 'default' => ''], + ['name' => 'date', 'type' => 'int(16)', 'NULL' => false, 'default' => '0'], + ['name' => 'updated', 'type' => 'int(16)', 'NULL' => false, 'default' => '0'] + ], + 'primary' => ['id'], + 'keys' => [['name' => 'owner', 'columns' => ['user']]] + ], false, true, $syslog_cnn); + + db_update_table('syslog_dashboard_panels', [ + 'type' => 'InnoDB', + 'columns' => [ + ['name' => 'id', 'type' => 'int(10)', 'NULL' => false, 'auto_increment' => true], + ['name' => 'dashboard_id', 'type' => 'int(10)', 'NULL' => false, 'default' => '0'], + ['name' => 'title', 'type' => 'varchar(128)', 'NULL' => false, 'default' => ''], + ['name' => 'expression', 'type' => 'text', 'NULL' => false], + ['name' => 'source', 'type' => 'varchar(16)', 'NULL' => false, 'default' => 'syslog'], + ['name' => 'removal', 'type' => 'int(10)', 'NULL' => false, 'default' => '1'], + ['name' => 'kind', 'type' => 'varchar(16)', 'NULL' => false, 'default' => 'timeseries'], + ['name' => 'chart', 'type' => 'varchar(16)', 'NULL' => false, 'default' => 'line'], + ['name' => 'field', 'type' => 'varchar(16)', 'NULL' => false, 'default' => 'host'], + ['name' => 'interval', 'type' => 'varchar(16)', 'NULL' => false, 'default' => 'dashboard'], + ['name' => 'timespan', 'type' => 'varchar(16)', 'NULL' => false, 'default' => 'dashboard'], + ['name' => 'top_n', 'type' => 'int(10)', 'NULL' => false, 'default' => '10'], + ['name' => 'width', 'type' => 'smallint(5)', 'unsigned' => true, 'NULL' => false, 'default' => '1'], + ['name' => 'height', 'type' => 'smallint(5)', 'unsigned' => true, 'NULL' => false, 'default' => '0'], + ['name' => 'position', 'type' => 'int(10)', 'NULL' => false, 'default' => '0'], + ['name' => 'date', 'type' => 'int(16)', 'NULL' => false, 'default' => '0'] + ], + 'primary' => ['id'], + 'keys' => [['name' => 'dashboard', 'columns' => ['dashboard_id']]] + ], false, true, $syslog_cnn); +} + +/** Create or update status storage. */ +function syslog_ensure_status_table(): void { + global $syslog_cnn; + + db_update_table('syslog_status', [ + 'type' => 'InnoDB', + 'columns' => [ + ['name' => 'name', 'type' => 'varchar(64)', 'NULL' => false, 'default' => ''], + ['name' => 'value', 'type' => 'text', 'NULL' => false], + ['name' => 'updated', 'type' => 'int(16)', 'NULL' => false, 'default' => '0'] + ], + 'primary' => ['name'] + ], false, true, $syslog_cnn); +} + /** * Create the durable, Syslog-owned remote collector replication outbox. * @@ -348,8 +421,8 @@ function syslog_create_partitioned_syslog_table($engine = 'InnoDB', $days = 30, * * @return void */ -function syslog_setup_table_new(array $options): void { - global $config, $settings, $syslogdb_default, $syslog_levels; +function syslog_setup_table_new(array $options, bool $repair = false): void { + global $config, $settings, $syslogdb_default, $syslog_levels, $syslog_cnn; syslog_connect(); @@ -404,7 +477,7 @@ function syslog_setup_table_new(array $options): void { } // validate some simple information - $truncate = isset($options['upgrade_type']) && $options['upgrade_type'] == 'truncate' ? true : false; + $truncate = !$repair && isset($options['upgrade_type']) && $options['upgrade_type'] == 'truncate'; $engine = syslog_install_storage_engine(isset($options['engine']) ? $options['engine'] : 'InnoDB', $options); $syslogexists = sizeof(syslog_db_fetch_row("SHOW TABLES FROM `$syslogdb_default` LIKE 'syslog'")); @@ -429,10 +502,12 @@ function syslog_setup_table_new(array $options): void { } // set table construction settings for the remote pollers - set_config_option('syslog_install_upgrade_type', empty($options['upgrade_type']) ? '' : $options['upgrade_type'], true); - set_config_option('syslog_install_engine', $engine, true); - set_config_option('syslog_install_db_type', empty($options['db_type']) ? '' : $options['db_type'], true); - set_config_option('syslog_install_days', empty($options['days']) ? '' : $options['days'], true); + if (!$repair) { + set_config_option('syslog_install_upgrade_type', empty($options['upgrade_type']) ? '' : $options['upgrade_type'], true); + set_config_option('syslog_install_engine', $engine, true); + set_config_option('syslog_install_db_type', empty($options['db_type']) ? '' : $options['db_type'], true); + set_config_option('syslog_install_days', empty($options['days']) ? '' : $options['days'], true); + } if ($truncate) { syslog_db_execute("DROP TABLE IF EXISTS `$syslogdb_default`.`syslog`"); @@ -537,15 +612,7 @@ function syslog_setup_table_new(array $options): void { ENGINE=$engine $row_format"); - $present = syslog_db_fetch_row("SHOW TABLES FROM `$syslogdb_default` LIKE 'syslog_reports'"); - - if (cacti_sizeof($present)) { - $newreport = syslog_db_column_exists('syslog_reports', 'body'); - } else { - $newreport = true; - } - - if ($truncate || !$newreport) { + if ($truncate) { syslog_db_execute("DROP TABLE IF EXISTS `$syslogdb_default`.`syslog_reports`"); } @@ -568,28 +635,12 @@ function syslog_setup_table_new(array $options): void { PRIMARY KEY (id)) ENGINE=InnoDB ROW_FORMAT=Dynamic"); + db_update_table('syslog_reports', [ + 'columns' => [['name' => 'body', 'type' => 'varchar(8192)', 'NULL' => false, 'default' => '0', 'after' => 'lastsent']], + 'primary' => ['id'] + ], false, true, $syslog_cnn); - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_saved_searches` ( - `id` int(10) NOT NULL auto_increment, - `name` varchar(128) NOT NULL default '', - `search` text NOT NULL, - `removal` int(10) NOT NULL default '1', - `grouping` int(10) NOT NULL default '0', - `user` varchar(32) NOT NULL default '', - `is_global` char(2) NOT NULL default '', - `date` int(16) NOT NULL default '0', - PRIMARY KEY (`id`), - KEY owner (`user`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); - - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_status` ( - `name` varchar(64) NOT NULL default '', - `value` text NOT NULL, - `updated` int(16) NOT NULL default '0', - PRIMARY KEY (`name`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); + syslog_ensure_status_table(); if ($truncate) { syslog_db_execute("DROP TABLE IF EXISTS `$syslogdb_default`.`syslog_hosts`"); @@ -617,7 +668,12 @@ function syslog_setup_table_new(array $options): void { ROW_FORMAT=Dynamic COMMENT='Contains all hosts currently in the syslog table'"); - syslog_db_execute("DROP TABLE IF EXISTS `$syslogdb_default`.`syslog_facilities`"); + $facilities_exists = syslog_db_table_exists('syslog_facilities', false); + + if (!$repair) { + syslog_db_execute("DROP TABLE IF EXISTS `$syslogdb_default`.`syslog_facilities`"); + $facilities_exists = false; + } syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_facilities` ( `facility_id` int(10) unsigned NOT NULL, @@ -628,13 +684,20 @@ function syslog_setup_table_new(array $options): void { ENGINE=InnoDB ROW_FORMAT=Dynamic"); - syslog_db_execute("INSERT INTO `$syslogdb_default`.`syslog_facilities` (facility_id, facility) VALUES + if (!$facilities_exists) { + syslog_db_execute("INSERT INTO `$syslogdb_default`.`syslog_facilities` (facility_id, facility) VALUES (0,'kern'), (1,'user'), (2,'mail'), (3,'daemon'), (4,'auth'), (5,'syslog'), (6,'lpd'), (7,'news'), (8,'uucp'), (9,'crond'), (10,'authpriv'), (11,'ftpd'), (12,'ntpd'), (13,'logaudit'), (14,'logalert'), (15,'crond'), (16,'local0'), (17,'local1'), (18,'local2'), (19,'local3'), (20,'local4'), (21,'local5'), (22,'local6'), (23,'local7')"); + } - syslog_db_execute("DROP TABLE IF EXISTS `$syslogdb_default`.`syslog_priorities`"); + $priorities_exists = syslog_db_table_exists('syslog_priorities', false); + + if (!$repair) { + syslog_db_execute("DROP TABLE IF EXISTS `$syslogdb_default`.`syslog_priorities`"); + $priorities_exists = false; + } syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_priorities` ( `priority_id` int(10) unsigned NOT NULL, @@ -645,8 +708,10 @@ function syslog_setup_table_new(array $options): void { ENGINE=InnoDB ROW_FORMAT=Dynamic"); - syslog_db_execute("INSERT INTO `$syslogdb_default`.`syslog_priorities` (priority_id, priority) VALUES + if (!$priorities_exists) { + syslog_db_execute("INSERT INTO `$syslogdb_default`.`syslog_priorities` (priority_id, priority) VALUES (0,'emerg'), (1,'alert'), (2,'crit'), (3,'err'), (4,'warning'), (5,'notice'), (6,'info'), (7,'debug'), (8,'other')"); + } syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_host_facilities` ( `host_id` int(10) unsigned NOT NULL, @@ -685,18 +750,64 @@ function syslog_setup_table_new(array $options): void { ENGINE=InnoDB ROW_FORMAT=Dynamic"); + syslog_ensure_saved_search_tables(); syslog_ensure_share_tables(); + syslog_ensure_dashboard_tables(); + syslog_create_device_rule_table(); + + if (!$repair) { + if (!isset($settings['syslog'])) { + syslog_config_settings(); + } + + foreach ($settings['syslog'] as $name => $values) { + if (isset($values['default']) && db_fetch_cell_prepared('SELECT value FROM settings WHERE name = ?', [$name]) === false) { + set_config_option($name, $values['default']); + } + } + set_config_option('syslog_install_upgrade_type', 'upgrade'); + } +} + +/** Complete an interrupted install or upgrade without dropping existing data. */ +function syslog_ensure_table_structures(): bool { + require_once(__DIR__ . '/settings.php'); + + $tables = [ + 'syslog', 'syslog_alert', 'syslog_incoming', 'syslog_alert_suppression', + 'syslog_remove', 'syslog_reports', 'syslog_status', 'syslog_programs', + 'syslog_hosts', 'syslog_facilities', 'syslog_priorities', 'syslog_host_facilities', + 'syslog_removed', 'syslog_logs', 'syslog_device_rule', + 'syslog_replication_output', 'syslog_replication_receipts', + 'syslog_replication_collectors', 'syslog_replication_recovery' + ]; + $rebuilt = false; - if (!isset($settings['syslog'])) { - syslog_config_settings(); + foreach ($tables as $table) { + if (!syslog_db_table_exists($table, false)) { + syslog_setup_table_new([], true); + $rebuilt = true; + break; + } } - foreach ($settings['syslog'] as $name => $values) { - if (isset($values['default'])) { - set_config_option($name, $values['default']); + if (!$rebuilt) { + syslog_ensure_status_table(); + syslog_ensure_saved_search_tables(); + syslog_ensure_share_tables(); + syslog_ensure_dashboard_tables(); + } + $tables = array_merge($tables, [ + 'syslog_saved_searches', 'syslog_saved_searches_perm', + 'syslog_dashboards', 'syslog_dashboards_perm', 'syslog_dashboard_panels' + ]); + + foreach ($tables as $table) { + if (!syslog_db_table_exists($table, false)) { + cacti_log("SYSLOG ERROR: Schema upgrade did not create $table", false, 'SYSLOG'); + return false; } } - // ensure that the setting if set previously to truncate is switched to upgrade - set_config_option('syslog_install_upgrade_type', 'upgrade'); + return true; } diff --git a/includes/settings.php b/includes/settings.php index 56ae969a..bf1a4233 100644 --- a/includes/settings.php +++ b/includes/settings.php @@ -339,51 +339,6 @@ function syslog_config_settings(): void { } } -/** Refresh permission labels, filenames and grouping from current realm IDs. */ -function syslog_refresh_permission_roles(): void { - global $user_auth_realms, $user_auth_realm_filenames, $user_auth_roles; - - $realms = db_fetch_assoc_prepared('SELECT id, file, display FROM plugin_realms WHERE plugin = ? ORDER BY id', ['syslog']); - if (!is_array($realms)) { - return; - } - $ids = []; - foreach ($realms as $realm) { - $id = (int) $realm['id'] + 100; - $ids[] = $id; - $user_auth_realms[$id] = $realm['display']; - foreach (explode(',', $realm['file']) as $file) { - $user_auth_realm_filenames[$file] = $id; - unset($_SESSION['sess_auth_names'][$file]); - } - } - $user_auth_roles[__('Syslog', 'syslog')] = array_values(array_unique($ids)); - - $rule_administrator = $user_auth_realm_filenames['syslog_rule_administrator.php'] ?? 0; - if ($rule_administrator && is_realm_allowed($rule_administrator)) { - foreach (['syslog_alerts.php', 'syslog_removal.php', 'syslog_reports.php'] as $file) { - $user_auth_realm_filenames[$file] = $rule_administrator; - } - } - - $administrator = $user_auth_realm_filenames['syslog_administrator.php'] ?? 0; - if ($administrator && is_realm_allowed($administrator)) { - foreach ([ - 'syslog.php', - 'syslog_alerts.php', - 'syslog_removal.php', - 'syslog_reports.php', - 'syslog_rule_administrator.php', - 'syslog_saved_searches.php', - 'syslog_saved_searches_share.php', - 'syslog_dashboards.php', - 'syslog_dashboards_share.php' - ] as $file) { - $user_auth_realm_filenames[$file] = $administrator; - } - } -} - /** * Setup the Syslog specific global arrays used throughout the plugin. * @@ -546,8 +501,6 @@ function syslog_config_arrays(): void { $menu_glyphs[__('Syslog Settings', 'syslog')] = 'fa fa-life-ring'; } - syslog_refresh_permission_roles(); - if (isset($_SESSION['syslog_info']) && $_SESSION['syslog_info'] != '') { $messages['syslog_info'] = ['message' => $_SESSION['syslog_info'], 'type' => 'info']; } @@ -579,20 +532,6 @@ function syslog_settings_bottom(): void { >\n" "Language-Team: Cacti Developers \n" @@ -25,7 +25,7 @@ msgstr "" msgid "Emergency" msgstr "" -#: includes/functions.php setup.php +#: includes/functions.php includes/settings.php msgid "Critical" msgstr "" @@ -37,11 +37,11 @@ msgstr "" msgid "Error" msgstr "" -#: includes/functions.php setup.php +#: includes/functions.php includes/settings.php msgid "Warning" msgstr "" -#: includes/functions.php setup.php +#: includes/functions.php includes/settings.php msgid "Notice" msgstr "" @@ -145,7 +145,7 @@ msgstr "" msgid "Permission denied." msgstr "" -#: includes/functions.php setup.php +#: includes/functions.php includes/utilities.php msgid "Invalid request. Please try again." msgstr "" @@ -294,1051 +294,1063 @@ msgstr "" msgid "Event Report - %s" msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard panel source." +#: includes/installer.php +msgid "Syslog Upgrade Advisor" msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard panel type." +#: includes/installer.php +#, php-format +msgid "Upgrade Syslog from %s to %s? Existing messages and rules will be preserved. Schema changes may take time." msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard chart type." +#: includes/installer.php +msgid "What upgrade/install type do you wish to use" msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard breakdown field." +#: includes/installer.php +msgid "When you have very large tables, performing a Truncate will be much quicker. If you are concerned about archive data, you can choose either Inline, which will freeze your browser for the period of this upgrade, or background, which will create a background process to bring your old syslog data from a backup table to the new syslog format. Again this process can take several hours." msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard interval." +#: includes/installer.php +msgid "Truncate Syslog Table" msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard timespan." +#: includes/installer.php +msgid "Inline Upgrade" msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard record type." +#: includes/installer.php +msgid "Background Upgrade" msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard top count." +#: includes/installer.php +msgid "Database Storage Engine" msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard panel width." +#: includes/installer.php +msgid "The storage engine for the analytical tables. Only InnoDB and, on MariaDB, Aria storage engines are supported. Any Remote Data Collector option requires InnoDB; incompatible combinations cannot be submitted." msgstr "" -#: lib/syslog_dashboard.php -msgid "Invalid dashboard panel height." +#: includes/installer.php +msgid "InnoDB Storage" msgstr "" -#: lib/syslog_dashboard.php syslog.php -#, php-format -msgid "Invalid logical search: %s" +#: includes/installer.php +msgid "Aria Storage" msgstr "" -#: lib/syslog_dashboard.php -msgid "Other" +#: includes/installer.php includes/settings.php +msgid "Remote Message Processing" msgstr "" -#: lib/syslog_dashboard.php -msgid "A valid dashboard panel is required." +#: includes/installer.php includes/settings.php +msgid "Enable Remote Data Collector Message Processing" msgstr "" -#: lib/syslog_dashboard.php -msgid "Dashboard panel not found." +#: includes/installer.php includes/settings.php +msgid "If your Remote Data Collectors have their own Syslog databases and process their messages independently, check this checkbox. By checking this Checkbox, your Remote Data Collectors will need to maintain their own 'config_local.php' file in order to inform Syslog to use an independent database for message display and processing. Please use the template file 'config_local.php.dist' for this purpose. WARNING: Syslog tables will be automatically created as soon as this option is enabled." msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "A name of up to 128 characters is required." +#: includes/installer.php includes/settings.php +msgid "Remote Data Collector Rules Sync" msgstr "" -#: lib/syslog_dashboard.php -msgid "A valid dashboard is required." +#: includes/installer.php includes/settings.php +msgid "If your Remote Data Collectors have their own Syslog databases and process thrie messages independently, check this checkbox if you wish the Main Cacti databases Alerts, Removal and Report rules to be sent to the Remote Cacti System." msgstr "" -#: lib/syslog_dashboard.php -msgid "Dashboard not found." +#: includes/installer.php includes/settings.php +msgid "Store Records on Remote Collector" msgstr "" -#: lib/syslog_dashboard.php -msgid "You may only share your own dashboards." +#: includes/installer.php includes/settings.php +msgid "Keep processed messages in the remote collector syslog table after they have been delivered to the Main Collector. When disabled, records are retained locally only while the Main Collector is unavailable and are removed after confirmed recovery delivery." msgstr "" -#: lib/syslog_dashboard.php -msgid "copy" +#: includes/installer.php +msgid "Retention Policy" msgstr "" -#: lib/syslog_dashboard.php -msgid "Dashboard could not be copied." +#: includes/installer.php +msgid "Choose how many days of Syslog values you wish to maintain in the database." msgstr "" -#: lib/syslog_dashboard.php -msgid "A valid panel position is required." +#: includes/installer.php +msgid "Partitions per Day" msgstr "" -#: lib/syslog_dashboard.php -msgid "A title of up to 128 characters is required." +#: includes/installer.php +msgid "Select the number of partitions per day that you wish to create." msgstr "" -#: lib/syslog_dashboard.php -msgid "Chart library unavailable on this Cacti installation." +#: includes/installer.php +#, php-format +msgid "%d Per Day" msgstr "" -#: lib/syslog_dashboard.php -msgid "Edit Panel" +#: includes/installer.php +msgid "Upgrade" msgstr "" -#: lib/syslog_dashboard.php -msgid "New Panel" +#: includes/installer.php +msgid "Install" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Save" +#: includes/installer.php +#, php-format +msgid "Syslog %s Advisor" msgstr "" -#: lib/syslog_dashboard.php setup.php syslog.php syslog_alerts.php -#: syslog_dashboards.php syslog_device_rules.php syslog_removal.php -#: syslog_reports.php syslog_saved_searches.php -msgid "Cancel" +#: includes/installer.php +msgid "WARNING: Syslog Upgrade is Time Consuming!!!" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Apply" +#: includes/installer.php +msgid "The upgrade of the 'main' syslog table can be a very time consuming process. As such, it is recommended that you either reduce the size of your syslog table prior to upgrading, or choose the background option

If you choose the background option, your legacy syslog table will be renamed, and a new syslog table will be created. Then, an upgrade process will be launched in the background. Again, this background process can quite a bit of time to complete. However, your data will be preserved

Regardless of your choice, all existing removal and alert rules will be maintained during the upgrade process.

Press 'Upgrade' to proceed with the upgrade, or 'Cancel' to return to the Plugins menu." msgstr "" -#: lib/syslog_dashboard.php setup.php syslog.php syslog_dashboards.php -#: syslog_device_rules.php syslog_removal.php syslog_saved_searches.php -msgid "Delete" +#: includes/installer.php +msgid "You can also set the database storage engine for the analytical tables syslog and syslog_remove. Only the InnoDB storage engine and, on MariaDB, the Aria storage engine are supported. If using MariaDB, we recommend the Aria Storage Engine when no Remote Data Collector options are enabled; collector options require InnoDB." msgstr "" -#: lib/syslog_dashboard.php -msgid "Delete this dashboard panel?" +#: includes/installer.php +msgid "You have several options to choose from when installing Syslog. The first is the Database Architecture. Partitioned tables are the only supported architecture as they prevent the size of the tables from becoming excessive thus slowing queries. Traditional non-partitioned tables are deprecated and are no longer offered for new installs." msgstr "" -#: lib/syslog_dashboard.php -msgid "Delete this dashboard and all of its panels?" +#: includes/installer.php +msgid "Aria storage is unavailable while any Remote Data Collector option is enabled. InnoDB has been selected." msgstr "" -#: lib/syslog_dashboard.php -msgid "Name your dashboard" +#: includes/installer.php +msgid "You can also select the retention duration. Please keep in mind that if you have several hosts logging to syslog, this table can become quite large." msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Make Private" +#: includes/installer.php +#, php-format +msgid "Syslog %s Settings" msgstr "" -#: lib/syslog_dashboard.php -msgid "Share with all" +#: includes/installer.php +msgid "What uninstall method do you want to use?" msgstr "" -#: lib/syslog_dashboard.php -msgid "Save as my copy" +#: includes/installer.php +msgid "When uninstalling syslog, you can remove everything, or only components, just in case you plan on re-installing in the future." msgstr "" -#: lib/syslog_dashboard.php -msgid "Syslog Dashboards" +#: includes/installer.php +msgid "Remove Everything (Logs, Tables, Settings)" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Dashboard" +#: includes/installer.php +msgid "Syslog Data Only" msgstr "" -#: lib/syslog_dashboard.php -msgid "Time Range" +#: includes/installer.php +msgid "Syslog Uninstall Preferences" msgstr "" -#: lib/syslog_dashboard.php -msgid "Last Hour" +#: includes/installer.php +msgid "Uninstall" msgstr "" -#: lib/syslog_dashboard.php -#, php-format -msgid "Last %d Hours" +#: includes/installer.php includes/utilities.php lib/syslog_dashboard.php +#: syslog.php syslog_alerts.php syslog_dashboards.php syslog_device_rules.php +#: syslog_removal.php syslog_reports.php syslog_saved_searches.php +msgid "Cancel" msgstr "" -#: lib/syslog_dashboard.php -msgid "Last Day" +#: includes/navigation.php includes/settings.php +msgid "Syslog" msgstr "" -#: lib/syslog_dashboard.php -msgid "Last Week" +#: includes/navigation.php +msgid "Syslog Removals" msgstr "" -#: lib/syslog_dashboard.php -#, php-format -msgid "Last %d Weeks" +#: includes/navigation.php +msgid "(Edit)" msgstr "" -#: lib/syslog_dashboard.php -msgid "Last Month" +#: includes/navigation.php +msgid "(Actions)" msgstr "" -#: lib/syslog_dashboard.php -#, php-format -msgid "Last %d Months" +#: includes/navigation.php +msgid "Syslog Alerts" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Refresh" +#: includes/navigation.php +msgid "Syslog Reports" +msgstr "" + +#: includes/navigation.php includes/settings.php syslog_saved_searches.php +msgid "Saved Search Templates" msgstr "" -#: lib/syslog_dashboard.php setup.php syslog_dashboards.php +#: includes/navigation.php includes/settings.php lib/syslog_dashboard.php +#: syslog_dashboards.php msgid "Dashboards" msgstr "" -#: lib/syslog_dashboard.php -msgid "My Dashboards" +#: includes/navigation.php +msgid "Display Syslog in Range" msgstr "" -#: lib/syslog_dashboard.php -msgid "Global Dashboards" +#: includes/settings.php +msgid "Monday" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Shared With Me" +#: includes/settings.php +msgid "Tuesday" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "New" +#: includes/settings.php +msgid "Wednesday" msgstr "" -#: lib/syslog_dashboard.php -msgid "Rename" +#: includes/settings.php +msgid "Friday" msgstr "" -#: lib/syslog_dashboard.php -msgid "Add Panel" +#: includes/settings.php +msgid "Saturday" msgstr "" -#: lib/syslog_dashboard.php -msgid "Create your first dashboard to visualize syslog trends." +#: includes/settings.php +msgid "Sunday" msgstr "" -#: lib/syslog_dashboard.php -msgid "Add a panel to start charting. Panels can use your saved searches." +#: includes/settings.php +msgid "Thursday" msgstr "" -#: lib/syslog_dashboard.php -msgid "Dashboard Panel" +#: includes/settings.php +msgid "Monday through Friday" msgstr "" -#: lib/syslog_dashboard.php -msgid "Title" +#: includes/settings.php +msgid "Saturday and Sunday" msgstr "" -#: lib/syslog_dashboard.php -msgid "Log Source" +#: includes/settings.php +msgid "Every day" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "System Logs" +#: includes/settings.php syslog_alerts.php syslog_device_rules.php +#: syslog_removal.php syslog_reports.php +msgid "Disabled" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Alert Logs" +#: includes/settings.php +msgid "General Settings" msgstr "" -#: lib/syslog_dashboard.php -msgid "Panel Type" +#: includes/settings.php +msgid "Syslog Enabled" msgstr "" -#: lib/syslog_dashboard.php -msgid "Time Series" +#: includes/settings.php +msgid "If this checkbox is set, records will be transferred from the Syslog Incoming table to the main syslog table and Alerts and Reports will be enabled. Please keep in mind that if the system is disabled log entries will still accumulate into the Syslog Incoming table as this is defined by the rsyslog or syslog-ng process." msgstr "" -#: lib/syslog_dashboard.php -msgid "Breakdown" +#: includes/settings.php +msgid "Strip Domains" msgstr "" -#: lib/syslog_dashboard.php -msgid "Chart" +#: includes/settings.php +msgid "A comma delimited list of domains that you wish to remove from the syslog hostname, Examples would be 'mydomain.com, otherdomain.com'" msgstr "" -#: lib/syslog_dashboard.php -msgid "Line" +#: includes/settings.php +msgid "Refresh Interval" msgstr "" -#: lib/syslog_dashboard.php -msgid "Area" +#: includes/settings.php +msgid "This is the time in seconds before the page refreshes." msgstr "" -#: lib/syslog_dashboard.php -msgid "Bar" +#: includes/settings.php +msgid "Max Report Records" msgstr "" -#: lib/syslog_dashboard.php -msgid "Interval" +#: includes/settings.php +msgid "For Threshold based Alerts, what is the maximum number that you wish to show in the report. This is used to limit the size of the html log and Email." msgstr "" -#: lib/syslog_dashboard.php -msgid "Auto" +#: includes/settings.php +#, php-format +msgid "%d Records" msgstr "" -#: lib/syslog_dashboard.php -msgid "Minute" +#: includes/settings.php +msgid "Maximum Syslog Processing Processes" msgstr "" -#: lib/syslog_dashboard.php -msgid "10 Minutes" +#: includes/settings.php +msgid "When set above 1, incoming message processing is spread across parallel worker processes. Each worker handles a disjoint range of incoming messages, speeding up hostname resolution and message transfer under heavy loads. A setting of 1 processes all messages in a single process. Worker processes are not supported on Windows or when the PHP pcntl and POSIX extensions are unavailable, in which case processing falls back to a single process." msgstr "" -#: lib/syslog_dashboard.php -msgid "Hour" +#: includes/settings.php +#, php-format +msgid "%d Worker Process" msgstr "" -#: lib/syslog_dashboard.php -msgid "Day" +#: includes/settings.php +#, php-format +msgid "%d Worker Processes" msgstr "" -#: lib/syslog_dashboard.php -msgid "Field" +#: includes/settings.php +msgid "Command for Opening Tickets" msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php -msgid "Program" +#: includes/settings.php +msgid "This command will be executed for opening Help Desk Tickets. The command will be required to parse multiple input parameters as follows: --alert-name, --severity, --hostlist, --message. The hostlist will be a comma delimited list of hosts impacted by the alert." msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php -msgid "Facility" +#: includes/settings.php +msgid "Host Discovery Options" msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php -msgid "Priority" +#: includes/settings.php +msgid "Enable Hostname Resolution" msgstr "" -#: lib/syslog_dashboard.php -msgid "Top Count" +#: includes/settings.php +msgid "If this checkbox is set, all hostnames are resolved via DNS lookup first (If enabled). If the DNS lookup fails, the system will attempt to resolve the hostname against the Cacti host table and replace it with the Cacti host description. If both DNS and Cacti lookups fail, records are assigned a prefix 'unresolved-Original_hostname'." msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Record Type" +#: includes/settings.php +msgid "Skip DNS Resolution for incoming hosts" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "All Records" +#: includes/settings.php +msgid "If this checkbox is set, the system will not attempt to resolve hosts via DNS lookups. This is useful for environments where DNS resolution is not possible or not desired." msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Main Records" +#: includes/settings.php +msgid "HTML Notification Settings" msgstr "" -#: lib/syslog_dashboard.php syslog.php -msgid "Removed Records" +#: includes/settings.php +msgid "Enable HTML Based Email" msgstr "" -#: lib/syslog_dashboard.php -msgid "Start from Saved Search" +#: includes/settings.php +msgid "If this checkbox is set, all Emails will be sent in HTML format. Otherwise, Emails will be sent in plain text." msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_alerts.php -#: syslog_device_rules.php syslog_reports.php -msgid "None" +#: includes/settings.php +msgid "Format File to Use" msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_saved_searches.php -msgid "Enter message text…" +#: includes/settings.php +msgid "Choose the custom html wrapper and CSS file to use. This file contains both html and CSS to wrap around your report. If it contains more than simply CSS, you need to place a special tag inside of the file. This format tag will be replaced by the report content. These files are located in the 'formats' directory." msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php -#: syslog_saved_searches.php -msgid "Remove condition" +#: includes/settings.php +msgid "Data Retention Settings" msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_saved_searches.php -msgid "Match group" +#: includes/settings.php +msgid "Syslog Retention" msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_saved_searches.php -msgid "Exclude group" +#: includes/settings.php +msgid "This is the number of days to keep events." msgstr "" -#: lib/syslog_dashboard.php -msgid "Dashboard Name" +#: includes/settings.php +msgid "Partition Pre-create Window" msgstr "" -#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_dashboards.php -#: syslog_reports.php syslog_saved_searches.php -msgid "Name" +#: includes/settings.php +msgid "This is the number of future daily partitions to maintain for partitioned Syslog tables." msgstr "" -#: setup.php -msgid "Please rename either your config.php.dist or config_local.php.dist files in the syslog directory, and change setup your database before installing." +#: includes/settings.php syslog_alerts.php +#, php-format +msgid "%d Day" msgstr "" -#: setup.php -msgid "Syslog" +#: includes/settings.php syslog_alerts.php +#, php-format +msgid "%d Days" msgstr "" -#: setup.php -msgid "What upgrade/install type do you wish to use" +#: includes/settings.php +msgid "Partition Recovery Limit" msgstr "" -#: setup.php -msgid "When you have very large tables, performing a Truncate will be much quicker. If you are concerned about archive data, you can choose either Inline, which will freeze your browser for the period of this upgrade, or background, which will create a background process to bring your old syslog data from a backup table to the new syslog format. Again this process can take several hours." +#: includes/settings.php +msgid "When partitions are missing (for example after maintenance downtime), at most this many missing partitions per table are created on each poller run. The remaining gap heals over subsequent runs; retention pruning stays deferred until the future partition horizon is fully restored. Applies to partitioned Syslog tables only." msgstr "" -#: setup.php -msgid "Truncate Syslog Table" +#: includes/settings.php +#, php-format +msgid "%d Partition per run" msgstr "" -#: setup.php -msgid "Inline Upgrade" +#: includes/settings.php +#, php-format +msgid "%d Partitions per run" msgstr "" -#: setup.php -msgid "Background Upgrade" +#: includes/settings.php +msgid "Collector Staleness Threshold" msgstr "" -#: setup.php -msgid "Database Storage Engine" +#: includes/settings.php +msgid "The Syslog Status page shows a warning when no new message has arrived within this many seconds. Set to 0 to use the default of 300 seconds." msgstr "" -#: includes/installer.php -msgid "The storage engine for the analytical tables. Only InnoDB and, on MariaDB, Aria storage engines are supported. Any Remote Data Collector option requires InnoDB; incompatible combinations cannot be submitted." +#: includes/settings.php +msgid "Default (300 seconds)" msgstr "" -#: setup.php -msgid "InnoDB Storage" +#: includes/settings.php +#, php-format +msgid "%d seconds" msgstr "" -#: setup.php -msgid "Aria Storage" +#: includes/settings.php +msgid "Collector Backlog Threshold" msgstr "" -#: setup.php -msgid "Retention Policy" +#: includes/settings.php +msgid "The Syslog Status page shows a warning when more than this many messages sit unprocessed in the Syslog Incoming table. Set to 0 to use the default of 10000 messages." msgstr "" -#: setup.php -msgid "Choose how many days of Syslog values you wish to maintain in the database." +#: includes/settings.php +msgid "Default (10000 messages)" msgstr "" -#: setup.php -msgid "Partitions per Day" +#: includes/settings.php +#, php-format +msgid "%d Messages" msgstr "" -#: setup.php -msgid "Select the number of partitions per day that you wish to create." +#: includes/settings.php +msgid "Syslog Alert Retention" msgstr "" -#: setup.php -#, php-format -msgid "%d Per Day" +#: includes/settings.php +msgid "This is the number of days to keep alert logs." msgstr "" -#: setup.php -msgid "Upgrade" +#: includes/settings.php +msgid "Alert Suppression and Maintenance" msgstr "" -#: setup.php -msgid "Install" +#: includes/settings.php +msgid "Maintenance Window Days" msgstr "" -#: setup.php -#, php-format -msgid "Syslog %s Advisor" +#: includes/settings.php +msgid "Days when all alert notifications are muted during the configured maintenance timeframe." msgstr "" -#: setup.php -msgid "WARNING: Syslog Upgrade is Time Consuming!!!" +#: includes/settings.php +msgid "Maintenance Window Starts" msgstr "" -#: setup.php -msgid "The upgrade of the 'main' syslog table can be a very time consuming process. As such, it is recommended that you either reduce the size of your syslog table prior to upgrading, or choose the background option

If you choose the background option, your legacy syslog table will be renamed, and a new syslog table will be created. Then, an upgrade process will be launched in the background. Again, this background process can quite a bit of time to complete. However, your data will be preserved

Regardless of your choice, all existing removal and alert rules will be maintained during the upgrade process.

Press 'Upgrade' to proceed with the upgrade, or 'Cancel' to return to the Plugins menu." +#: includes/settings.php +msgid "Local start time for the global maintenance window." msgstr "" -#: setup.php -msgid "You can also select the retention duration. Please keep in mind that if you have several hosts logging to syslog, this table can become quite large." +#: includes/settings.php +msgid "Maintenance Window Ends" msgstr "" -#: includes/installer.php -msgid "You can also set the database storage engine for the analytical tables syslog and syslog_remove. Only the InnoDB storage engine and, on MariaDB, the Aria storage engine are supported. If using MariaDB, we recommend the Aria Storage Engine when no Remote Data Collector options are enabled; collector options require InnoDB." +#: includes/settings.php +msgid "Local end time for the global maintenance window. An earlier end time continues into the next day." msgstr "" -#: setup.php -msgid "You have several options to choose from when installing Syslog. The first is the Database Architecture. Partitioned tables are the only supported architecture as they prevent the size of the tables from becoming excessive thus slowing queries. Traditional non-partitioned tables are deprecated and are no longer offered for new installs." +#: includes/settings.php +msgid "One-time Maintenance Starts" msgstr "" -#: setup.php -#, php-format -msgid "Syslog %s Settings" +#: includes/settings.php +msgid "Optional local date and time to start a one-time global maintenance window." msgstr "" -#: setup.php -msgid "What uninstall method do you want to use?" +#: includes/settings.php +msgid "One-time Maintenance Ends" msgstr "" -#: setup.php -msgid "When uninstalling syslog, you can remove everything, or only components, just in case you plan on re-installing in the future." +#: includes/settings.php +msgid "Optional local date and time to end a one-time global maintenance window. Format: YYYY-MM-DD HH:MM." msgstr "" -#: setup.php -msgid "Remove Everything (Logs, Tables, Settings)" +#: includes/settings.php +msgid "Default Alert Cooldown" msgstr "" -#: setup.php -msgid "Syslog Data Only" +#: includes/settings.php +msgid "Suppress any repeat notification for the same rule and reporting scope for this many minutes. Per-rule values override this setting. Set to 0 to disable." msgstr "" -#: setup.php -msgid "Syslog Uninstall Preferences" +#: includes/settings.php +msgid "Default Duplicate Suppression" msgstr "" -#: setup.php -msgid "Uninstall" +#: includes/settings.php +msgid "Suppress a notification with the same matched message set for this many minutes. Per-rule values override this setting. Set to 0 to disable." +msgstr "" + +#: includes/settings.php +msgid "Remote Syslog Recovery" +msgstr "" + +#: includes/settings.php +msgid "Maximum Recovery Records Per Execution" msgstr "" -#: setup.php -msgid "Monday" +#: includes/settings.php +msgid "Maximum number of retained remote Syslog events sent to the Main Collector by one recovery worker execution. Each central database transaction remains capped at 100 records. Increase gradually while observing Main Collector load and lock waits." msgstr "" -#: setup.php -msgid "Tuesday" +#: includes/settings.php +msgid "Recovery Pause Between Batches" msgstr "" -#: setup.php -msgid "Wednesday" +#: includes/settings.php +msgid "Pause between each 100-record Main Collector delivery transaction. A longer pause lowers Main Collector pressure; a shorter pause speeds backlog convergence." msgstr "" -#: setup.php -msgid "Friday" +#: includes/settings.php +msgid "No pause" msgstr "" -#: setup.php -msgid "Saturday" +#: includes/settings.php +#, php-format +msgid "%d milliseconds" msgstr "" -#: setup.php -msgid "Sunday" +#: includes/settings.php +#, php-format +msgid "%d second" msgstr "" -#: setup.php -msgid "Thursday" +#: includes/settings.php lib/syslog_dashboard.php syslog.php +#: syslog_dashboards.php syslog_device_rules.php syslog_removal.php +#: syslog_saved_searches.php +msgid "Delete" msgstr "" -#: setup.php -msgid "Monday through Friday" +#: includes/settings.php syslog_device_rules.php syslog_removal.php +msgid "Disable" msgstr "" -#: setup.php -msgid "Saturday and Sunday" +#: includes/settings.php syslog_device_rules.php syslog_removal.php +msgid "Enable" msgstr "" -#: setup.php -msgid "Every day" +#: includes/settings.php syslog.php syslog_dashboards.php +#: syslog_device_rules.php syslog_removal.php syslog_saved_searches.php +msgid "Export" msgstr "" -#: setup.php syslog_alerts.php syslog_device_rules.php syslog_removal.php -#: syslog_reports.php -msgid "Disabled" +#: includes/settings.php +msgid "Indefinite" msgstr "" -#: setup.php -msgid "General Settings" +#: includes/settings.php syslog_alerts.php +#, php-format +msgid "%d Week" msgstr "" -#: setup.php -msgid "Syslog Enabled" +#: includes/settings.php syslog_alerts.php +#, php-format +msgid "%d Weeks" msgstr "" -#: setup.php -msgid "If this checkbox is set, records will be transferred from the Syslog Incoming table to the main syslog table and Alerts and Reports will be enabled. Please keep in mind that if the system is disabled log entries will still accumulate into the Syslog Incoming table as this is defined by the rsyslog or syslog-ng process." +#: includes/settings.php +#, php-format +msgid "%d Month" msgstr "" -#: setup.php -msgid "Strip Domains" +#: includes/settings.php +#, php-format +msgid "%d Months" msgstr "" -#: setup.php -msgid "A comma delimited list of domains that you wish to remove from the syslog hostname, Examples would be 'mydomain.com, otherdomain.com'" +#: includes/settings.php +#, php-format +msgid "%d Year" msgstr "" -#: setup.php -msgid "Refresh Interval" +#: includes/settings.php syslog.php syslog_dashboards.php syslog_reports.php +msgid "Never" msgstr "" -#: setup.php -msgid "This is the time in seconds before the page refreshes." +#: includes/settings.php +#, php-format +msgid "%d Minute" msgstr "" -#: setup.php -msgid "Max Report Records" +#: includes/settings.php syslog_alerts.php +#, php-format +msgid "%d Minutes" msgstr "" -#: setup.php -msgid "For Threshold based Alerts, what is the maximum number that you wish to show in the report. This is used to limit the size of the html log and Email." +#: includes/settings.php +msgid "Begins with" msgstr "" -#: setup.php -#, php-format -msgid "%d Records" +#: includes/settings.php +msgid "Contains" msgstr "" -#: setup.php -msgid "Maximum Syslog Processing Processes" +#: includes/settings.php +msgid "Ends with" msgstr "" -#: setup.php -msgid "When set above 1, incoming message processing is spread across parallel worker processes. Each worker handles a disjoint range of incoming messages, speeding up hostname resolution and message transfer under heavy loads. A setting of 1 processes all messages in a single process. Worker processes are not supported on Windows or when the PHP pcntl and POSIX extensions are unavailable, in which case processing falls back to a single process." +#: includes/settings.php +msgid "Hostname is" msgstr "" -#: setup.php -#, php-format -msgid "%d Worker Process" +#: includes/settings.php +msgid "Program is" msgstr "" -#: setup.php -#, php-format -msgid "%d Worker Processes" +#: includes/settings.php +msgid "Facility is" msgstr "" -#: setup.php -msgid "Command for Opening Tickets" +#: includes/settings.php syslog_alerts.php +msgid "Filter Builder" msgstr "" -#: setup.php -msgid "This command will be executed for opening Help Desk Tickets. The command will be required to parse multiple input parameters as follows: --alert-name, --severity, --hostlist, --message. The hostlist will be a comma delimited list of hosts impacted by the alert." +#: includes/settings.php syslog_alerts.php +msgid "SQL Expression" msgstr "" -#: setup.php -msgid "Host Discovery Options" +#: includes/settings.php +msgid "Daily" msgstr "" -#: setup.php -msgid "Enable Hostname Resolution" +#: includes/settings.php +msgid "Weekly" msgstr "" -#: setup.php -msgid "If this checkbox is set, all hostnames are resolved via DNS lookup first (If enabled). If the DNS lookup fails, the system will attempt to resolve the hostname against the Cacti host table and replace it with the Cacti host description. If both DNS and Cacti lookups fail, records are assigned a prefix 'unresolved-Original_hostname'." +#: includes/settings.php +msgid "Import/Export" msgstr "" -#: setup.php -msgid "Skip DNS Resolution for incoming hosts" +#: includes/settings.php +msgid "Alert Rules" msgstr "" -#: setup.php -msgid "If this checkbox is set, the system will not attempt to resolve hosts via DNS lookups. This is useful for environments where DNS resolution is not possible or not desired." +#: includes/settings.php +msgid "Syslog Settings" msgstr "" -#: setup.php -msgid "HTML Notification Settings" +#: includes/settings.php syslog_device_rules.php +msgid "Device Alert Rules" msgstr "" -#: setup.php -msgid "Enable HTML Based Email" +#: includes/settings.php +msgid "Removal Rules" msgstr "" -#: setup.php -msgid "If this checkbox is set, all Emails will be sent in HTML format. Otherwise, Emails will be sent in plain text." +#: includes/settings.php +msgid "Report Rules" msgstr "" -#: setup.php -msgid "Format File to Use" +#: includes/utilities.php +#, php-format +msgid "There were %s Device records removed from the Syslog database" msgstr "" -#: setup.php -msgid "Choose the custom html wrapper and CSS file to use. This file contains both html and CSS to wrap around your report. If it contains more than simply CSS, you need to place a special tag inside of the file. This format tag will be replaced by the report content. These files are located in the 'formats' directory." +#: includes/utilities.php +msgid "Syslog Utilities" msgstr "" -#: setup.php -msgid "Data Retention Settings" +#: includes/utilities.php +msgid "Purge Syslog Devices" msgstr "" -#: setup.php -msgid "Syslog Retention" +#: includes/utilities.php +msgid "Are you sure you want to purge stale Syslog devices?" msgstr "" -#: setup.php -msgid "This is the number of days to keep events." +#: includes/utilities.php +msgid "Confirm Purge" msgstr "" -#: setup.php -msgid "Partition Pre-create Window" +#: includes/utilities.php syslog_alerts.php syslog_dashboards.php +#: syslog_device_rules.php syslog_removal.php syslog_reports.php +#: syslog_saved_searches.php +msgid "Continue" msgstr "" -#: setup.php -msgid "This is the number of future daily partitions to maintain for partitioned Syslog tables." +#: includes/utilities.php +msgid "This menu pick provides a means to remove Devices that are no longer reporting into Cacti's syslog server." msgstr "" -#: setup.php syslog_alerts.php -#, php-format -msgid "%d Day" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard panel source." msgstr "" -#: setup.php syslog_alerts.php -#, php-format -msgid "%d Days" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard panel type." msgstr "" -#: setup.php -msgid "Partition Recovery Limit" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard chart type." msgstr "" -#: setup.php -msgid "When partitions are missing (for example after maintenance downtime), at most this many missing partitions per table are created on each poller run. The remaining gap heals over subsequent runs; retention pruning stays deferred until the future partition horizon is fully restored. Applies to partitioned Syslog tables only." +#: lib/syslog_dashboard.php +msgid "Invalid dashboard breakdown field." msgstr "" -#: setup.php -#, php-format -msgid "%d Partition per run" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard interval." msgstr "" -#: setup.php -#, php-format -msgid "%d Partitions per run" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard timespan." msgstr "" -#: setup.php -msgid "Collector Staleness Threshold" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard record type." msgstr "" -#: setup.php -msgid "The Syslog Status page shows a warning when no new message has arrived within this many seconds. Set to 0 to use the default of 300 seconds." +#: lib/syslog_dashboard.php +msgid "Invalid dashboard top count." msgstr "" -#: setup.php -msgid "Default (300 seconds)" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard panel width." msgstr "" -#: setup.php -#, php-format -msgid "%d seconds" +#: lib/syslog_dashboard.php +msgid "Invalid dashboard panel height." msgstr "" -#: setup.php -msgid "Collector Backlog Threshold" +#: lib/syslog_dashboard.php syslog.php +#, php-format +msgid "Invalid logical search: %s" msgstr "" -#: setup.php -msgid "The Syslog Status page shows a warning when more than this many messages sit unprocessed in the Syslog Incoming table. Set to 0 to use the default of 10000 messages." +#: lib/syslog_dashboard.php +msgid "Other" msgstr "" -#: setup.php -msgid "Default (10000 messages)" +#: lib/syslog_dashboard.php +msgid "A valid dashboard panel is required." msgstr "" -#: setup.php -#, php-format -msgid "%d Messages" +#: lib/syslog_dashboard.php +msgid "Dashboard panel not found." msgstr "" -#: setup.php -msgid "Syslog Alert Retention" +#: lib/syslog_dashboard.php syslog.php +msgid "A name of up to 128 characters is required." msgstr "" -#: setup.php -msgid "This is the number of days to keep alert logs." +#: lib/syslog_dashboard.php +msgid "A valid dashboard is required." msgstr "" -#: setup.php -msgid "Alert Suppression and Maintenance" +#: lib/syslog_dashboard.php +msgid "Dashboard not found." msgstr "" -#: setup.php -msgid "Maintenance Window Days" +#: lib/syslog_dashboard.php +msgid "You may only share your own dashboards." msgstr "" -#: setup.php -msgid "Days when all alert notifications are muted during the configured maintenance timeframe." +#: lib/syslog_dashboard.php +msgid "copy" msgstr "" -#: setup.php -msgid "Maintenance Window Starts" +#: lib/syslog_dashboard.php +msgid "Dashboard could not be copied." msgstr "" -#: setup.php -msgid "Local start time for the global maintenance window." +#: lib/syslog_dashboard.php +msgid "A valid panel position is required." msgstr "" -#: setup.php -msgid "Maintenance Window Ends" +#: lib/syslog_dashboard.php +msgid "A title of up to 128 characters is required." msgstr "" -#: setup.php -msgid "Local end time for the global maintenance window. An earlier end time continues into the next day." +#: lib/syslog_dashboard.php +msgid "Chart library unavailable on this Cacti installation." msgstr "" -#: setup.php -msgid "One-time Maintenance Starts" +#: lib/syslog_dashboard.php +msgid "Edit Panel" msgstr "" -#: setup.php -msgid "Optional local date and time to start a one-time global maintenance window." +#: lib/syslog_dashboard.php +msgid "New Panel" msgstr "" -#: setup.php -msgid "One-time Maintenance Ends" +#: lib/syslog_dashboard.php syslog.php +msgid "Save" msgstr "" -#: setup.php -msgid "Optional local date and time to end a one-time global maintenance window. Format: YYYY-MM-DD HH:MM." +#: lib/syslog_dashboard.php syslog.php +msgid "Apply" msgstr "" -#: setup.php -msgid "Default Alert Cooldown" +#: lib/syslog_dashboard.php +msgid "Delete this dashboard panel?" msgstr "" -#: setup.php -msgid "Suppress any repeat notification for the same rule and reporting scope for this many minutes. Per-rule values override this setting. Set to 0 to disable." +#: lib/syslog_dashboard.php +msgid "Delete this dashboard and all of its panels?" msgstr "" -#: setup.php -msgid "Default Duplicate Suppression" +#: lib/syslog_dashboard.php +msgid "Name your dashboard" msgstr "" -#: setup.php -msgid "Suppress a notification with the same matched message set for this many minutes. Per-rule values override this setting. Set to 0 to disable." +#: lib/syslog_dashboard.php syslog.php +msgid "Make Private" msgstr "" -#: includes/installer.php -msgid "Remote Message Processing" +#: lib/syslog_dashboard.php +msgid "Share with all" msgstr "" -#: includes/installer.php includes/settings.php -msgid "Enable Remote Data Collector Message Processing" +#: lib/syslog_dashboard.php +msgid "Save as my copy" msgstr "" -#: includes/installer.php includes/settings.php -msgid "If your Remote Data Collectors have their own Syslog databases and process their messages independently, check this checkbox. By checking this Checkbox, your Remote Data Collectors will need to maintain their own 'config_local.php' file in order to inform Syslog to use an independent database for message display and processing. Please use the template file 'config_local.php.dist' for this purpose. WARNING: Syslog tables will be automatically created as soon as this option is enabled." +#: lib/syslog_dashboard.php +msgid "Syslog Dashboards" msgstr "" -#: includes/installer.php includes/settings.php -msgid "Remote Data Collector Rules Sync" +#: lib/syslog_dashboard.php syslog.php +msgid "Dashboard" msgstr "" -#: includes/installer.php includes/settings.php -msgid "If your Remote Data Collectors have their own Syslog databases and process thrie messages independently, check this checkbox if you wish the Main Cacti databases Alerts, Removal and Report rules to be sent to the Remote Cacti System." +#: lib/syslog_dashboard.php +msgid "Time Range" msgstr "" -#: includes/installer.php includes/settings.php -msgid "Store Records on Remote Collector" +#: lib/syslog_dashboard.php +msgid "Last Hour" msgstr "" -#: includes/installer.php includes/settings.php -msgid "Keep processed messages in the remote collector syslog table after they have been delivered to the Main Collector. When disabled, records are retained locally only while the Main Collector is unavailable and are removed after confirmed recovery delivery." +#: lib/syslog_dashboard.php +#, php-format +msgid "Last %d Hours" msgstr "" -#: includes/installer.php -msgid "Aria storage is unavailable while any Remote Data Collector option is enabled. InnoDB has been selected." +#: lib/syslog_dashboard.php +msgid "Last Day" msgstr "" -#: includes/installer.php setup.php -msgid "Aria storage cannot be used when any Remote Data Collector option is enabled. Select InnoDB or disable all Remote Data Collector options." +#: lib/syslog_dashboard.php +msgid "Last Week" msgstr "" -#: setup.php -msgid "Remote Syslog Recovery" +#: lib/syslog_dashboard.php +#, php-format +msgid "Last %d Weeks" msgstr "" -#: setup.php -msgid "Maximum Recovery Records Per Execution" +#: lib/syslog_dashboard.php +msgid "Last Month" msgstr "" -#: setup.php -msgid "Maximum number of retained remote Syslog events sent to the Main Collector by one recovery worker execution. Each central database transaction remains capped at 100 records. Increase gradually while observing Main Collector load and lock waits." +#: lib/syslog_dashboard.php +#, php-format +msgid "Last %d Months" msgstr "" -#: setup.php -msgid "Recovery Pause Between Batches" +#: lib/syslog_dashboard.php syslog.php +msgid "Refresh" msgstr "" -#: setup.php -msgid "Pause between each 100-record Main Collector delivery transaction. A longer pause lowers Main Collector pressure; a shorter pause speeds backlog convergence." +#: lib/syslog_dashboard.php +msgid "My Dashboards" msgstr "" -#: setup.php -msgid "No pause" +#: lib/syslog_dashboard.php +msgid "Global Dashboards" msgstr "" -#: setup.php -#, php-format -msgid "%d milliseconds" +#: lib/syslog_dashboard.php syslog.php +msgid "Shared With Me" msgstr "" -#: setup.php -#, php-format -msgid "%d second" +#: lib/syslog_dashboard.php syslog.php +msgid "New" msgstr "" -#: setup.php syslog_device_rules.php syslog_removal.php -msgid "Disable" +#: lib/syslog_dashboard.php +msgid "Rename" msgstr "" -#: setup.php syslog_device_rules.php syslog_removal.php -msgid "Enable" +#: lib/syslog_dashboard.php +msgid "Add Panel" msgstr "" -#: setup.php syslog.php syslog_dashboards.php syslog_device_rules.php -#: syslog_removal.php syslog_saved_searches.php -msgid "Export" +#: lib/syslog_dashboard.php +msgid "Create your first dashboard to visualize syslog trends." msgstr "" -#: setup.php -msgid "Indefinite" +#: lib/syslog_dashboard.php +msgid "Add a panel to start charting. Panels can use your saved searches." msgstr "" -#: setup.php syslog_alerts.php -#, php-format -msgid "%d Week" +#: lib/syslog_dashboard.php +msgid "Dashboard Panel" msgstr "" -#: setup.php syslog_alerts.php -#, php-format -msgid "%d Weeks" +#: lib/syslog_dashboard.php +msgid "Title" msgstr "" -#: setup.php -#, php-format -msgid "%d Month" +#: lib/syslog_dashboard.php +msgid "Log Source" msgstr "" -#: setup.php -#, php-format -msgid "%d Months" +#: lib/syslog_dashboard.php syslog.php +msgid "System Logs" msgstr "" -#: setup.php -#, php-format -msgid "%d Year" +#: lib/syslog_dashboard.php syslog.php +msgid "Alert Logs" msgstr "" -#: setup.php syslog.php syslog_dashboards.php syslog_reports.php -msgid "Never" +#: lib/syslog_dashboard.php +msgid "Panel Type" msgstr "" -#: setup.php -#, php-format -msgid "%d Minute" +#: lib/syslog_dashboard.php +msgid "Time Series" msgstr "" -#: setup.php syslog_alerts.php -#, php-format -msgid "%d Minutes" +#: lib/syslog_dashboard.php +msgid "Breakdown" msgstr "" -#: setup.php -msgid "Begins with" +#: lib/syslog_dashboard.php +msgid "Chart" msgstr "" -#: setup.php -msgid "Contains" +#: lib/syslog_dashboard.php +msgid "Line" msgstr "" -#: setup.php -msgid "Ends with" +#: lib/syslog_dashboard.php +msgid "Area" msgstr "" -#: setup.php -msgid "Hostname is" +#: lib/syslog_dashboard.php +msgid "Bar" msgstr "" -#: setup.php -msgid "Program is" +#: lib/syslog_dashboard.php +msgid "Interval" msgstr "" -#: setup.php -msgid "Facility is" +#: lib/syslog_dashboard.php +msgid "Auto" msgstr "" -#: setup.php syslog_alerts.php -msgid "Filter Builder" +#: lib/syslog_dashboard.php +msgid "Minute" msgstr "" -#: setup.php syslog_alerts.php -msgid "SQL Expression" +#: lib/syslog_dashboard.php +msgid "10 Minutes" msgstr "" -#: setup.php -msgid "Daily" +#: lib/syslog_dashboard.php +msgid "Hour" msgstr "" -#: setup.php -msgid "Weekly" +#: lib/syslog_dashboard.php +msgid "Day" msgstr "" -#: setup.php -msgid "Import/Export" +#: lib/syslog_dashboard.php +msgid "Field" msgstr "" -#: setup.php -msgid "Alert Rules" +#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php +msgid "Program" msgstr "" -#: setup.php -msgid "Syslog Settings" +#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php +msgid "Facility" msgstr "" -#: setup.php syslog_device_rules.php -msgid "Device Alert Rules" +#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php +msgid "Priority" msgstr "" -#: setup.php -msgid "Removal Rules" +#: lib/syslog_dashboard.php +msgid "Top Count" msgstr "" -#: setup.php -msgid "Report Rules" +#: lib/syslog_dashboard.php syslog.php +msgid "Record Type" msgstr "" -#: setup.php syslog_saved_searches.php -msgid "Saved Search Templates" +#: lib/syslog_dashboard.php syslog.php +msgid "All Records" msgstr "" -#: setup.php -msgid "Syslog Removals" +#: lib/syslog_dashboard.php syslog.php +msgid "Main Records" msgstr "" -#: setup.php -msgid "(Edit)" +#: lib/syslog_dashboard.php syslog.php +msgid "Removed Records" msgstr "" -#: setup.php -msgid "(Actions)" +#: lib/syslog_dashboard.php +msgid "Start from Saved Search" msgstr "" -#: setup.php -msgid "Syslog Alerts" +#: lib/syslog_dashboard.php syslog.php syslog_alerts.php +#: syslog_device_rules.php syslog_reports.php +msgid "None" msgstr "" -#: setup.php -msgid "Syslog Reports" +#: lib/syslog_dashboard.php syslog.php syslog_saved_searches.php +msgid "Enter message text…" msgstr "" -#: setup.php -msgid "Display Syslog in Range" +#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_removal.php +#: syslog_saved_searches.php +msgid "Remove condition" msgstr "" -#: setup.php -#, php-format -msgid "There were %s Device records removed from the Syslog database" +#: lib/syslog_dashboard.php syslog.php syslog_saved_searches.php +msgid "Match group" msgstr "" -#: setup.php -msgid "Syslog Utilities" +#: lib/syslog_dashboard.php syslog.php syslog_saved_searches.php +msgid "Exclude group" msgstr "" -#: setup.php -msgid "Purge Syslog Devices" +#: lib/syslog_dashboard.php +msgid "Dashboard Name" msgstr "" -#: setup.php -msgid "Are you sure you want to purge stale Syslog devices?" +#: lib/syslog_dashboard.php syslog.php syslog_alerts.php syslog_dashboards.php +#: syslog_reports.php syslog_saved_searches.php +msgid "Name" msgstr "" #: setup.php -msgid "Confirm Purge" -msgstr "" - -#: setup.php syslog_alerts.php syslog_dashboards.php syslog_device_rules.php -#: syslog_removal.php syslog_reports.php syslog_saved_searches.php -msgid "Continue" +msgid "Please rename either your config.php.dist or config_local.php.dist files in the syslog directory, and change setup your database before installing." msgstr "" #: setup.php -msgid "This menu pick provides a means to remove Devices that are no longer reporting into Cacti's syslog server." +msgid "Aria storage cannot be used when any Remote Data Collector option is enabled. Select InnoDB or disable all Remote Data Collector options." msgstr "" #: syslog.php @@ -1357,10 +1369,6 @@ msgstr "" msgid "seconds" msgstr "" -#: syslog.php -msgid "min/avg/max" -msgstr "" - #: syslog.php msgid "Unavailable" msgstr "" diff --git a/setup.php b/setup.php index 262ed712..5cb5e0d0 100644 --- a/setup.php +++ b/setup.php @@ -64,7 +64,6 @@ function plugin_syslog_install() { api_plugin_register_hook('syslog', 'top_graph_header_tabs', 'syslog_show_tab', 'includes/navigation.php'); api_plugin_register_hook('syslog', 'poller_bottom', 'syslog_poller_bottom', 'includes/processing.php'); api_plugin_register_hook('syslog', 'graph_buttons', 'syslog_graph_buttons', 'includes/navigation.php'); - api_plugin_register_hook('syslog', 'config_insert', 'syslog_config_insert', 'includes/settings.php'); api_plugin_register_hook('syslog', 'utilities_list', 'syslog_utilities_list', 'includes/utilities.php'); api_plugin_register_hook('syslog', 'utilities_action', 'syslog_utilities_action', 'includes/utilities.php'); @@ -77,7 +76,6 @@ function plugin_syslog_install() { api_plugin_register_realm('syslog', 'syslog_saved_searches.php,syslog_dashboards.php', 'Syslog Administration', 1); api_plugin_register_realm('syslog', 'syslog_saved_searches_share.php', 'Share Saved Templates', 1); api_plugin_register_realm('syslog', 'syslog_dashboards_share.php', 'Share Dashboards', 1); - api_plugin_register_realm('syslog', 'syslog_administrator.php', 'Syslog Administrator', 1); if (isset_request_var('install')) { if (!$bg_inprocess) { @@ -153,15 +151,13 @@ function syslog_execute_update(int $syslog_exists, array $options): void { db_execute('DELETE FROM plugin_realms WHERE plugin="syslog"'); db_execute('DELETE FROM plugin_db_changes WHERE plugin="syslog"'); db_execute('DELETE FROM plugin_hooks WHERE name="syslog"'); - } elseif (isset($options['upgrade_type'])) { - if ($options['upgrade_type'] == 'truncate') { - syslog_setup_table_new($options); - } } else { - syslog_setup_table_new($options); + syslog_setup_table_new($options, $syslog_exists && ($options['upgrade_type'] ?? '') !== 'truncate'); } - set_config_option('syslog_retention', $options['days']); + if (isset($options['days'])) { + set_config_option('syslog_retention', $options['days']); + } } /** @@ -231,11 +227,18 @@ function plugin_syslog_check_config(): bool { return false; } - if (api_plugin_installed('flowview')) { - syslog_check_upgrade(); + $version = plugin_syslog_version(); + $installed = db_fetch_cell_prepared('SELECT version FROM plugin_config WHERE directory = ?', ['syslog']); + if (get_current_page() === 'plugins.php' && get_nfilter_request_var('mode') === 'enable' && + $installed !== false && isset($version['version']) && version_compare((string) $installed, $version['version'], '<') && + !isset_request_var('syslog_upgrade_confirm')) { + require_once(__DIR__ . '/includes/installer.php'); + syslog_upgrade_advisor((string) $installed, $version['version']); } - return true; + require_once(__DIR__ . '/includes/settings.php'); + syslog_upgrade_hook_module_paths(); + return syslog_check_upgrade(); } /** @@ -246,13 +249,46 @@ function plugin_syslog_check_config(): bool { function plugin_syslog_upgrade(): bool { require_once(__DIR__ . '/includes/settings.php'); - // Here we will upgrade to the newest version - api_plugin_register_hook('syslog', 'settings_bottom', 'syslog_settings_bottom', 'includes/settings.php', 1); + syslog_upgrade_hook_module_paths(); syslog_check_upgrade(); return false; } +/** Move existing Cacti hook rows from the old setup.php facade to their modules. */ +function syslog_upgrade_hook_module_paths(): void { + $hooks = [ + 'config_arrays' => ['syslog_config_arrays', 'includes/settings.php'], + 'draw_navigation_text' => ['syslog_draw_navigation_text', 'includes/navigation.php'], + 'config_settings' => ['syslog_config_settings', 'includes/settings.php'], + 'settings_bottom' => ['syslog_settings_bottom', 'includes/settings.php'], + 'top_header_tabs' => ['syslog_show_tab', 'includes/navigation.php'], + 'top_graph_header_tabs' => ['syslog_show_tab', 'includes/navigation.php'], + 'poller_bottom' => ['syslog_poller_bottom', 'includes/processing.php'], + 'graph_buttons' => ['syslog_graph_buttons', 'includes/navigation.php'], + 'config_insert' => ['syslog_config_insert', 'includes/settings.php'], + 'utilities_list' => ['syslog_utilities_list', 'includes/utilities.php'], + 'utilities_action' => ['syslog_utilities_action', 'includes/utilities.php'], + 'replicate_out' => ['syslog_replicate_out', 'includes/processing.php'] + ]; + $registered = db_fetch_assoc_prepared('SELECT hook FROM plugin_hooks WHERE name = ? AND file = ?', ['syslog', 'setup.php']); + if (!is_array($registered) || !$registered) { + return; + } + $modules = []; + foreach ($registered as $row) { + if (isset($hooks[$row['hook']])) { + [$function, $file] = $hooks[$row['hook']]; + db_execute_prepared('UPDATE plugin_hooks SET `function` = ?, file = ? WHERE name = ? AND hook = ? AND file = ?', [$function, $file, 'syslog', $row['hook'], 'setup.php']); + $modules[$file] = true; + } + } + + foreach (array_keys($modules) as $file) { + require_once(__DIR__ . '/' . $file); + } +} + /** * Determine which Syslog config file is present, and if it is for a remote database. * @@ -373,16 +409,6 @@ function syslog_connect(): bool { } } - if ($connected && !syslog_db_table_exists('syslog') && api_plugin_is_enabled('syslog')) { - cacti_log('Setting Up Database Tables Since they do not exist', false, 'SYSLOG'); - - if (!isset($syslog_install_options)) { - $syslog_install_options = []; - } - - require_once(__DIR__ . '/includes/schema.php'); - syslog_setup_table_new($syslog_install_options); - } } return $connected; @@ -394,8 +420,6 @@ function syslog_connect(): bool { * @return void */ function syslog_upgrade_saved_search_realm(): void { - global $user_auth_realm_filenames; - $admin = null; $template = null; $realms = db_fetch_assoc_prepared('SELECT id, file, display FROM plugin_realms WHERE plugin = ?', ['syslog']); @@ -423,15 +447,8 @@ function syslog_upgrade_saved_search_realm(): void { return; } $template = $admin; - api_plugin_replicate_config(); } - // A legacy standalone realm retains its grants. Administrators can also - // access Templates, without granting legacy template users other admin pages. - // Update the already-loaded map so the repair works on this request too. - if ($template['id'] == $admin['id'] || api_plugin_user_realm_auth('syslog_alerts.php')) { - $user_auth_realm_filenames['syslog_saved_searches.php'] = (int) $admin['id'] + 100; - } } /** @@ -440,8 +457,6 @@ function syslog_upgrade_saved_search_realm(): void { * @return void */ function syslog_upgrade_dashboard_realm(): void { - global $user_auth_realm_filenames; - $realms = db_fetch_assoc_prepared('SELECT id, file, display FROM plugin_realms WHERE plugin = ?', ['syslog']); if (is_array($realms)) { @@ -460,10 +475,6 @@ function syslog_upgrade_dashboard_realm(): void { [$realm['file'] . ',syslog_dashboards.php', $realm['id'], 'syslog'])) { return; } - api_plugin_replicate_config(); - - // Update the already-loaded map so the repair works on this request too. - $user_auth_realm_filenames['syslog_dashboards.php'] = (int) $realm['id'] + 100; return; } @@ -472,7 +483,6 @@ function syslog_upgrade_dashboard_realm(): void { /** Give Rule Administrators access to device alert rules. */ function syslog_upgrade_device_rule_realm(): void { - global $user_auth_realm_filenames; $realms = db_fetch_assoc_prepared('SELECT id, file, display FROM plugin_realms WHERE plugin = ?', ['syslog']); if (!is_array($realms)) { return; @@ -487,9 +497,7 @@ function syslog_upgrade_device_rule_realm(): void { if (!db_execute_prepared('UPDATE plugin_realms SET file = ? WHERE id = ? AND plugin = ?', [implode(',', $files), $realm['id'], 'syslog'])) { return; } - api_plugin_replicate_config(); } - $user_auth_realm_filenames['syslog_device_rules.php'] = (int) $realm['id'] + 100; return; } } @@ -506,8 +514,6 @@ function syslog_upgrade_device_rule_realm(): void { * @return void */ function syslog_upgrade_rule_permissions(): void { - global $user_auth_realm_filenames; - $realms = db_fetch_assoc_prepared('SELECT id, file, display FROM plugin_realms WHERE plugin = ?', ['syslog']); if (!is_array($realms)) { @@ -528,15 +534,13 @@ function syslog_upgrade_rule_permissions(): void { 'syslog_dashboards.php' ])); $files[] = 'syslog_rule_administrator.php'; + $files[] = 'syslog_device_rules.php'; if (!db_execute_prepared('UPDATE plugin_realms SET file = ? WHERE id = ? AND plugin = ?', [implode(',', $files), $realm['id'], 'syslog'])) { return; } - api_plugin_replicate_config(); - $user_auth_realm_filenames['syslog_rule_administrator.php'] = (int) $realm['id'] + 100; - return; } } @@ -552,11 +556,9 @@ function syslog_upgrade_rule_permissions(): void { * @return bool Whether duplicate repair succeeded. */ function syslog_upgrade_consolidate_rule_realms(): bool { - $changed = false; - foreach (['Rule Viewer', 'Rule Administrator'] as $display) { $realms = db_fetch_assoc_prepared('SELECT id, file FROM plugin_realms WHERE plugin = ? AND display = ? ORDER BY id', ['syslog', $display]); - $files = $display === 'Rule Viewer' ? 'syslog_alerts.php,syslog_removal.php,syslog_reports.php' : 'syslog_rule_administrator.php'; + $files = $display === 'Rule Viewer' ? 'syslog_alerts.php,syslog_removal.php,syslog_reports.php' : 'syslog_rule_administrator.php,syslog_device_rules.php'; if (!is_array($realms) || !$realms || (count($realms) === 1 && $realms[0]['file'] === $files)) { continue; @@ -587,12 +589,114 @@ function syslog_upgrade_consolidate_rule_realms(): bool { db_rollback_transaction(); return false; } - $changed = true; + } + return true; +} + +/** Replace synthetic role inheritance with explicit Cacti realm grants. */ +function syslog_upgrade_explicit_realm_grants(): bool { + $realms = db_fetch_assoc_prepared('SELECT id, display FROM plugin_realms WHERE plugin = ?', ['syslog']); + if (!is_array($realms)) { + return false; } - if ($changed) { - api_plugin_replicate_config(); + $ids = []; + foreach ($realms as $realm) { + $ids[$realm['display']][] = (int) $realm['id'] + 100; + } + $viewer = $ids['Rule Viewer'][0] ?? 0; + $administrator = $ids['Rule Administrator'][0] ?? 0; + $umbrella = $ids['Syslog Administrator'] ?? []; + if (!$viewer || !$administrator || !db_begin_transaction()) { + return false; } + + $source_ids = array_merge([$viewer, $administrator], $umbrella); + $placeholders = implode(',', array_fill(0, count($source_ids), '?')); + $direct = db_fetch_assoc_prepared("SELECT user_id, realm_id FROM user_auth_realm WHERE realm_id IN ($placeholders)", $source_ids); + $groups = db_fetch_assoc_prepared("SELECT group_id, realm_id FROM user_auth_group_realm WHERE realm_id IN ($placeholders)", $source_ids); + if (!is_array($direct) || !is_array($groups)) { + db_rollback_transaction(); + return false; + } + $direct_grants = []; + $group_grants = []; + foreach ($direct as $grant) { + $direct_grants[(int) $grant['user_id']][(int) $grant['realm_id']] = true; + } + foreach ($groups as $grant) { + $group_grants[(int) $grant['group_id']][(int) $grant['realm_id']] = true; + } + $affected = []; + foreach ($direct_grants as $user_id => $granted) { + if (array_intersect($umbrella, array_keys($granted)) || (isset($granted[$administrator]) && !isset($granted[$viewer]))) { + $affected[$user_id] = true; + } + } + foreach ($group_grants as $group_id => $granted) { + if (array_intersect($umbrella, array_keys($granted)) || (isset($granted[$administrator]) && !isset($granted[$viewer]))) { + $members = db_fetch_assoc_prepared('SELECT user_id FROM user_auth_group_members WHERE group_id = ?', [$group_id]); + if (!is_array($members)) { + db_rollback_transaction(); + return false; + } + foreach ($members as $member) { + $affected[(int) $member['user_id']] = true; + } + } + } + + $grants = [$administrator => [$viewer]]; + $all_pages = []; + foreach ($ids as $display => $realm_ids) { + if ($display !== 'Syslog Administrator') { + $all_pages = array_merge($all_pages, $realm_ids); + } + } + foreach ($umbrella as $source) { + $grants[$source] = array_values(array_unique($all_pages)); + } + + foreach ($grants as $source => $targets) { + foreach ($targets as $target) { + foreach (['user_auth_realm' => 'user_id', 'user_auth_group_realm' => 'group_id'] as $table => $owner) { + if (!db_execute_prepared("INSERT IGNORE INTO $table ($owner, realm_id) SELECT $owner, ? FROM $table WHERE realm_id = ?", [$target, $source])) { + db_rollback_transaction(); + return false; + } + } + } + } + + foreach ($umbrella as $source) { + foreach (['user_auth_realm', 'user_auth_group_realm'] as $table) { + if (!db_execute_prepared("DELETE FROM $table WHERE realm_id = ?", [$source])) { + db_rollback_transaction(); + return false; + } + } + if (!db_execute_prepared('DELETE FROM plugin_realms WHERE plugin = ? AND id = ?', ['syslog', $source - 100])) { + db_rollback_transaction(); + return false; + } + } + + foreach (array_keys($affected) as $user_id) { + if (!db_execute_prepared('DELETE FROM user_auth_cache WHERE user_id = ?', [$user_id]) || + !db_execute_prepared('UPDATE user_auth SET reset_perms = FLOOR(RAND() * 4294967295) + 1 WHERE id = ?', [$user_id])) { + db_rollback_transaction(); + return false; + } + } + + if (!db_commit_transaction()) { + db_rollback_transaction(); + return false; + } + if (isset($_SESSION['sess_user_id'], $affected[(int) $_SESSION['sess_user_id']])) { + unset($_SESSION['sess_user_realms'], $_SESSION['sess_auth_names']); + } + return true; } @@ -604,9 +708,10 @@ function syslog_upgrade_consolidate_rule_realms(): bool { function syslog_upgrade_create_permission_realms(): bool { $targets = [ 'Rule Viewer' => 'syslog_alerts.php,syslog_removal.php,syslog_reports.php', - 'Rule Administrator' => 'syslog_rule_administrator.php', + 'Rule Administrator' => 'syslog_rule_administrator.php,syslog_device_rules.php', 'Syslog Administration' => 'syslog_saved_searches.php,syslog_dashboards.php', - 'Syslog Administrator' => 'syslog_administrator.php' + 'Share Saved Templates' => 'syslog_saved_searches_share.php', + 'Share Dashboards' => 'syslog_dashboards_share.php' ]; $realms = db_fetch_assoc_prepared('SELECT id, file, display FROM plugin_realms WHERE plugin = ?', ['syslog']); if (!is_array($realms)) { @@ -686,116 +791,91 @@ function syslog_upgrade_create_permission_realms(): bool { } } + $new_ids = array_values(array_intersect_key($target_ids, $missing)); + if (!$new_ids) { + db_rollback_transaction(); + return false; + } + $placeholders = implode(',', array_fill(0, count($new_ids), '?')); + $affected = db_fetch_assoc_prepared("SELECT user_id FROM user_auth_realm WHERE realm_id IN ($placeholders) + UNION SELECT ugm.user_id FROM user_auth_group_realm AS ugr + INNER JOIN user_auth_group_members AS ugm ON ugm.group_id = ugr.group_id + WHERE ugr.realm_id IN ($placeholders)", array_merge($new_ids, $new_ids)); + if (!is_array($affected)) { + db_rollback_transaction(); + return false; + } + foreach ($affected as $row) { + $user_id = (int) $row['user_id']; + if (!db_execute_prepared('DELETE FROM user_auth_cache WHERE user_id = ?', [$user_id]) || + !db_execute_prepared('UPDATE user_auth SET reset_perms = FLOOR(RAND() * 4294967295) + 1 WHERE id = ?', [$user_id])) { + db_rollback_transaction(); + return false; + } + } + if (!db_commit_transaction()) { db_rollback_transaction(); return false; } - api_plugin_replicate_config(); + foreach ($affected as $row) { + if (isset($_SESSION['sess_user_id']) && (int) $_SESSION['sess_user_id'] === (int) $row['user_id']) { + unset($_SESSION['sess_user_realms'], $_SESSION['sess_auth_names']); + break; + } + } return true; } -/** - * Upgrade the Syslog database schema for legacy installs. - * - * @return void - */ -function syslog_check_upgrade(): void { - global $config, $syslogdb_default, $syslog_levels, $syslog_upgrade; +/** Upgrade the Syslog schema and permissions at install/upgrade time. */ +function syslog_check_upgrade(): bool { + global $config, $syslogdb_default, $syslog_levels, $syslog_upgrade, $syslog_cnn; require_once(__DIR__ . '/includes/schema.php'); require_once(__DIR__ . '/includes/settings.php'); - syslog_connect(); + if (!syslog_connect()) { + return false; + } if (!syslog_upgrade_create_permission_realms()) { - return; + return false; } syslog_upgrade_saved_search_realm(); syslog_upgrade_dashboard_realm(); syslog_upgrade_rule_permissions(); if (!syslog_upgrade_consolidate_rule_realms()) { - return; + return false; } - // Realm registration is install-only in Cacti. Legacy migrations below preserve - // and adjust existing realm IDs without calling the guarded registration API. + // Cacti's registration API merges overlapping filenames; preserve legacy + // realm IDs and grants while moving those pages to their explicit realms. syslog_upgrade_device_rule_realm(); - syslog_refresh_permission_roles(); - - // Let's only run this check if we are on a page that actually needs the data - $files = ['plugins.php', 'syslog.php', 'syslog_removal.php', 'syslog_alerts.php', 'syslog_device_rules.php', 'syslog_reports.php', 'syslog_saved_searches.php', 'syslog_dashboards.php']; - - if (substr($_SERVER['SCRIPT_FILENAME'], -18) != 'syslog_process.php' && !in_array(get_current_page(), $files, true)) { - return; + if (!syslog_upgrade_explicit_realm_grants()) { + return false; } - syslog_ensure_share_tables(); + if (!syslog_ensure_table_structures()) { + return false; + } + db_execute_prepared('DELETE FROM plugin_hooks WHERE name = ? AND hook = ?', ['syslog', 'config_insert']); // don't let this script timeout ini_set('max_execution_time', 0); - if (function_exists('api_plugin_upgrade_register')) { - if (!api_plugin_upgrade_register('syslog')) { - // This table was introduced after the original remote schema. Ensure - // an already-current remote collector can repair the omission without - // requiring a plugin version change. - syslog_create_device_rule_table(); - // No upgrade required, but still warn about deprecated table layouts - syslog_notice_traditional_tables(true); - - return; - } - } else { - $version = plugin_syslog_version(); - $current = $version['version']; - $old = db_fetch_cell("SELECT version FROM plugin_config WHERE directory='syslog'"); - - if ($current != $old) { - api_plugin_register_hook('syslog', 'replicate_out', 'syslog_replicate_out', 'includes/processing.php', 1); - - db_execute_prepared('UPDATE plugin_config SET - version = ?, name = ?, author = ?, webpage = ? - WHERE directory = ?', - [ - $version['version'], - $version['longname'], - $version['author'], - $version['homepage'], - $version['name'] - ] - ); - } else { - syslog_create_device_rule_table(); - // No upgrade required, but still warn about deprecated table layouts - syslog_notice_traditional_tables(true); - - return; - } + $version = plugin_syslog_version(); + $installed = db_fetch_cell_prepared('SELECT version FROM plugin_config WHERE directory = ?', ['syslog']); + if ($installed !== false && version_compare((string) $installed, $version['version'], '>=')) { + syslog_notice_traditional_tables(true); + include_once($config['base_path'] . '/lib/poller.php'); + api_plugin_replicate_config(); + return true; } - if (!syslog_db_column_exists('syslog_alert', 'hash')) { - syslog_db_add_column('syslog_alert', [ - 'name' => 'hash', - 'type' => 'varchar(32)', - 'NULL' => false, - 'default' => '', - 'after' => 'id'] - ); - - syslog_db_add_column('syslog_remove', [ - 'name' => 'hash', - 'type' => 'varchar(32)', - 'NULL' => false, - 'default' => '', - 'after' => 'id'] - ); - - syslog_db_add_column('syslog_reports', [ - 'name' => 'hash', - 'type' => 'varchar(32)', - 'NULL' => false, - 'default' => '', - 'after' => 'id'] - ); + foreach (['syslog_alert', 'syslog_remove', 'syslog_reports'] as $table) { + db_update_table($table, [ + 'columns' => [['name' => 'hash', 'type' => 'varchar(32)', 'NULL' => false, 'default' => '', 'after' => 'id']], + 'primary' => ['id'] + ], false, true, $syslog_cnn); } if (syslog_db_column_exists('syslog_incoming', 'date')) { @@ -804,143 +884,93 @@ function syslog_check_upgrade(): void { CHANGE COLUMN `time` logtime timestamp default '0000-00-00';"); } - if (syslog_db_column_exists('syslog_alert', 'hash')) { - $alerts = syslog_db_fetch_assoc('SELECT * + $alerts = syslog_db_fetch_assoc('SELECT * FROM syslog_alert WHERE hash IS NULL OR hash = ""'); - if (cacti_sizeof($alerts)) { - foreach ($alerts as $a) { - $hash = get_hash_syslog($a['id'], 'syslog_alert'); - syslog_db_execute_prepared('UPDATE syslog_alert + if (cacti_sizeof($alerts)) { + foreach ($alerts as $a) { + $hash = get_hash_syslog($a['id'], 'syslog_alert'); + syslog_db_execute_prepared('UPDATE syslog_alert SET hash = ? WHERE id = ?', - [$hash, $a['id']]); - } + [$hash, $a['id']]); } } - if (syslog_db_column_exists('syslog_remove', 'hash')) { - $removes = syslog_db_fetch_assoc('SELECT * + $removes = syslog_db_fetch_assoc('SELECT * FROM syslog_remove WHERE hash IS NULL OR hash = ""'); - if (cacti_sizeof($removes)) { - foreach ($removes as $r) { - $hash = get_hash_syslog($r['id'], 'syslog_remove'); - syslog_db_execute_prepared('UPDATE syslog_remove + if (cacti_sizeof($removes)) { + foreach ($removes as $r) { + $hash = get_hash_syslog($r['id'], 'syslog_remove'); + syslog_db_execute_prepared('UPDATE syslog_remove SET hash = ? WHERE id = ?', - [$hash, $r['id']]); - } + [$hash, $r['id']]); } } - if (syslog_db_column_exists('syslog_reports', 'hash')) { - $reports = syslog_db_fetch_assoc('SELECT * + $reports = syslog_db_fetch_assoc('SELECT * FROM syslog_reports WHERE hash IS NULL OR hash = ""'); - if (cacti_sizeof($reports)) { - foreach ($reports as $r) { - $hash = get_hash_syslog($r['id'], 'syslog_reports'); - syslog_db_execute_prepared('UPDATE syslog_reports + if (cacti_sizeof($reports)) { + foreach ($reports as $r) { + $hash = get_hash_syslog($r['id'], 'syslog_reports'); + syslog_db_execute_prepared('UPDATE syslog_reports SET hash = ? WHERE id = ?', - [$hash, $r['id']]); - } + [$hash, $r['id']]); } } - if (syslog_db_table_exists('syslog_saved_searches', false) && syslog_db_column_exists('syslog_saved_searches', 'hash')) { - $searches = syslog_db_fetch_assoc('SELECT * + $searches = syslog_db_fetch_assoc('SELECT * FROM syslog_saved_searches WHERE hash IS NULL OR hash = ""'); - if (cacti_sizeof($searches)) { - foreach ($searches as $s) { - $hash = get_hash_syslog($s['id'], 'syslog_saved_searches'); - syslog_db_execute_prepared('UPDATE syslog_saved_searches + if (cacti_sizeof($searches)) { + foreach ($searches as $s) { + $hash = get_hash_syslog($s['id'], 'syslog_saved_searches'); + syslog_db_execute_prepared('UPDATE syslog_saved_searches SET hash = ? WHERE id = ?', - [$hash, $s['id']]); - } + [$hash, $s['id']]); } } - // These tables were introduced after the original plugin schema. On a - // partially upgraded remote collector create them below before attempting - // their data migration; otherwise the column probe itself emits an SQL - // error on every poller invocation. - if (syslog_db_table_exists('syslog_dashboards', false) && syslog_db_column_exists('syslog_dashboards', 'hash')) { - $dashboards = syslog_db_fetch_assoc('SELECT * + $dashboards = syslog_db_fetch_assoc('SELECT * FROM syslog_dashboards WHERE hash IS NULL OR hash = ""'); - if (cacti_sizeof($dashboards)) { - foreach ($dashboards as $d) { - $hash = get_hash_syslog($d['id'], 'syslog_dashboards'); - syslog_db_execute_prepared('UPDATE syslog_dashboards + if (cacti_sizeof($dashboards)) { + foreach ($dashboards as $d) { + $hash = get_hash_syslog($d['id'], 'syslog_dashboards'); + syslog_db_execute_prepared('UPDATE syslog_dashboards SET hash = ? WHERE id = ?', - [$hash, $d['id']]); - } + [$hash, $d['id']]); } } - if (!syslog_db_column_exists('syslog_alert', 'level')) { - syslog_db_add_column('syslog_alert', [ - 'name' => 'level', - 'type' => 'int(10)', - 'unsigned' => true, - 'NULL' => false, - 'default' => '0', - 'after' => 'method'] - ); - } - - if (!syslog_db_column_exists('syslog_alert', 'notify')) { - syslog_db_add_column('syslog_alert', [ - 'name' => 'notify', - 'type' => 'int(10)', - 'unsigned' => true, - 'NULL' => false, - 'default' => '0', - 'after' => 'email'] - ); - } - - if (!syslog_db_column_exists('syslog_alert', 'body')) { - syslog_db_add_column('syslog_alert', [ - 'name' => 'body', - 'type' => 'varchar(8192)', - 'NULL' => false, - 'default' => '', - 'after' => 'message'] - ); - } - - foreach ([ - ['cooldown_minutes', 'int(10)', '-1', 'repeat_alert'], - ['deduplication_minutes', 'int(10)', '-1', 'cooldown_minutes'], - ['suppression_schedule', 'text', '', 'deduplication_minutes'], - ['maintenance_mode', 'varchar(16)', 'inherit', 'suppression_schedule'], - ['maintenance_days', 'varchar(32)', '1,2,3,4,5', 'maintenance_mode'], - ['maintenance_start', 'char(5)', '00:00', 'maintenance_days'], - ['maintenance_end', 'char(5)', '00:00', 'maintenance_start'], - ['maintenance_datetime_start', 'varchar(16)', '', 'maintenance_end'], - ['maintenance_datetime_end', 'varchar(16)', '', 'maintenance_datetime_start'] - ] as [$name, $type, $default, $after]) { - if (!syslog_db_column_exists('syslog_alert', $name)) { - syslog_db_add_column('syslog_alert', [ - 'name' => $name, - 'type' => $type, - 'NULL' => false, - 'default' => $default, - 'after' => $after] - ); - } - } + db_update_table('syslog_alert', [ + 'columns' => [ + ['name' => 'level', 'type' => 'int(10)', 'unsigned' => true, 'NULL' => false, 'default' => '0', 'after' => 'method'], + ['name' => 'notify', 'type' => 'int(10)', 'unsigned' => true, 'NULL' => false, 'default' => '0', 'after' => 'email'], + ['name' => 'body', 'type' => 'varchar(8192)', 'NULL' => false, 'default' => '', 'after' => 'message'], + ['name' => 'cooldown_minutes', 'type' => 'int(10)', 'NULL' => false, 'default' => '-1', 'after' => 'repeat_alert'], + ['name' => 'deduplication_minutes', 'type' => 'int(10)', 'NULL' => false, 'default' => '-1', 'after' => 'cooldown_minutes'], + ['name' => 'suppression_schedule', 'type' => 'text', 'NULL' => false, 'default' => '', 'after' => 'deduplication_minutes'], + ['name' => 'maintenance_mode', 'type' => 'varchar(16)', 'NULL' => false, 'default' => 'inherit', 'after' => 'suppression_schedule'], + ['name' => 'maintenance_days', 'type' => 'varchar(32)', 'NULL' => false, 'default' => '1,2,3,4,5', 'after' => 'maintenance_mode'], + ['name' => 'maintenance_start', 'type' => 'char(5)', 'NULL' => false, 'default' => '00:00', 'after' => 'maintenance_days'], + ['name' => 'maintenance_end', 'type' => 'char(5)', 'NULL' => false, 'default' => '00:00', 'after' => 'maintenance_start'], + ['name' => 'maintenance_datetime_start', 'type' => 'varchar(16)', 'NULL' => false, 'default' => '', 'after' => 'maintenance_end'], + ['name' => 'maintenance_datetime_end', 'type' => 'varchar(16)', 'NULL' => false, 'default' => '', 'after' => 'maintenance_datetime_start'] + ], + 'primary' => ['id'] + ], false, true, $syslog_cnn); syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_alert_suppression` ( `alert_id` int(10) unsigned NOT NULL, @@ -958,147 +988,23 @@ function syslog_check_upgrade(): void { // Removal rules now store the same structured filter JSON as alert rules. syslog_db_execute('ALTER TABLE syslog_remove MODIFY column message TEXT NOT NULL'); - if (!syslog_db_column_exists('syslog_reports', 'notify')) { - syslog_db_add_column('syslog_reports', [ - 'name' => 'notify', - 'type' => 'int(10)', - 'unsigned' => true, - 'NULL' => false, - 'default' => '0', - 'after' => 'email'] - ); - } + db_update_table('syslog_reports', [ + 'columns' => [['name' => 'notify', 'type' => 'int(10)', 'unsigned' => true, 'NULL' => false, 'default' => '0', 'after' => 'email']], + 'primary' => ['id'] + ], false, true, $syslog_cnn); syslog_db_execute('ALTER TABLE syslog_reports MODIFY column body VARCHAR(8192) NOT NULL default ""'); - if (!syslog_db_table_exists('syslog_saved_searches', false)) { - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_saved_searches` ( - `id` int(10) NOT NULL auto_increment, - `hash` varchar(32) NOT NULL default '', - `name` varchar(128) NOT NULL default '', - `search` text NOT NULL, - `removal` int(10) NOT NULL default '1', - `grouping` int(10) NOT NULL default '0', - `user` varchar(32) NOT NULL default '', - `is_global` char(2) NOT NULL default '', - `date` int(16) NOT NULL default '0', - PRIMARY KEY (`id`), - KEY owner (`user`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); - } - - if (!syslog_db_column_exists('syslog_saved_searches', 'hash')) { - syslog_db_add_column('syslog_saved_searches', [ - 'name' => 'hash', - 'type' => 'varchar(32)', - 'NULL' => false, - 'default' => '', - 'after' => 'id'] - ); - } - - if (!syslog_db_table_exists('syslog_dashboards', false)) { - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_dashboards` ( - `id` int(10) NOT NULL auto_increment, - `hash` varchar(32) NOT NULL default '', - `name` varchar(128) NOT NULL default '', - `user` varchar(32) NOT NULL default '', - `is_global` char(2) NOT NULL default '', - `date` int(16) NOT NULL default '0', - `updated` int(16) NOT NULL default '0', - PRIMARY KEY (`id`), - KEY owner (`user`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); - } - - if (!syslog_db_column_exists('syslog_dashboards', 'hash')) { - syslog_db_add_column('syslog_dashboards', [ - 'name' => 'hash', - 'type' => 'varchar(32)', - 'NULL' => false, - 'default' => '', - 'after' => 'id'] - ); - } - - if (!syslog_db_table_exists('syslog_dashboard_panels', false)) { - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_dashboard_panels` ( - `id` int(10) NOT NULL auto_increment, - `dashboard_id` int(10) NOT NULL default '0', - `title` varchar(128) NOT NULL default '', - `expression` text NOT NULL, - `source` varchar(16) NOT NULL default 'syslog', - `removal` int(10) NOT NULL default '1', - `kind` varchar(16) NOT NULL default 'timeseries', - `chart` varchar(16) NOT NULL default 'line', - `field` varchar(16) NOT NULL default 'host', - `interval` varchar(16) NOT NULL default 'dashboard', - `timespan` varchar(16) NOT NULL default 'dashboard', - `top_n` int(10) NOT NULL default '10', - `width` smallint(5) unsigned NOT NULL default '1', - `height` smallint(5) unsigned NOT NULL default '0', - `position` int(10) NOT NULL default '0', - `date` int(16) NOT NULL default '0', - PRIMARY KEY (`id`), - KEY dashboard (`dashboard_id`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); - } - - // Panel resizing persists a grid span and chart height per panel. - if (!syslog_db_column_exists('syslog_dashboard_panels', 'width')) { - syslog_db_add_column('syslog_dashboard_panels', [ - 'name' => 'width', - 'type' => 'smallint(5) unsigned', - 'NULL' => false, - 'default' => '1', - 'after' => 'top_n'] - ); - } - - if (!syslog_db_column_exists('syslog_dashboard_panels', 'height')) { - syslog_db_add_column('syslog_dashboard_panels', [ - 'name' => 'height', - 'type' => 'smallint(5) unsigned', - 'NULL' => false, - 'default' => '0', - 'after' => 'width'] - ); - } - - // Sharing persists a global flag on the dashboard itself. - if (!syslog_db_column_exists('syslog_dashboards', 'is_global')) { - syslog_db_add_column('syslog_dashboards', [ - 'name' => 'is_global', - 'type' => 'char(2)', - 'NULL' => false, - 'default' => '', - 'after' => 'user'] - ); - } - - if (!syslog_db_table_exists('syslog_status', false)) { - syslog_db_execute("CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_status` ( - `name` varchar(64) NOT NULL default '', - `value` text NOT NULL, - `updated` int(16) NOT NULL default '0', - PRIMARY KEY (`name`)) - ENGINE=InnoDB - ROW_FORMAT=Dynamic"); - } else { - syslog_db_execute("ALTER TABLE `$syslogdb_default`.`syslog_status` MODIFY column `value` TEXT NOT NULL"); - } - - syslog_create_device_rule_table(); - syslog_create_replication_output_table(); syslog_create_replication_receipts_table(); syslog_create_replication_collectors_table(); syslog_create_replication_recovery_table(); syslog_ensure_message_text(); syslog_ensure_replication_history_columns(); + syslog_refresh_plugin_version(); + include_once($config['base_path'] . '/lib/poller.php'); + api_plugin_replicate_config(); + return true; } @@ -1118,6 +1024,23 @@ function plugin_syslog_version(): array { return []; } +/** Keep the installed plugin metadata aligned with INFO without reinstalling. */ +function syslog_refresh_plugin_version(): void { + $version = plugin_syslog_version(); + $stored_version = db_fetch_cell_prepared('SELECT version FROM plugin_config WHERE directory = ?', ['syslog']); + if (!isset($version['version']) || ($stored_version !== false && version_compare((string) $stored_version, $version['version'], '>='))) { + return; + } + + db_execute_prepared('UPDATE plugin_config SET version = ?, name = ?, author = ?, webpage = ? WHERE directory = ?', [ + $version['version'], + $version['longname'] ?? '', + $version['author'] ?? '', + $version['homepage'] ?? '', + 'syslog' + ]); +} + /** * Check that the Syslog plugin dependencies are met. * diff --git a/syslog.php b/syslog.php index 44fbc7d0..317d53f9 100644 --- a/syslog.php +++ b/syslog.php @@ -1748,19 +1748,24 @@ function get_syslog_messages(string &$sql_where, int|string $rows, string $tab): $sql_order $sql_limit"; } elseif (get_request_var('removal') == '1') { - $query_sql = "( + $branch_limit = isset_request_var('export') ? 10000 : (int) $rows * get_request_var('page'); + $query_sql = "SELECT * FROM ( SELECT $message_columns, `syslog_programs`.`program`, 'main' AS mtype FROM `$syslogdb_default`.`syslog` AS syslog LEFT JOIN `$syslogdb_default`.`syslog_programs` ON syslog.program_id=syslog_programs.program_id $sql_where - ) UNION ( + $sql_order + LIMIT $branch_limit + ) AS main_rows UNION ALL SELECT * FROM ( SELECT $message_columns, `syslog_programs`.`program`, 'remove' AS mtype FROM `$syslogdb_default`.`syslog_removed` AS syslog LEFT JOIN `$syslogdb_default`.`syslog_programs` ON syslog.program_id = syslog_programs.program_id $sql_where - ) + $sql_order + LIMIT $branch_limit + ) AS removed_rows $sql_order $sql_limit"; } else { diff --git a/syslog_dashboards.php b/syslog_dashboards.php index c3a475ac..39fe6431 100644 --- a/syslog_dashboards.php +++ b/syslog_dashboards.php @@ -5,9 +5,7 @@ include_once('./plugins/syslog/includes/database.php'); include_once(__DIR__ . '/lib/syslog_dashboard.php'); -// The page is part of the Syslog Administration realm. Accept the explicit -// realm lookup too so pre-existing installs work before realm repair runs. -if (!api_plugin_user_realm_auth('syslog_dashboards.php') && !api_plugin_user_realm_auth('syslog_alerts.php')) { +if (!syslog_realm_allowed('Syslog Administration')) { die(__('Permission denied.', 'syslog')); } @@ -744,4 +742,4 @@ function syslog_dashboard_edit($row, $error) { } print "

" . __('Renaming applies to every viewer of this dashboard. Panels are managed by their owners from the Syslog Dashboard tab.', 'syslog') . '

'; form_save_button('syslog_dashboards.php', '', 'edit', false); -} \ No newline at end of file +} diff --git a/syslog_saved_searches.php b/syslog_saved_searches.php index fe721535..5cb4254f 100644 --- a/syslog_saved_searches.php +++ b/syslog_saved_searches.php @@ -4,10 +4,7 @@ include_once('./plugins/syslog/includes/functions.php'); include_once('./plugins/syslog/includes/database.php'); -// The page was originally registered in its own realm and is now part of -// Syslog Administration. Accept both mappings so existing installations do -// not lose access when the realm registration changes. -if (!api_plugin_user_realm_auth('syslog_saved_searches.php') && !api_plugin_user_realm_auth('syslog_alerts.php')) { +if (!syslog_realm_allowed('Syslog Administration')) { die(__('Permission denied.', 'syslog')); } diff --git a/tests/Security/DashboardPermissionsTest.php b/tests/Security/DashboardPermissionsTest.php index ccdb16f5..83d33571 100644 --- a/tests/Security/DashboardPermissionsTest.php +++ b/tests/Security/DashboardPermissionsTest.php @@ -139,9 +139,8 @@ return $global_dashboard; }); - test_override('api_plugin_user_realm_auth', function ($file) { - return false; - }); + test_override('db_fetch_assoc_prepared', fn ($sql, $params) => ($params[1] ?? '') === 'Syslog Administration' ? [['id' => 7]] : []); + test_override('is_realm_allowed', fn ($id) => false); $executed = []; @@ -203,9 +202,8 @@ return false; }); - test_override('api_plugin_user_realm_auth', function ($file) { - return true; - }); + test_override('db_fetch_assoc_prepared', fn ($sql, $params) => ($params[1] ?? '') === 'Syslog Administration' ? [['id' => 7]] : []); + test_override('is_realm_allowed', fn ($id) => $id === 107); $executed = []; @@ -274,4 +272,4 @@ expect($result)->toHaveKey('error'); expect($result['error'])->toBe('Invalid dashboard chart type.'); -}); \ No newline at end of file +}); diff --git a/tests/Security/DashboardRealmUpgradeTest.php b/tests/Security/DashboardRealmUpgradeTest.php index 68c82abd..006c50b9 100644 --- a/tests/Security/DashboardRealmUpgradeTest.php +++ b/tests/Security/DashboardRealmUpgradeTest.php @@ -3,109 +3,35 @@ +-------------------------------------------------------------------------+ | Copyright (C) 2004-2026 The Cacti Group | +-------------------------------------------------------------------------+ - | Cacti: The Complete RRDTool-based Graphing Solution | - +-------------------------------------------------------------------------+ */ -/* - * Regression coverage for the dashboard realm upgrade: legacy installs must - * gain access to the dashboards console page inside the admin realm without - * the realm id changing (which would orphan existing grants), and the - * migration must be idempotent and fail closed when the admin realm itself - * is missing. - */ - -it('folds the dashboards realm into the admin realm without changing the realm id', function () { - // syslog_upgrade_dashboard_realm() (setup.php) reads/writes this by - // `global`; binding it here up front keeps every reference below on the - // same storage instead of a closure-local shadow. - global $user_auth_realm_filenames, $test_realms, $test_writes, $test_replications; - +it('adds dashboard access to a legacy admin realm without changing its id', function () { syslog_load_plugin_source('setup.php'); + $realms = [['id' => 7, 'file' => 'syslog_alerts.php,syslog_removal.php,syslog_reports.php,syslog_saved_searches.php', 'display' => 'Legacy Admin']]; + $writes = 0; - test_override('db_fetch_assoc_prepared', function ($sql, $params) { - global $test_realms; - - return $test_realms; + test_override('db_fetch_assoc_prepared', function () use (&$realms) { + return $realms; }); - - test_override('db_execute_prepared', function ($sql, $params) { - global $test_realms, $test_writes; - - foreach ($test_realms as &$realm) { + test_override('db_execute_prepared', function ($sql, $params) use (&$realms, &$writes) { + foreach ($realms as &$realm) { if ($realm['id'] == $params[1]) { $realm['file'] = $params[0]; } } - - $test_writes++; - - return true; - }); - - test_override('api_plugin_replicate_config', function () { - global $test_replications; - - $test_replications++; - }); - - test_override('api_plugin_user_realm_auth', function ($file) { - global $user_auth_realm_filenames; - + $writes++; return true; }); - // An install whose admin realm predates the dashboards page. - $test_realms = [['id' => 7, 'file' => 'syslog_alerts.php,syslog_removal.php,syslog_reports.php,syslog_saved_searches.php']]; - $user_auth_realm_filenames = ['syslog_alerts.php' => 107]; - - $test_writes = 0; - $test_replications = 0; - syslog_upgrade_dashboard_realm(); - - if ($test_realms[0]['id'] !== 7) { - throw new RuntimeException('Admin realm ID preserved'); - } - - if (!str_contains($test_realms[0]['file'], 'syslog_dashboards.php')) { - throw new RuntimeException('Dashboards page added to the admin realm'); - } - - if (($user_auth_realm_filenames['syslog_dashboards.php'] ?? 0) !== 107) { - throw new RuntimeException('Current request can reach the dashboards page'); - } - - if ($test_replications !== $test_writes) { - throw new RuntimeException('Only changed mappings replicated'); - } - + expect($realms[0]['id'])->toBe(7) + ->and($realms[0]['file'])->toContain('syslog_dashboards.php') + ->and($writes)->toBe(1); syslog_upgrade_dashboard_realm(); + expect($writes)->toBe(1, 'Repeated upgrade is idempotent'); - if ($test_writes !== 1) { - throw new RuntimeException('Migration is idempotent'); - } - - // An install already carrying the mapping must not be touched. - $before = $test_realms; - $test_writes = 0; - + $realms = []; + $writes = 0; syslog_upgrade_dashboard_realm(); - - if ($test_realms !== $before || $test_writes !== 0) { - throw new RuntimeException('Existing mapping preserved'); - } - - // Missing admin realm: nothing is written. - $test_realms = []; - $user_auth_realm_filenames = []; - $test_writes = 0; - - syslog_upgrade_dashboard_realm(); - - if ($test_writes !== 0 || $user_auth_realm_filenames !== []) { - throw new RuntimeException('Missing admin realm fails closed'); - } - - expect(true)->toBeTrue(); -}); \ No newline at end of file + expect($writes)->toBe(0, 'Missing admin realm fails closed'); +}); diff --git a/tests/Security/DashboardSharePermissionsTest.php b/tests/Security/DashboardSharePermissionsTest.php index d8f41c5a..5419e5ec 100644 --- a/tests/Security/DashboardSharePermissionsTest.php +++ b/tests/Security/DashboardSharePermissionsTest.php @@ -14,10 +14,7 @@ */ it('only allows the owner or an admin to toggle a dashboard share', function () { - // Real syslog_dashboard_admin() and syslog_dashboard_share() delegate to - // api_plugin_user_realm_auth(), which is overridden below; loading them - // here keeps this test correct regardless of what other test files - // already loaded. + // Exercise the real realm-ID checks, not filename mappings. syslog_load_plugin_source('includes/functions.php'); syslog_load_plugin_source('lib/syslog_dashboard.php'); @@ -33,9 +30,11 @@ return $GLOBALS['row']; }); - test_override('api_plugin_user_realm_auth', function ($file) { - return $GLOBALS['admin'] || ($GLOBALS['share'] && $file === 'syslog_dashboards_share.php'); + test_override('db_fetch_assoc_prepared', function ($sql, $params) { + $id = ['Syslog Administration' => 7, 'Share Dashboards' => 8][$params[1] ?? ''] ?? 0; + return $id ? [['id' => $id]] : []; }); + test_override('is_realm_allowed', fn ($id) => ($id === 107 && $GLOBALS['admin']) || ($id === 108 && $GLOBALS['share'])); test_override('syslog_db_execute_prepared', function ($sql, $params) { $GLOBALS['writes']++; @@ -74,4 +73,4 @@ } expect(true)->toBeTrue(); -}); \ No newline at end of file +}); diff --git a/tests/Security/ItemShareVisibilityTest.php b/tests/Security/ItemShareVisibilityTest.php index 747bc0e7..1d5d6e68 100644 --- a/tests/Security/ItemShareVisibilityTest.php +++ b/tests/Security/ItemShareVisibilityTest.php @@ -16,20 +16,105 @@ it('repairs missing share tables even when no version upgrade is pending', function () { syslog_load_plugin_source('includes/schema.php'); - $GLOBALS['syslogdb_default'] = 'cacti'; - $created = []; - - test_override('syslog_db_table_exists', fn ($table) => $table === 'syslog_dashboards_perm'); - test_override('syslog_db_execute', function ($sql) use (&$created) { $created[] = $sql; return true; }); + $GLOBALS['syslog_cnn'] = new stdClass(); + $updated = []; + test_override('db_update_table', function ($table, $definition, $remove, $log, $connection) use (&$updated) { + $updated[$table] = $definition; + expect($remove)->toBeFalse()->and($connection)->toBe($GLOBALS['syslog_cnn']); + return true; + }); syslog_ensure_share_tables(); - expect($created)->toHaveCount(1) - ->and($created[0])->toContain('CREATE TABLE IF NOT EXISTS `cacti`.`syslog_saved_searches_perm`'); + expect(array_keys($updated))->toBe(['syslog_dashboards_perm', 'syslog_saved_searches_perm']) + ->and($updated['syslog_saved_searches_perm']['primary'])->toBe(['search_id', 'type', 'item_id']); + + $setup = plugin_test_read_source('setup.php'); + expect(strpos($setup, 'syslog_ensure_table_structures();')) + ->toBeLessThan(strpos($setup, 'syslog_refresh_plugin_version();')); +}); + +it('repairs missing dashboard tables even when no version upgrade is pending', function () { + syslog_load_plugin_source('includes/schema.php'); + $GLOBALS['syslog_cnn'] = new stdClass(); + $updated = []; + test_override('db_update_table', function ($table, $definition, $remove, $log, $connection) use (&$updated) { + $updated[$table] = $definition; + expect($remove)->toBeFalse()->and($connection)->toBe($GLOBALS['syslog_cnn']); + return true; + }); + + syslog_ensure_dashboard_tables(); + + expect(array_keys($updated))->toBe(['syslog_dashboards', 'syslog_dashboard_panels']) + ->and(array_column($updated['syslog_dashboard_panels']['columns'], 'name'))->toContain('width', 'height'); + + $setup = plugin_test_read_source('setup.php'); + expect(strpos($setup, 'syslog_ensure_table_structures();')) + ->toBeLessThan(strpos($setup, 'syslog_refresh_plugin_version();')); +}); + +it('repairs the saved-search table even when no version upgrade is pending', function () { + syslog_load_plugin_source('includes/schema.php'); + $GLOBALS['syslog_cnn'] = new stdClass(); + $updated = []; + test_override('db_update_table', function ($table, $definition, $remove, $log, $connection) use (&$updated) { + $updated[$table] = $definition; + expect($remove)->toBeFalse()->and($connection)->toBe($GLOBALS['syslog_cnn']); + return true; + }); + + syslog_ensure_saved_search_tables(); + + expect(array_keys($updated))->toBe(['syslog_saved_searches']) + ->and(array_column($updated['syslog_saved_searches']['columns'], 'name'))->toContain('hash'); + + $setup = plugin_test_read_source('setup.php'); + expect(strpos($setup, 'syslog_ensure_table_structures();')) + ->toBeLessThan(strpos($setup, 'syslog_refresh_plugin_version();')); +}); + +it('updates status storage through Cacti schema metadata', function () { + syslog_load_plugin_source('includes/schema.php'); + $GLOBALS['syslog_cnn'] = new stdClass(); + $definition = null; + test_override('db_update_table', function ($table, $data, $remove, $log, $connection) use (&$definition) { + expect($table)->toBe('syslog_status') + ->and($remove)->toBeFalse() + ->and($connection)->toBe($GLOBALS['syslog_cnn']); + $definition = $data; + return true; + }); + + syslog_ensure_status_table(); + + expect($definition['primary'])->toBe(['name']) + ->and($definition['columns'][1]['type'])->toBe('text'); +}); + +it('rebuilds the complete core schema when any core table is absent', function () { + $schema = plugin_test_read_source('includes/schema.php'); + + expect($schema)->toContain("function syslog_ensure_table_structures(): bool") + ->and($schema)->toContain("syslog_setup_table_new([], true);") + ->and($schema)->toContain("'syslog_replication_recovery'") + ->and($schema)->toContain("syslog_ensure_dashboard_tables();"); $setup = plugin_test_read_source('setup.php'); - expect(strpos($setup, 'syslog_ensure_share_tables();')) - ->toBeLessThan(strpos($setup, "api_plugin_upgrade_register('syslog')")); + expect(strpos($setup, 'syslog_ensure_table_structures();')) + ->toBeLessThan(strpos($setup, 'syslog_refresh_plugin_version();')); +}); + +it('tracks every plugin table in the upgrade completeness check', function () { + $schema = plugin_test_read_source('includes/schema.php'); + preg_match_all('/CREATE TABLE IF NOT EXISTS `\$syslogdb_default`\.`(syslog[^`]+)`/', $schema, $created); + preg_match('/function syslog_ensure_table_structures\(\): bool \{(.*?)foreach \(\$tables as \$table\) \{/s', $schema, $inventory); + preg_match_all("/'(syslog[^']+)'/", $inventory[1], $checked); + preg_match('/\$tables = array_merge\(\$tables, \[(.*?)\]\);/s', $schema, $additional); + preg_match_all("/'(syslog[^']+)'/", $additional[1], $extras); + + expect(array_values(array_diff(array_unique($created[1]), array_merge($checked[1], $extras[1])))) + ->toBe([], 'Every CREATE TABLE target is verified before the upgrade succeeds'); }); it('resolves shared ids from user and group grants', function () { diff --git a/tests/Security/LogicalMessageSearchTest.php b/tests/Security/LogicalMessageSearchTest.php index 39211f97..60422f89 100644 --- a/tests/Security/LogicalMessageSearchTest.php +++ b/tests/Security/LogicalMessageSearchTest.php @@ -185,6 +185,10 @@ if ($tab === 'syslog' && $removal === '1') { expect(substr_count($GLOBALS['captured_sql'], $predicate))->toBe(2, 'Both union branches filtered'); + if ($grouping === '0') { + expect(substr_count($GLOBALS['captured_sql'], 'LIMIT 20'))->toBe(2, 'Each source is bounded before the union'); + expect(str_contains($GLOBALS['captured_sql'], 'UNION ALL'))->toBeTrue('Disjoint record types need no deduplication'); + } expect(str_contains($GLOBALS['captured_sql'], '`syslog`.*'))->toBeFalse('Union projection must not depend on matching table schemas'); expect(substr_count($GLOBALS['captured_sql'], 'syslog.replication_source_event_id'))->toBe(0, 'Operational tracking columns are not part of the display contract'); } @@ -196,6 +200,12 @@ get_syslog_messages($sql_where, 20, $tab); expect(str_contains($GLOBALS['captured_sql'], 'LIMIT 10000'))->toBeTrue('Export limit retained'); + if ($tab === 'syslog') { + $GLOBALS['request']['removal'] = '1'; + $GLOBALS['request']['grouping'] = '0'; + get_syslog_messages($sql_where, 20, $tab); + expect(substr_count($GLOBALS['captured_sql'], 'LIMIT 10000'))->toBe(3, 'Export keeps the full cap in both sources and the result'); + } unset($GLOBALS['request']['export']); } diff --git a/tests/Security/RealmIdPermissionsTest.php b/tests/Security/RealmIdPermissionsTest.php new file mode 100644 index 00000000..a0a554fd --- /dev/null +++ b/tests/Security/RealmIdPermissionsTest.php @@ -0,0 +1,41 @@ + 7], ['id' => 8]] : []; + }); + test_override('is_realm_allowed', function ($id) use (&$allowed) { + return in_array($id, $allowed, true); + }); + + expect(syslog_saved_search_admin())->toBeTrue('A granted duplicate realm remains usable') + ->and(syslog_dashboard_admin())->toBeTrue() + ->and(syslog_realm_allowed('Missing Realm'))->toBeFalse(); + $allowed = []; + expect(syslog_saved_search_admin())->toBeFalse('No implicit filename fallback'); +}); + +it('allows sharing saved searches through either the admin or share realm', function () { + syslog_load_plugin_source('includes/functions.php'); + $allowed = [109]; + test_override('db_fetch_assoc_prepared', function ($sql, $params) { + $id = ['Syslog Administration' => 7, 'Share Saved Templates' => 9][$params[1] ?? ''] ?? 0; + return $id ? [['id' => $id]] : []; + }); + test_override('is_realm_allowed', function ($id) use (&$allowed) { + return in_array($id, $allowed, true); + }); + + expect(syslog_saved_search_share())->toBeTrue(); + $allowed = [107]; + expect(syslog_saved_search_share())->toBeTrue(); + $allowed = []; + expect(syslog_saved_search_share())->toBeFalse(); +}); diff --git a/tests/Security/RemoteCollectorInstallOptionsTest.php b/tests/Security/RemoteCollectorInstallOptionsTest.php index 64d772e6..98534438 100644 --- a/tests/Security/RemoteCollectorInstallOptionsTest.php +++ b/tests/Security/RemoteCollectorInstallOptionsTest.php @@ -83,7 +83,7 @@ expect(strpos($setup, 'syslog_install_storage_engine('))->not->toBeFalse(); $compatibility_check = strpos($update, 'if (!syslog_install_storage_engine_is_compatible('); $settings_save = strpos($update, 'set_config_option($option', $compatibility_check); - $table_setup = strpos($update, 'syslog_setup_table_new($options)', $compatibility_check); + $table_setup = strpos($update, 'syslog_setup_table_new($options, $syslog_exists', $compatibility_check); expect($compatibility_check)->not->toBeFalse(); expect($settings_save)->toBeGreaterThan($compatibility_check); diff --git a/tests/Security/RulePreviewTest.php b/tests/Security/RulePreviewTest.php index 3b42de4f..76bf8cbc 100644 --- a/tests/Security/RulePreviewTest.php +++ b/tests/Security/RulePreviewTest.php @@ -207,9 +207,7 @@ function rule_preview_capture_db(array &$calls, array $overrides = []): void { $GLOBALS['request'] = []; $GLOBALS['__test_db_calls'] = []; - test_override('api_plugin_user_realm_auth', function ($page) { - return false; - }); + test_override('db_fetch_assoc_prepared', fn () => []); test_override('csrf_check', function ($fatal = true) { return true; @@ -229,9 +227,11 @@ function rule_preview_capture_db(array &$calls, array $overrides = []): void { $GLOBALS['request'] = []; $GLOBALS['__test_db_calls'] = []; - test_override('api_plugin_user_realm_auth', function ($page) { - return true; + test_override('db_fetch_assoc_prepared', function ($sql, $params) { + $id = ['Rule Viewer' => 1, 'Rule Administrator' => 2][$params[1] ?? ''] ?? 0; + return $id ? [['id' => $id]] : []; }); + test_override('is_realm_allowed', fn ($id) => true); test_override('csrf_check', function ($fatal = true) { return false; @@ -251,9 +251,11 @@ function rule_preview_capture_db(array &$calls, array $overrides = []): void { $GLOBALS['request'] = []; $GLOBALS['__test_db_calls'] = []; - test_override('api_plugin_user_realm_auth', function ($page) { - return true; + test_override('db_fetch_assoc_prepared', function ($sql, $params) { + $id = ['Rule Viewer' => 1, 'Rule Administrator' => 2][$params[1] ?? ''] ?? 0; + return $id ? [['id' => $id]] : []; }); + test_override('is_realm_allowed', fn ($id) => true); $_SERVER['REQUEST_METHOD'] = 'GET'; diff --git a/tests/Security/SavedSearchDeletePermissionsTest.php b/tests/Security/SavedSearchDeletePermissionsTest.php index 3fecb0e9..7bdf0482 100644 --- a/tests/Security/SavedSearchDeletePermissionsTest.php +++ b/tests/Security/SavedSearchDeletePermissionsTest.php @@ -14,8 +14,7 @@ */ it('only allows the owner or an admin to delete a shared saved search', function () { - // Real syslog_saved_search_admin() (from functions.php) delegates to - // api_plugin_user_realm_auth(), which is overridden below; loading it + // Real syslog_saved_search_admin() checks Cacti's realm ID; loading it // here (rather than faking syslog_saved_search_admin directly) keeps // this test correct regardless of what other test files already loaded. syslog_load_plugin_source('includes/functions.php'); @@ -46,9 +45,8 @@ return $GLOBALS['row']; }); - test_override('api_plugin_user_realm_auth', function ($file) { - return $GLOBALS['admin']; - }); + test_override('db_fetch_assoc_prepared', fn ($sql, $params) => ($params[1] ?? '') === 'Syslog Administration' ? [['id' => 7]] : []); + test_override('is_realm_allowed', fn ($id) => $id === 107 && $GLOBALS['admin']); test_override('syslog_db_execute_prepared', function ($sql, $params) { $GLOBALS['writes']++; diff --git a/tests/Security/SavedSearchRealmUpgradeTest.php b/tests/Security/SavedSearchRealmUpgradeTest.php index 7de49456..2e91ad16 100644 --- a/tests/Security/SavedSearchRealmUpgradeTest.php +++ b/tests/Security/SavedSearchRealmUpgradeTest.php @@ -3,117 +3,46 @@ +-------------------------------------------------------------------------+ | Copyright (C) 2004-2026 The Cacti Group | +-------------------------------------------------------------------------+ - | Cacti: The Complete RRDTool-based Graphing Solution | - +-------------------------------------------------------------------------+ */ -/* - * Regression coverage for the saved-search realm upgrade: legacy installs - * must gain access to the saved-search template realm without granting - * template-only users any other admin page, and the migration must be - * idempotent and fail closed when the admin realm itself is missing. - */ - -it('folds the saved-search realm into the admin realm without widening existing grants', function () { - // syslog_upgrade_saved_search_realm() (setup.php) reads/writes this by - // `global`; binding it here up front keeps every reference below on the - // same storage instead of a closure-local shadow. - global $user_auth_realm_filenames, $test_realms, $test_grants, $test_writes, $test_replications; - +it('adds saved-search access to a legacy admin realm without changing its id', function () { syslog_load_plugin_source('setup.php'); + $realms = []; + $writes = 0; - test_override('db_fetch_assoc_prepared', function ($sql, $params) { - global $test_realms; - - return $test_realms; + test_override('db_fetch_assoc_prepared', function () use (&$realms) { + return $realms; }); - - test_override('db_execute_prepared', function ($sql, $params) { - global $test_realms, $test_writes; - - foreach ($test_realms as &$realm) { + test_override('db_execute_prepared', function ($sql, $params) use (&$realms, &$writes) { + foreach ($realms as &$realm) { if ($realm['id'] == $params[1]) { $realm['file'] = $params[0]; } } - - $test_writes++; - + $writes++; return true; }); - test_override('api_plugin_replicate_config', function () { - global $test_replications; - - $test_replications++; - }); - - test_override('api_plugin_user_realm_auth', function ($file) { - global $user_auth_realm_filenames, $test_grants; - - return in_array($user_auth_realm_filenames[$file] ?? 0, $test_grants, true); - }); - foreach (['missing', 'shared', 'standalone'] as $layout) { - foreach ([[], [107], [108]] as $grants) { - $test_realms = [['id' => 7, 'file' => 'syslog_alerts.php,syslog_removal.php,syslog_reports.php']]; - $user_auth_realm_filenames = ['syslog_alerts.php' => 107]; - - if ($layout === 'shared') { - $test_realms[0]['file'] .= ',syslog_saved_searches.php'; - $user_auth_realm_filenames['syslog_saved_searches.php'] = 107; - } elseif ($layout === 'standalone') { - $test_realms[] = ['id' => 8, 'file' => 'syslog_saved_searches.php']; - $user_auth_realm_filenames['syslog_saved_searches.php'] = 108; - } - - $before = $test_realms; - $test_grants = $grants; - $test_writes = 0; - $test_replications = 0; - - syslog_upgrade_saved_search_realm(); - - $allowed = in_array(107, $grants, true) || ($layout === 'standalone' && in_array(108, $grants, true)); - - if (api_plugin_user_realm_auth('syslog_saved_searches.php') !== $allowed) { - throw new RuntimeException("$layout: template access"); - } - - if (api_plugin_user_realm_auth('syslog_alerts.php') !== in_array(107, $grants, true)) { - throw new RuntimeException("$layout: admin access unchanged"); - } - - if ($test_grants !== $grants) { - throw new RuntimeException('Grants remain unchanged'); - } - - if ($test_realms[0]['id'] !== 7) { - throw new RuntimeException('Admin realm ID preserved'); - } - - if ($layout !== 'missing' && $test_realms !== $before) { - throw new RuntimeException('Existing mappings preserved'); - } - - syslog_upgrade_saved_search_realm(); - - // $test_writes is a running total across both calls: idempotent - // re-runs must not add a second write once the mapping exists. - if ($test_writes !== ($layout === 'missing' ? 1 : 0)) { - throw new RuntimeException('Migration is idempotent'); - } - - if ($test_replications !== $test_writes) { - throw new RuntimeException('Only changed mappings replicated'); - } + $realms = [['id' => 7, 'file' => 'syslog_alerts.php,syslog_removal.php,syslog_reports.php', 'display' => 'Legacy Admin']]; + if ($layout === 'shared') { + $realms[0]['file'] .= ',syslog_saved_searches.php'; + } elseif ($layout === 'standalone') { + $realms[] = ['id' => 8, 'file' => 'syslog_saved_searches.php', 'display' => 'Templates']; + } + $writes = 0; + syslog_upgrade_saved_search_realm(); + expect($realms[0]['id'])->toBe(7) + ->and($writes)->toBe($layout === 'missing' ? 1 : 0); + if ($layout === 'missing') { + expect($realms[0]['file'])->toContain('syslog_saved_searches.php'); } + syslog_upgrade_saved_search_realm(); + expect($writes)->toBe($layout === 'missing' ? 1 : 0, 'Repeated upgrade is idempotent'); } - $test_realms = []; - $user_auth_realm_filenames = []; - + $realms = []; + $writes = 0; syslog_upgrade_saved_search_realm(); - - expect($user_auth_realm_filenames)->toBe([], 'Missing admin realm fails closed'); + expect($writes)->toBe(0, 'Missing admin realm fails closed'); }); diff --git a/tests/Security/TraditionalTableDeprecationTest.php b/tests/Security/TraditionalTableDeprecationTest.php index bbf9a92e..ed03ecf0 100644 --- a/tests/Security/TraditionalTableDeprecationTest.php +++ b/tests/Security/TraditionalTableDeprecationTest.php @@ -110,9 +110,9 @@ expect(strpos($m[0], 'syslog_notice_traditional_tables(false)')) ->not->toBeFalse('Traditional maintenance path must raise the throttled notice'); - // The UI check raises the notice when no version upgrade is required. + // The explicit upgrade check raises the notice when no version upgrade is required. expect(substr_count($setup, 'syslog_notice_traditional_tables(true)')) - ->toBeGreaterThanOrEqual(2, 'The UI upgrade check must raise the notice on both no-upgrade paths'); + ->toBeGreaterThanOrEqual(1, 'The upgrade check must raise the notice when already current'); // The notice is throttled through the settings table, not spamming. expect(strpos($functions, 'syslog_traditional_notice')) diff --git a/tests/Unit/DeviceRulePolicyTest.php b/tests/Unit/DeviceRulePolicyTest.php index 6da9c88d..49eb7889 100644 --- a/tests/Unit/DeviceRulePolicyTest.php +++ b/tests/Unit/DeviceRulePolicyTest.php @@ -48,7 +48,10 @@ $setup = plugin_test_read_source('setup.php'); expect($schema)->toContain('CREATE TABLE IF NOT EXISTS `$syslogdb_default`.`syslog_device_rule`'); - expect($setup)->toContain('syslog_create_device_rule_table();'); + expect(substr($schema, strpos($schema, 'function syslog_setup_table_new'), + strpos($schema, 'function syslog_ensure_table_structures') - strpos($schema, 'function syslog_setup_table_new'))) + ->toContain('syslog_create_device_rule_table();'); + expect($setup)->not->toContain('syslog_create_device_rule_table();'); expect($processing) ->toContain("replicate_out_table(\$rcnn_id, \$tdata, 'syslog_device_rule', \$remote_poller_id)") ->toContain("db_fetch_assoc('SELECT * FROM syslog_device_rule')") diff --git a/tests/Unit/MessageRuleLinksTest.php b/tests/Unit/MessageRuleLinksTest.php index 8909005a..83d084a2 100644 --- a/tests/Unit/MessageRuleLinksTest.php +++ b/tests/Unit/MessageRuleLinksTest.php @@ -16,14 +16,17 @@ syslog_load_plugin_source('includes/functions.php'); $allowed = []; - - test_override('api_plugin_user_realm_auth', function ($page) use (&$allowed) { - return in_array($page, $allowed, true); + test_override('db_fetch_assoc_prepared', function ($sql, $params) { + $id = ['Rule Viewer' => 1, 'Rule Administrator' => 2][$params[1] ?? ''] ?? 0; + return $id ? [['id' => $id]] : []; + }); + test_override('is_realm_allowed', function ($id) use (&$allowed) { + return in_array($id, $allowed, true); }); expect(syslog_message_rule_links(42, 'main', '2026-09-13 00:12:42'))->toBe([], 'Readers have no rule actions'); - $allowed = ['syslog_alerts.php', 'syslog_removal.php', 'syslog_rule_administrator.php']; + $allowed = [101, 102]; $links = syslog_message_rule_links(43, 'main', '2026-09-13 00:12:42'); foreach (['alarm' => 'syslog_alerts.php', 'removal' => 'syslog_removal.php'] as $action => $page) { @@ -34,7 +37,6 @@ expect($query['id'] === '43' && $query['action'] === 'newedit' && $query['date'] === '2026-09-13 00:12:42')->toBeTrue('Editor targets the selected record'); } - $allowed[] = 'syslog_device_rules.php'; $links = syslog_message_rule_links(43, 'main', '2026-09-13 00:12:42', 'platinum-router'); parse_str(parse_url($links['device'], PHP_URL_QUERY), $query); expect(strpos($links['device'], 'syslog_device_rules.php?') === 0 && $query['host'] === 'platinum-router')->toBeTrue('Permitted users can seed a device-wide rule from the selected message host'); diff --git a/tests/Unit/SetupModuleBoundaryTest.php b/tests/Unit/SetupModuleBoundaryTest.php index 250f2d9b..2d1c07f5 100644 --- a/tests/Unit/SetupModuleBoundaryTest.php +++ b/tests/Unit/SetupModuleBoundaryTest.php @@ -26,19 +26,64 @@ 'syslog_upgrade_device_rule_realm', 'syslog_upgrade_rule_permissions', 'syslog_upgrade_consolidate_rule_realms', + 'syslog_upgrade_explicit_realm_grants', 'syslog_upgrade_create_permission_realms', 'syslog_check_upgrade', 'plugin_syslog_version', + 'syslog_refresh_plugin_version', + 'syslog_upgrade_hook_module_paths', 'syslog_check_dependencies', ]; expect($matches[1])->toEqualCanonicalizing($allowed); }); -it('keeps config-array dependencies in the directly loaded settings module', function () { +it('leaves Cacti realm maps to the plugin API', function () { $settings = plugin_test_read_source('includes/settings.php'); - expect($settings)->toContain('function syslog_refresh_permission_roles(): void'); + expect($settings)->not->toContain('user_auth_realm_filenames'); +}); + +it('runs realm and version reconciliation only during install or upgrade', function () { + $setup = plugin_test_read_source('setup.php'); + $check_config = substr($setup, strpos($setup, 'function plugin_syslog_check_config')); + $check_upgrade = substr($setup, strpos($setup, 'function syslog_check_upgrade')); + $plugin_upgrade = substr($setup, strpos($setup, 'function plugin_syslog_upgrade')); + + expect($check_config)->toContain('syslog_upgrade_hook_module_paths();') + ->toContain('syslog_check_upgrade();') + ->not->toContain("api_plugin_installed('flowview')"); + expect($plugin_upgrade)->toContain('syslog_upgrade_hook_module_paths();'); + expect($check_upgrade)->toContain('syslog_refresh_plugin_version();') + ->toContain('syslog_upgrade_create_permission_realms()'); + expect(strpos($check_upgrade, 'syslog_refresh_plugin_version();')) + ->toBeGreaterThan(strpos($check_upgrade, 'syslog_ensure_replication_history_columns();')); + expect($setup)->not->toContain("api_plugin_register_hook('syslog', 'config_insert'") + ->not->toContain('if (!defined(\'IN_PLUGIN_INSTALL\')'); + expect(plugin_test_read_source('includes/settings.php')) + ->not->toContain('$permission_realms_repaired = syslog_upgrade_create_permission_realms();'); + expect($setup)->toContain("'config_arrays' => ['syslog_config_arrays', 'includes/settings.php']"); +}); + +it('confirms a version upgrade on re-enable without using Cacti installation', function () { + $setup = plugin_test_read_source('setup.php'); + $check_config = substr($setup, strpos($setup, 'function plugin_syslog_check_config'), + strpos($setup, 'function plugin_syslog_upgrade') - strpos($setup, 'function plugin_syslog_check_config')); + $advisor = plugin_test_read_source('includes/installer.php'); + $advisor = substr($advisor, strpos($advisor, 'function syslog_upgrade_advisor'), + strpos($advisor, 'function syslog_install_advisor') - strpos($advisor, 'function syslog_upgrade_advisor')); + + expect($check_config)->toContain("get_nfilter_request_var('mode') === 'enable'") + ->toContain('version_compare((string) $installed, $version[\'version\'], \'<\')') + ->toContain("!isset_request_var('syslog_upgrade_confirm')") + ->toContain('syslog_upgrade_advisor('); + expect($advisor)->toContain("form_hidden_box('mode', 'enable'") + ->toContain("form_hidden_box('syslog_upgrade_confirm', '1'") + ->toContain("syslog_confirm_button('install', 'plugins.php', 1, true)") + ->not->toContain("form_hidden_box('mode', 'install'") + ->not->toContain('syslog_setup_table_new('); + expect(plugin_test_read_source('includes/installer.php')) + ->toContain("submitPageUsingPost('plugins.php?mode=enable&id=syslog&syslog_upgrade_confirm=1')"); }); it('boots the compatibility facade before Cacti can invoke registered callbacks', function () { @@ -84,7 +129,6 @@ 'syslog_config_arrays' => 'settings.php', 'syslog_config_settings' => 'settings.php', 'syslog_settings_bottom' => 'settings.php', - 'syslog_config_insert' => 'settings.php', 'syslog_show_tab' => 'navigation.php', 'syslog_draw_navigation_text' => 'navigation.php', 'syslog_graph_buttons' => 'navigation.php', diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index 48e5a81b..d2e9ac8a 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -235,6 +235,12 @@ function db_table_exists($table, $connection = false) { } } +if (!function_exists('db_update_table')) { + function db_update_table($table, $data, $removecolumns = false, $log = true, $connection = false) { + return test_call_override('db_update_table', [$table, $data, $removecolumns, $log, $connection], true); + } +} + if (!function_exists('syslog_db_table_exists')) { function syslog_db_table_exists($table, $log = true) { return test_call_override('syslog_db_table_exists', [$table, $log], db_table_exists($table)); diff --git a/tests/regression/rule_realm_upgrade_test.php b/tests/regression/rule_realm_upgrade_test.php index 06f38573..86a563c6 100644 --- a/tests/regression/rule_realm_upgrade_test.php +++ b/tests/regression/rule_realm_upgrade_test.php @@ -1,6 +1,6 @@ exec('CREATE TABLE plugin_realms (id INTEGER PRIMARY KEY AUTOINCREMENT, plugin TEXT, file TEXT, display TEXT)'); $db->exec('CREATE TABLE user_auth_realm (user_id INTEGER, realm_id INTEGER, PRIMARY KEY(user_id, realm_id))'); $db->exec('CREATE TABLE user_auth_group_realm (group_id INTEGER, realm_id INTEGER, PRIMARY KEY(group_id, realm_id))'); +$db->exec('CREATE TABLE user_auth_group_members (group_id INTEGER, user_id INTEGER)'); function db_fetch_assoc_prepared($sql, $params = []) { global $db; $stmt = $db->prepare($sql); @@ -16,129 +17,79 @@ function db_fetch_assoc_prepared($sql, $params = []) { return $stmt->fetchAll(PDO::FETCH_ASSOC); } function db_execute_prepared($sql, $params = []) { - global $db, $fail_grants; - if (!empty($fail_grants) && strpos($sql, 'INSERT IGNORE INTO user_auth_group_realm') === 0) { return false; } + global $db, $fail_grants, $invalidated; + if (!empty($fail_grants) && str_starts_with($sql, 'INSERT IGNORE INTO user_auth_group_realm')) { + return false; + } + if (str_starts_with($sql, 'DELETE FROM user_auth_cache')) { + $invalidated[] = (int) $params[0]; + return true; + } + if (str_starts_with($sql, 'UPDATE user_auth SET reset_perms')) { + return true; + } return $db->prepare(str_replace('INSERT IGNORE', 'INSERT OR IGNORE', $sql))->execute($params); } function db_begin_transaction() { global $db; return $db->beginTransaction(); } function db_commit_transaction() { global $db; return $db->commit(); } function db_rollback_transaction() { global $db; return $db->rollBack(); } -function api_plugin_replicate_config() {} -function api_plugin_valid_entrypoint($plugin, $function) { return true; } -function api_plugin_user_realm_auth($file) { return false; } -function is_realm_allowed($realm) { - global $allowed_realms; - return in_array($realm, $allowed_realms ?? [], true); -} -function cacti_sizeof($value) { return count($value); } -function __($text, $domain = '') { return $text; } function check($condition, $message) { if (!$condition) { throw new RuntimeException($message); } } -// Upgrade an installation that only has the legacy User and combined Admin realms. $db->exec("INSERT INTO plugin_realms(plugin,file,display) VALUES ('syslog','syslog.php','Syslog User'), - ('syslog','syslog_alerts.php,syslog_removal.php,syslog_reports.php,syslog_saved_searches.php,syslog_dashboards.php','Syslog Administration')"); -$db->exec('INSERT INTO user_auth_realm VALUES (1,101),(2,102)'); -$db->exec('INSERT INTO user_auth_group_realm VALUES (10,101),(20,102)'); -check(syslog_upgrade_create_permission_realms(), 'Legacy realm migration must succeed'); -$upgrade_realms = $db->query('SELECT id,file,display FROM plugin_realms')->fetchAll(PDO::FETCH_ASSOC); -$realm_ids = []; -foreach ($upgrade_realms as $realm) { - $realm_ids[$realm['display']] = (int) $realm['id'] + 100; -} -check(isset($realm_ids['Rule Viewer'], $realm_ids['Rule Administrator'], $realm_ids['Syslog Administration'], $realm_ids['Syslog Administrator']), 'All granular realm records must be created'); -check((int) $db->query('SELECT COUNT(*) FROM user_auth_realm WHERE user_id=1 AND realm_id=' . $realm_ids['Rule Viewer'])->fetchColumn() === 1, 'Legacy Syslog User grant must become Rule Viewer'); -check((int) $db->query('SELECT COUNT(*) FROM user_auth_realm WHERE user_id=2 AND realm_id=' . $realm_ids['Rule Administrator'])->fetchColumn() === 1, 'Legacy admin grant must become Rule Administrator'); -check((int) $db->query('SELECT COUNT(*) FROM user_auth_realm WHERE user_id=2 AND realm_id=' . $realm_ids['Syslog Administration'])->fetchColumn() === 1, 'Legacy admin grant must retain Syslog Administration'); -check((int) $db->query('SELECT COUNT(*) FROM user_auth_group_realm WHERE group_id=20 AND realm_id=' . $realm_ids['Syslog Administration'])->fetchColumn() === 1, 'Legacy group grant must retain Syslog Administration'); -$before_realms = $db->query('SELECT * FROM plugin_realms ORDER BY id')->fetchAll(PDO::FETCH_ASSOC); -$before_users = $db->query('SELECT * FROM user_auth_realm ORDER BY user_id,realm_id')->fetchAll(PDO::FETCH_ASSOC); -check(syslog_upgrade_create_permission_realms(), 'Repeated realm migration must succeed'); -check($before_realms === $db->query('SELECT * FROM plugin_realms ORDER BY id')->fetchAll(PDO::FETCH_ASSOC), 'Realm creation must be idempotent'); -check($before_users === $db->query('SELECT * FROM user_auth_realm ORDER BY user_id,realm_id')->fetchAll(PDO::FETCH_ASSOC), 'Grant mapping must not reapply on later requests'); -$db->exec('DELETE FROM plugin_realms; DELETE FROM user_auth_realm; DELETE FROM user_auth_group_realm; DELETE FROM sqlite_sequence WHERE name = "plugin_realms"'); + ('syslog','syslog_alerts.php,syslog_removal.php,syslog_reports.php,syslog_saved_searches.php,syslog_dashboards.php','Legacy Administration'), + ('syslog','syslog_administrator.php','Syslog Administrator')"); +$db->exec('INSERT INTO user_auth_realm VALUES (1,101),(2,102),(3,103)'); +$db->exec('INSERT INTO user_auth_group_realm VALUES (10,102),(11,103)'); +$db->exec('INSERT INTO user_auth_group_members VALUES (10,5),(11,4)'); +$invalidated = []; -// Exercise Cacti's actual filename-overlap registration algorithm. -$source = file_get_contents($argv[1]); -$start = strpos($source, 'function api_plugin_register_realm('); -$end = strpos($source, 'function api_plugin_remove_realms(', $start); -eval(substr($source, $start, $end - $start)); - -$viewer_files = 'syslog_alerts.php,syslog_removal.php,syslog_reports.php'; -$admin_file = 'syslog_rule_administrator.php'; -$syslog_administrator_file = 'syslog_administrator.php'; -foreach ([['syslog.php', 'Syslog User'], [$admin_file, 'Rule Administrator'], [$admin_file, 'Rule Administrator'], [$admin_file, 'Rule Viewer'], [$viewer_files, 'Rule Viewer']] as $row) { - db_execute_prepared('INSERT INTO plugin_realms(plugin,file,display) VALUES(?,?,?)', ['syslog', $row[0], $row[1]]); -} -// Overlapping and disjoint grants must survive, including group-only access. -$db->exec('INSERT INTO user_auth_realm VALUES (1,102),(1,103),(2,103),(3,105)'); -$db->exec('INSERT INTO user_auth_group_realm VALUES (9,103),(10,105)'); -$before = $db->query('SELECT * FROM plugin_realms')->fetchAll(); -$grants_before = $db->query('SELECT * FROM user_auth_realm')->fetchAll(); -$fail_grants = true; -check(!syslog_upgrade_consolidate_rule_realms(), 'A failed grant copy must abort repair'); -check($before === $db->query('SELECT * FROM plugin_realms')->fetchAll(), 'Failure must retain all realm records'); -check($grants_before === $db->query('SELECT * FROM user_auth_realm')->fetchAll(), 'Failure must roll back grant changes'); -$fail_grants = false; +check(syslog_upgrade_create_permission_realms(), 'Granular realm creation succeeds'); +sort($invalidated); +check($invalidated === [1,2,5], 'Legacy grant migration invalidates affected sessions'); +$invalidated = []; +check(syslog_upgrade_consolidate_rule_realms(), 'Duplicate realm repair succeeds'); +check(syslog_upgrade_explicit_realm_grants(), 'Explicit-grant migration succeeds'); -$_SESSION['sess_auth_names'] = ['syslog_alerts.php' => 999]; -$user_auth_roles = ['Syslog' => [999]]; -for ($request = 0; $request < 50; $request++) { - syslog_upgrade_saved_search_realm(); - syslog_upgrade_dashboard_realm(); - syslog_upgrade_rule_permissions(); - check(syslog_upgrade_consolidate_rule_realms(), 'Repair must succeed'); - api_plugin_register_realm('syslog', $viewer_files, 'Rule Viewer', false); - api_plugin_register_realm('syslog', $admin_file, 'Rule Administrator', false); - api_plugin_register_realm('syslog', $syslog_administrator_file, 'Syslog Administrator', false); - syslog_refresh_permission_roles(); - check((int) $db->query('SELECT COUNT(*) FROM plugin_realms')->fetchColumn() === 4, 'Repeated upgrades must not add realms'); - check($user_auth_roles['Syslog'] === [101, 102, 104, 106], 'Every surviving realm must appear once under Syslog'); - check($user_auth_realm_filenames['syslog_alerts.php'] === 104, 'Viewer filename must use the surviving Viewer realm'); - check($user_auth_realm_filenames[$admin_file] === 102, 'Administrator must remain separate'); - check(!isset($user_auth_realm_filenames['syslog_dashboards.php']), 'Viewer must not acquire dashboard administration'); -} -$syslog_administrator_id = (int) $db->query("SELECT id FROM plugin_realms WHERE file = '$syslog_administrator_file'")->fetchColumn() + 100; -$allowed_realms = [$syslog_administrator_id]; -syslog_refresh_permission_roles(); -foreach (['syslog.php', 'syslog_alerts.php', 'syslog_removal.php', 'syslog_reports.php', $admin_file, 'syslog_saved_searches.php', 'syslog_saved_searches_share.php', 'syslog_dashboards.php', 'syslog_dashboards_share.php'] as $page) { - check(($user_auth_realm_filenames[$page] ?? 0) === $syslog_administrator_id, 'Syslog Administrator must imply every Syslog permission'); +$realms = $db->query("SELECT id, display FROM plugin_realms WHERE plugin = 'syslog'")->fetchAll(PDO::FETCH_ASSOC); +$ids = []; +foreach ($realms as $realm) { + $ids[$realm['display']] = (int) $realm['id'] + 100; } -$allowed_realms = []; -syslog_refresh_permission_roles(); - -// A Rule Administrator must be able to see and open the rule menu entries -// without acquiring saved-search or dashboard administration. -$rule_administrator_id = (int) $db->query("SELECT id FROM plugin_realms WHERE display = 'Rule Administrator'")->fetchColumn() + 100; -$allowed_realms = [$rule_administrator_id]; -syslog_refresh_permission_roles(); -foreach (['syslog_alerts.php', 'syslog_removal.php', 'syslog_reports.php'] as $page) { - check(($user_auth_realm_filenames[$page] ?? 0) === $rule_administrator_id, 'Rule Administrator must imply rule menu access'); +foreach (['Rule Viewer', 'Rule Administrator', 'Syslog Administration', 'Share Saved Templates', 'Share Dashboards'] as $display) { + check(isset($ids[$display]), "$display exists"); } -foreach (['syslog_saved_searches.php', 'syslog_dashboards.php'] as $page) { - check(($user_auth_realm_filenames[$page] ?? 0) !== $rule_administrator_id, 'Rule Administrator must not acquire template or dashboard administration'); +check(!isset($ids['Syslog Administrator']), 'Umbrella realm retired'); +foreach (['user_auth_realm' => ['user_id', 3], 'user_auth_group_realm' => ['group_id', 11]] as $table => [$owner, $id]) { + foreach ($ids as $realm_id) { + $count = $db->query("SELECT COUNT(*) FROM $table WHERE $owner = $id AND realm_id = $realm_id")->fetchColumn(); + check((int) $count === 1, 'Umbrella grants copied to each explicit realm'); + } } -$allowed_realms = []; -syslog_refresh_permission_roles(); +check((int) $db->query('SELECT COUNT(*) FROM user_auth_realm WHERE user_id = 2 AND realm_id = ' . $ids['Rule Viewer'])->fetchColumn() === 1, + 'Legacy rule administrator can open viewer pages'); +check((int) $db->query('SELECT COUNT(*) FROM user_auth_group_realm WHERE group_id = 10 AND realm_id = ' . $ids['Rule Viewer'])->fetchColumn() === 1, + 'Rule administrator group can open viewer pages'); +check((int) $db->query('SELECT COUNT(*) FROM user_auth_realm WHERE user_id = 1 AND realm_id = ' . $ids['Rule Administrator'])->fetchColumn() === 0, + 'Viewer was not elevated to administrator'); +sort($invalidated); +check($invalidated === [2,3,4,5], 'Changed direct and group permissions invalidate active sessions'); -check($db->query('SELECT user_id,realm_id FROM user_auth_realm ORDER BY user_id')->fetchAll(PDO::FETCH_NUM) === [[1,102],[2,102],[3,104]], 'Preserve all user grants without elevating Viewer'); -check($db->query('SELECT group_id,realm_id FROM user_auth_group_realm ORDER BY group_id')->fetchAll(PDO::FETCH_NUM) === [[9,102],[10,104]], 'Preserve group grants and remove obsolete IDs'); -check(!isset($_SESSION['sess_auth_names']['syslog_alerts.php']), 'Invalidate stale filename cache'); +$before = $db->query('SELECT user_id, realm_id FROM user_auth_realm ORDER BY user_id, realm_id')->fetchAll(PDO::FETCH_NUM); +$invalidated = []; +check(syslog_upgrade_explicit_realm_grants(), 'Repeated migration succeeds'); +check($before === $db->query('SELECT user_id, realm_id FROM user_auth_realm ORDER BY user_id, realm_id')->fetchAll(PDO::FETCH_NUM), + 'Repeated migration changes no grants'); +check($invalidated === [], 'Repeated migration does not invalidate sessions again'); -// A single corrupted Viewer must also be repaired before registration. -db_execute_prepared('UPDATE plugin_realms SET file = ? WHERE id = ?', [$admin_file, 4]); -check(syslog_upgrade_consolidate_rule_realms(), 'Repair a single corrupted Viewer'); -check($db->query('SELECT file FROM plugin_realms WHERE id=4')->fetchColumn() === $viewer_files, 'Restore canonical Viewer files'); +$fail_grants = true; +$before = $db->query('SELECT group_id, realm_id FROM user_auth_group_realm ORDER BY group_id, realm_id')->fetchAll(PDO::FETCH_NUM); +check(!syslog_upgrade_explicit_realm_grants(), 'Failed grant copy aborts migration'); +check($before === $db->query('SELECT group_id, realm_id FROM user_auth_group_realm ORDER BY group_id, realm_id')->fetchAll(PDO::FETCH_NUM), + 'Failed migration rolls back'); -// A legacy combined administration realm becomes Rule Administrator only; -// saved-search templates and dashboards must be granted separately. -db_execute_prepared('INSERT INTO plugin_realms(plugin,file,display) VALUES(?,?,?)', [ - 'syslog', - 'syslog_alerts.php,syslog_removal.php,syslog_reports.php,syslog_saved_searches.php,syslog_dashboards.php', - 'Syslog Administration' -]); -$legacy_id = (int) $db->lastInsertId(); -syslog_upgrade_rule_permissions(); -$legacy_files = (string) $db->query("SELECT file FROM plugin_realms WHERE id = $legacy_id")->fetchColumn(); -check($legacy_files === $admin_file, 'Legacy rule administration must not retain templates or dashboards'); -print "rule_realm_upgrade_test passed (50 upgrade cycles, grants, rollback, grouping, stale cache)\n"; +$setup = file_get_contents(dirname(__DIR__, 2) . '/setup.php'); +check(str_contains($setup, "api_plugin_register_realm('syslog', 'syslog.php'"), 'Fresh install uses Cacti realm API'); +check(!str_contains($setup, "api_plugin_register_realm('syslog', 'syslog_administrator.php'"), 'Fresh install has no umbrella realm'); +check(!str_contains($setup, 'user_auth_realm_filenames'), 'Plugin does not rewrite Cacti filename maps'); +print "rule_realm_upgrade_test passed\n";