Files
cacti/api/include/db_functions.php
T
TheWitnessandCopilot b993471579 Fix #6539 - Allow Standard Cacti Log Files to be Rotated on Demand,PHPdoc, and Typing (#6540)
* Fix #6539 - Allow Standard Cacti Log Files to be Rotated on Demand from the Cacti UI

* QA: php-cs-fixer updates and documentation for lib/database.php

* QA: Update .gitignore for composer files

* QA: Update .gitignore for composer files

* QA: Back off .gitignore change its duplicated

* QA: Fix typing of db_has_permissions

* QA: Fixing typing issue in db_format_index_create

* Update lib/clog_webapi.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* QA: Fix additional code issues

* QA: Applying more defaults to add device

* QA: Fix at the database layer

* QA: Fixes based upon automated testing

* QA: Fix at the database layer

* QA: After new SQL checking

* QA: Skip MacTrack oui database download warnings

* QA: Fixing type issue

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Some minor changes to avoid errors in testing

* QA: Unused variable

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Minor introduced code defect

* QA: Adding some unifying space around concatination

* QA: Consistent comments and new function alias'

* QA: Multi-line comment corrections

* QA: Fix SQL Style

* QA: Missed one function alias

* QA: PHPStan Level 2 and PHPDoc fixes

* QA: PHPStan Level 2 and PHPDoc Fixes

* QA: PHPStan Level 6 Fixes

* QA: Fix lib/xml.php to PHPStan Level 6

* QA: Wrong grep -v

* PHPStan Level 6 for lib/vdef.php

* PHPStan Level 8 for lib/time.php

* QA: Raise PHPStan to Level 6 for lib/database.php

* QA: Remove @deprecated tag from grv()

* QA: Switch to htmle() alias over html_escape()

* QA: More addressing PHPStan issues at Level 6

* QA: Change recommended by Copilot for Readability

* QA: More PHPStan fixes

* QA: More PHPStan fixes

* QA: More PHPStan fixes for lib/utility.php

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Some phpseclib3 fixups to update to latest

* QA: More fixups for graphing for magic strings

* QA: More PHPStan fixes at Level 6

* QA: Fixing order string type on get_allowed_graphs()

* QA: Fixing improper use of print

* QA: Optimization of color_templates

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Raise PHPStan to Level 6 More Functions

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues testing Cacti

* QA: Copilot recommendation

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: Installer failing

* Undo ownership changes to remove tar errors

* QA: Updates after php-cs-fixer

* QA: Adding code checks to syntax checking

* QA Change syntax flow language

* QA: Reduce the number of cures

* QA: Use sequentional option

* Change runner to self-hosted for check job

* Fix typo in OS version in workflow file

* Add Git installation step in workflow

Added step to install Git if not already installed.

* Add '-y' flag to apt-get install and update commands

Updated apt-get commands to include '-y' for automatic confirmation.

* Change workflow OS version to 'ubuntu-latest'

Updated the workflow to use 'ubuntu-latest' instead of 'ubuntu:latest' and modified the 'runs-on' configuration.

* Remove sequential option from phpcsfixer script

* Remove PHPCS fixer step from workflow

Removed PHP coding standards check from workflow.

* Move code checks to before plugins

Moving these core checks to before the plugins are installed as we really want to check only the base Cacti here.

* Add Composer validation and caching to start of workflow

* QA: Addressing PHPStan additional

* QA: Restore composer lock file

* QA: Addressing PHPStan additional

* QA: Addressing PHPStan additional

* QA: Complete LDAP Project

* QA: php-cs-fixer doing it's thing

* QA: Syntax is already happening in PHPStan

* QA: The md5 function no longer exists

* QA: Update after ldap changes

* QA: Undo that last password hack

* QA: Attempting to switch to MariaDB 11.8

* QA: Attempting to switch to MariaDB 11.8

* QA: Resolving some issues with PHP8.4

* QA: Resolving some issues with PHP8.4

* QA: Add PHP8.5 to the matrix

* QA: Removing PHP8.5 due to dependency issues

* QA: Add the DB version to the testing output

* QA: Switch back to MySQL for the moment

* QA: More fidelity in reporting

* QA: The pipe must be mixed

* QA: Moving audit_schema.php changes from 1.2.x

* QA: Moving audit_schema.php changes from 1.2.x

* QA: Restore mysql monitoring

* QA: php-cs-fixer issues

* QA: Attempt to roll back to MariaDB 10.6

* QA: Attempt to fix check_all_pages.sh

* QA: Make installer functional again

* QA: Fix installer php-cs-fixer issues

* QA: Type to a string

* QA: Get the database version correctly

* QA: Fix issue with reports in check_all_pages.sh

* QA: Check the server version in the right place

* Feature: Support SHA256 as a new hash for passwords

* QA: Function definition and return typing audit

* QA: Refinement of form types

* QA: Refinement of form types

* QA: Consistency of comments

* QA: Issues with php-cs-fixer and more comments

* QA: More typing issues

* QA: php-cs-fixer on merge conflict resolution

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-25 10:13:56 -05:00

493 lines
17 KiB
PHP

<?php
function get_hosts(array $params = []) : mixed {
$sql = 'SELECT id as host_id, description as host_description, hostname, host_template_id,
poller_id, site_id, disabled, status, status_fail_date AS last_fail_date,
status_rec_date AS last_recovery_date, status_last_error AS last_error,
availability, polling_time, total_polls, failed_polls, snmp_sysDescr AS snmp_description,
snmp_sysUpTimeInstance AS snmp_uptime, snmp_sysLocation AS snmp_location, last_updated
FROM host';
$conditions = [];
$values = [];
if (isset($params['host_id']) && $params['host_id'] !== '') {
if (!is_array($params['host_id'])) {
$params['host_id'] = explode(',', $params['host_id']);
}
$params['host_id'] = array_map('trim', $params['host_id']);
$params['host_id'] = array_filter($params['host_id'], fn ($v) => $v !== '');
if (!empty($params['host_id'])) {
$placeholders = implode(',', array_fill(0, count($params['host_id']), '?'));
$conditions[] = "id IN ($placeholders)";
$values = array_merge($values, $params['host_id']);
}
}
if (isset($params['poller_id']) && $params['poller_id'] !== '') {
if (!is_array($params['poller_id'])) {
$params['poller_id'] = explode(',', $params['poller_id']);
}
$params['poller_id'] = array_map('trim', $params['poller_id']);
$params['poller_id'] = array_filter($params['poller_id'], fn ($v) => $v !== '');
if (!empty($params['poller_id'])) {
$placeholders = implode(',', array_fill(0, count($params['poller_id']), '?'));
$conditions[] = "poller_id IN ($placeholders)";
$values = array_merge($values, $params['poller_id']);
}
}
if (isset($params['site_id']) && $params['site_id'] !== '') {
if (!is_array($params['site_id'])) {
$params['site_id'] = explode(',', $params['site_id']);
}
$params['site_id'] = array_map('trim', $params['site_id']);
$params['site_id'] = array_filter($params['site_id'], fn ($v) => $v !== '');
if (!empty($params['site_id'])) {
$placeholders = implode(',', array_fill(0, count($params['site_id']), '?'));
$conditions[] = "site_id IN ($placeholders)";
$values = array_merge($values, $params['site_id']);
}
}
if (isset($params['template_id']) && $params['template_id'] !== '') {
if (!is_array($params['template_id'])) {
$params['template_id'] = explode(',', $params['template_id']);
}
$params['template_id'] = array_map('trim', $params['template_id']);
$params['template_id'] = array_filter($params['template_id'], fn ($v) => $v !== '');
if (!empty($params['template_id'])) {
$placeholders = implode(',', array_fill(0, count($params['template_id']), '?'));
$conditions[] = "host_template_id IN ($placeholders)";
$values = array_merge($values, $params['template_id']);
}
}
if (isset($params['status'])) {
$conditions[] = 'status = ?';
$values[] = $params['status'];
}
if (isset($params['snmp_location'])) {
$conditions[] = 'snmp_sysLocation LIKE ?';
$values[] = '%' . $params['snmp_location'] . '%';
}
// Apply WHERE clause if needed
if (!empty($conditions)) {
$sql .= ' WHERE ' . implode(' AND ', $conditions);
}
return $values ? db_fetch_assoc_prepared($sql, $values) : db_fetch_assoc($sql);
}
/**
* Retrieves a list of graphs associated with a specific host.
*
* This function queries the database to fetch graph details for a given host ID.
* It returns an array of graphs, each containing the host ID, graph ID, and RRD name.
*
* @param int $host_id The ID of the host for which to retrieve the graph list. Defaults to 0.
*
* @return array An associative array with the key "graphs" containing a list of graphs.
* Each graph is represented as an associative array with the following keys:
* - "host_id" (int): The ID of the host.
* - "graph_id" (int): The ID of the graph.
* - "rrd_name" (string): The name of the RRD file.
*/
function get_graph_list(int $host_id = 0) : array {
if (filter_var($host_id, FILTER_VALIDATE_INT) === false || $host_id < 0) {
return ['graphs' => ['Host must be a valid INT']];
}
$sql = "SELECT DISTINCT h.id as host_id, dl.id as graph_id, dtd.name_cache as rrd_name, pi.rrd_name as rrd_path
FROM poller_item AS pi
INNER JOIN data_local AS dl
ON dl.id = pi.local_data_id
INNER JOIN data_template_data AS dtd
ON dtd.local_data_id = pi.local_data_id
INNER JOIN host AS h
ON pi.host_id = h.id
WHERE rrd_name != ''
AND h.id = ?";
$rows = db_fetch_assoc_prepared($sql, [$host_id]);
$graphs = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$graphs[] = [
'host_id' => $row['host_id'],
'graph_id' => $row['graph_id'],
'rrd_name' => $row['rrd_name']
];
}
}
return ['graphs' => $graphs];
}
function get_poller_status(int $poller_id = 0) : mixed {
if (filter_var($poller_id, FILTER_VALIDATE_INT) === false || $poller_id < 0) {
return ['error' => 'Poller ID must be a valid INT'];
}
$sql = 'SELECT id, name, disabled, status, hostname, total_time, max_time,
min_time, avg_time, total_polls, processes, threads, last_update, last_status
FROM poller';
$values = [];
if ($poller_id > 0) {
$sql .= ' WHERE id = ?';
$values[] = $poller_id;
}
return $values ? db_fetch_assoc_prepared($sql, $values) : db_fetch_assoc($sql);
}
function get_host_templates(int $template_id = 0) : mixed {
if (filter_var($template_id, FILTER_VALIDATE_INT) === false || $template_id < 0) {
return ['templates' => ['Template ID must be a valid INT']];
}
$sql = 'SELECT id,name,class FROM host_template';
$values = [];
if ($template_id > 0) {
$sql .= ' WHERE id = ?';
$values[] = $template_id;
}
return $values ? db_fetch_assoc_prepared($sql, $values) : db_fetch_assoc($sql);
}
function get_cacti_status() : array {
// Get Cacti version
$cacti_version = db_fetch_cell('SELECT cacti AS version FROM version LIMIT 1');
$poller_type = read_config_option('poller_type');
$poller_enabled = read_config_option('poller_enabled');
$total_hosts = db_fetch_cell('SELECT COUNT(*) as count FROM host');
$total_data_sources = db_fetch_cell('SELECT COUNT(*) as count FROM data_local');
$poller_output_size = db_fetch_cell('SELECT COUNT(*) as count FROM poller_output');
$boost_table_size = db_fetch_cell('SELECT COUNT(*) as count FROM poller_output_boost');
if ($poller_enabled === 'on') {
$poller_enabled = 'Global Polling is enabled';
} else {
$poller_enabled = 'Global polling is disabled';
}
$poller_type = ($poller_type === '2') ? 'Spine' : (($poller_type === '1') ? 'cmd.php' : $poller_type);
// Get installed plugins
$plugins = db_fetch_assoc('SELECT name, status, version FROM plugin_config');
$installed_plugins = [];
if (is_array($plugins)) {
foreach ($plugins as $plugin) {
$status = ($plugin['status'] == 1) ? 'enabled' : 'disabled';
$installed_plugins[] = [
'name' => $plugin['name'],
'status' => $status,
'version' => $plugin['version']
];
}
}
$cacti_status = [
[
'cacti_version' => $cacti_version,
'poller_type' => $poller_type,
'poller_enabled' => $poller_enabled,
'total_hosts' => $total_hosts,
'poller_output_size' => $poller_output_size,
'boost_table_size' => $boost_table_size,
'total_data_sources' => $total_data_sources,
'installed_plugins' => $installed_plugins
]
];
return ['cacti_status' => $cacti_status];
}
function get_boost_status() : array {
$boost_rrd_update_enable = read_config_option('boost_rrd_update_enable');
if ($boost_rrd_update_enable !== 'on') {
return ['boost_rrd_update_enable' => 'Boost is disabled'];
}
$boost_last_end_time = read_config_option('boost_last_end_time');
$boost_last_start_time = read_config_option('boost_last_start_time');
$boost_next_run_time = read_config_option('boost_next_run_time');
$stats_detail_boost = read_config_option('stats_detail_boost');
$total_boost_records = db_fetch_cell('SELECT COUNT(*) as count FROM poller_output_boost');
$boost_status = [
'boost_rrd_update_enable' => $boost_rrd_update_enable,
'boost_last_end_time' => $boost_last_end_time,
'boost_last_start_time' => $boost_last_start_time,
'boost_next_run_time' => $boost_next_run_time,
'stats_detail_boost' => $stats_detail_boost,
'total_boost_records' => $total_boost_records
];
return ['boost_status' => $boost_status];
}
function get_dsstats_status() : array {
$check_dsstats_enabled = read_config_option('dsstats_enable');
if ($check_dsstats_enabled !== 'on') {
return ['dsstats_status' => 'Data Source Statistics is disabled'];
}
$dsstats_status = [
'dsstats_poller_memory_limit' => read_config_option('dsstats_poller_mem_limit'),
'dsstats_timeout' => read_config_option('dsstats_timeout'),
'dsstats_parallel_processes' => read_config_option('dsstats_parallel'),
'dsstats_mode' => (read_config_option('dsstats_mode') == 0 ? 'Peak/Average Only' : 'All Metrics'),
'dsstats_hourly_duration' => read_config_option('dsstats_hourly_duration'),
'dsstats_daily_interval' => read_config_option('dsstats_daily_interval'),
'dsstats_weekly_retention' => read_config_option('dsstats_weekly_retention'),
'dsstats_monthly_retention' => read_config_option('dsstats_monthly_retention'),
'dsstats_yearly_retention' => read_config_option('dsstats_yearly_retention'),
'dsstats_last_run_time' => read_config_option('dsstats_last_major_run_time'),
'dsstats_peak' => read_config_option('dsstats_peak'),
];
return ['dsstats_status' => $dsstats_status];
}
function get_cacti_db_status() : array {
$status_rows = db_fetch_assoc('SHOW STATUS');
$status_assoc = [];
foreach ($status_rows as $row) {
if (isset($row['Variable_name']) && isset($row['Value'])) {
$status_assoc[$row['Variable_name']] = $row['Value'];
}
}
$db_status = [
'max_used_connections' => $status_assoc['Max_used_connections'] ?? 0,
'threads_connected' => $status_assoc['Threads_connected'] ?? 0,
'threads_running' => $status_assoc['Threads_running'] ?? 0,
'Aborted_clients' => $status_assoc['Aborted_clients'] ?? 0,
'Aborted_connects' => $status_assoc['Aborted_connects'] ?? 0,
'Uptime' => $status_assoc['Uptime'] ?? 0,
'innodb_buffer_pool_size' => $status_assoc['Innodb_buffer_pool_size'] ?? 0,
'innodb_buffer_pool_pages_total' => $status_assoc['Innodb_buffer_pool_pages_total'] ?? 0,
'innodb_buffer_pool_pages_free' => $status_assoc['Innodb_buffer_pool_pages_free'] ?? 0,
'innodb_buffer_pool_pages_data' => $status_assoc['Innodb_buffer_pool_pages_data'] ?? 0,
'innodb_buffer_pool_pages_dirty' => $status_assoc['Innodb_buffer_pool_pages_dirty'] ?? 0
];
return ['db_status' => $db_status];
}
function get_thresholds(array $params = []) : array {
$check_thold_installed = db_fetch_assoc("SHOW TABLES LIKE 'thold_data'");
if (empty($check_thold_installed)) {
return ['thold_status' => 'Thresholds plugin is not installed'];
}
$sql = 'SELECT h.id as HOST_ID, description AS HOST_DESCRIPTION, h.hostname, data_source_name, thold_hi, thold_low, lastread, lastchanged, thold_fail_count, thold_enabled
FROM thold_data td
INNER JOIN host h
ON td.host_id = h.id';
$conditions = [];
$values = [];
if (isset($params['host_id']) && $params['host_id'] !== '') {
if (!is_array($params['host_id'])) {
$params['host_id'] = explode(',', $params['host_id']);
}
$params['host_id'] = array_map('trim', $params['host_id']);
$params['host_id'] = array_filter($params['host_id'], fn ($v) => $v !== '');
if (!empty($params['host_id'])) {
$placeholders = implode(',', array_fill(0, count($params['host_id']), '?'));
$conditions[] = "h.id IN ($placeholders)";
$values = array_merge($values, $params['host_id']);
}
}
if (isset($params['host_description'])) {
$conditions[] = 'h.description LIKE ?';
$values[] = '%' . $params['host_description'] . '%';
}
if (isset($params['hostname'])) {
$conditions[] = 'h.hostname LIKE ?';
$values[] = '%' . $params['hostname'] . '%';
}
if (isset($params['data_source_name'])) {
$conditions[] = 'td.data_source_name LIKE ?';
$values[] = '%' . $params['data_source_name'] . '%';
}
if (isset($params['template_id']) && $params['template_id'] !== '') {
if (!is_array($params['template_id'])) {
$params['template_id'] = explode(',', $params['template_id']);
}
$params['template_id'] = array_map('trim', $params['template_id']);
$params['template_id'] = array_filter($params['template_id'], fn ($v) => $v !== '');
if (!empty($params['template_id'])) {
$placeholders = implode(',', array_fill(0, count($params['template_id']), '?'));
$conditions[] = "host_template_id IN ($placeholders)";
$values = array_merge($values, $params['template_id']);
}
}
if (!empty($conditions)) {
$sql .= ' WHERE ' . implode(' AND ', $conditions);
}
$rows = $values ? db_fetch_assoc_prepared($sql, $values) : db_fetch_assoc($sql);
$thresholds = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$thresholds[] = [
'host_id' => $row['HOST_ID'],
'host_description' => $row['HOST_DESCRIPTION'],
'hostname' => $row['hostname'],
'data_source_name' => $row['data_source_name'],
'thold_hi' => $row['thold_hi'],
'thold_low' => $row['thold_low'],
'lastread' => $row['lastread'],
'lastchanged' => $row['lastchanged'],
'thold_fail_count' => $row['thold_fail_count'],
'thold_enabled' => $row['thold_enabled']
];
}
}
return ['thresholds' => $thresholds];
}
function get_threshold_status() : array {
$check_thold_installed = db_fetch_assoc("SHOW TABLES LIKE 'thold_data'");
if (empty($check_thold_installed)) {
return ['thold_status' => 'Thresholds plugin is not installed'];
}
$check_daemon_enabled = read_config_option('thold_daemon_enable');
$daemon_status = [];
if (empty($check_daemon_enabled)) {
$daemon_status = ['thold_status' => 'Thold daemon is not enabled'];
} else {
$daemon_status = [
'thold_daemon_dead_notification' => read_config_option('thold_daemon_dead_notification'),
'thold_daemon_debug' => (read_config_option('thold_daemon_debug') === 'on') ? 'enabled' : 'disabled',
'thold_daemon_down_notify_time' => read_config_option('thold_daemon_down_notify_time'),
'thold_daemon_enable' => ($check_daemon_enabled === 'on') ? 'enabled' : 'disabled'
];
}
$poller_stats = read_config_option('stats_thold');
$total_thresholds = db_fetch_cell('SELECT COUNT(*) as cnt FROM thold_data') ?? 0;
$enabled_thresholds = db_fetch_cell('SELECT COUNT(*) as cnt FROM thold_data WHERE thold_enabled = 1') ?? 0;
$disabled_thresholds = db_fetch_cell('SELECT COUNT(*) as cnt FROM thold_data WHERE thold_enabled = 0') ?? 0;
return [
'thold_status' => [
'daemon' => $daemon_status,
'poller_stats' => $poller_stats,
'total_thresholds' => $total_thresholds,
'enabled_thresholds' => $enabled_thresholds,
'disabled_thresholds' => $disabled_thresholds
]
];
}
// automation functions
function get_automation_networks(array $params = []) : mixed {
$sql = 'SELECT id, poller_id, name, subnet_range, ignore_ips, dns_servers, enabled,
notification_email, up_hosts, snmp_hosts, ping_method, ping_timeout, ping_retries,
start_at, next_start, last_runtime, last_started, last_status
FROM automation_networks';
$conditions = [];
$values = [];
if (isset($params['network_id']) && $params['network_id'] !== '') {
if (!is_array($params['network_id'])) {
$params['network_id'] = explode(',', $params['network_id']);
}
$params['network_id'] = array_map('trim', $params['network_id']);
$params['network_id'] = array_filter($params['network_id'], fn ($v) => $v !== '');
if (!empty($params['network_id'])) {
$placeholders = implode(',', array_fill(0, count($params['network_id']), '?'));
$conditions[] = "id IN ($placeholders)";
$values = array_merge($values, $params['network_id']);
}
}
if (isset($params['network_name']) && $params['network_name'] !== '') {
$conditions[] = 'name LIKE ?';
$values[] = '%' . $params['network_name'] . '%';
}
if (isset($params['subnet_range']) && $params['subnet_range'] !== '') {
$conditions[] = 'subnet_range LIKE ?';
$values[] = '%' . $params['subnet_range'] . '%';
}
if (!empty($conditions)) {
$sql .= ' WHERE ' . implode(' AND ', $conditions);
}
// Use prepared variant when values exist (placeholders present)
if (!empty($values)) {
return db_fetch_assoc_prepared($sql, $values);
}
return db_fetch_assoc($sql);
}
function get_automation_status() : array {
$sql = 'SELECT DISTINCT ap.network_id, an.name, an.subnet_range, an.last_started
FROM automation_processes ap
INNER JOIN automation_networks an ON ap.network_id = an.id';
$rows = db_fetch_assoc($sql);
$automation_status = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$automation_status[] = [
'network_id' => $row['network_id'],
'network_name' => $row['name'],
'subnet_range' => $row['subnet_range'],
'last_started' => $row['last_started']
];
}
}
return ['running_automations' => $automation_status];
}