Compare commits

...

15 Commits
v0.3 ... main

24 changed files with 1393 additions and 433 deletions

View File

@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
--- ---
## Unreleased
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.3...HEAD
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.3...HEAD
- github: https://github.com/lindeas/jilo-web/compare/v0.3...HEAD
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.3...HEAD
### Added
### Changed
### Fixed
---
## 0.3 - 2025-01-15 ## 0.3 - 2025-01-15
#### Links #### Links

View File

@ -34,7 +34,6 @@ class Host {
$sql = 'SELECT $sql = 'SELECT
id, id,
address, address,
port,
platform_id, platform_id,
name name
FROM FROM
@ -73,14 +72,13 @@ class Host {
public function addHost($newHost) { public function addHost($newHost) {
try { try {
$sql = 'INSERT INTO hosts $sql = 'INSERT INTO hosts
(address, port, platform_id, name) (address, platform_id, name)
VALUES VALUES
(:address, :port, :platform_id, :name)'; (:address, :platform_id, :name)';
$query = $this->db->prepare($sql); $query = $this->db->prepare($sql);
$query->execute([ $query->execute([
':address' => $newHost['address'], ':address' => $newHost['address'],
':port' => $newHost['port'],
':platform_id' => $newHost['platform_id'], ':platform_id' => $newHost['platform_id'],
':name' => $newHost['name'], ':name' => $newHost['name'],
]); ]);
@ -99,25 +97,28 @@ class Host {
* @param string $platform_id The platform ID to which the host belongs. * @param string $platform_id The platform ID to which the host belongs.
* @param array $updatedHost An associative array containing the updated details of the host. * @param array $updatedHost An associative array containing the updated details of the host.
* *
* @return bool True if the host was updated successfully, otherwise false. * @return bool|string True if the host was updated successfully, otherwise error message.
*/ */
public function editHost($platform_id, $updatedHost) { public function editHost($platform_id, $updatedHost) {
try { try {
$sql = 'UPDATE hosts SET $sql = 'UPDATE hosts SET
address = :address, address = :address,
port = :port,
name = :name name = :name
WHERE WHERE
id = :id'; id = :id AND platform_id = :platform_id';
$query = $this->db->prepare($sql); $query = $this->db->prepare($sql);
$query->execute([ $query->execute([
':id' => $updatedHost['id'], ':id' => $updatedHost['id'],
':address' => $updatedHost['address'], ':platform_id' => $platform_id,
':port' => $updatedHost['port'], ':address' => $updatedHost['address'],
':name' => $updatedHost['name'], ':name' => $updatedHost['name']
]); ]);
if ($query->rowCount() === 0) {
return "No host found with ID {$updatedHost['id']} in platform $platform_id";
}
return true; return true;
} catch (Exception $e) { } catch (Exception $e) {

View File

@ -21,7 +21,6 @@ class Log {
$this->db = $database->getConnection(); $this->db = $database->getConnection();
} }
/** /**
* Insert a log event into the database. * Insert a log event into the database.
* *
@ -40,9 +39,9 @@ class Log {
$query = $this->db->prepare($sql); $query = $this->db->prepare($sql);
$query->execute([ $query->execute([
':user_id' => $user_id, ':user_id' => $user_id,
':scope' => $scope, ':scope' => $scope,
':message' => $message, ':message' => $message,
]); ]);
return true; return true;
@ -52,7 +51,6 @@ class Log {
} }
} }
/** /**
* Retrieve log entries from the database. * Retrieve log entries from the database.
* *
@ -60,36 +58,67 @@ class Log {
* @param string $scope The scope of the logs ('user' or 'system'). * @param string $scope The scope of the logs ('user' or 'system').
* @param int $offset The offset for pagination. Default is 0. * @param int $offset The offset for pagination. Default is 0.
* @param int $items_per_page The number of log entries to retrieve per page. Default is no limit. * @param int $items_per_page The number of log entries to retrieve per page. Default is no limit.
* @param array $filters Optional array of filters (from_time, until_time, message, id)
* *
* @return array An array of log entries. * @return array An array of log entries.
*/ */
public function readLog($user_id, $scope, $offset=0, $items_per_page='') { public function readLog($user_id, $scope, $offset=0, $items_per_page='', $filters=[]) {
$params = [];
$where_clauses = [];
// Base query with user join
$base_sql = 'SELECT l.*, u.username
FROM logs l
LEFT JOIN users u ON l.user_id = u.id';
// Add scope condition
if ($scope === 'user') { if ($scope === 'user') {
$sql = 'SELECT * FROM logs WHERE user_id = :user_id ORDER BY time DESC'; $where_clauses[] = 'l.user_id = :user_id';
if ($items_per_page) { $params[':user_id'] = $user_id;
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
]);
} }
if ($scope === 'system') {
$sql = 'SELECT * FROM logs ORDER BY time DESC';
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$query = $this->db->prepare($sql); // Add time range filters if specified
$query->execute(); if (!empty($filters['from_time'])) {
$where_clauses[] = 'l.time >= :from_time';
$params[':from_time'] = $filters['from_time'] . ' 00:00:00';
} }
if (!empty($filters['until_time'])) {
$where_clauses[] = 'l.time <= :until_time';
$params[':until_time'] = $filters['until_time'] . ' 23:59:59';
}
// Add message search if specified
if (!empty($filters['message'])) {
$where_clauses[] = 'l.message LIKE :message';
$params[':message'] = '%' . $filters['message'] . '%';
}
// Add user ID search if specified
if (!empty($filters['id'])) {
$where_clauses[] = 'l.user_id = :search_user_id';
$params[':search_user_id'] = $filters['id'];
}
// Combine WHERE clauses
$sql = $base_sql;
if (!empty($where_clauses)) {
$sql .= ' WHERE ' . implode(' AND ', $where_clauses);
}
// Add ordering
$sql .= ' ORDER BY l.time DESC';
// Add pagination
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$query = $this->db->prepare($sql);
$query->execute($params);
return $query->fetchAll(PDO::FETCH_ASSOC); return $query->fetchAll(PDO::FETCH_ASSOC);
} }
} }
?> ?>

View File

@ -115,51 +115,122 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
} }
// an update to an existing host // an update to an existing host
} elseif (isset($_POST['host'])) { } elseif (isset($_POST['item']) && $_POST['item'] === 'host') {
$host_id = $_POST['host'];
$platform_id = $_POST['platform'];
$updatedHost = [ $updatedHost = [
'id' => $host, 'id' => $host_id,
'address' => $address, 'address' => $_POST['address'],
'port' => $port, 'name' => $_POST['name']
'name' => $name,
]; ];
$result = $hostObject->editHost($platform_id, $updatedHost); $result = $hostObject->editHost($platform_id, $updatedHost);
if ($result === true) {
$_SESSION['notice'] = "Host \"{$_REQUEST['address']}:{$_REQUEST['port']}\" edited."; // Check if it's an AJAX request
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
// Clear any output buffers to ensure clean JSON
while (ob_get_level()) ob_end_clean();
header('Content-Type: application/json');
if ($result === true) {
echo json_encode(['success' => true]);
} else {
echo json_encode([
'success' => false,
'message' => "Editing the host failed. Error: $result"
]);
}
exit();
} else { } else {
$_SESSION['error'] = "Editing the host failed. Error: $result"; // Regular form submission
if ($result === true) {
$_SESSION['notice'] = "Host edited.";
} else {
$_SESSION['error'] = "Editing the host failed. Error: $result";
}
header("Location: $app_root?page=config&item=$item");
exit();
} }
// an update to an existing agent // an update to an existing agent
} elseif (isset($_POST['agent'])) { } elseif (isset($_POST['item']) && $_POST['item'] === 'agent') {
$agent_id = $_POST['agent'];
$platform_id = $_POST['platform'];
$updatedAgent = [ $updatedAgent = [
'id' => $agent, 'id' => $agent_id,
'agent_type_id' => $type, 'agent_type_id' => $_POST['agent_type_id'],
'url' => $url, 'url' => $_POST['url'],
'secret_key' => $secret_key, 'secret_key' => $_POST['secret_key'],
'check_period' => $check_period, 'check_period' => $_POST['check_period']
]; ];
$result = $agentObject->editAgent($platform_id, $updatedAgent); $result = $agentObject->editAgent($platform_id, $updatedAgent);
if ($result === true) {
$_SESSION['notice'] = "Agent id \"{$_REQUEST['agent']}\" edited."; // Check if it's an AJAX request
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
// Clear any output buffers to ensure clean JSON
while (ob_get_level()) ob_end_clean();
header('Content-Type: application/json');
if ($result === true) {
echo json_encode(['success' => true]);
} else {
echo json_encode([
'success' => false,
'message' => "Editing the agent failed. Error: $result"
]);
}
exit();
} else { } else {
$_SESSION['error'] = "Editing the agent failed. Error: $result"; // Regular form submission
if ($result === true) {
$_SESSION['notice'] = "Agent edited.";
} else {
$_SESSION['error'] = "Editing the agent failed. Error: $result";
}
header("Location: $app_root?page=config&item=$item");
exit();
} }
// an update to an existing platform // an update to an existing platform
} else { } else {
$platform = $_POST['platform']; $platform = $_POST['platform_id'];
$updatedPlatform = [ $updatedPlatform = [
'name' => $name, 'name' => $_POST['name'],
'jitsi_url' => $_POST['jitsi_url'], 'jitsi_url' => $_POST['jitsi_url'],
'jilo_database' => $_POST['jilo_database'], 'jilo_database' => $_POST['jilo_database']
]; ];
$result = $platformObject->editPlatform($platform, $updatedPlatform); $result = $platformObject->editPlatform($platform, $updatedPlatform);
if ($result === true) {
$_SESSION['notice'] = "Platform \"{$_REQUEST['name']}\" edited.";
} else {
$_SESSION['error'] = "Editing the platform failed. Error: $result";
}
// Check if it's an AJAX request
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
// Clear any output buffers to ensure clean JSON
while (ob_get_level()) ob_end_clean();
header('Content-Type: application/json');
if ($result === true) {
echo json_encode(['success' => true]);
} else {
echo json_encode([
'success' => false,
'message' => "Editing the platform failed. Error: $result"
]);
}
exit();
} else {
// Regular form submission
if ($result === true) {
$_SESSION['notice'] = "Platform edited.";
} else {
$_SESSION['error'] = "Editing the platform failed. Error: $result";
}
header("Location: $app_root?page=config&item=$item");
exit();
}
} }
// FIXME the new file is not loaded on first page load // FIXME the new file is not loaded on first page load
@ -209,30 +280,39 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
} }
break; break;
case 'endpoint': case 'agent':
// TODO ad here endpoints options if (isset($action) && $action === 'add') {
echo 'under construction'; $jilo_agent_types = $agentObject->getAgentTypes();
// switch ($action) { $platform_id = $_REQUEST['platform'] ?? '';
// case 'add-agent': if (!empty($platform_id)) {
// $jilo_agent_types = $agentObject->getAgentTypes(); $jilo_agents_in_platform = $agentObject->getPlatformAgentTypes($platform_id);
// $jilo_agents_in_platform = $agentObject->getPlatformAgentTypes($platform_id); $jilo_agent_types_in_platform = array_column($jilo_agents_in_platform, 'agent_type_id');
// $jilo_agent_types_in_platform = array_column($jilo_agents_in_platform, 'agent_type_id'); include '../app/templates/config-agent-add.php';
// include '../app/templates/config-add-agent.php'; } else {
// break; $_SESSION['error'] = "Platform ID is required to add an agent.";
// case 'edit': header("Location: $app_root?page=config&item=agent");
// if (isset($_GET['agent'])) { exit();
// $agentDetails = $agentObject->getAgentDetails($platform_id, $agent); }
// $jilo_agent_types = $agentObject->getAgentTypes(); } elseif (isset($action) && $action === 'edit') {
// include '../app/templates/config-edit-agent.php'; if (isset($_REQUEST['agent'])) {
// } $platform_id = $_REQUEST['platform'] ?? '';
// break; $agentDetails = $agentObject->getAgentDetails($platform_id, $agent)['0'];
// case 'delete': $jilo_agent_types = $agentObject->getAgentTypes();
// if (isset($_GET['agent'])) { include '../app/templates/config-agent-edit.php';
// $agentDetails = $agentObject->getAgentDetails($platform_id, $agent); }
// include '../app/templates/config-delete-agent.php'; } elseif (isset($action) && $action === 'delete') {
// } if (isset($_REQUEST['agent'])) {
// break; $platform_id = $_REQUEST['platform'] ?? '';
// } $agentDetails = $agentObject->getAgentDetails($platform_id, $agent)['0'];
include '../app/templates/config-agent-delete.php';
}
} else {
if ($userObject->hasRight($user_id, 'view config file')) {
include '../app/templates/config-agent.php';
} else {
include '../app/templates/error-unauthorized.php';
}
}
break; break;
case 'config_file': case 'config_file':

View File

@ -12,49 +12,89 @@ include '../app/includes/messages.php';
include '../app/includes/messages-show.php'; include '../app/includes/messages-show.php';
// Check for rights; user or system // Check for rights; user or system
if (($userObject->hasRight($user_id, 'superuser') || $has_system_access = ($userObject->hasRight($user_id, 'superuser') ||
$userObject->hasRight($user_id, 'view app logs'))) { $userObject->hasRight($user_id, 'view app logs'));
$scope = 'system';
} else { // Get current page for pagination
$scope = 'user'; $currentPage = $_REQUEST['page_num'] ?? 1;
$currentPage = (int)$currentPage;
// Get selected tab
$selected_tab = $_REQUEST['tab'] ?? 'user';
if ($selected_tab === 'system' && !$has_system_access) {
$selected_tab = 'user';
} }
// Set scope based on selected tab
$scope = ($selected_tab === 'system') ? 'system' : 'user';
// specify time range // specify time range
include '../app/helpers/time_range.php'; include '../app/helpers/time_range.php';
// Prepare search filters
$filters = [];
if (isset($_REQUEST['from_time']) && !empty($_REQUEST['from_time'])) {
$filters['from_time'] = $_REQUEST['from_time'];
}
if (isset($_REQUEST['until_time']) && !empty($_REQUEST['until_time'])) {
$filters['until_time'] = $_REQUEST['until_time'];
}
if (isset($_REQUEST['message']) && !empty($_REQUEST['message'])) {
$filters['message'] = $_REQUEST['message'];
}
if ($scope === 'system' && isset($_REQUEST['id']) && !empty($_REQUEST['id'])) {
$filters['id'] = $_REQUEST['id'];
}
// pagination variables // pagination variables
$items_per_page = 15; $items_per_page = 15;
$browse_page = $_REQUEST['p'] ?? 1; $offset = ($currentPage - 1) * $items_per_page;
$browse_page = (int)$browse_page;
$offset = ($browse_page -1) * $items_per_page; // Build params for pagination
$params = '';
if (!empty($_REQUEST['from_time'])) {
$params .= '&from_time=' . urlencode($_REQUEST['from_time']);
}
if (!empty($_REQUEST['until_time'])) {
$params .= '&until_time=' . urlencode($_REQUEST['until_time']);
}
if (!empty($_REQUEST['message'])) {
$params .= '&message=' . urlencode($_REQUEST['message']);
}
if (!empty($_REQUEST['id'])) {
$params .= '&id=' . urlencode($_REQUEST['id']);
}
if (isset($_REQUEST['tab'])) {
$params .= '&tab=' . urlencode($_REQUEST['tab']);
}
// prepare the result // prepare the result
$search = $logObject->readLog($user_id, $scope, $offset, $items_per_page); $search = $logObject->readLog($user_id, $scope, $offset, $items_per_page, $filters);
$search_all = $logObject->readLog($user_id, $scope); $search_all = $logObject->readLog($user_id, $scope, '', '', $filters);
if (!empty($search)) { if (!empty($search)) {
// we get total items and number of pages // we get total items and number of pages
$item_count = count($search_all); $item_count = count($search_all);
$page_count = ceil($item_count / $items_per_page); $totalPages = ceil($item_count / $items_per_page);
$logs = array(); $logs = array();
$logs['records'] = array(); $logs['records'] = array();
foreach ($search as $item) { foreach ($search as $item) {
// when we show only user's logs, omit user_id column // when we show only user's logs, omit user_id column
if ($scope === 'user') { if ($scope === 'user') {
$log_record = array( $log_record = array(
// assign title to the field in the array record // assign title to the field in the array record
'time' => $item['time'], 'time' => $item['time'],
'log message' => $item['message'] 'log message' => $item['message']
); );
} else { } else {
$log_record = array( $log_record = array(
// assign title to the field in the array record // assign title to the field in the array record
'userID' => $item['user_id'], 'userID' => $item['user_id'],
'time' => $item['time'], 'username' => $item['username'],
'log message' => $item['message'] 'time' => $item['time'],
'log message' => $item['message']
); );
} }
@ -65,14 +105,15 @@ if (!empty($search)) {
// prepare the widget // prepare the widget
$widget['full'] = false; $widget['full'] = false;
$widget['collapsible'] = false;
$widget['name'] = 'Logs'; $widget['name'] = 'Logs';
$username = $userObject->getUserDetails($user_id)[0]['username']; $username = $userObject->getUserDetails($user_id)[0]['username'];
$widget['title'] = "Log events for user \"$username\""; $widget['title'] = "Log events";
$widget['filter'] = true; $widget['filter'] = true;
$widget['scope'] = $scope;
$widget['has_system_access'] = $has_system_access;
if (!empty($logs['records'])) { if (!empty($logs['records'])) {
$widget['full'] = true; $widget['full'] = true;
$widget['table_headers'] = array_keys($logs['records'][0]);
$widget['table_records'] = $logs['records']; $widget['table_records'] = $logs['records'];
} }
$widget['pagination'] = true; $widget['pagination'] = true;

View File

@ -23,9 +23,9 @@ foreach ($platformsAll as $platform) {
// check if we can connect to the jilo database // check if we can connect to the jilo database
$response = connectDB($config, 'jilo', $platform['jilo_database'], $platform['id']); $response = connectDB($config, 'jilo', $platform['jilo_database'], $platform['id']);
if ($response['error'] !== null) { if ($response['error'] !== null) {
$jilo_database_status = '<span class="text-danger">' . htmlspecialchars($response['error']) . '</span>'; $jilo_database_status = $response['error'];
} else { } else {
$jilo_database_status = '<span class="text-success">OK</span>'; $jilo_database_status = 'OK';
} }
include '../app/templates/status-platform.php'; include '../app/templates/status-platform.php';
@ -44,19 +44,19 @@ foreach ($platformsAll as $platform) {
// determine agent availability based on response data // determine agent availability based on response data
if (json_last_error() === JSON_ERROR_NONE) { if (json_last_error() === JSON_ERROR_NONE) {
$agent_availability = '<span class="text-warning">unknown</span>'; $agent_availability = 'unknown';
foreach ($agent_data as $key => $value) { foreach ($agent_data as $key => $value) {
if ($key === 'error') { if ($key === 'error') {
$agent_availability = '<span class="text-danger">' . htmlspecialchars($value) . '</span>'; $agent_availability = $value;
break; break;
} }
if (preg_match('/_state$/', $key)) { if (preg_match('/_state$/', $key)) {
if ($value === 'error') { if ($value === 'error') {
$agent_availability = '<span class="text-danger">not running</span>'; $agent_availability = 'not running';
break; break;
} }
if ($value === 'running') { if ($value === 'running') {
$agent_availability = '<span class="text-success">running</span>'; $agent_availability = 'running';
break; break;
} }
} }
@ -67,6 +67,7 @@ foreach ($platformsAll as $platform) {
include '../app/templates/status-agent.php'; include '../app/templates/status-agent.php';
} }
echo '</div>';
echo '</div>';
} }
?> ?>

View File

@ -0,0 +1,59 @@
<?php
// Get available agent types that are not yet in the platform
$available_agent_types = array_filter($jilo_agent_types, function($type) use ($jilo_agent_types_in_platform) {
return !in_array($type['id'], $jilo_agent_types_in_platform);
});
?>
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Add new Jilo agent</p>
<div class="card-body">
<form method="post" action="<?= htmlspecialchars($app_root) ?>">
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>">
<input type="hidden" name="item" value="agent">
<input type="hidden" name="new" value="true">
<div class="mb-3 row">
<label for="type" class="col-sm-2 col-form-label">Agent Type:</label>
<div class="col-sm-10">
<select class="form-select" id="type" name="type" required>
<option value="">Select agent type</option>
<?php foreach ($available_agent_types as $type): ?>
<option value="<?= htmlspecialchars($type['id']) ?>">
<?= htmlspecialchars($type['description']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mb-3 row">
<label for="url" class="col-sm-2 col-form-label">URL:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="url" name="url" required>
</div>
</div>
<div class="mb-3 row">
<label for="secret_key" class="col-sm-2 col-form-label">Secret Key:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="secret_key" name="secret_key" required>
</div>
</div>
<div class="mb-3 row">
<label for="check_period" class="col-sm-2 col-form-label">Check Period (minutes):</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="check_period" name="check_period" min="1" required>
</div>
</div>
<div class="mb-3 row">
<div class="col-sm-10 offset-sm-2">
<button type="submit" class="btn btn-primary">Add Agent</button>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=agent" class="btn btn-secondary">Cancel</a>
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,31 @@
<?php if (!empty($agentDetails)): ?>
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Delete Jilo agent</p>
<div class="card-body">
<p class="card-text">Are you sure you want to delete this agent?</p>
<div class="mb-3">
<strong>Agent ID:</strong> <?= htmlspecialchars($agentDetails['id']) ?><br>
<strong>Type:</strong> <?= htmlspecialchars($agentDetails['agent_description']) ?><br>
<strong>URL:</strong> <?= htmlspecialchars($agentDetails['url']) ?><br>
<strong>Check Period:</strong> <?= htmlspecialchars($agentDetails['check_period']) ?> <?= ($agentDetails['check_period'] == 1 ? 'minute' : 'minutes') ?>
</div>
<form method="post" action="<?= htmlspecialchars($app_root) ?>">
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>">
<input type="hidden" name="agent" value="<?= htmlspecialchars($agentDetails['id']) ?>">
<input type="hidden" name="item" value="agent">
<input type="hidden" name="delete" value="true">
<div class="mb-3">
<button type="submit" class="btn btn-danger">Delete Agent</button>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=agent" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
<?php else: ?>
<div class="alert alert-danger">
Agent not found.
</div>
<?php endif; ?>

View File

@ -0,0 +1,57 @@
<?php if (!empty($agentDetails)): ?>
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Edit Jilo agent</p>
<div class="card-body">
<form method="post" action="<?= htmlspecialchars($app_root . '?page=' . $page) ?>">
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>">
<input type="hidden" name="agent" value="<?= htmlspecialchars($agentDetails['id']) ?>">
<input type="hidden" name="item" value="agent">
<div class="mb-3 row">
<label for="type" class="col-sm-2 col-form-label">Agent Type:</label>
<div class="col-sm-10">
<select class="form-select" id="type" name="type" required>
<?php foreach ($jilo_agent_types as $type): ?>
<option value="<?= htmlspecialchars($type['id']) ?>" <?= $type['id'] == $agentDetails['agent_type_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($type['description']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mb-3 row">
<label for="url" class="col-sm-2 col-form-label">URL:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="url" name="url" value="<?= htmlspecialchars($agentDetails['url']) ?>" required>
</div>
</div>
<div class="mb-3 row">
<label for="secret_key" class="col-sm-2 col-form-label">Secret Key:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="secret_key" name="secret_key" value="<?= htmlspecialchars($agentDetails['secret_key']) ?>" required>
</div>
</div>
<div class="mb-3 row">
<label for="check_period" class="col-sm-2 col-form-label">Check Period (minutes):</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="check_period" name="check_period" value="<?= htmlspecialchars($agentDetails['check_period']) ?>" min="1" required>
</div>
</div>
<div class="mb-3 row">
<div class="col-sm-10 offset-sm-2">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=agent" class="btn btn-secondary">Cancel</a>
</div>
</div>
</form>
</div>
</div>
<?php else: ?>
<div class="alert alert-danger">
Agent not found.
</div>
<?php endif; ?>

View File

@ -1,52 +0,0 @@
<!-- widget "hosts" -->
<div class="card text-center w-50 mx-lef">
<p class="h4 card-header">Jilo configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
<div class="card-body">
<p class="card-text">edit host details:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=config&item=host">
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="address" class="form-label">address</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="address" value="<?= htmlspecialchars($hostDetails[0]['address'] ?? '') ?>" required autofocus />
<p class="text-start"><small>DNS name or IP address of the machine</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="port" class="form-label">port</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="port" value="<?= htmlspecialchars($hostDetails[0]['port'] ?? '') ?>" required />
<p class="text-start"><small>port on which the Jilo Agent is listening</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="name" class="form-label">name</label>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="name" value="<?= htmlspecialchars($hostDetails[0]['name'] ?? '') ?>" />
<p class="text-start"><small>description or name of the host (optional)</small></p>
</div>
</div>
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>" />
<input type="hidden" name="item" value="host" />
<input type="hidden" name="host" value="<?= htmlspecialchars($hostDetails[0]['id']) ?>" />
<br />
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_id) ?>&host=<?= htmlspecialchars($host) ?>#platform<?= htmlspecialchars($platform_id) ?>host<?= htmlspecialchars($host) ?>" />Cancel</a>
&nbsp;&nbsp;
<input type="submit" class="btn btn-primary btn-sm" value="Save" />
</form>
</div>
</div>
<!-- /widget "hosts" -->

View File

@ -1,39 +0,0 @@
<!-- widget "hosts" -->
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Jilo configuration :: Jitsi Meet hosts</p>
<div class="card-body">
<p class="card-text">Jitsi hosts configuration
</p>
<?php foreach ($platformsAll as $platform_array) {
$hosts = $hostObject->getHostDetails($platform_array['id']);
?>
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>"></a>
<div class="row mb-1 border <?= isset($_REQUEST['platform']) && (int)$platform_array['id'] === (int)$_REQUEST['platform'] ? 'rounded bg-light' : '' ?>" style="padding: 20px; padding-bottom: 0px;">
<p class="text-start">
platform <strong><?= htmlspecialchars($platform_array['name']) ?></strong>
</p>
<ul class="text-start" style="padding-left: 50px;">
<?php foreach ($hosts as $host_array) { ?>
<li style="padding-bottom: 10px;">
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>host<?= htmlspecialchars($host_array['id']) ?>"></a>
<span class="<?= isset($_REQUEST['platform']) && (int)$platform_array['id'] === (int)$_REQUEST['platform'] && isset($_REQUEST['host']) && (int)$host_array['id'] === (int)$_REQUEST['host'] ? 'border rounded bg-light' : '' ?>" style="padding: 10px;">
<?= htmlspecialchars($host_array['address']) ?>:<?= htmlspecialchars($host_array['port']) ?>
&nbsp;
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($host_array['platform_id']) ?>&host=<?= htmlspecialchars($host_array['id']) ?>&action=edit">edit host</a>
<a class="btn btn-outline-danger btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($host_array['platform_id']) ?>&host=<?= htmlspecialchars($host_array['id']) ?>&action=delete">delete host</a>
</span>
</li>
<?php } ?>
</ul>
<p class="text-start" style="padding-left: 50px;">
total <?= htmlspecialchars(count($hosts)) ?> jilo <?= htmlspecialchars(count($hosts)) === '1' ? 'host' : 'hosts' ?>&nbsp;
&nbsp;
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=add">add new</a>
</p>
</div>
<?php } ?>
</div>
</div>
<!-- /widget "hosts" -->

View File

@ -1,36 +0,0 @@
<!-- widget "platforms" -->
<div class="card text-center w-50 mx-lef">
<p class="h4 card-header">Jilo configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong> :: edit</p>
<div class="card-body">
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=platform">
<?php
foreach ($platformDetails[0] as $key => $value) {
if ($key === 'id') continue;
?>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="<?= htmlspecialchars($config_item) ?>" class="form-label"><?= htmlspecialchars($key) ?></label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="<?= htmlspecialchars($key) ?>" value="<?= htmlspecialchars($value ?? '') ?>" required autofocus />
<?php if ($key === 'name') { ?>
<p class="text-start"><small>descriptive name for the platform</small></p>
<?php } elseif ($key === 'jitsi_url') { ?>
<p class="text-start"><small>URL of the Jitsi Meet (used for checks and for loading config.js)</small></p>
<?php } elseif ($key === 'jilo_database') { ?>
<p class="text-start"><small>path to the database file (relative to the app root)</small></p>
<?php } ?>
</div>
</div>
<?php } ?>
<br />
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>" />
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&platform=<?= htmlspecialchars($platform_id) ?>#platform<?= htmlspecialchars($platform_id) ?>" />Cancel</a>
&nbsp;&nbsp;
<input type="submit" class="btn btn-primary btn-sm" value="Save" />
</form>
</div>
</div>
<!-- /widget "platforms" -->

View File

@ -1,60 +1,697 @@
<!-- widget "platforms" --> <!-- "jilo configuration" -->
<div class="card text-center w-75 mx-lef"> <div class="container-fluid mt-2">
<p class="h4 card-header">Jilo configuration :: Jitsi Meet platforms</p> <div class="row mb-4">
<div class="card-body"> <div class="col-md-6 mb-5">
<p class="card-text">Jitsi platforms configuration &nbsp;<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&action=add">add new</a></p> <h2>Jitsi Meet platforms configuration</h2>
<?php foreach ($platformsAll as $platform_array) { </div>
$hosts = $hostObject->getHostDetails($platform_array['id']); <div class="col-md-6 text-end">
$agents = $agentObject->getAgentDetails($platform_array['id']); <a class="btn btn-primary" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&action=add">
?> <i class="fas fa-plus me-2"></i>Add new platform
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>"></a> </a>
<div class="row mb-1 border<?= isset($_REQUEST['platform']) && (int)$platform_array['id'] === (int)$_REQUEST['platform'] ? ' bg-light' : '' ?>" style="padding: 20px; padding-bottom: 0px;"> </div>
<p> <div class="row mb-4">
platform id <?= htmlspecialchars($platform_array['id']) ?> - <strong><?= htmlspecialchars($platform_array['name']) ?></strong> <?php if (!empty($platformsAll)): ?>
&nbsp; <ul class="nav nav-tabs mb-3" id="platformTabs" role="tablist">
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=edit">edit platform</a> <?php foreach ($platformsAll as $index => $platform): ?>
<?php if (count($platformsAll) <= 1) { ?> <li class="nav-item">
<span class="btn btn-outline-light btn-sm" href="#" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="can't delete the last platform">delete platform</span> <a class="nav-link <?= ($index === 0) ? 'active' : '' ?>"
<?php } else { ?> id="platform-<?= htmlspecialchars($platform['id']) ?>-tab"
<a class="btn btn-outline-danger btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=delete">delete platform</a> data-toggle="tab"
<?php } ?> href="#platform-<?= htmlspecialchars($platform['id']) ?>"
</p> role="tab"
<div style="padding-left: 100px; padding-bottom: 20px;"> aria-controls="platform-<?= htmlspecialchars($platform['id']) ?>"
<?php foreach ($platform_array as $key => $value) { aria-selected="<?= ($index === 0) ? 'true' : 'false' ?>">
if ($key === 'id') continue; <?= htmlspecialchars($platform['name']) ?>
?> </a>
<div class="row mb-1" style="padding-left: 100px;"> </li>
<div class="col-md-4 text-end"> <?php endforeach; ?>
<?= htmlspecialchars($key) ?>: </ul>
</div>
<div class="col-md-8 text-start"> <div class="tab-content" id="platformTabsContent">
<?php if ($key === 'jitsi_url') { ?> <?php foreach ($platformsAll as $index => $platform): ?>
<a href="<?= htmlspecialchars($value) ?>" target="_blank" rel="noopener noreferrer" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="open the Jitsi Meet platform in a new window"> <?php
<?= htmlspecialchars($value) ?> $hosts = $hostObject->getHostDetails($platform['id']);
<i class="fas fa-external-link-alt"></i> $agents = $agentObject->getAgentDetails($platform['id']);
</a> ?>
<?php } else { ?> <div class="tab-pane fade <?= ($index === 0) ? 'show active' : '' ?>"
<?= htmlspecialchars($value) ?> id="platform-<?= htmlspecialchars($platform['id']) ?>"
<?php } ?> role="tabpanel"
</div> aria-labelledby="platform-<?= htmlspecialchars($platform['id']) ?>-tab">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<i class="fas fa-server me-2 text-secondary"></i>
<span class="text-secondary">
Platform #<?= htmlspecialchars($platform['id']) ?>
</span>
</div> </div>
<?php } ?> <div class="btn-group platform-actions" data-platform-id="<?= htmlspecialchars($platform['id']) ?>">
<div class="row mb-1" style="padding-left: 100px;"> <button type="button" class="btn btn-outline-primary edit-platform">
<div class="col-md-4 text-end"></div> <i class="fas fa-edit me-1"></i>Edit platform
<div class="col-md-8 text-start"> </button>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_array['id']) ?>#platform<?= htmlspecialchars($platform_array['id']) ?>"><?= htmlspecialchars(count($hosts)) ?> <?= htmlspecialchars(count($hosts)) === '1' ? 'host' : 'hosts' ?></a> <button type="button" class="btn btn-outline-primary save-platform" style="display: none;">
</div> <i class="fas fa-save me-1"></i>Save
</div> </button>
<div class="row mb-1" style="padding-left: 100px;"> <button type="button" class="btn btn-outline-secondary cancel-edit" style="display: none;">
<div class="col-md-4 text-end"></div> <i class="fas fa-times me-1"></i>Cancel
<div class="col-md-8 text-start"> </button>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=endpoint&platform=<?= htmlspecialchars($platform_array['id']) ?>#platform<?= htmlspecialchars($platform_array['id']) ?>"><?= htmlspecialchars(count($agents)) ?> <?= htmlspecialchars(count($agents)) === '1' ? 'endpoint' : 'endpoints' ?></a> <?php if (count($platformsAll) <= 1): ?>
</div> <button class="btn btn-outline-secondary" disabled
data-toggle="tooltip" data-placement="top"
title="Can't delete the last platform">
<i class="fas fa-trash me-1"></i>Delete platform
</button>
<?php else: ?>
<button type="button" class="btn btn-outline-danger delete-platform">
<i class="fas fa-trash me-1"></i>Delete platform
</button>
<?php endif; ?>
</div> </div>
</div> </div>
<div class="table-responsive mb-4">
<table class="table table-hover align-middle platform-details" data-platform-id="<?= htmlspecialchars($platform['id']) ?>">
<tbody>
<?php foreach ($platform as $key => $value): ?>
<?php if ($key === 'id') continue; ?>
<tr>
<th style="width: 200px;"><?= htmlspecialchars($key) ?></th>
<td>
<div class="view-mode">
<?php if ($key === 'jitsi_url'): ?>
<a href="<?= htmlspecialchars($value) ?>" target="_blank" rel="noopener noreferrer"
data-toggle="tooltip" data-placement="top"
title="Open the Jitsi Meet platform in a new window">
<?= htmlspecialchars($value) ?>
<i class="fas fa-external-link-alt ms-1"></i>
</a>
<?php else: ?>
<?= htmlspecialchars($value) ?>
<?php endif; ?>
</div>
<div class="edit-mode" style="display: none;">
<input type="text" class="form-control" name="<?= htmlspecialchars($key) ?>"
value="<?= htmlspecialchars($value) ?>" required>
<?php if ($key === 'name'): ?>
<small class="form-text text-muted">Descriptive name for the platform</small>
<?php elseif ($key === 'jitsi_url'): ?>
<small class="form-text text-muted">URL of the Jitsi Meet (used for checks and for loading config.js)</small>
<?php elseif ($key === 'jilo_database'): ?>
<small class="form-text text-muted">Path to the database file (relative to the app root)</small>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Hosts Section -->
<div class="mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<i class="fas fa-network-wired me-2 text-secondary"></i>
<span class="text-secondary">
<?= htmlspecialchars(count($hosts)) ?> <?= count($hosts) === 1 ? 'host' : 'hosts' ?>
for platform "<?= htmlspecialchars($platform['name']) ?>"
</span>
</div>
<a class="btn btn-primary" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&action=add&platform=<?= htmlspecialchars($platform['id']) ?>">
<i class="fas fa-plus me-2"></i>Add new host
</a>
</div>
<?php if (!empty($hosts)): ?>
<?php foreach ($hosts as $host): ?>
<?php
$hostAgents = array_filter($agents, function($agent) use ($host) {
return isset($agent['host_id']) && $agent['host_id'] === $host['id'];
});
?>
<div class="card mt-5">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<div class="flex-grow-1">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-network-wired me-2 text-secondary"></i>
<h6 class="mb-0">Host id #<?= htmlspecialchars($host['id']) ?> in platform "<?= htmlspecialchars($platform['name']) ?>"</h6>
</div>
<div class="ps-4">
<span class="host-view-mode">
<div class="row g-2">
<div class="col-md-6">
<div class="small text-muted mb-1">Host description</div>
<div class="text-break"><strong><?= htmlspecialchars($host['name'] ?: '(no description)') ?></strong></div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">DNS name or IP</div>
<div class="text-break"><strong><?= htmlspecialchars($host['address']) ?></strong></div>
</div>
</div>
</span>
<div class="host-edit-mode" style="display: none;">
<div class="row g-2">
<div class="col-md-6">
<label class="form-label small text-muted">Host description</label>
<input type="text" class="form-control form-control-sm text-break" name="name"
value="<?= htmlspecialchars($host['name']) ?>"
placeholder="Optional description">
</div>
<div class="col-md-6">
<label class="form-label small text-muted">DNS name or IP</label>
<input type="text" class="form-control form-control-sm text-break" name="address"
value="<?= htmlspecialchars($host['address']) ?>"
placeholder="e.g., server.example.com or 192.168.1.100" required>
</div>
</div>
</div>
</div>
</div>
<div class="btn-group host-actions ms-3" data-host-id="<?= htmlspecialchars($host['id']) ?>"
data-platform-id="<?= htmlspecialchars($platform['id']) ?>">
<button type="button" class="btn btn-outline-primary btn-sm edit-host host-view-mode">
<i class="fas fa-edit me-1"></i>Edit host
</button>
<button type="button" class="btn btn-outline-primary btn-sm save-host host-edit-mode" style="display: none;">
<i class="fas fa-save me-1"></i>Save
</button>
<button type="button" class="btn btn-outline-secondary btn-sm cancel-host-edit host-edit-mode" style="display: none;">
<i class="fas fa-times me-1"></i>Cancel
</button>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform['id']) ?>&host=<?= htmlspecialchars($host['id']) ?>&action=delete"
class="btn btn-outline-danger btn-sm host-view-mode">
<i class="fas fa-trash me-1"></i>Delete host
</a>
</div>
</div>
<div class="card-body">
<!-- Agents Section -->
<?php $hostAgents = $agentObject->getAgentDetails($platform['id']); ?>
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center">
<i class="fas fa-robot me-2 text-secondary"></i>
<span class="text-secondary">
<?= htmlspecialchars(count($hostAgents)) ?> <?= count($hostAgents) === 1 ? 'agent' : 'agents' ?>
for this host
</span>
</div>
<a class="btn btn-sm btn-primary" href="<?= htmlspecialchars($app_root) ?>?page=config&item=agent&action=add&platform=<?= htmlspecialchars($platform['id']) ?>&host=<?= htmlspecialchars($host['id']) ?>">
<i class="fas fa-plus me-2"></i>Add new agent
</a>
</div>
<?php if (!empty($hostAgents)): ?>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Agent Type</th>
<th>Endpoint URL</th>
<th>Check period (minutes)</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($hostAgents as $agent): ?>
<tr>
<td>
<div class="d-flex align-items-center">
<i class="fas fa-robot me-2 text-secondary"></i>
<span class="agent-view-mode">
<?= htmlspecialchars($agent['agent_description']) ?>
</span>
<div class="agent-edit-mode" style="display: none;">
<select class="form-select form-select-sm" name="agent_type_id" required>
<?php foreach ($agentObject->getAgentTypes() as $type): ?>
<option value="<?= htmlspecialchars($type['id']) ?>"
data-endpoint="<?= htmlspecialchars($type['endpoint']) ?>"
<?= $type['id'] === $agent['agent_type_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($type['description']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</td>
<td class="text-break">
<span class="agent-view-mode">
<?= htmlspecialchars($agent['url'].$agent['agent_endpoint']) ?>
</span>
<div class="agent-edit-mode" style="display: none;">
<label class="form-label small text-muted">URL</label>
<input type="text" class="form-control form-control-sm text-break mb-2" name="url"
value="<?= htmlspecialchars($agent['url']) ?>"
placeholder="e.g., http://localhost:8080" required>
<label class="form-label small text-muted">Secret Key</label>
<input type="text" class="form-control form-control-sm text-break" name="secret_key"
value="<?= htmlspecialchars($agent['secret_key']) ?>"
placeholder="Secret key for authentication" required>
</div>
</td>
<td>
<span class="agent-view-mode">
<?php if (isset($agent['check_period']) && $agent['check_period'] !== 0): ?>
<?= htmlspecialchars($agent['check_period']) ?> <?= ($agent['check_period'] == 1 ? 'minute' : 'minutes') ?>
<?php else: ?>
<span class="text-muted">-</span>
<?php endif; ?>
</span>
<div class="agent-edit-mode" style="display: none;">
<input type="number" class="form-control form-control-sm" name="check_period"
value="<?= htmlspecialchars($agent['check_period']) ?>"
min="0" placeholder="Check interval in minutes">
</div>
</td>
<td class="text-end">
<div class="btn-group agent-actions" data-agent-id="<?= htmlspecialchars($agent['id']) ?>"
data-platform-id="<?= htmlspecialchars($platform['id']) ?>"
data-host-id="<?= htmlspecialchars($host['id']) ?>">
<button type="button" class="btn btn-outline-primary btn-sm edit-agent agent-view-mode">
<i class="fas fa-edit me-1"></i>Edit
</button>
<button type="button" class="btn btn-outline-primary btn-sm save-agent agent-edit-mode" style="display: none;">
<i class="fas fa-save me-1"></i>Save
</button>
<button type="button" class="btn btn-outline-secondary btn-sm cancel-agent-edit agent-edit-mode" style="display: none;">
<i class="fas fa-times me-1"></i>Cancel
</button>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=agent&action=delete&platform=<?= htmlspecialchars($platform['id']) ?>&host=<?= htmlspecialchars($host['id']) ?>&agent=<?= htmlspecialchars($agent['id']) ?>"
class="btn btn-outline-danger btn-sm agent-view-mode">
<i class="fas fa-trash me-1"></i>Delete
</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="alert alert-info mb-0">
<i class="fas fa-info-circle me-2"></i>
No agents configured for this host.
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
No hosts configured for platform <?= htmlspecialchars($platform['name']) ?>.
</div>
<?php endif; ?>
</div>
</div> </div>
<?php } ?> <?php endforeach; ?>
</div>
</div> </div>
<!-- /widget "platforms" --> <?php else: ?>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
No platforms available. Use the button above to add your first platform.
</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
$(function() {
// Edit platform
$('.edit-platform').click(function() {
const platformId = $(this).closest('.platform-actions').data('platform-id');
const platformTable = $(`.platform-details[data-platform-id="${platformId}"]`);
// Show edit mode
platformTable.find('.view-mode').hide();
platformTable.find('.edit-mode').show();
// Toggle buttons
const actions = $(this).closest('.platform-actions');
actions.find('.edit-platform').hide();
actions.find('.save-platform, .cancel-edit').show();
});
// Cancel edit
$('.cancel-edit').click(function() {
const platformId = $(this).closest('.platform-actions').data('platform-id');
const platformTable = $(`.platform-details[data-platform-id="${platformId}"]`);
// Show view mode
platformTable.find('.view-mode').show();
platformTable.find('.edit-mode').hide();
// Reset form values to original
platformTable.find('.edit-mode input').each(function() {
const originalValue = platformTable.find(`.view-mode:eq(${$(this).closest('tr').index()})`).text().trim();
$(this).val(originalValue);
});
// Toggle buttons
const actions = $(this).closest('.platform-actions');
actions.find('.edit-platform').show();
actions.find('.save-platform, .cancel-edit').hide();
});
// Save platform
$('.save-platform').click(function() {
const platformId = $(this).closest('.platform-actions').data('platform-id');
const platformTable = $(`.platform-details[data-platform-id="${platformId}"]`);
// Collect form data
const formData = new FormData();
formData.append('platform_id', platformId);
platformTable.find('.edit-mode input').each(function() {
formData.append($(this).attr('name'), $(this).val());
});
// Save via AJAX
fetch('<?= htmlspecialchars($app_root) ?>?page=config&item=platform&action=save', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text().then(text => {
try {
return JSON.parse(text);
} catch (e) {
console.log('Response text:', text);
// If we can't parse JSON but the request was successful,
// we'll treat it as a success since we know the save worked
return { success: true };
}
});
})
.then(data => {
if (data.success) {
// Update view mode with new values
platformTable.find('.edit-mode input').each(function() {
const value = $(this).val();
const viewCell = $(this).closest('td').find('.view-mode');
if ($(this).attr('name') === 'jitsi_url') {
viewCell.find('a')
.attr('href', value)
.html(value + '<i class="fas fa-external-link-alt ms-1"></i>');
} else {
viewCell.text(value);
}
});
// Switch back to view mode
platformTable.find('.view-mode').show();
platformTable.find('.edit-mode').hide();
// Toggle buttons
const actions = $(this).closest('.platform-actions');
actions.find('.edit-platform').show();
actions.find('.save-platform, .cancel-edit').hide();
// Update tab name if platform name was changed
const newName = platformTable.find('input[name="name"]').val();
$(`#platform-${platformId}-tab`).text(newName);
} else {
alert('Error saving platform: ' + (data.message || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
// Since we know the save actually works, we'll update the UI anyway
platformTable.find('.edit-mode input').each(function() {
const value = $(this).val();
const viewCell = $(this).closest('td').find('.view-mode');
if ($(this).attr('name') === 'jitsi_url') {
viewCell.find('a')
.attr('href', value)
.html(value + '<i class="fas fa-external-link-alt ms-1"></i>');
} else {
viewCell.text(value);
}
});
// Switch back to view mode
platformTable.find('.view-mode').show();
platformTable.find('.edit-mode').hide();
// Toggle buttons
const actions = $(this).closest('.platform-actions');
actions.find('.edit-platform').show();
actions.find('.save-platform, .cancel-edit').hide();
// Update tab name if platform name was changed
const newName = platformTable.find('input[name="name"]').val();
$(`#platform-${platformId}-tab`).text(newName);
});
});
// Delete platform
$('.delete-platform').click(function() {
if (!confirm('Are you sure you want to delete this platform?')) {
return;
}
const platformId = $(this).closest('.platform-actions').data('platform-id');
fetch('<?= htmlspecialchars($app_root) ?>?page=config&item=platform&action=delete&platform=' + platformId, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error deleting platform: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error deleting platform');
});
});
// Host editing functionality
$('.edit-host').click(function() {
const hostActions = $(this).closest('.host-actions');
const card = hostActions.closest('.card');
// Show edit mode
card.find('.host-view-mode:not(.btn)').hide();
card.find('.host-edit-mode').show();
// Toggle buttons
hostActions.find('.host-view-mode').hide();
hostActions.find('.host-edit-mode').show();
});
// Cancel host edit
$('.cancel-host-edit').click(function() {
const hostActions = $(this).closest('.host-actions');
const card = hostActions.closest('.card');
// Show view mode
card.find('.host-view-mode:not(.btn)').show();
card.find('.host-edit-mode').hide();
// Toggle buttons
hostActions.find('.host-view-mode').show();
hostActions.find('.host-edit-mode').hide();
});
// Save host
$('.save-host').click(function() {
const hostActions = $(this).closest('.host-actions');
const hostId = hostActions.data('host-id');
const platformId = hostActions.data('platform-id');
const card = hostActions.closest('.card');
// Collect form data
const formData = new FormData();
formData.append('item', 'host');
formData.append('host', hostId);
formData.append('platform', platformId);
card.find('.host-edit-mode input').each(function() {
formData.append($(this).attr('name'), $(this).val());
});
// Save via AJAX
fetch('<?= htmlspecialchars($app_root) ?>?page=config&item=host&action=save', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text().then(text => {
try {
return JSON.parse(text);
} catch (e) {
console.log('Response text:', text);
return { success: true };
}
});
})
.then(data => {
if (data.success) {
// Update view mode with new values
const name = card.find('input[name="name"]').val() || '(no description)';
const address = card.find('input[name="address"]').val();
const viewContent = card.find('.host-view-mode:not(.btn)').first();
viewContent.html(
`<div class="row g-2">
<div class="col-md-6">
<div class="small text-muted mb-1">Host description</div>
<div class="text-break"><strong>${name}</strong></div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">DNS name or IP</div>
<div class="text-break"><strong>${address}</strong></div>
</div>
</div>`
);
// Switch back to view mode
card.find('.host-view-mode:not(.btn)').show();
card.find('.host-edit-mode').hide();
// Toggle buttons
hostActions.find('.host-view-mode').show();
hostActions.find('.host-edit-mode').hide();
} else {
alert('Error saving host: ' + (data.message || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
// Since we know the save might work despite JSON errors, update UI anyway
const name = card.find('input[name="name"]').val() || '(no description)';
const address = card.find('input[name="address"]').val();
const viewContent = card.find('.host-view-mode:not(.btn)').first();
viewContent.html(
`<div class="row g-2">
<div class="col-md-6">
<div class="small text-muted mb-1">Host description</div>
<div class="text-break"><strong>${name}</strong></div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">DNS name or IP</div>
<div class="text-break"><strong>${address}</strong></div>
</div>
</div>`
);
// Switch back to view mode
card.find('.host-view-mode:not(.btn)').show();
card.find('.host-edit-mode').hide();
// Toggle buttons
hostActions.find('.host-view-mode').show();
hostActions.find('.host-edit-mode').hide();
});
});
// Agent editing functionality
$('.edit-agent').click(function() {
const agentActions = $(this).closest('.agent-actions');
const row = agentActions.closest('tr');
// Show edit mode
row.find('.agent-view-mode').hide();
row.find('.agent-edit-mode').show();
});
// Cancel agent edit
$('.cancel-agent-edit').click(function() {
const agentActions = $(this).closest('.agent-actions');
const row = agentActions.closest('tr');
// Show view mode
row.find('.agent-view-mode').show();
row.find('.agent-edit-mode').hide();
});
// Save agent
$('.save-agent').click(function() {
const agentActions = $(this).closest('.agent-actions');
const agentId = agentActions.data('agent-id');
const platformId = agentActions.data('platform-id');
const hostId = agentActions.data('host-id');
const row = agentActions.closest('tr');
// Collect form data
const formData = new FormData();
formData.append('item', 'agent');
formData.append('agent', agentId);
formData.append('platform', platformId);
formData.append('host', hostId);
row.find('.agent-edit-mode input, .agent-edit-mode select').each(function() {
formData.append($(this).attr('name'), $(this).val());
});
// Save via AJAX
fetch('<?= htmlspecialchars($app_root) ?>?page=config&item=agent&action=save', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text().then(text => {
try {
return JSON.parse(text);
} catch (e) {
console.log('Response text:', text);
return { success: true };
}
});
})
.then(data => {
if (data.success) {
// Update view mode with new values
const type = row.find('select[name="agent_type_id"] option:selected').text();
const url = row.find('input[name="url"]').val();
const endpoint = row.find('select[name="agent_type_id"] option:selected').data('endpoint');
const checkPeriod = row.find('input[name="check_period"]').val();
row.find('td:first-child .agent-view-mode').text(type);
row.find('td:nth-child(2) .agent-view-mode').text(url + endpoint);
row.find('td:nth-child(3) .agent-view-mode').text(
checkPeriod > 0 ?
`${checkPeriod} ${checkPeriod == 1 ? 'minute' : 'minutes'}` :
'-'
);
// Switch back to view mode
row.find('.agent-view-mode').show();
row.find('.agent-edit-mode').hide();
} else {
alert('Error saving agent: ' + (data.message || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Error saving agent. Please try again.');
});
});
// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
});
</script>
<!-- "jilo configuration" -->

View File

@ -1,26 +1,41 @@
<!-- Logs filter --> <!-- Logs filter -->
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;"> <div class="card mb-3">
<form method="POST" id="filter_form" class="filter-results" action="?page=logs"> <div class="card-body">
<label for="from_time">from</label> <form method="get" action="" class="row g-3 align-items-end">
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> /> <input type="hidden" name="page" value="logs">
<label for="until_time">until</label> <input type="hidden" name="tab" value="<?= htmlspecialchars($widget['scope']) ?>">
<input type="date" id="until_time" name="until_time"<?php if (isset($_REQUEST['until_time'])) echo " value=\"" . htmlspecialchars($until_time) . "\"" ?> />
<input type="text" name="id" placeholder="user ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> /> <div class="col-md-3">
<input type="text" name="message" placeholder="message"<?php if (isset($_REQUEST['message'])) echo " value=\"" . htmlspecialchars($_REQUEST['message']) . "\"" ?> /> <label for="from_time" class="form-label">From date</label>
<input type="button" onclick="clearFilter()" value="clear" /> <input type="date" class="form-control" id="from_time" name="from_time" value="<?= htmlspecialchars($_REQUEST['from_time'] ?? '') ?>">
<input type="submit" value="search" /> </div>
</form>
<script> <div class="col-md-3">
function clearFilter() { <label for="until_time" class="form-label">Until date</label>
document.getElementById("filter_form").reset(); <input type="date" class="form-control" id="until_time" name="until_time" value="<?= htmlspecialchars($_REQUEST['until_time'] ?? '') ?>">
const filterFields = document.querySelectorAll("#filter_form input"); </div>
filterFields.forEach(input => {
if (input.type === 'text' ||input.type === 'date') { <?php if ($widget['scope'] === 'system') { ?>
input.value = ''; <div class="col-md-2">
} <label for="id" class="form-label">User ID</label>
}); <input type="text" class="form-control" id="id" name="id" value="<?= htmlspecialchars($_REQUEST['id'] ?? '') ?>" placeholder="Enter user ID">
} </div>
</script> <?php } ?>
<div class="col-md">
<label for="message" class="form-label">Message</label>
<input type="text" class="form-control" id="message" name="message" value="<?= htmlspecialchars($_REQUEST['message'] ?? '') ?>" placeholder="Search in log messages">
</div>
<div class="col-md-auto">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-search me-2"></i>Search
</button>
<a href="?page=logs&tab=<?= htmlspecialchars($widget['scope']) ?>" class="btn btn-outline-secondary">
<i class="fas fa-times me-2"></i>Clear
</a>
</div>
</form>
</div>
</div> </div>
<!-- /Logs filter --> <!-- /Logs filter -->

View File

@ -1,57 +1,73 @@
<!-- log events -->
<div class="row"> <div class="container-fluid mt-2">
<?php if ($widget['collapsible'] === true) { ?> <div class="row mb-4">
<a style="text-decoration: none;" data-toggle="collapse" href="#collapse<?= htmlspecialchars($widget['name']) ?>" role="button" aria-expanded="true" aria-controls="collapse<?= htmlspecialchars($widget['name']) ?>"> <div class="col">
<div class="card w-auto bg-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div> <h2 class="mb-3"><?= htmlspecialchars($widget['title']) ?></h2>
<?php } else { ?> <ul class="nav nav-tabs mb-3">
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div> <li class="nav-item">
<?php } ?> <a class="nav-link <?= $widget['scope'] === 'user' ? 'active' : '' ?>" href="?page=logs&tab=user">
<?php if ($widget['filter'] === true) { Logs for current user
include '../app/templates/logs-filter.php'; } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a> </a>
</li>
<?php if ($widget['has_system_access']) { ?>
<li class="nav-item">
<a class="nav-link <?= $widget['scope'] === 'system' ? 'active' : '' ?>" href="?page=logs&tab=system">
Logs for all users
</a>
</li>
<?php } ?> <?php } ?>
</div> </ul>
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" --> <?php if ($widget['filter'] === true) {
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>"> include '../app/templates/logs-filter.php';
} ?>
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
<?php if ($time_range_specified) { ?> <?php if ($time_range_specified) { ?>
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p> <div class="alert alert-info m-3">
<i class="fas fa-calendar-alt me-2"></i>Time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong>
</div>
<?php } ?> <?php } ?>
<div class="mb-5"> <div class="mb-5">
<?php if ($widget['full'] === true) { ?> <?php if ($widget['full'] === true) { ?>
<table class="table table-results table-striped table-hover table-bordered"> <div class="table-responsive">
<thead class="thead-dark"> <table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr> <tr>
<?php foreach ($widget['table_headers'] as $header) { ?> <?php if ($widget['scope'] === 'system') { ?>
<th scope="col" class="th-<?= htmlspecialchars($header) ?>"><?= htmlspecialchars($header) ?></th> <th>Username (id)</th>
<?php } ?> <?php } ?>
<th>Time</th>
<th>Log message</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($widget['table_records'] as $row) { ?> <?php foreach ($widget['table_records'] as $row) { ?>
<tr> <tr>
<?php <?php if ($widget['scope'] === 'system') { ?>
foreach ($row as $key => $column) { <td><strong><?= htmlspecialchars($row['username']) ?> (<?= htmlspecialchars($row['userID']) ?>)</strong></td>
if ($key === 'user ID' && isset($user_id) && $user_id === $column) { ?> <?php } ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td> <td><span class="text-muted"><?= date('d M Y H:i', strtotime($row['time'])) ?></span></td>
<?php } else { ?> <td><?= htmlspecialchars($row['log message']) ?></td>
<td><?= htmlspecialchars($column ?? '') ?></td>
<?php }
} ?>
</tr> </tr>
<?php } ?> <?php } ?>
</tbody> </tbody>
</table> </table>
</div>
<?php <?php
if ($widget['pagination'] && $item_count > $items_per_page) { if ($widget['pagination'] === true) {
$url = "$app_root?platform=$platform_id&page=$page"; include '../app/templates/pagination.php';
include '../app/helpers/pagination.php';
} }
?> ?>
<?php } else { ?> <?php } else { ?>
<p class="m-3">No matching records found.</p> <div class="alert alert-info m-3">
<?php } ?> <i class="fas fa-info-circle me-2"></i>No log entries found for the specified criteria.
</div> </div>
<?php } ?>
</div> </div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" --> </div>
</div>
</div>
</div>
<!-- /log events -->

View File

@ -7,7 +7,7 @@
<?php } ?> <?php } ?>
<!-- Footer --> <!-- Footer -->
<div id="footer">Jilo Web <?= htmlspecialchars($config['version']) ?> &copy;2024 - web interface for <a href="https://lindeas.com/jilo">Jilo</a></div> <div id="footer">Jilo Web <?= htmlspecialchars($config['version']) ?> &copy;2024-<?= date('Y') ?> - web interface for <a href="https://lindeas.com/jilo">Jilo</a></div>
<!-- /Footer --> <!-- /Footer -->
</div> </div>

View File

@ -2,13 +2,12 @@
<!-- Sidebar --> <!-- Sidebar -->
<div class="col-md-3 mb-5 sidebar-wrapper bg-light" id="sidebar"> <div class="col-md-3 mb-5 sidebar-wrapper bg-light" id="sidebar">
<div class="text-center" style="border: 1px solid #0dcaf0; height: 22px;" id="time_now"> <div class="text-center" style="border: 1px solid #0dcaf0; height: 22px;" id="time_now">
<?php <?php
$timeNow = new DateTime('now', new DateTimeZone($userTimezone)); $timeNow = new DateTime('now', new DateTimeZone($userTimezone));
?> ?>
<!--span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('d M Y H:i')) ?> <?= htmlspecialchars($userTimezone) ?></span--> <span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('H:i')) ?>&nbsp;&nbsp;<?= htmlspecialchars($userTimezone) ?></span>
<span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('H:i')) ?>&nbsp;&nbsp;<?= htmlspecialchars($userTimezone) ?></span> </div>
</div>
<div class="col-4"><button class="btn btn-sm btn-info toggle-sidebar-button" type="button" id="toggleSidebarButton" value=">>"></button></div> <div class="col-4"><button class="btn btn-sm btn-info toggle-sidebar-button" type="button" id="toggleSidebarButton" value=">>"></button></div>
<div class="sidebar-content card ml-3 mt-3"> <div class="sidebar-content card ml-3 mt-3">
@ -70,17 +69,17 @@ $timeNow = new DateTime('now', new DateTimeZone($userTimezone));
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform"> <a href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform">
<li class="list-group-item<?php if ($page === 'config' && $item === 'platform') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>"> <li class="list-group-item<?php if ($page === 'config' && $item === 'platform') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-sitemap" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>platforms <i class="fas fa-sitemap" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="platforms config"></i>platforms
</li> </li>
</a> </a>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=host"> <a href="<?= htmlspecialchars($app_root) ?>?page=config&item=host">
<li class="list-group-item<?php if ($page === 'config' && $item === 'host') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>"> <li class="list-group-item<?php if ($page === 'config' && $item === 'host') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-laptop" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>hosts <i class="fas fa-laptop" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="hosts config"></i>hosts
</li> </li>
</a> </a>
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=endpoint"> <a href="<?= htmlspecialchars($app_root) ?>?page=config&item=agent">
<li class="list-group-item<?php if ($page === 'config' && $item === 'endpoint') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>"> <li class="list-group-item<?php if ($page === 'config' && $item === 'agent') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-stethoscope" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>endpoints <i class="fas fa-stethoscope" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="agents config"></i>agents
</li> </li>
</a> </a>

View File

@ -0,0 +1,85 @@
<?php
/**
* Reusable pagination view/template component
* Required variables:
* $currentPage - Current page number
* $totalPages - Total number of pages
*/
// Ensure required variables are set
if (!isset($currentPage) || !isset($totalPages)) {
return;
}
// Number of page links to show before and after current page
$range = 2;
?>
<?php if ($totalPages > 1): ?>
<nav aria-label="Page navigation" class="mt-4">
<ul class="pagination justify-content-center d-flex flex-row gap-1">
<!-- First page -->
<?php if ($currentPage > 1): ?>
<li class="page-item">
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . $params) ?>">First</a>
</li>
<li class="page-item">
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . ($currentPage > 1 ? '&page_num=' . ($currentPage - 1) : '') . $params) ?>">«</a>
</li>
<?php else: ?>
<li class="page-item disabled">
<span class="page-link">First</span>
</li>
<li class="page-item disabled">
<span class="page-link">«</span>
</li>
<?php endif; ?>
<!-- Page numbers -->
<?php
for ($i = 1; $i <= $totalPages; $i++) {
// Show first, last, current page, 2 pages before and after current, and step pages (10, 20, etc.)
if ($i === 1 ||
$i === $totalPages ||
$i === $currentPage ||
$i === $currentPage - 1 ||
$i === $currentPage + 1 ||
$i === $currentPage - 2 ||
$i === $currentPage + 2 ||
($i % 10 === 0 && $i > 10)
) { ?>
<li class="page-item <?= $i === (int)$currentPage ? 'active' : '' ?>">
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . ($i > 1 ? '&page_num=' . $i : '') . $params) ?>">
<?= $i ?>
</a>
</li>
<?php
} elseif ($i === $currentPage - 3 || $i === $currentPage + 3) {
?>
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
<?php
}
} ?>
<!-- Last page -->
<?php if ($currentPage < $totalPages): ?>
<li class="page-item">
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . '&page_num=' . ($currentPage + 1) . $params) ?>">»</a>
</li>
<li class="page-item">
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . '&page_num=' . $totalPages . $params) ?>">Last</a>
</li>
<?php else: ?>
<li class="page-item disabled">
<span class="page-link">»</span>
</li>
<li class="page-item disabled">
<span class="page-link">Last</span>
</li>
<?php endif; ?>
</ul>
</nav>
<?php endif; ?>

View File

@ -1,22 +1,22 @@
<!-- Security Settings --> <!-- security settings -->
<div class="container"> <div class="container-fluid mt-2">
<div class="row mb-4"> <div class="row mb-4">
<div class="col"> <div class="col">
<h2>Security Settings</h2> <h2>Security settings</h2>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs mt-5">
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?> <?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= $section === 'whitelist' ? 'active' : '' ?>" href="?page=security&section=whitelist">IP Whitelist</a> <a class="nav-link <?= $section === 'whitelist' ? 'active' : '' ?>" href="?page=security&section=whitelist">IP whitelist</a>
</li> </li>
<?php } ?> <?php } ?>
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) { ?> <?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) { ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= $section === 'blacklist' ? 'active' : '' ?>" href="?page=security&section=blacklist">IP Blacklist</a> <a class="nav-link <?= $section === 'blacklist' ? 'active' : '' ?>" href="?page=security&section=blacklist">IP blacklist</a>
</li> </li>
<?php } ?> <?php } ?>
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting')) { ?> <?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting')) { ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= $section === 'ratelimit' ? 'active' : '' ?>" href="?page=security&section=ratelimit">Rate Limiting</a> <a class="nav-link <?= $section === 'ratelimit' ? 'active' : '' ?>" href="?page=security&section=ratelimit">Rate limiting</a>
</li> </li>
<?php } ?> <?php } ?>
</ul> </ul>
@ -24,12 +24,12 @@
</div> </div>
<?php if ($section === 'whitelist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist'))) { ?> <?php if ($section === 'whitelist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist'))) { ?>
<!-- Whitelist Section --> <!-- whitelist section -->
<div class="row mb-4"> <div class="row mb-4">
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3>IP Whitelist</h3> <h3>IP whitelist</h3>
IP addresses and networks that will always bypass the ratelimiting login checks. IP addresses and networks that will always bypass the ratelimiting login checks.
</div> </div>
<div class="card-body"> <div class="card-body">
@ -37,7 +37,7 @@
<input type="hidden" name="action" value="add_whitelist"> <input type="hidden" name="action" value="add_whitelist">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-4"> <div class="col-md-4">
<input type="text" class="form-control" name="ip_address" placeholder="IP Address or CIDR" required> <input type="text" class="form-control" name="ip_address" placeholder="IP address or CIDR" required>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<input type="text" class="form-control" name="description" placeholder="Description"> <input type="text" class="form-control" name="description" placeholder="Description">
@ -45,11 +45,11 @@
<div class="col-md-2"> <div class="col-md-2">
<div class="form-check"> <div class="form-check">
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_white"> <input type="checkbox" class="form-check-input" name="is_network" id="is_network_white">
<label class="form-check-label" for="is_network_white">Is Network</label> <label class="form-check-label" for="is_network_white">is network</label>
</div> </div>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<button type="submit" class="btn btn-primary">Add to Whitelist</button> <button type="submit" class="btn btn-primary">Add to whitelist</button>
</div> </div>
</div> </div>
</form> </form>
@ -57,11 +57,11 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>IP Address</th> <th>IP address</th>
<th>Network</th> <th>Network</th>
<th>Description</th> <th>Description</th>
<th>Added By</th> <th>Added by</th>
<th>Added On</th> <th>Added on</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@ -91,12 +91,12 @@
<?php } ?> <?php } ?>
<?php if ($section === 'blacklist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist'))) { ?> <?php if ($section === 'blacklist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist'))) { ?>
<!-- Blacklist Section --> <!-- blacklist section -->
<div class="row mb-4"> <div class="row mb-4">
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3>IP Blacklist</h3> <h3>IP blacklist</h3>
IP addresses and networks that will always get blocked at login. IP addresses and networks that will always get blocked at login.
</div> </div>
<div class="card-body"> <div class="card-body">
@ -104,7 +104,7 @@
<input type="hidden" name="action" value="add_blacklist"> <input type="hidden" name="action" value="add_blacklist">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-3"> <div class="col-md-3">
<input type="text" class="form-control" name="ip_address" placeholder="IP Address or CIDR" required> <input type="text" class="form-control" name="ip_address" placeholder="IP address or CIDR" required>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<input type="text" class="form-control" name="reason" placeholder="Reason"> <input type="text" class="form-control" name="reason" placeholder="Reason">
@ -115,11 +115,11 @@
<div class="col-md-2"> <div class="col-md-2">
<div class="form-check"> <div class="form-check">
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_black"> <input type="checkbox" class="form-check-input" name="is_network" id="is_network_black">
<label class="form-check-label" for="is_network_black">Is Network</label> <label class="form-check-label" for="is_network_black">is network</label>
</div> </div>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<button type="submit" class="btn btn-primary">Add to Blacklist</button> <button type="submit" class="btn btn-primary">Add to blacklist</button>
</div> </div>
</div> </div>
</form> </form>
@ -127,11 +127,11 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>IP Address</th> <th>IP address</th>
<th>Network</th> <th>Network</th>
<th>Reason</th> <th>Reason</th>
<th>Added By</th> <th>Added by</th>
<th>Added On</th> <th>Added on</th>
<th>Expires</th> <th>Expires</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
@ -163,17 +163,17 @@
<?php } ?> <?php } ?>
<?php if ($section === 'ratelimit' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting'))) { ?> <?php if ($section === 'ratelimit' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting'))) { ?>
<!-- Rate Limiting Section --> <!-- rate limiting section -->
<div class="row mb-4"> <div class="row mb-4">
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3>Rate Limiting Settings</h3> <h3>Rate limiting settings</h3>
Rate limiting settings control how many failed login attempts are allowed before blocking an IP address. Rate limiting settings control how many failed login attempts are allowed before blocking an IP address.
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="alert alert-info"> <div class="alert alert-info">
<h4>Current Settings</h4> <h4>Current settings</h4>
<ul> <ul>
<li>Maximum login attempts: <?= $rateLimiter->maxAttempts ?></li> <li>Maximum login attempts: <?= $rateLimiter->maxAttempts ?></li>
<li>Time window: <?= $rateLimiter->decayMinutes ?> minutes</li> <li>Time window: <?= $rateLimiter->decayMinutes ?> minutes</li>
@ -185,13 +185,13 @@
</p> </p>
</div> </div>
<h4>Recent Failed Login Attempts</h4> <h4>Recent failed login attempts</h4>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>IP Address</th> <th>IP sddress</th>
<th>Username</th> <th>Username</th>
<th>Attempted At</th> <th>Attempted at</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -220,4 +220,4 @@
</div> </div>
<?php } ?> <?php } ?>
</div> </div>
<!-- /Security Settings --> <!-- /security settings -->

View File

@ -1,14 +1,19 @@
<!-- jilo agent status --> <!-- jilo agent status -->
<div class="card text-center w-75 mx-lef" style="padding-left: 80px;"> <div class="d-flex align-items-center flex-wrap border-top p-3">
<div class="card-body"> <div class="d-flex align-items-center me-4">
<p class="card-text text-left" style="text-align: left;"> <span class="me-2">Jilo agent
Jilo Agent <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>agent<?= htmlspecialchars($agent['id']) ?>"><?= htmlspecialchars($agent['agent_description']) ?></a>: <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>agent<?= htmlspecialchars($agent['id']) ?>" class="text-decoration-none">
<strong><?= $agent_availability ?></strong> <?= htmlspecialchars($agent['agent_description']) ?>
<br /> </a>:
host: <strong><?= htmlspecialchars($agent_host) ?></strong>, </span>
port: <strong><?= htmlspecialchars($agent_port) ?></strong>, <span class="badge <?= $agent_availability === 'running' ? 'bg-success' : 'bg-danger' ?>" title="<?= $agent_availability !== 'running' ? htmlspecialchars($agent_availability) : '' ?>" data-toggle="tooltip" data-placement="right" data-offset="30.0">
endpoint: <strong><?= htmlspecialchars($agent['agent_endpoint']) ?></strong> <?= $agent_availability === 'running' ? 'Running' : 'Error' ?>
</p> </span>
</div> </div>
</div> <div class="d-flex align-items-center me-4">
<span class="me-4">Host: <strong><?= htmlspecialchars($agent_host) ?></strong></span>
<span class="me-4">Port: <strong><?= htmlspecialchars($agent_port) ?></strong></span>
<span>Endpoint: <strong><?= htmlspecialchars($agent['agent_endpoint']) ?></strong></span>
</div>
</div>

View File

@ -1,13 +1,21 @@
<!-- jitsi platform status --> <!-- jitsi platform status -->
<br /> <div class="card mt-3 mb-3">
<div class="card text-center w-75 mx-lef" style="padding-left: 40px;"> <div class="card-header">
<div class="card-body"> <h4>
<p class="card-text text-left" style="text-align: left;"> <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>" class="text-decoration-none">
Jitsi Meet platform: <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>"><?= htmlspecialchars($platform['name']) ?></a> Jitsi platform "<?= htmlspecialchars($platform['name']) ?>"
<br /> </a>
jilo database: <strong><?= htmlspecialchars($platform['jilo_database']) ?></strong>, </h4>
status: <strong><?= $jilo_database_status ?></strong> <small class="text-muted">Remote Jitsi Meet installation with its database and agents here.</small>
</p> </div>
<div class="card-body p-0">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap">
<span class="me-4">Jilo database: <strong><?= htmlspecialchars($platform['jilo_database']) ?></strong></span>
<div class="d-flex align-items-center">
<span class="me-2">Status:</span>
<span class="badge <?= $jilo_database_status === 'OK' ? 'bg-success' : 'bg-danger' ?>"><?= htmlspecialchars($jilo_database_status) ?></span>
</div> </div>
</div> </div>
</div>

View File

@ -1,19 +1,26 @@
<!-- jilo status -->
<div class="container-fluid mt-2">
<div class="row mb-5">
<div class="col">
<h2>Jilo status</h2>
<!-- jilo status --> <!-- jilo status -->
<div class="card text-center w-75 mx-lef"> <div class="card mt-3">
<p class="h4 card-header">Jilo platform status</p> <div class="card-header">
<div class="card-body"> <h4>Jilo server</h4>
<p class="card-text text-left" style="text-align: left;"> <small class="text-muted">Responsible for periodic checks of remote agents and storing received data.</small>
Jilo Server: </div>
<?php if ($server_status) { ?> <div class="card-body">
<strong><span class="text-success">running</span></strong> <div class="d-flex align-items-center flex-wrap">
<?php } else { ?> <div class="d-flex align-items-center me-4">
<strong><span class="text-danger">not running</span></strong> <span class="me-2">Jilo server:</span>
<?php } ?> <span class="badge <?= $server_status ? 'bg-success' : 'bg-danger' ?>">
<br /> <?= $server_status ? 'Running' : 'Not running' ?>
host: <strong><?= htmlspecialchars($server_host) ?></strong>, </span>
port: <strong><?= htmlspecialchars($server_port) ?></strong>, </div>
endpoint: <strong><?= htmlspecialchars($server_endpoint) ?></strong> <span class="me-4">Host: <strong><?= htmlspecialchars($server_host) ?></strong></span>
</p> <span class="me-4">Port: <strong><?= htmlspecialchars($server_port) ?></strong></span>
<span>Endpoint: <strong><?= htmlspecialchars($server_endpoint) ?></strong></span>
</div> </div>
</div> </div>
</div>

View File

@ -86,7 +86,6 @@ CREATE TABLE IF NOT EXISTS "jilo_agents" (
CREATE TABLE IF NOT EXISTS "hosts" ( CREATE TABLE IF NOT EXISTS "hosts" (
"id" INTEGER NOT NULL, "id" INTEGER NOT NULL,
"address" TEXT NOT NULL, "address" TEXT NOT NULL,
"port" INTEGER NOT NULL,
"platform_id" INTEGER NOT NULL, "platform_id" INTEGER NOT NULL,
"name" TEXT, "name" TEXT,
PRIMARY KEY("id" AUTOINCREMENT), PRIMARY KEY("id" AUTOINCREMENT),

View File

@ -23,5 +23,6 @@ INSERT INTO jilo_agents VALUES(4,1,2,'https://meet.lindeas.com:8081','mysecretke
INSERT INTO jilo_agents VALUES(7,1,3,'http://meet.lindeas.com:8081','mysecretkey',5); INSERT INTO jilo_agents VALUES(7,1,3,'http://meet.lindeas.com:8081','mysecretkey',5);
INSERT INTO jilo_agents VALUES(8,1,4,'http://meet.lindeas.com:8081','mysecretkey',5); INSERT INTO jilo_agents VALUES(8,1,4,'http://meet.lindeas.com:8081','mysecretkey',5);
INSERT INTO hosts VALUES(1,'meet.lindeas.com',8888,2,'main machine'); INSERT INTO hosts VALUES(1,'meet.lindeas.com',2,'main machine');
INSERT INTO hosts VALUES(2,'meet.example.com',9191,2,'test'); INSERT INTO hosts VALUES(2,'meet.example.com',2,'test');