Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions includes/schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,9 @@ function syslog_setup_table_new(array $options, bool $repair = false): void {
syslog_ensure_share_tables();
syslog_ensure_dashboard_tables();
syslog_create_device_rule_table();
if (!$repair) {
syslog_dashboard_seed_default();
}

if (!$repair) {
if (!isset($settings['syslog'])) {
Expand Down
19 changes: 13 additions & 6 deletions js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,14 @@ function syslogDashboardBindResize(handle, card, panel, axis) {
}

function actionButtons() {
// Shared dashboards are view-only for non-owners.
if (!syslogDashboard.canManage) return [];

var buttons = [];
var actions = [['refresh', 'fa-sync', syslogDashboard.text.refreshPanel]];
if (syslogDashboard.canManage) {
actions.push(['edit', 'fa-pen', 'Edit'], ['delete', 'fa-trash', 'Delete']);
}
// fa-pen: Font Awesome 5 (Cacti 1.2) dropped the FA4 fa-pencil name,
// which rendered an empty glyph and made the button look missing.
[['edit', 'fa-pen', 'Edit'], ['up', 'fa-arrow-up', 'Move up'], ['down', 'fa-arrow-down', 'Move down'], ['delete', 'fa-trash', 'Delete']].forEach(function(entry) {
actions.forEach(function(entry) {
var button = document.createElement('button');
button.type = 'button';
button.className = 'syslogDashboardCardButton';
Expand Down Expand Up @@ -254,7 +255,10 @@ function syslogDashboardRenderChart(panel, data) {
return;
}

if (status) status.hidden = true;
if (status) {
status.hidden = false;
status.textContent = syslogDashboard.text.totalMessages + ': ' + Number(data.total).toLocaleString();
}

var config;

Expand Down Expand Up @@ -724,7 +728,10 @@ if (window.jQuery) window.jQuery(function() {
var panelId = parseInt(this.closest('.syslogDashboardCard').id.replace('syslog_panel_', ''), 10);
if (!panelId) return;

if (action === 'edit') {
if (action === 'refresh') {
var panel = syslogDashboardPanelById(panelId);
if (panel) syslogDashboardLoadPanel(panel);
} else if (action === 'edit') {
syslogPanelDialogOpen(panelId);
} else if (action === 'up' || action === 'down') {
syslogDashboardPost({
Expand Down
13 changes: 12 additions & 1 deletion lib/syslog_dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ function syslog_dashboard(): void {
$dashboard_id = 0;
}

// Never auto-select a shared dashboard; only an owned one opens by default.
// Prefer an owned dashboard, then the built-in shared default.
if ($dashboard_id === 0 && cacti_sizeof($dashboards)) {
$username = syslog_dashboard_username();

Expand All @@ -1276,6 +1276,15 @@ function syslog_dashboard(): void {
}
}

if ($dashboard_id === 0) {
foreach ($dashboards as $dashboard) {
if ($dashboard['user'] === '' && $dashboard['is_global'] === 'on' && $dashboard['name'] === 'default') {
$dashboard_id = (int) $dashboard['id'];
break;
}
}
}

$selected = syslog_dashboard_load($dashboard_id);

// Match the server-side write permission of every dashboard endpoint.
Expand Down Expand Up @@ -1354,6 +1363,8 @@ function syslog_dashboard(): void {
canShare: <?php print $can_share ? 'true' : 'false'; ?>,
shared: <?php print $is_shared ? 'true' : 'false'; ?>,
text: {
refreshPanel: <?php print syslog_json_safe(__('Refresh Panel', 'syslog')); ?>,
totalMessages: <?php print syslog_json_safe(__('Total messages', 'syslog')); ?>,
libraryUnavailable: <?php print syslog_json_safe(__('Chart library unavailable on this Cacti installation.', 'syslog')); ?>,
editPanel: <?php print syslog_json_safe(__('Edit Panel', 'syslog')); ?>,
newPanel: <?php print syslog_json_safe(__('New Panel', 'syslog')); ?>,
Expand Down
36 changes: 32 additions & 4 deletions locales/po/cacti.pot
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Cacti 1.3\n"
"Project-Id-Version: Cacti 1.2.32\n"
"Report-Msgid-Bugs-To: developers@cacti.net\n"
"POT-Creation-Date: 2026-10-09 15:06-0400\n"
"POT-Creation-Date: 2026-10-10 09:25-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Cacti Developers <developers@cacti.net>\n"
"Last-Translator: Cacti Developers <developers@cacti.net>>\n"
"Language-Team: Cacti Developers <developers@cacti.net>\n"
"Language: \n"
"MIME-Version: 1.0\n"
Expand Down Expand Up @@ -145,7 +145,7 @@ msgstr ""
msgid "Permission denied."
msgstr ""

#: includes/functions.php includes/utilities.php
#: includes/functions.php includes/utilities.php syslog.php
msgid "Invalid request. Please try again."
msgstr ""

Expand Down Expand Up @@ -1079,6 +1079,14 @@ msgstr ""
msgid "A title of up to 128 characters is required."
msgstr ""

#: lib/syslog_dashboard.php
msgid "Refresh Panel"
msgstr ""

#: lib/syslog_dashboard.php
msgid "Total messages"
msgstr ""

#: lib/syslog_dashboard.php
msgid "Chart library unavailable on this Cacti installation."
msgstr ""
Expand Down Expand Up @@ -1353,6 +1361,26 @@ msgstr ""
msgid "Aria storage cannot be used when any Remote Data Collector option is enabled. Select InnoDB or disable all Remote Data Collector options."
msgstr ""

#: setup.php
msgid "Top 10 Hosts"
msgstr ""

#: setup.php
msgid "Syslog Messages - Past Hour"
msgstr ""

#: setup.php
msgid "Messages by Severity"
msgstr ""

#: setup.php
msgid "Top 10 Programs"
msgstr ""

#: setup.php
msgid "Alert Activity"
msgstr ""

#: syslog.php
msgid "Syslog Viewer"
msgstr ""
Expand Down
65 changes: 65 additions & 0 deletions setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,7 @@ function syslog_check_upgrade(bool $force = false): bool {
if (!syslog_ensure_table_structures()) {
return false;
}
syslog_dashboard_seed_default();
db_execute_prepared('DELETE FROM plugin_hooks WHERE name = ? AND hook = ?', ['syslog', 'config_insert']);

// don't let this script timeout
Expand Down Expand Up @@ -1008,6 +1009,70 @@ function syslog_check_upgrade(bool $force = false): bool {
}


/** Create dashboard structures and seed the shared starter during install/upgrade. */
function syslog_dashboard_seed_default(): void {
global $syslogdb_default;

syslog_ensure_dashboard_tables();

$lock = 'syslog_dashboard_' . md5($syslogdb_default);
if (!syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 5)', [$lock])) {
return;
}

try {
if (syslog_db_fetch_cell_prepared("SELECT id FROM `$syslogdb_default`.`syslog_dashboards` WHERE hash = ? LIMIT 1", [md5('syslog_default_dashboard')])) {
return;
}

if (!syslog_db_execute('START TRANSACTION')) {
return;
}

$now = time();
if (!syslog_db_execute_prepared("INSERT INTO `$syslogdb_default`.`syslog_dashboards`
(hash, name, `user`, is_global, `date`, updated) VALUES (?, ?, ?, ?, ?, ?)", [md5('syslog_default_dashboard'), 'default', '', 'on', $now, $now])) {
syslog_db_execute('ROLLBACK');
return;
}

$id = (int) syslog_db_fetch_insert_id();
if ($id <= 0) {
syslog_db_execute('ROLLBACK');
return;
}

$panels = [
[__('Top 10 Hosts', 'syslog'), 'syslog', 'breakdown', 'donut', 'host', 'dashboard'],
[__('Syslog Messages - Past Hour', 'syslog'), 'syslog', 'timeseries', 'area', 'host', '3600'],
[__('Messages by Severity', 'syslog'), 'syslog', 'breakdown', 'donut', 'priority', 'dashboard'],
[__('Top 10 Programs', 'syslog'), 'syslog', 'breakdown', 'donut', 'program', 'dashboard'],
[__('Alert Activity', 'syslog'), 'alerts', 'timeseries', 'bar', 'host', 'dashboard']
];

foreach ($panels as $position => [$title, $source, $kind, $chart, $field, $timespan]) {
if (!syslog_db_execute_prepared("INSERT INTO `$syslogdb_default`.`syslog_dashboard_panels`
(dashboard_id, title, expression, source, removal, kind, chart, field,
`interval`, timespan, top_n, width, height, position, `date`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[$id, $title, '', $source, 1, $kind, $chart, $field, 'auto', $timespan, 10, 1, 0, $position + 1, $now])) {
syslog_db_execute('ROLLBACK');
return;
}
}

if (!syslog_db_execute('COMMIT')) {
syslog_db_execute('ROLLBACK');
}
} catch (Throwable $error) {
syslog_db_execute('ROLLBACK');
throw $error;
} finally {
syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', [$lock]);
}
}


/**
* Return the version information for the Syslog plugin.
*
Expand Down
55 changes: 55 additions & 0 deletions tests/Unit/DashboardDefaultTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

it('seeds valid starter panels atomically and does not reseed an existing default', function () {
syslog_load_plugin_source('setup.php');
syslog_load_plugin_source('lib/syslog_dashboard.php');
test_override('syslog_ensure_dashboard_tables', fn () => null);
test_override('syslog_db_table_exists', fn () => true);
test_override('syslog_db_column_exists', fn () => true);
$GLOBALS['syslogdb_default'] = 'syslog';
test_override('syslog_dashboard_username', fn () => 'admin');
test_override('syslog_db_fetch_insert_id', fn () => 42);
$existing = false;
$calls = [];
test_override('syslog_db_fetch_cell_prepared', function ($sql, $params) use (&$existing) {
return str_contains($sql, 'GET_LOCK') || str_contains($sql, 'RELEASE_LOCK') ? 1 : ($existing ? 42 : 0);
});
test_override('syslog_db_execute', function ($sql) use (&$calls) {
$calls[] = $sql;
return true;
});
$fail = false;
test_override('syslog_db_execute_prepared', function ($sql, $params) use (&$calls, &$fail) {
$calls[] = [$sql, $params];
return !$fail || !str_contains($sql, 'syslog_dashboard_panels');
});

syslog_dashboard_seed_default();
expect($calls[0])->toBe('START TRANSACTION');
expect($calls[1][1][1])->toBe('default');
expect($calls[1][1][2])->toBe('');
expect($calls[1][1][3])->toBe('on');
expect(end($calls))->toBe('COMMIT');
$panels = array_slice($calls, 2, 5);
expect($panels)->toHaveCount(5);
foreach ($panels as [$sql, $params]) {
expect(syslog_dashboard_panel_settings([
'source' => $params[3], 'removal' => (string) $params[4],
'kind' => $params[5], 'chart' => $params[6], 'field' => $params[7],
'interval' => $params[8], 'timespan' => $params[9], 'top_n' => $params[10]
]))->toBeArray();
expect($params[2])->toBe('');
}
expect($panels[1][1][9])->toBe('3600');

$calls = [];
$existing = true;
syslog_dashboard_seed_default();
expect($calls)->toBe([]);

$existing = false;
$fail = true;
syslog_dashboard_seed_default();
expect(end($calls))->toBe('ROLLBACK');
expect($calls)->not->toContain('COMMIT');
});
Loading