Compare commits

..

No commits in common. "main" and "v0.2" have entirely different histories.
main ... v0.2

93 changed files with 491 additions and 3765 deletions

View File

@ -7,44 +7,10 @@ All notable changes to this project will be documented in this file.
## Unreleased
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.2.1...HEAD
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.2.1...HEAD
- github: https://github.com/lindeas/jilo-web/compare/v0.2.1...HEAD
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.2.1...HEAD
### Added
- Added Jilo Server check and notice on error
- Added status page
---
## 0.2.1 - 2024-10-17
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.2...v0.2.1
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.2...v0.2.1
- github: https://github.com/lindeas/jilo-web/compare/v0.2...v0.2.1
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.2...v0.2.1
### Added
- Added support for managing Jilo Agents
- Authenticating to Jilo Agents with JWT tokens with a shared secret key
- Added Jilo Agent functionality to fetch data, cache it and manage the cache
- Added more fields and avatar image to user profile
- Added pagination (with ellipses) for the longer listings
- Added initial support for application logs
- Added help page
- Added support for graphs by Chart.js
- Added "graphs" section in sidebar with graphs and latest data pages
### Changed
- Jitsi platforms config moved from file to SQLite database
- Left sidebar menu items reordered
### Fixed
- All output HTML sanitized
- Sanitized input forms data
- Fixed error in calculation of monthly total conferences on front page
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.2...HEAD
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.2...HEAD
- github: https://github.com/lindeas/jilo-web/compare/v0.2...HEAD
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.2...HEAD
---

View File

@ -26,7 +26,7 @@ To see a demo install, go to https://work.lindeas.com/jilo-web-demo/
## version
Current version: **0.2.1** released on **2024-10-17**
Current version: **0.2** released on **2024-08-31**
## license
@ -36,14 +36,11 @@ Bootstrap is used in this project and is licensed under the MIT License. See lic
JQuery is used in this project and is licensed under the MIT License. See license-jquery file.
Chart.js is used in this project and is licensed under the MIT License. See license-chartjs file.
## requirements
- web server (deb: apache | nginx)
- php support in the web server (deb: php-fpm | libapache2-mod-php)
- pdo and pdo_sqlite support in php (deb: php-db, php-sqlite3) uncomment in php.ini: ;extension=pdo_sqlite
- php-curl module
## installation

27
TODO.md 100644
View File

@ -0,0 +1,27 @@
# Jilo Web
## TODO
- ~~jilo-web.db outside web root~~
- ~~jilo-web.db writable by web server user~~
- major refactoring after v0.1
- - ~~add bootstrap template~~
- - ~~clean up the code to follow model-view--controller style~~
- - ~~no HTML inside PHP code~~
- - put all additional functions in files in a separate folder
- - ~~reduce try/catch usage, use it only for critical errors~~
- - move all SQL code in the model classes, one query per method
- - add 'limit' to SQL and make pagination
- - pretty URLs routing (no htaccess, it needs to be in PHP code so that it can be used with all servers)
- add mysql/mariadb option

View File

@ -1,291 +0,0 @@
<?php
class Agent {
private $db;
public function __construct($database) {
$this->db = $database->getConnection();
}
// get details of a specified agent ID (or all) in a specified platform ID
public function getAgentDetails($platform_id, $agent_id = '') {
$sql = 'SELECT
ja.id,
ja.platform_id,
ja.agent_type_id,
ja.url,
ja.secret_key,
ja.check_period,
jat.description AS agent_description,
jat.endpoint AS agent_endpoint
FROM
jilo_agents ja
JOIN
jilo_agent_types jat ON ja.agent_type_id = jat.id
WHERE
platform_id = :platform_id';
if ($agent_id !== '') {
$sql .= ' AND ja.id = :agent_id';
}
$query = $this->db->prepare($sql);
$query->bindParam(':platform_id', $platform_id);
if ($agent_id !== '') {
$query->bindParam(':agent_id', $agent_id);
}
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// get details of a specified agent ID
public function getAgentIDDetails($agent_id) {
$sql = 'SELECT
ja.id,
ja.platform_id,
ja.agent_type_id,
ja.url,
ja.secret_key,
ja.check_period,
jat.description AS agent_description,
jat.endpoint AS agent_endpoint
FROM
jilo_agents ja
JOIN
jilo_agent_types jat ON ja.agent_type_id = jat.id
WHERE
ja.id = :agent_id';
$query = $this->db->prepare($sql);
$query->bindParam(':agent_id', $agent_id);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// get agent types
public function getAgentTypes() {
$sql = 'SELECT *
FROM jilo_agent_types
ORDER BY id';
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// get agent types already configured for a platform
public function getPlatformAgentTypes($platform_id) {
$sql = 'SELECT
id,
agent_type_id
FROM
jilo_agents
WHERE
platform_id = :platform_id';
$query = $this->db->prepare($sql);
$query->bindParam(':platform_id', $platform_id);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// add new agent
public function addAgent($platform_id, $newAgent) {
try {
$sql = 'INSERT INTO jilo_agents
(platform_id, agent_type_id, url, secret_key, check_period)
VALUES
(:platform_id, :agent_type_id, :url, :secret_key, :check_period)';
$query = $this->db->prepare($sql);
$query->execute([
':platform_id' => $platform_id,
':agent_type_id' => $newAgent['type_id'],
':url' => $newAgent['url'],
':secret_key' => $newAgent['secret_key'],
':check_period' => $newAgent['check_period'],
]);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// edit an existing agent
public function editAgent($platform_id, $updatedAgent) {
try {
$sql = 'UPDATE jilo_agents SET
agent_type_id = :agent_type_id,
url = :url,
secret_key = :secret_key,
check_period = :check_period
WHERE
id = :agent_id
AND
platform_id = :platform_id';
$query = $this->db->prepare($sql);
$query->execute([
':agent_type_id' => $updatedAgent['agent_type_id'],
':url' => $updatedAgent['url'],
':secret_key' => $updatedAgent['secret_key'],
':check_period' => $updatedAgent['check_period'],
':agent_id' => $updatedAgent['id'],
':platform_id' => $platform_id,
]);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// delete an agent
public function deleteAgent($agent_id) {
try {
$sql = 'DELETE FROM jilo_agents
WHERE
id = :agent_id';
$query = $this->db->prepare($sql);
$query->bindParam(':agent_id', $agent_id);
$query->execute();
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// check for agent cache
public function checkAgentCache($agent_id) {
$agent_cache_name = 'agent' . $agent_id . '_cache';
$agent_cache_time = 'agent' . $agent_id . '_time';
return isset($_SESSION[$agent_cache_name]) && isset($_SESSION[$agent_cache_time]) && (time() - $_SESSION[$agent_cache_time] < 600);
}
// method for base64 URL encoding for JWT tokens
private function base64UrlEncode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
// generate a JWT token for jilo agent
public function generateAgentToken($payload, $secret_key) {
// header
$header = json_encode([
'typ' => 'JWT',
'alg' => 'HS256'
]);
$base64Url_header = $this->base64UrlEncode($header);
// payload
$payload = json_encode($payload);
$base64Url_payload = $this->base64UrlEncode($payload);
// signature
$signature = hash_hmac('sha256', $base64Url_header . "." . $base64Url_payload, $secret_key, true);
$base64Url_signature = $this->base64UrlEncode($signature);
// build the JWT
$jwt = $base64Url_header . "." . $base64Url_payload . "." . $base64Url_signature;
return $jwt;
}
// fetch result from jilo agent API
public function fetchAgent($agent_id, $force = false) {
// we need agent details for URL and JWT token
$agentDetails = $this->getAgentIDDetails($agent_id);
// Safe exit in case the agent is not found
if (empty($agentDetails)) {
return json_encode(['error' => 'Agent not found']);
}
$agent = $agentDetails[0];
$agent_cache_name = 'agent' . $agent_id . '_cache';
$agent_cache_time = 'agent' . $agent_id . '_time';
// check if the cache is still valid, unless force-refresh is requested
if (!$force && $this->checkAgentCache($agent_id)) {
return $_SESSION[$agent_cache_name];
}
// generate the JWT token
$payload = [
'agent_id' => $agent_id,
'timestamp' => time()
];
$jwt = $this->generateAgentToken($payload, $agent['secret_key']);
// Make the API request
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $agent['url'] . $agent['agent_endpoint']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // timeout 10 seconds
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $jwt,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$curl_error = curl_error($ch);
$curl_errno = curl_errno($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// curl error
if ($curl_errno) {
return json_encode(['error' => 'curl error: ' . $curl_error]);
}
// response is not 200 OK
if ($http_code !== 200) {
return json_encode(['error' => 'HTTP error: ' . $http_code]);
}
// other custom error(s)
if (strpos($response, 'Auth header not received') !== false) {
return json_encode(['error' => 'Auth header not received']);
}
// Cache the result and the timestamp if the response is successful
// We decode it so that it's pure JSON and not escaped
$_SESSION[$agent_cache_name] = json_decode($response, true);
$_SESSION[$agent_cache_time] = time();
return $response;
}
// clear agent cache
public function clearAgentCache($agent_id) {
$_SESSION["agent{$agent_id}_cache"] = '';
$_SESSION["agent{$agent_id}_cache_time"] = '';
}
// get latest stored jilo agents data
public function getLatestData($platform_id, $agent_type, $metric_type) {
// retrieves data already stored in db from another function (or the jilo-server to-be)
}
}
?>

View File

@ -9,7 +9,7 @@ class Component {
// list of component events
public function jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time, $offset=0, $items_per_page='') {
public function jitsiComponents($jitsi_component, $component_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -31,31 +31,13 @@ FROM
WHERE
jitsi_component = %s
AND
component_id = %s";
if ($event_type != '' && $event_type != 'event_type') {
$sql .= "
AND
event_type LIKE '%%%s%%'";
}
$sql .= "
component_id = %s
AND
(time >= '%s 00:00:00' AND time <= '%s 23:59:59')
ORDER BY
time";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
// FIXME this needs to be done with bound params instead of sprintf
if ($event_type != '' && $event_type != 'event_type') {
$sql = sprintf($sql, $jitsi_component, $component_id, $event_type, $from_time, $until_time);
$sql = str_replace("LIKE '%'", "LIKE '%", $sql);
$sql = str_replace("'%'\nAND", "%' AND", $sql);
} else {
$sql = sprintf($sql, $jitsi_component, $component_id, $from_time, $until_time);
}
$query = $this->db->prepare($sql);
$query->execute();

View File

@ -9,7 +9,7 @@ class Conference {
// search/list specific conference ID
public function conferenceById($conference_id, $from_time, $until_time, $offset=0, $items_per_page='') {
public function conferenceById($conference_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -69,11 +69,6 @@ AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$sql = sprintf($sql, $conference_id, $from_time, $until_time, $conference_id, $from_time, $until_time);
$query = $this->db->prepare($sql);
@ -84,7 +79,7 @@ ORDER BY
// search/list specific conference name
public function conferenceByName($conference_name, $from_time, $until_time, $offset=0, $items_per_page='') {
public function conferenceByName($conference_name, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -144,11 +139,6 @@ AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$sql = sprintf($sql, $conference_name, $from_time, $until_time, $conference_name, $from_time, $until_time);
$query = $this->db->prepare($sql);
@ -159,7 +149,7 @@ ORDER BY
// list of all conferences
public function conferencesAllFormatted($from_time, $until_time, $offset=0, $items_per_page='') {
public function conferencesAllFormatted($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -244,11 +234,6 @@ WHERE (ce.time >= '%s 00:00:00' AND ce.time <= '%s 23:59:59')
ORDER BY
c.id";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$sql = sprintf($sql, $from_time, $until_time);
$query = $this->db->prepare($sql);
@ -274,52 +259,25 @@ ORDER BY
$until_time = htmlspecialchars(strip_tags($until_time));
// number of conferences for time period (if given)
// FIXME sometimes there is no start/end time, find a way around this
// NB we need to cross check with first occurrence of "bridge selected"
// as in Jicofo logs there is no way to get the time for conference ID creation
$sql = "
SELECT COUNT(*) AS conferences
FROM (
SELECT DISTINCT
(SELECT COALESCE
(
(SELECT ce.time
SELECT COUNT(c.conference_id) as conferences
FROM
conferences c
LEFT JOIN (
SELECT ce.conference_id, MIN(ce.time) as first_event_time
FROM conference_events ce
WHERE
ce.conference_id = c.conference_id
AND
ce.conference_event = 'conference created'
),
(SELECT ce.time
FROM conference_events ce
WHERE
ce.conference_id = c.conference_id
AND
ce.conference_event = 'bridge selected'
)
)
) AS start,
(SELECT COALESCE
(
(SELECT ce.time
FROM conference_events ce
WHERE
ce.conference_id = c.conference_id
AND
(ce.conference_event = 'conference expired' OR ce.conference_event = 'conference stopped')
),
(SELECT pe.time
FROM participant_events pe
WHERE
pe.event_param = c.conference_id
ORDER BY pe.time DESC
LIMIT 1
)
)
) AS end
FROM conferences c
JOIN
WHERE ce.conference_event = 'bridge selected'
GROUP BY ce.conference_id
) AS first_event ON c.conference_id = first_event.conference_id
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE (start >= '%s 00:00:00' AND end <= '%s 23:59:59')
) AS subquery";
WHERE
(ce.time >= '%s 00:00:00' AND ce.time <= '%s 23:59:59')
AND (ce.conference_event = 'conference created'
OR (ce.conference_event = 'bridge selected' AND ce.time = first_event.first_event_time)
)";
$sql = sprintf($sql, $from_time, $until_time);

View File

@ -2,10 +2,15 @@
class Config {
public function getPlatformDetails($config, $platform_id) {
$platformDetails = $config['platforms'][$platform_id];
return $platformDetails;
}
// loading the config.js
public function getPlatformConfigjs($jitsiUrl, $raw = false) {
public function getPlatformConfigjs($platformDetails, $raw = false) {
// constructing the URL
$configjsFile = $jitsiUrl . '/config.js';
$configjsFile = $platformDetails['jitsi_url'] . '/config.js';
// default content, if we can't get the file contents
$platformConfigjs = "The file $configjsFile can't be loaded.";
@ -45,9 +50,9 @@ class Config {
// loading the interface_config.js
public function getPlatformInterfaceConfigjs($jitsiUrl, $raw = false) {
public function getPlatformInterfaceConfigjs($platformDetails, $raw = false) {
// constructing the URL
$interfaceConfigjsFile = $jitsiUrl . '/interface_config.js';
$interfaceConfigjsFile = $platformDetails['jitsi_url'] . '/interface_config.js';
// default content, if we can't get the file contents
$platformInterfaceConfigjs = "The file $interfaceConfigjsFile can't be loaded.";

View File

@ -1,5 +1,7 @@
<?php
require '../app/helpers/errors.php';
class Database {
private $pdo;
@ -42,8 +44,6 @@ class Database {
try {
$this->pdo = new PDO("sqlite:" . $options['dbFile']);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// enable foreign key constraints (not ON by default in SQLite3)
$this->pdo->exec('PRAGMA foreign_keys = ON;');
} catch (PDOException $e) {
$error = getError('SQLite connection failed: ', $e->getMessage());
}
@ -66,7 +66,7 @@ class Database {
$this->pdo = new PDO($dsn, $options['user'], $options['password'] ?? '');
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
$error = getError('MySQL connection failed: ', $e->getMessage(), $config['environment']);
$error = getError('MySQL connection failed: ', $config['environment'], $e->getMessage());
}
}

View File

@ -1,62 +0,0 @@
<?php
class Log {
private $db;
public function __construct($database) {
$this->db = $database->getConnection();
}
// insert log event
public function insertLog($user_id, $message, $scope='user') {
try {
$sql = 'INSERT INTO logs
(user_id, scope, message)
VALUES
(:user_id, :scope, :message)';
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
':scope' => $scope,
':message' => $message,
]);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// read logs
public function readLog($user_id, $scope, $offset=0, $items_per_page='') {
if ($scope === 'user') {
$sql = 'SELECT * FROM logs WHERE user_id = :user_id 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);
$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);
$query->execute();
}
return $query->fetchAll(PDO::FETCH_ASSOC);
}
}
?>

View File

@ -9,7 +9,7 @@ class Participant {
// search/list specific participant ID
public function conferenceByParticipantId($participant_id, $from_time, $until_time, $offset=0, $items_per_page='') {
public function conferenceByParticipantId($participant_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -69,11 +69,6 @@ AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$sql = sprintf($sql, $participant_id, $from_time, $until_time, $participant_id, $from_time, $until_time);
$query = $this->db->prepare($sql);
@ -84,7 +79,7 @@ ORDER BY
// search/list specific participant name (stats_id)
public function conferenceByParticipantName($participant_name, $from_time, $until_time, $offset=0, $items_per_page='') {
public function conferenceByParticipantName($participant_name, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -144,11 +139,6 @@ AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$sql = sprintf($sql, $participant_name, $from_time, $until_time, $participant_name, $from_time, $until_time);
$query = $this->db->prepare($sql);
@ -159,7 +149,7 @@ ORDER BY
// search/list specific participant IP
public function conferenceByParticipantIP($participant_ip, $from_time, $until_time, $offset=0, $items_per_page='') {
public function conferenceByParticipantIP($participant_ip, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -219,11 +209,6 @@ AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$sql = sprintf($sql, $participant_ip, $from_time, $until_time, $participant_ip, $from_time, $until_time);
$query = $this->db->prepare($sql);
@ -234,7 +219,7 @@ ORDER BY
// list of all participants
public function participantsAll($from_time, $until_time, $offset=0, $items_per_page='') {
public function participantsAll($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
@ -261,11 +246,6 @@ WHERE
pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59'
ORDER BY p.id";
if ($items_per_page) {
$items_per_page = (int)$items_per_page;
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
}
$sql = sprintf($sql, $from_time, $until_time);
$query = $this->db->prepare($sql);

View File

@ -1,94 +0,0 @@
<?php
class Platform {
private $db;
public function __construct($database) {
$this->db = $database->getConnection();
}
// get details of a specified platform ID (or all)
public function getPlatformDetails($platform_id = '') {
$sql = 'SELECT * FROM platforms';
if ($platform_id !== '') {
$sql .= ' WHERE id = :platform_id';
$query = $this->db->prepare($sql);
$query->bindParam(':platform_id', $platform_id);
} else {
$query = $this->db->prepare($sql);
}
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// add new platform
public function addPlatform($newPlatform) {
try {
$sql = 'INSERT INTO platforms
(name, jitsi_url, jilo_database)
VALUES
(:name, :jitsi_url, :jilo_database)';
$query = $this->db->prepare($sql);
$query->execute([
':name' => $newPlatform['name'],
':jitsi_url' => $newPlatform['jitsi_url'],
':jilo_database' => $newPlatform['jilo_database'],
]);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// edit an existing platform
public function editPlatform($platform_id, $updatedPlatform) {
try {
$sql = 'UPDATE platforms SET
name = :name,
jitsi_url = :jitsi_url,
jilo_database = :jilo_database
WHERE
id = :platform_id';
$query = $this->db->prepare($sql);
$query->execute([
':name' => $updatedPlatform['name'],
':jitsi_url' => $updatedPlatform['jitsi_url'],
':jilo_database' => $updatedPlatform['jilo_database'],
':platform_id' => $platform_id,
]);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// delete a platform
public function deletePlatform($platform_id) {
try {
$sql = 'DELETE FROM platforms
WHERE
id = :platform_id';
$query = $this->db->prepare($sql);
$query->bindParam(':platform_id', $platform_id);
$query->execute();
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
}
?>

View File

@ -1,34 +0,0 @@
<?php
class Server {
private $db;
public function __construct($database) {
$this->db = $database->getConnection();
}
// get Jilo Server status
public function getServerStatus($host = '127.0.0.1', $port = 8080, $endpoint = '/health') {
$url = "http://$host:$port$endpoint";
$options = [
'http' => [
'method' => 'GET',
'timeout' => 3,
],
];
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);
// We check the response if it's 200 OK
if ($response !== false && isset($http_response_header) && strpos($http_response_header[0], '200 OK') !== false) {
return true;
}
// If it's not 200 OK
return false;
}
}
?>

View File

@ -9,51 +9,12 @@ class User {
// registration
public function register($username, $password) {
try {
// we have two inserts, start a transaction
$this->db->beginTransaction();
// hash the password, don't store it plain
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$query = $this->db->prepare("INSERT INTO users (username, password) VALUES (:username, :password)");
$query->bindParam(':username', $username);
$query->bindParam(':password', $hashedPassword);
// insert into users table
$sql = 'INSERT
INTO users (username, password)
VALUES (:username, :password)';
$query = $this->db->prepare($sql);
$query->bindValue(':username', $username);
$query->bindValue(':password', $hashedPassword);
// execute the first query
if (!$query->execute()) {
// rollback on error
$this->db->rollBack();
return false;
}
// insert the last user id into users_meta table
$sql2 = 'INSERT
INTO users_meta (user_id)
VALUES (:user_id)';
$query2 = $this->db->prepare($sql2);
$query2->bindValue(':user_id', $this->db->lastInsertId());
// execute the second query
if (!$query2->execute()) {
// rollback on error
$this->db->rollBack();
return false;
}
// if all is OK, commit the transaction
$this->db->commit();
return true;
} catch (Exception $e) {
// rollback on any error
$this->db->rollBack();
return $e->getMessage();
}
return $query->execute();
}
// login
@ -72,256 +33,6 @@ class User {
}
}
// get user ID from username
// FIXME not used now?
public function getUserId($username) {
$sql = 'SELECT id FROM users WHERE username = :username';
$query = $this->db->prepare($sql);
$query->bindParam(':username', $username);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// get user details
public function getUserDetails($user_id) {
$sql = 'SELECT
um.*,
u.username
FROM
users_meta um
LEFT JOIN users u
ON um.user_id = u.id
WHERE
u.id = :user_id';
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
]);
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// add user right
public function addUserRight($user_id, $right_id) {
$sql = 'INSERT INTO users_rights
(user_id, right_id)
VALUES
(:user_id, :right_id)';
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
':right_id' => $right_id,
]);
}
// remove user right
public function removeUserRight($user_id, $right_id) {
$sql = 'DELETE FROM users_rights
WHERE
user_id = :user_id
AND
right_id = :right_id';
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
':right_id' => $right_id,
]);
}
// get all rights
public function getAllRights() {
$sql = 'SELECT
id AS right_id,
name AS right_name
FROM rights
ORDER BY id ASC';
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// get user rights
public function getUserRights($user_id) {
$sql = 'SELECT
u.id AS user_id,
r.id AS right_id,
r.name AS right_name
FROM
users u
LEFT JOIN users_rights ur
ON u.id = ur.user_id
LEFT JOIN rights r
ON ur.right_id = r.id
WHERE
u.id = :user_id';
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
]);
$result = $query->fetchAll(PDO::FETCH_ASSOC);
// ensure specific entries are included in the result
$specialEntries = [];
// user 1 is always superuser
if ($user_id == 1) {
$specialEntries = [
[
'user_id' => 1,
'right_id' => 1,
'right_name' => 'superuser'
]
];
// user 2 is always demo
} elseif ($user_id == 2) {
$specialEntries = [
[
'user_id' => 2,
'right_id' => 100,
'right_name' => 'demo user'
]
];
}
// merge the special entries with the existing results
$result = array_merge($specialEntries, $result);
// remove duplicates if necessary
$result = array_unique($result, SORT_REGULAR);
// return the modified result
return $result;
}
// check if the user has a specific right
function hasRight($user_id, $right_name) {
$userRights = $this->getUserRights($user_id);
$userHasRight = false;
// superuser always has all the rights
if ($user_id === 1) {
$userHasRight = true;
}
foreach ($userRights as $right) {
if ($right['right_name'] === $right_name) {
$userHasRight = true;
break;
}
}
return $userHasRight;
}
// update an existing user
public function editUser($user_id, $updatedUser) {
try {
$sql = 'UPDATE users_meta SET
name = :name,
email = :email,
timezone = :timezone,
bio = :bio
WHERE user_id = :user_id';
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
':name' => $updatedUser['name'],
':email' => $updatedUser['email'],
':timezone' => $updatedUser['timezone'],
':bio' => $updatedUser['bio']
]);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// remove an avatar
public function removeAvatar($user_id, $old_avatar = '') {
try {
// remove from database
$sql = 'UPDATE users_meta SET
avatar = NULL
WHERE user_id = :user_id';
$query = $this->db->prepare($sql);
$query->execute([
':user_id' => $user_id,
]);
// delete the old avatar file
if ($old_avatar && file_exists($old_avatar)) {
unlink($old_avatar);
}
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
// change an avatar
public function changeAvatar($user_id, $avatar_file, $avatars_path) {
try {
// check if the file was uploaded
if (isset($avatar_file) && $avatar_file['error'] === UPLOAD_ERR_OK) {
$fileTmpPath = $avatar_file['tmp_name'];
$fileName = $avatar_file['name'];
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
// validate file extension
if (in_array($fileExtension, ['jpg', 'png', 'jpeg'])) {
$newFileName = md5(time() . $fileName) . '.' . $fileExtension;
$dest_path = $avatars_path . $newFileName;
// move the file to avatars folder
if (move_uploaded_file($fileTmpPath, $dest_path)) {
try {
// update user's avatar path in DB
$sql = 'UPDATE users_meta SET
avatar = :avatar
WHERE user_id = :user_id';
$query = $this->db->prepare($sql);
$query->execute([
':avatar' => $newFileName,
':user_id' => $user_id
]);
// all went OK
$_SESSION['notice'] .= 'Avatar updated successfully. ';
return true;
} catch (Exception $e) {
return $e->getMessage();
}
} else {
$_SESSION['error'] .= 'Error moving the uploaded file. ';
}
} else {
$_SESSION['error'] .= 'Invalid avatar file type. ';
}
} else {
$_SESSION['error'] .= 'Error uploading the avatar file. ';
}
} catch (Exception $e) {
return $e->getMessage();
}
}
}
?>

View File

@ -26,15 +26,32 @@ return [
// default is ../app/jilo-web.db
'sqlite_file' => '../app/jilo-web.db',
],
// avatars path
'avatars_path' => 'uploads/avatars/',
// default avatar
'default_avatar' => 'static/default_avatar.png',
// system info
'version' => '0.2.1',
'version' => '0.2',
// development has verbose error messages, production has not
'environment' => 'development',
// *************************************
// Maintained by the app, edit with care
// *************************************
'platforms' => [
'0' => [
'name' => 'lindeas',
'jitsi_url' => 'https://meet.lindeas.com',
'jilo_database' => '../../jilo/jilo-meet.lindeas.db',
],
'1' => [
'name' => 'meet.example.com',
'jitsi_url' => 'https://meet.example.com',
'jilo_database' => '../../jilo/jilo.db',
],
'2' => [
'name' => 'test3',
'jitsi_url' => 'https://test3.example.com',
'jilo_database' => '../../jilo/jilo2.db',
],
],
];
?>

View File

@ -0,0 +1,25 @@
<?php
// Function to format arrays with square brackets
function formatArray(array $array, $indentLevel = 2) {
$indent = str_repeat(' ', $indentLevel); // 4 spaces per indent level
$output = "[\n";
foreach ($array as $key => $value) {
$output .= $indent . "'" . $key . "'" . ' => ';
if (is_array($value)) {
$output .= formatArray($value, $indentLevel + 1);
} else {
$output .= var_export($value, true);
}
$output .= ",\n";
}
$output .= str_repeat(' ', $indentLevel - 1) . ']';
return $output;
}
?>

View File

@ -1,13 +1,14 @@
<?php
// connect to database
function connectDB($config, $database = '', $dbFile = '', $platformId = '') {
function connectDB($config, $database = '', $platform_id = '') {
// connecting ti a jilo sqlite database
if ($database === 'jilo') {
try {
$dbFile = $config['platforms'][$platform_id]['jilo_database'] ?? null;
if (!$dbFile || !file_exists($dbFile)) {
throw new Exception(getError("Invalid platform ID \"{$platformId}\", database file \"{$dbFile}\" not found."));
throw new Exception(getError("Invalid platform ID \"{$platform_id}\", database file \"{$dbFile}\"not found."));
}
$db = new Database([
'type' => 'sqlite',

View File

@ -1,127 +0,0 @@
<div style="position: relative; width: 800px; height: 400px;">
<div id="current-period-<?= $data['graph_name'] ?>" style="text-align: center; position: absolute; top: 0; left: 0; right: 0; z-index: 10; font-size: 14px; background-color: rgba(255, 255, 255, 0.7);"></div>
<canvas id="graph_<?= $data['graph_name'] ?>" style="margin-top: 20px; margin-bottom: 50px;"></canvas>
</div>
<script>
var ctx = document.getElementById('graph_<?= $data['graph_name'] ?>').getContext('2d');
var chartData0 = <?php echo json_encode($data['data0']); ?>;
var chartData1 = <?php echo json_encode($data['data1']); ?>;
var timeRangeName = '';
var labels = chartData0.map(function(item) {
return item.date;
});
var values0 = chartData0.map(function(item) {
return item.value;
});
var values1 = chartData1.map(function(item) {
return item.value;
});
var graph_<?= $data['graph_name'] ?> = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '<?= $data['graph_data0_label'] ?? '' ?>',
data: values0,
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
fill: false
},
{
label: '<?= $data['graph_data1_label'] ?? '' ?>',
data: values1,
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1,
fill: false
}
]
},
options: {
layout: {
padding: {
top: 30
}
},
scales: {
x: {
type: 'time',
time: {
unit: 'day'
}
},
y: {
beginAtZero: true
}
},
plugins: {
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
// enabled: true,
mode: 'x',
drag: {
enabled: true, // Enable drag to select range
borderColor: 'rgba(255, 99, 132, 0.3)',
borderWidth: 1
},
onZoom: function({ chart }) {
propagateZoom(chart); // Propagate the zoom to all graphs
setActive(document.getElementById('custom_range'));
}
}
},
legend: {
position: 'bottom',
labels: {
boxWidth: 20,
padding: 10
}
},
title: {
display: true,
text: '<?= $data['graph_title'] ?>',
font: {
size: 16,
weight: 'bold'
},
padding: {
bottom: 10
}
}
}
}
});
// Store the graphs in an array
graphs.push({
graph: graph_<?= $data['graph_name'] ?>,
label: document.getElementById('current-period-<?= $data['graph_name'] ?>')
});
// Update the time range label
function updatePeriodLabel(chart, labelElement) {
var startDate = chart.scales.x.min;
var endDate = chart.scales.x.max;
if (timeRangeName == 'today') {
labelElement.innerHTML = 'Currently displaying: ' + timeRangeName + ' (' + new Date(startDate).toLocaleDateString() + ')';
} else {
labelElement.innerHTML = 'Currently displaying: ' + timeRangeName + ' (' + new Date(startDate).toLocaleDateString() + ' - ' + new Date(endDate).toLocaleDateString() + ')';
}
}
// Attach the update function to the 'zoom' event
graph_<?= $data['graph_name'] ?>.options.plugins.zoom.onZoom = function({ chart }) {
updatePeriodLabel(chart, document.getElementById('current-period-<?= $data['graph_name'] ?>'));
};
// Update the label initially when the chart is rendered
updatePeriodLabel(graph_<?= $data['graph_name'] ?>, document.getElementById('current-period-<?= $data['graph_name'] ?>'));
</script>

View File

@ -1,22 +0,0 @@
<?php
function getUserIP() {
// get directly the user IP
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
// if user is behind some proxy
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
// get only the first IP if there are more
if (strpos($ip, ',') !== false) {
$ip = explode(',', $ip)[0];
}
return trim($ip);
}
?>

View File

@ -1,81 +0,0 @@
<div class="text-center">
<div class="pagination">
<?php
$param = '';
if (isset($_REQUEST['id'])) {
$param .= '&id=' . htmlspecialchars($_REQUEST['id']);
}
if (isset($_REQUEST['name'])) {
$param .= '&name=' . htmlspecialchars($_REQUEST['name']);
}
if (isset($_REQUEST['ip'])) {
$param .= '&ip=' . htmlspecialchars($_REQUEST['ip']);
}
if (isset($_REQUEST['event'])) {
$param .= '&event=' . htmlspecialchars($_REQUEST['event']);
}
if (isset($_REQUEST['from_time'])) {
$param .= '&from_time=' . htmlspecialchars($from_time);
}
if (isset($_REQUEST['until_time'])) {
$param .= '&until_time=' . htmlspecialchars($until_time);
}
$max_visible_pages = 10;
$step_pages = 10;
if ($browse_page > 1) {
echo '<span><a href="' . htmlspecialchars($url) . '&p=1">first</a></span>';
} else {
echo '<span>first</span>';
}
for ($i = 1; $i <= $page_count; $i++) {
// always show the first, last, step pages (10, 20, 30, etc.),
// and the pages close to the current one
if (
$i === 1 || // first page
$i === $page_count || // last page
$i === $browse_page || // current page
$i === $browse_page -1 ||
$i === $browse_page +1 ||
$i === $browse_page -2 ||
$i === $browse_page +2 ||
($i % $step_pages === 0 && $i > $max_visible_pages) // the step pages - 10, 20, etc.
) {
if ($i === $browse_page) {
// current page, no link
if ($browse_page > 1) {
echo '<span><a href="' . htmlspecialchars($app_root) . '?platform=' . htmlspecialchars($platform_id) . '&page=' . htmlspecialchars($page) . $param . '&p=' . (htmlspecialchars($browse_page) -1) . '"><<</a></span>';
} else {
echo '<span><<</span>';
}
echo '[' . htmlspecialchars($i) . ']';
if ($browse_page < $page_count) {
echo '<span><a href="' . htmlspecialchars($app_root) . '?platform=' . htmlspecialchars($platform_id) . '&page=' . htmlspecialchars($page) . $param . '&p=' . (htmlspecialchars($browse_page) +1) . '">>></a></span>';
} else {
echo '<span>>></span>';
}
} else {
// other pages
echo '<span><a href="' . htmlspecialchars($app_root) . '?platform=' . htmlspecialchars($platform_id) . '&page=' . htmlspecialchars($page) . $param . '&p=' . htmlspecialchars($i) . '">[' . htmlspecialchars($i) . ']</a></span>';
}
// show ellipses between distant pages
} elseif (
$i === $browse_page -3 ||
$i === $browse_page +3
) {
echo '<span>...</span>';
}
}
if ($browse_page < $page_count) {
echo '<span><a href="' . htmlspecialchars($app_root) . '?platform=' . htmlspecialchars($platform_id) . '&page=' . htmlspecialchars($page) . $param . '&p=' . (htmlspecialchars($page_count)) . '">last</a></span>';
} else {
echo '<span>last</span>';
}
?>
</div>
</div>

View File

@ -1,48 +0,0 @@
<?php
// get the UTC offset of a specified timezone
function getUTCOffset($timezone) {
$formattedOffset = '';
if (isset($timezone)) {
$datetime = new DateTime("now", new DateTimeZone($timezone));
$offsetInSeconds = $datetime->getOffset();
$hours = intdiv($offsetInSeconds, 3600);
$minutes = ($offsetInSeconds % 3600) / 60;
$formattedOffset = sprintf("UTC%+03d:%02d", $hours, $minutes); // Format UTC+01:00
}
return $formattedOffset;
}
// switch platforms
function switchPlatform($platform_id) {
// get the current URL and parse it
$scheme = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$current_url = "$scheme://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$url_components = parse_url($current_url);
// parse query parameters if they exist
parse_str($url_components['query'] ?? '', $query_params);
// check if the 'platform' parameter is set
if (isset($query_params['platform'])) {
// change the platform to the new platform_id
$query_params['platform'] = $platform_id;
$new_query_string = http_build_query($query_params);
// there is no 'platform', we redirect to front page of the new platform_id
} else {
$new_query_string = 'platform=' . $platform_id;
}
// rebuild the query and the URL
$new_url = $scheme . '://' . $url_components['host'] . $url_components['path'] . '?' . $new_query_string;
// return the new URL with the new platform_id
return $new_url;
}
?>

View File

@ -4,14 +4,40 @@
function renderConfig($configPart, $indent, $platform=false, $parent='') {
global $app_root;
global $config;
if ($parent === 'platforms') {
?>
<div class="col-md-8 text-start">
<a class="btn btn-secondary" style="padding: 0px;" href="<?= $app_root ?>?page=config&action=add">add</a>
</div>
<div class="border bg-light" style="padding-left: <?= $indent ?>px; padding-bottom: 20px; padding-top: 20px;">
<?php } else {
?>
<div style="padding-left: <?= $indent ?>px; padding-bottom: 20px;">
<?php foreach ($configPart as $config_item => $config_value) { ?>
<?php
}
foreach ($configPart as $config_item => $config_value) {
if ($parent === 'platforms') {
$indent = 0;
}
?>
<div class="row mb-1" style="padding-left: <?= $indent ?>px;">
<div class="col-md-4 text-end">
<?= htmlspecialchars($config_item) ?>:
</div>
<?php
if ($parent === 'platforms') { ?>
<div class="col-md-8 text-start">
<a class="btn btn-secondary" style="padding: 2px;" href="<?= $app_root ?>?platform=<?= htmlspecialchars($config_item) ?>&page=config&action=edit">edit</a>
<?php
// we don't delete the last platform
if (count($configPart) <= 1) { ?>
<span class="btn btn-light" style="padding: 2px;" href="#" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="can't delete the last platform">delete</span>
<?php } else { ?>
<a class="btn btn-danger" style="padding: 2px;" href="<?= $app_root ?>?platform=<?= htmlspecialchars($config_item) ?>&page=config&action=delete">delete</a>
<?php } ?>
</div>
<?php }
if (is_array($config_value)) {
// here we render recursively nested arrays
$indent = $indent + 50;
@ -29,11 +55,15 @@ function renderConfig($configPart, $indent, $platform=false, $parent='') {
?>
<div class="border col-md-8 text-start">
<?= htmlspecialchars($config_value ?? '')?>
</div>
<?php } ?>
</div>
<?php } ?>
<?= $platform ?>
</div>
<?php
}
?>
</div>
<?php
}
echo '</div>';
}
?>

View File

@ -1,51 +0,0 @@
<?php
// sanitize all input vars that may end up in URLs or forms
$platform_id = htmlspecialchars($_REQUEST['platform']);
if (isset($_REQUEST['page'])) {
$page = htmlspecialchars($_REQUEST['page']);
} else {
$page = 'dashboard';
}
if (isset($_REQUEST['item'])) {
$item = htmlspecialchars($_REQUEST['item']);
} else {
$item = '';
}
if (isset($_REQUEST['from_time'])) {
$from_time = htmlspecialchars($_REQUEST['from_time']);
}
if (isset($_REQUEST['until_time'])) {
$until_time = htmlspecialchars($_REQUEST['until_time']);
}
if (isset($_SESSION['notice'])) {
$notice = htmlspecialchars($_SESSION['notice']); // 'notice' for all non-critical messages
}
if (isset($_SESSION['error'])) {
$error = htmlspecialchars($_SESSION['error']); // 'error' for errors
}
// agents
if (isset($_POST['type'])) {
$type = htmlspecialchars($_POST['type']);
}
if (isset($_POST['url'])) {
$url = htmlspecialchars($_POST['url']);
}
if (isset($_POST['secret_key'])) {
$secret_key = htmlspecialchars($_POST['secret_key']);
}
if (isset($_POST['check_period'])) {
$check_period = htmlspecialchars($_POST['check_period']);
}
// platforms
if (isset($_POST['name'])) {
$name = htmlspecialchars($_POST['name']);
}
?>

View File

@ -1,14 +1,16 @@
<?php
$time_range_specified = false;
if (!isset($from_time) || (isset($from_time) && $from_time == '')) {
if (!isset($_REQUEST['from_time']) || (isset($_REQUEST['from_time']) && $_REQUEST['from_time'] == '')) {
$from_time = '0000-01-01';
} else {
$from_time = $_REQUEST['from_time'];
$time_range_specified = true;
}
if (!isset($until_time) || (isset($until_time) && $until_time == '')) {
if (!isset($_REQUEST['until_time']) || (isset($_REQUEST['until_time']) && $_REQUEST['until_time'] == '')) {
$until_time = '9999-12-31';
} else {
$until_time = $_REQUEST['until_time'];
$time_range_specified = true;
}

View File

@ -1,56 +0,0 @@
<?php
$action = $_REQUEST['action'] ?? '';
$agent = $_REQUEST['agent'] ?? '';
require '../app/classes/agent.php';
$agentObject = new Agent($dbWeb);
// if it's a POST request, it's saving to cache
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// read the JSON sent from javascript
$data = file_get_contents("php://input");
$result = json_decode($data, true);
// store the data in the session
if ($result) {
$_SESSION["agent{$agent}_cache"] = $result;
$_SESSION["agent{$agent}_cache_time"] = time(); // store the cache time
echo json_encode([
'status' => 'success',
'message' => "Cache for agent {$agent} is stored."
]);
} elseif ($result === null && !empty($agent)) {
unset($_SESSION["agent{$agent}_cache"]);
unset($_SESSION["agent{$agent}_cache_time"]);
echo json_encode([
'status' => 'success',
'message' => "Cache for agent {$agent} is cleared."
]);
} else {
echo json_encode([
'status' => 'error',
'message' => 'Invalid data'
]);
}
//// if it's a GET request, it's read/load from cache
//} elseif ($loadcache === true) {
//
// // check if cached data exists in session
// if (isset($_SESSION["agent{$agent}_cache"])) {
// // return the cached data in JSON format
// echo json_encode(['status' => 'success', 'data' => $_SESSION["agent{$agent}_cache"]]);
// } else {
// // if no cached data exists
// echo json_encode(['status' => 'error', 'message' => 'No cached data found']);
// }
// no form submitted, show the templates
} else {
$agentDetails = $agentObject->getAgentDetails($platform_id);
include '../app/templates/agent-list.php';
}
?>

View File

@ -1,19 +1,28 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/component.php';
// connect to database
$db = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
require '../app/helpers/database.php';
$db = connectDB($config, 'jilo', $platform_id);
// specify time range
include '../app/helpers/time_range.php';
// jitsi component events list
// we use $_REQUEST, so that both links and forms work
// if it's there, but empty, we make it same as the field name; otherwise assign the value
$jitsi_component = !empty($_REQUEST['name']) ? "'" . $_REQUEST['name'] . "'" : 'jitsi_component';
$component_id = !empty($_REQUEST['id']) ? "'" . $_REQUEST['id'] . "'" : 'component_id';
$event_type = !empty($_REQUEST['event']) ? "'" . $_REQUEST['event'] . "'" : 'event_type';
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
$jitsi_component = "'" . $_REQUEST['name'] . "'";
$component_id = 'component_id';
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$component_id = "'" . $_REQUEST['id'] . "'";
$jitsi_component = 'jitsi_component';
} else {
// we need the variables to use them later in sql for columnname = columnname
$jitsi_component = 'jitsi_component';
$component_id = 'component_id';
}
//
@ -22,23 +31,12 @@ $event_type = !empty($_REQUEST['event']) ? "'" . $_REQUEST['event'] . "'" : 'eve
// list of all component events (default)
$componentObject = new Component($db);
// pagination variables
$items_per_page = 15;
$browse_page = $_REQUEST['p'] ?? 1;
$browse_page = (int)$browse_page;
$offset = ($browse_page -1) * $items_per_page;
$component = new Component($db);
// prepare the result
$search = $componentObject->jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time, $offset, $items_per_page);
$search_all = $componentObject->jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time);
$search = $component->jitsiComponents($jitsi_component, $component_id, $from_time, $until_time);
if (!empty($search)) {
// we get total items and number of pages
$item_count = count($search_all);
$page_count = ceil($item_count / $items_per_page);
$components = array();
$components['records'] = array();
@ -61,8 +59,9 @@ if (!empty($search)) {
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'AllComponents';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = true;
$widget['pagination'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
@ -80,6 +79,6 @@ if (!empty($components['records'])) {
}
// display the widget
include '../app/templates/event-list-components.php';
include('../app/templates/widget.php');
?>

View File

@ -1,20 +1,18 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/conference.php';
// connect to database
$db = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
require '../app/helpers/database.php';
$db = connectDB($config, 'jilo', $platform_id);
// specify time range
include '../app/helpers/time_range.php';
// conference id/name are specified when searching specific conference(s)
// either id OR name, id has precedence
// we use $_REQUEST, so that both links and forms work
// if it's there, but empty, we make it same as the field name; otherwise assign the value
//$conferenceName = !empty($_REQUEST['name']) ? "'" . $_REQUEST['name'] . "'" : 'conference_name';
//$conferenceId = !empty($_REQUEST['id']) ? "'" . $_REQUEST['id'] . "'" : 'conference_id';
if (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$conferenceId = $_REQUEST['id'];
unset($_REQUEST['name']);
@ -33,33 +31,20 @@ if (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
//
$conferenceObject = new Conference($db);
// pagination variables
$items_per_page = 15;
$browse_page = $_REQUEST['p'] ?? 1;
$browse_page = (int)$browse_page;
$offset = ($browse_page -1) * $items_per_page;
$conference = new Conference($db);
// search and list specific conference ID
if (isset($conferenceId)) {
$search = $conferenceObject->conferenceById($conferenceId, $from_time, $until_time, $offset, $items_per_page);
$search_all = $conferenceObject->conferenceById($conferenceId, $from_time, $until_time);
$search = $conference->conferenceById($conferenceId, $from_time, $until_time);
// search and list specific conference name
} elseif (isset($conferenceName)) {
$search = $conferenceObject->conferenceByName($conferenceName, $from_time, $until_time, $offset, $items_per_page);
$search_all = $conferenceObject->conferenceByName($conferenceName, $from_time, $until_time);
$search = $conference->conferenceByName($conferenceName, $from_time, $until_time);
// list of all conferences (default)
} else {
$search = $conferenceObject->conferencesAllFormatted($from_time, $until_time, $offset, $items_per_page);
$search_all = $conferenceObject->conferencesAllFormatted($from_time, $until_time);
$search = $conference->conferencesAllFormatted($from_time, $until_time);
}
if (!empty($search)) {
// we get total items and number of pages
$item_count = count($search_all);
$page_count = ceil($item_count / $items_per_page);
$conferences = array();
$conferences['records'] = array();
@ -126,7 +111,6 @@ $widget['name'] = 'Conferences';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = true;
$widget['pagination'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
@ -144,6 +128,6 @@ if (!empty($conferences['records'])) {
}
// display the widget
include '../app/templates/event-list-conferences.php';
include('../app/templates/widget.php');
?>

View File

@ -1,105 +1,98 @@
<?php
$action = $_REQUEST['action'] ?? '';
$agent = $_REQUEST['agent'] ?? '';
require_once '../app/classes/config.php';
require '../app/helpers/errors.php';
require '../app/helpers/config.php';
require '../app/classes/config.php';
require '../app/classes/agent.php';
$configObject = new Config();
$agentObject = new Agent($dbWeb);
$configure = new Config();
// if a form is submitted, it's from the edit page
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// FIXME - if editing the flat file is no more needed, remove this
// // load the config file and initialize a copy
// $content = file_get_contents($config_file);
// $updatedContent = $content;
// new agent adding
if (isset($_POST['new']) && isset($_POST['item']) && $_POST['new'] === 'true' && $_POST['item'] === 'agent') {
$newAgent = [
'type_id' => $type,
'url' => $url,
'secret_key' => $secret_key,
'check_period' => $check_period,
];
$result = $agentObject->addAgent($platform_id, $newAgent);
if ($result === true) {
$_SESSION['notice'] = "New Jilo Agent added.";
} else {
$_SESSION['error'] = "Adding the agent failed. Error: $result";
}
// load the config file and initialize a copy
$content = file_get_contents($config_file);
$updatedContent = $content;
// new platform adding
} elseif (isset($_POST['new']) && $_POST['new'] === 'true') {
if (isset($_POST['new']) && $_POST['new'] === 'true') {
$newPlatform = [
'name' => $name,
'name' => $_POST['name'],
'jitsi_url' => $_POST['jitsi_url'],
'jilo_database' => $_POST['jilo_database'],
];
$platformObject->addPlatform($newPlatform);
// deleting an agent
} elseif (isset($_POST['delete']) && isset($_POST['agent']) && $_POST['delete'] === 'true') {
$result = $agentObject->deleteAgent($agent);
if ($result === true) {
$_SESSION['notice'] = "Agent id \"{$_REQUEST['agent']}\" deleted.";
} else {
$_SESSION['error'] = "Deleting the agent failed. Error: $result";
}
// Determine the next available index for the new platform
$nextIndex = count($config['platforms']);
// Add the new platform to the platforms array
$config['platforms'][$nextIndex] = $newPlatform;
// Rebuild the PHP array syntax for the platforms
$platformsArray = formatArray($config['platforms']);
// Replace the platforms section in the config file
$updatedContent = preg_replace(
'/\'platforms\'\s*=>\s*\[[\s\S]+?\],/s',
"'platforms' => {$platformsArray}",
$content
);
$updatedContent = preg_replace('/\s*\]\n/s', "\n", $updatedContent);
// deleting a platform
} elseif (isset($_POST['delete']) && $_POST['delete'] === 'true') {
$platform = $_POST['platform'];
$platformObject->deletePlatform($platform);
// an update to an existing agent
} elseif (isset($_POST['agent'])) {
$updatedAgent = [
'id' => $agent,
'agent_type_id' => $type,
'url' => $url,
'secret_key' => $secret_key,
'check_period' => $check_period,
];
$result = $agentObject->editAgent($platform_id, $updatedAgent);
if ($result === true) {
$_SESSION['notice'] = "Agent id \"{$_REQUEST['agent']}\" edited.";
} else {
$_SESSION['error'] = "Editing the agent failed. Error: $result";
}
$config['platforms'][$platform]['name'] = $_POST['name'];
$config['platforms'][$platform]['jitsi_url'] = $_POST['jitsi_url'];
$config['platforms'][$platform]['jilo_database'] = $_POST['jilo_database'];
$platformsArray = formatArray($config['platforms'][$platform], 3);
$updatedContent = preg_replace(
"/\s*'$platform'\s*=>\s*\[\s*'name'\s*=>\s*'[^']*',\s*'jitsi_url'\s*=>\s*'[^']*,\s*'jilo_database'\s*=>\s*'[^']*',\s*\],/s",
"",
$content
);
// an update to an existing platform
} else {
$platform = $_POST['platform'];
$updatedPlatform = [
'name' => $name,
'jitsi_url' => $_POST['jitsi_url'],
'jilo_database' => $_POST['jilo_database'],
];
$platformObject->editPlatform($platform, $updatedPlatform);
$config['platforms'][$platform]['name'] = $_POST['name'];
$config['platforms'][$platform]['jitsi_url'] = $_POST['jitsi_url'];
$config['platforms'][$platform]['jilo_database'] = $_POST['jilo_database'];
$platformsArray = formatArray($config['platforms'][$platform], 3);
$updatedContent = preg_replace(
"/\s*'$platform'\s*=>\s*\[\s*'name'\s*=>\s*'[^']*',\s*'jitsi_url'\s*=>\s*'[^']*',\s*'jilo_database'\s*=>\s*'[^']*',\s*\],/s",
"\n '{$platform}' => {$platformsArray},",
$content
);
}
// FIXME - if this is not needed for editing the flat file, remove it
// // check if file is writable
// if (!is_writable($config_file)) {
// $_SESSION['error'] = getError('Configuration file is not writable.');
// header("Location: $app_root?platform=$platform_id&page=config");
// exit();
// }
//
// // try to update the config file
// if (file_put_contents($config_file, $updatedContent) !== false) {
// // update successful
// $_SESSION['notice'] = "Configuration for {$_POST['name']} is updated.";
// } else {
// // unsuccessful
// $error = error_get_last();
// $_SESSION['error'] = getError('Error updating the config: ' . ($error['message'] ?? 'unknown error'));
// }
// check if file is writable
if (!is_writable($config_file)) {
$_SESSION['error'] = getError('Configuration file is not writable.');
header("Location: $app_root?platform=$platform_id&page=config");
exit();
}
// try to update the config file
if (file_put_contents($config_file, $updatedContent) !== false) {
// update successful
$_SESSION['notice'] = "Configuration for {$_POST['name']} is updated.";
} else {
// unsuccessful
$error = error_get_last();
$_SESSION['error'] = getError('Error updating the config: ' . ($error['message'] ?? 'unknown error'));
}
// FIXME the new file is not loaded on first page load
unset($config);
@ -114,51 +107,32 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
case 'configjs':
$mode = $_REQUEST['mode'] ?? '';
$raw = ($mode === 'raw');
$platformConfigjs = $configObject->getPlatformConfigjs($platformDetails[0]['jitsi_url'], $raw);
include '../app/templates/config-list-configjs.php';
$platformDetails = $configure->getPlatformDetails($config, $platform_id);
$platformConfigjs = $configure->getPlatformConfigjs($platformDetails, $raw);
include('../app/templates/config-list-configjs.php');
break;
case 'interfaceconfigjs':
$mode = $_REQUEST['mode'] ?? '';
$raw = ($mode === 'raw');
$platformInterfaceConfigjs = $configObject->getPlatformInterfaceConfigjs($platformDetails[0]['jitsi_url'], $raw);
include '../app/templates/config-list-interfaceconfigjs.php';
$platformDetails = $configure->getPlatformDetails($config, $platform_id);
$platformInterfaceConfigjs = $configure->getPlatformInterfaceConfigjs($platformDetails, $raw);
include('../app/templates/config-list-interfaceconfigjs.php');
break;
// if there is no $item, we work on the local config DB
// if there is no $item, we work on the local config file
default:
switch ($action) {
case 'add-agent':
$jilo_agent_types = $agentObject->getAgentTypes();
$jilo_agents_in_platform = $agentObject->getPlatformAgentTypes($platform_id);
$jilo_agent_types_in_platform = array_column($jilo_agents_in_platform, 'agent_type_id');
include '../app/templates/config-add-agent.php';
break;
case 'add':
include '../app/templates/config-add-platform.php';
include('../app/templates/config-add-platform.php');
break;
case 'edit':
if (isset($_GET['agent'])) {
$agentDetails = $agentObject->getAgentDetails($platform_id, $agent);
$jilo_agent_types = $agentObject->getAgentTypes();
include '../app/templates/config-edit-agent.php';
} else {
include '../app/templates/config-edit-platform.php';
}
include('../app/templates/config-edit-platform.php');
break;
case 'delete':
if (isset($_GET['agent'])) {
$agentDetails = $agentObject->getAgentDetails($platform_id, $agent);
include '../app/templates/config-delete-agent.php';
} else {
include '../app/templates/config-delete-platform.php';
}
include('../app/templates/config-delete-platform.php');
break;
default:
if ($userObject->hasRight($user_id, 'view config file')) {
include '../app/templates/config-list.php';
} else {
include '../app/templates/error-unauthorized.php';
}
include('../app/templates/config-list.php');
}
}
}

View File

@ -1,10 +1,12 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/conference.php';
require '../app/classes/participant.php';
// connect to database
$db = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
require '../app/helpers/database.php';
$db = connectDB($config, 'jilo', $platform_id);
//
@ -14,8 +16,8 @@ $db = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform
////
// monthly usage
$conferenceObject = new Conference($db);
$participantObject = new Participant($db);
$conference = new Conference($db);
$participant = new Participant($db);
// monthly conferences for the last year
$fromMonth = (new DateTime())->sub(new DateInterval('P1Y'));
@ -36,8 +38,8 @@ while ($fromMonth < $thisMonth) {
$from_time = $fromMonth->format('Y-m-d');
$until_time = $untilMonth->format('Y-m-d');
$searchConferenceNumber = $conferenceObject->conferenceNumber($from_time, $until_time);
$searchParticipantNumber = $participantObject->participantNumber($from_time, $until_time);
$searchConferenceNumber = $conference->conferenceNumber($from_time, $until_time);
$searchParticipantNumber = $participant->participantNumber($from_time, $until_time);
// pretty format for displaying the month in the widget
$month = $fromMonth->format('F Y');
@ -69,15 +71,14 @@ $widget['filter'] = false;
if (!empty($searchConferenceNumber) && !empty($searchParticipantNumber)) {
$widget['full'] = true;
}
$widget['pagination'] = false;
// display the widget
include '../app/templates/widget-monthly.php';
include('../app/templates/widget-monthly.php');
////
// conferences in last 2 days
$conference = new Conference($db);
// time range limit
$from_time = date('Y-m-d', time() - 60 * 60 * 24 * 2);
@ -85,7 +86,7 @@ $until_time = date('Y-m-d', time());
$time_range_specified = true;
// prepare the result
$search = $conferenceObject->conferencesAllFormatted($from_time, $until_time);
$search = $conference->conferencesAllFormatted($from_time, $until_time);
if (!empty($search)) {
$conferences = array();
@ -129,14 +130,14 @@ if (!empty($conferences['records'])) {
$widget['table_headers'] = array_keys($conferences['records'][0]);
$widget['table_records'] = $conferences['records'];
}
$widget['pagination'] = false;
// display the widget
include '../app/templates/widget.php';
include('../app/templates/widget.php');
////
// last 10 conferences
$conference = new Conference($db);
// all time
$from_time = '0000-01-01';
@ -146,7 +147,7 @@ $time_range_specified = false;
$conference_number = 10;
// prepare the result
$search = $conferenceObject->conferencesAllFormatted($from_time, $until_time);
$search = $conference->conferencesAllFormatted($from_time, $until_time);
if (!empty($search)) {
$conferences = array();
@ -190,7 +191,6 @@ $widget['title'] = 'The last ' . $conference_number . ' conferences';
$widget['collapsible'] = true;
$widget['collapsed'] = false;
$widget['filter'] = false;
$widget['pagination'] = false;
if (!empty($conferences['records'])) {
$widget['full'] = true;
@ -199,6 +199,6 @@ if (!empty($conferences['records'])) {
}
// display the widget
include '../app/templates/widget.php';
include('../app/templates/widget.php');
?>

View File

@ -1,49 +0,0 @@
<?php
// FIXME example data
$one = date('Y-m-d',strtotime("-5 days"));
$two = date('Y-m-d',strtotime("-4 days"));
$three = date('Y-m-d',strtotime("-2 days"));
$four = date('Y-m-d',strtotime("-1 days"));
$graph[0]['data0'] = [
['date' => $one, 'value' => 10],
['date' => $two, 'value' => 20],
['date' => $three, 'value' => 15],
['date' => $four, 'value' => 25],
];
$graph[0]['data1'] = [
['date' => $one, 'value' => 12],
['date' => $two, 'value' => 23],
['date' => $three, 'value' => 11],
['date' => $four, 'value' => 27],
];
$graph[0]['graph_name'] = 'conferences';
$graph[0]['graph_title'] = 'Conferences in "' . htmlspecialchars($platformDetails[0]['name']) . '" over time';
$graph[0]['graph_data0_label'] = 'Conferences from Jitsi logs (Jilo)';
$graph[0]['graph_data1_label'] = 'Conferences from Jitsi API (Jilo Agents)';
$graph[1]['data0'] = [
['date' => $one, 'value' => 20],
['date' => $two, 'value' => 30],
['date' => $three, 'value' => 15],
['date' => $four, 'value' => 55],
];
$graph[1]['data1'] = [
['date' => $one, 'value' => 22],
['date' => $two, 'value' => 33],
['date' => $three, 'value' => 11],
['date' => $four, 'value' => 57],
];
$graph[1]['graph_name'] = 'participants';
$graph[1]['graph_title'] = 'Participants in "' . htmlspecialchars($platformDetails[0]['name']) . '" over time';
$graph[1]['graph_data0_label'] = 'Participants from Jitsi logs (Jilo)';
$graph[1]['graph_data1_label'] = 'Participants from Jitsi API (Jilo Agents)';
include '../app/templates/graphs-combined.php';
?>

View File

@ -1,5 +0,0 @@
<?php
include '../app/templates/help-main.php';
?>

View File

@ -1,29 +0,0 @@
<?php
require '../app/classes/agent.php';
$agentObject = new Agent($dbWeb);
$latestJvbConferences = $agentObject->getLatestData($platform_id, 'jvb', 'conferences');
$latestJvbParticipants = $agentObject->getLatestData($platform_id, 'jvb', 'participants');
$latestJicofoConferences = $agentObject->getLatestData($platform_id, 'jicofo', 'conferences');
$latestJicofoParticipants = $agentObject->getLatestData($platform_id, 'jicofo', 'participants');
$widget['records'] = array();
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'LatestData';
$widget['title'] = 'Latest data from Jilo Agents';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = false;
if (!empty($latestJvbConferences) && !empty($latestJvbParticipants) && !empty($latestJicofoConferences) && !empty($latestJicofoParticipants)) {
$widget['full'] = true;
}
$widget['pagination'] = true;
include '../app/templates/latest-data.php';
?>

View File

@ -1,19 +1,25 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/user.php';
// clear the global error var before login
unset($error);
try {
// connect to database
$dbWeb = connectDB($config);
require '../app/helpers/database.php';
$db = connectDB($config);
$user = new User($db);
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
$username = $_POST['username'];
$password = $_POST['password'];
// login successful
if ( $userObject->login($username, $password) ) {
if ( $user->login($username, $password) ) {
// if remember_me is checked, max out the session
if (isset($_POST['remember_me'])) {
// 30*24*60*60 = 30 days
@ -40,17 +46,13 @@ try {
// redirect to index
$_SESSION['notice'] = "Login successful";
$user_id = $userObject->getUserId($username)[0]['id'];
$logObject->insertLog($user_id, "Login: User \"$username\" logged in. IP: $user_IP", 'user');
header('Location: ' . htmlspecialchars($app_root));
header('Location: index.php');
exit();
// login failed
} else {
$_SESSION['error'] = "Login failed.";
$user_id = $userObject->getUserId($username)[0]['id'];
$logObject->insertLog($user_id, "Login: Failed login attempt for user \"$username\". IP: $user_IP", 'user');
header('Location: ' . htmlspecialchars($app_root));
header('Location: index.php');
exit();
}
}

View File

@ -1,71 +0,0 @@
<?php
//
// logs listings
//
// specify time range
include '../app/helpers/time_range.php';
// pagination variables
$items_per_page = 15;
$browse_page = $_REQUEST['p'] ?? 1;
$browse_page = (int)$browse_page;
$offset = ($browse_page -1) * $items_per_page;
// user or system
$scope = 'user';
// prepare the result
$search = $logObject->readLog($user_id, $scope, $offset, $items_per_page);
$search_all = $logObject->readLog($user_id, $scope);
if (!empty($search)) {
// we get total items and number of pages
$item_count = count($search_all);
$page_count = ceil($item_count / $items_per_page);
$logs = array();
$logs['records'] = array();
foreach ($search as $item) {
// when we show only user's logs, omit user_id column
if ($scope === 'user') {
$log_record = array(
// assign title to the field in the array record
'time' => $item['time'],
'log message' => $item['message']
);
} else {
$log_record = array(
// assign title to the field in the array record
'userID' => $item['user_id'],
'time' => $item['time'],
'log message' => $item['message']
);
}
// populate the result array
array_push($logs['records'], $log_record);
}
}
// prepare the widget
$widget['full'] = false;
$widget['collapsible'] = false;
$widget['name'] = 'Logs';
$username = $userObject->getUserDetails($user_id)[0]['username'];
$widget['title'] = "Log events for user \"$username\"";
$widget['filter'] = true;
if (!empty($logs['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($logs['records'][0]);
$widget['table_records'] = $logs['records'];
}
$widget['pagination'] = true;
// display the widget
include '../app/templates/logs-list.php';
?>

View File

@ -1,9 +1,11 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/participant.php';
// connect to database
$db = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
require '../app/helpers/database.php';
$db = connectDB($config, 'jilo', $platform_id);
// specify time range
include '../app/helpers/time_range.php';
@ -32,38 +34,24 @@ if (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
// Participant listings
//
$participantObject = new Participant($db);
// pagination variables
$items_per_page = 15;
$browse_page = $_REQUEST['p'] ?? 1;
$browse_page = (int)$browse_page;
$offset = ($browse_page -1) * $items_per_page;
$participant = new Participant($db);
// search and list specific participant ID
if (isset($participantId)) {
$search = $participantObject->conferenceByParticipantId($participantId, $from_time, $until_time, $offset, $items_per_page);
$search_all = $participantObject->conferenceByParticipantId($participantId, $from_time, $until_time);
$search = $participant->conferenceByParticipantId($participantId, $from_time, $until_time, $participantId, $from_time, $until_time);
// search and list specific participant name (stats_id)
} elseif (isset($participantName)) {
$search = $participantObject->conferenceByParticipantName($participantName, $from_time, $until_time, $offset, $items_per_page);
$search_all = $participantObject->conferenceByParticipantName($participantName, $from_time, $until_time);
$search = $participant->conferenceByParticipantName($participantName, $from_time, $until_time);
// search and list specific participant IP
} elseif (isset($participantIp)) {
$search = $participantObject->conferenceByParticipantIP($participantIp, $from_time, $until_time, $offset, $items_per_page);
$search_all = $participantObject->conferenceByParticipantIP($participantIp, $from_time, $until_time);
$search = $participant->conferenceByParticipantIP($participantIp, $from_time, $until_time);
// list of all participants (default)
} else {
// prepare the result
$search = $participantObject->participantsAll($from_time, $until_time, $offset, $items_per_page);
$search_all = $participantObject->participantsAll($from_time, $until_time);
$search = $participant->participantsAll($from_time, $until_time);
}
if (!empty($search)) {
// we get total items and number of pages
$item_count = count($search_all);
$page_count = ceil($item_count / $items_per_page);
$participants = array();
$participants['records'] = array();
@ -130,7 +118,6 @@ $widget['name'] = 'Participants';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = true;
$widget['pagination'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
@ -150,6 +137,6 @@ if (!empty($participants['records'])) {
}
// display the widget
include '../app/templates/widget.php';
include('../app/templates/widget.php');
?>

View File

@ -1,84 +1,5 @@
<?php
$action = $_REQUEST['action'] ?? '';
// if a form is submitted, it's from the edit page
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$item = $_REQUEST['item'] ?? '';
// avatar removal
if ($item === 'avatar' && $action === 'remove') {
$result = $userObject->removeAvatar($user_id, $config['avatars_path'].$userDetails[0]['avatar']);
if ($result === true) {
$_SESSION['notice'] .= "Avatar for user \"{$userDetails[0]['username']}\" is removed. ";
} else {
$_SESSION['error'] .= "Removing the avatar failed. Error: $result ";
}
header("Location: $app_root?page=profile");
exit();
}
// update the profile
$updatedUser = [
'name' => $_POST['name'] ?? '',
'email' => $_POST['email'] ?? '',
'timezone' => $_POST['timezone'] ?? '',
'bio' => $_POST['bio'] ?? '',
];
$result = $userObject->editUser($user_id, $updatedUser);
if ($result === true) {
$_SESSION['notice'] .= "User details for \"{$updatedUser['name']}\" are edited. ";
} else {
$_SESSION['error'] .= "Editing the user details failed. Error: $result ";
}
// update the rights
$newRights = $_POST['rights'] ?? array();
// extract the new right_ids
$userRightsIds = array_column($userRights, 'right_id');
// what rights we need to add
$rightsToAdd = array_diff($newRights, $userRightsIds);
if (!empty($rightsToAdd)) {
foreach ($rightsToAdd as $rightId) {
$userObject->addUserRight($user_id, $rightId);
}
}
// what rights we need to remove
$rightsToRemove = array_diff($userRightsIds, $newRights);
if (!empty($rightsToRemove)) {
foreach ($rightsToRemove as $rightId) {
$userObject->removeUserRight($user_id, $rightId);
}
}
// update the avatar
if (!empty($_FILES['avatar_file']['tmp_name'])) {
$result = $userObject->changeAvatar($user_id, $_FILES['avatar_file'], $config['avatars_path']);
}
header("Location: $app_root?page=profile");
exit();
// no form submitted, show the templates
} else {
$avatar = !empty($userDetails[0]['avatar']) ? $config['avatars_path'] . $userDetails[0]['avatar'] : $config['default_avatar'];
$default_avatar = empty($userDetails[0]['avatar']) ? true : false;
switch ($action) {
case 'edit':
$allRights = $userObject->getAllRights();
$allTimezones = timezone_identifiers_list();
// if timezone is already set, we pass a flag for JS to not autodetect browser timezone
$isTimezoneSet = !empty($userDetails[0]['timezone']);
include '../app/templates/profile-edit.php';
break;
default:
include '../app/templates/profile.php';
}
}
include('../app/templates/widget-profile.php');
?>

View File

@ -3,37 +3,36 @@
// registration is allowed, go on
if ($config['registration_enabled'] === true) {
// require '../app/classes/user.php';
require_once '../app/classes/database.php';
require '../app/classes/user.php';
unset($error);
try {
// connect to database
$dbWeb = connectDB($config);
require '../app/helpers/database.php';
$db = connectDB($config);
// $userObject = new User($dbWeb);
$user = new User($db);
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
$username = $_POST['username'];
$password = $_POST['password'];
// registering
$result = $userObject->register($username, $password);
// redirect to login
if ($result === true) {
if ( $user->register($username, $password) ) {
$_SESSION['notice'] = "Registration successful.<br />You can log in now.";
header('Location: ' . htmlspecialchars($app_root));
header('Location: index.php');
exit();
// registration fail, redirect to login
} else {
$_SESSION['error'] = "Registration failed. $result";
header('Location: ' . htmlspecialchars($app_root));
$_SESSION['error'] = "Registration failed.";
header('Location: index.php');
exit();
}
}
} catch (Exception $e) {
$error = $e->getMessage();
$error = getError('There was an unexpected error. Please try again.', $e->getMessage());
}
include '../app/templates/block-message.php';

View File

@ -1,52 +0,0 @@
<?php
// Jilo components status checks
//
require '../app/classes/agent.php';
$agentObject = new Agent($dbWeb);
include '../app/templates/status-server.php';
foreach ($platformsAll as $platform) {
include '../app/templates/status-platform.php';
$agentDetails = $agentObject->getAgentDetails($platform['id']);
foreach ($agentDetails as $agent) {
$agent_url = parse_url($agent['url']);
$agent_protocol = isset($agent_url['scheme']) ? $agent_url['scheme']: '';
$agent_host = isset($agent_url['host']) ? $agent_url['host']: '';
$agent_port = isset($agent_url['port']) ? $agent_url['port']: '';
// we get agent data to check availability
$agent_response = $agentObject->fetchAgent($agent['id'], true);
$agent_data = json_decode($agent_response);
if (json_last_error() === JSON_ERROR_NONE) {
$agent_availability = '<span class="text-warning">unknown</span>';
foreach ($agent_data as $key => $value) {
if ($key === 'error') {
$agent_availability = '<span class="text-danger">' . htmlspecialchars($value) . '</span>';
break;
}
if (preg_match('/_state$/', $key)) {
if ($value === 'error') {
$agent_availability = '<span class="text-danger">not running</span>';
break;
}
if ($value === 'running') {
$agent_availability = '<span class="text-success">running</span>';
break;
}
}
}
} else {
$agent_availability = 'json error';
}
include '../app/templates/status-agent.php';
}
}
?>

View File

@ -1,39 +0,0 @@
<!-- jilo agents -->
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Jilo Agents on platform <?= htmlspecialchars($platform_id) ?> (<?= htmlspecialchars($platformDetails[0]['name']) ?>)</p>
<div class="card-body">
<?php foreach ($agentDetails as $agent) { ?>
<p class="card-text text-left" style="text-align: left;">
agent id: <strong><?= htmlspecialchars($agent['id']) ?></strong>
agent type: <?= htmlspecialchars($agent['agent_type_id']) ?> (<strong><?= htmlspecialchars($agent['agent_description']) ?></strong>)
<br />
endpoint: <strong><?= htmlspecialchars($agent['url']) ?><?= htmlspecialchars($agent['agent_endpoint']) ?></strong>
<br />
<?php
$payload = [
'iss' => 'Jilo Web',
'aud' => $config['domain'],
'iat' => time(),
'exp' => time() + 3600,
'agent_id' => $agent['id']
];
$jwt = $agentObject->generateAgentToken($payload, $agent['secret_key']);
// print_r($_SESSION);
?>
<?php if (isset($_SESSION["agent{$agent['id']}_cache"])) { ?>
<button id="agent<?= htmlspecialchars($agent['id']) ?>-status" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get the agent status" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '/status', '<?= htmlspecialchars($jwt) ?>', true)">get status</button>
<button id="agent<?= htmlspecialchars($agent['id']) ?>-fetch" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get data from the agent" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '<?= htmlspecialchars($agent['agent_endpoint']) ?>', '<?= htmlspecialchars($jwt) ?>', true)">fetch data</button>
<button id="agent<?= htmlspecialchars($agent['id']) ?>-cache" class="btn btn-secondary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="load cache" onclick="loadCache('<?= htmlspecialchars($agent['id']) ?>')">load cache</button>
<button id="agent<?= htmlspecialchars($agent['id']) ?>-clear" class="btn btn-danger" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="clear cache" onclick="clearCache('<?= htmlspecialchars($agent['id']) ?>')">clear cache</button>
<span id="cacheInfo<?= htmlspecialchars($agent['id']) ?>" style="margin: 5px 0;"></span>
<?php } else { ?>
<button id="agent<?= htmlspecialchars($agent['id']) ?>-status" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get the agent status" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '/status', '<?= htmlspecialchars($jwt) ?>', true)">get status</button>
<button id="agent<?= htmlspecialchars($agent['id']) ?>-fetch" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get data from the agent" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '<?= htmlspecialchars($agent['agent_endpoint']) ?>', '<?= htmlspecialchars($jwt) ?>')">fetch data</button>
<button style="display: none" disabled id="agent<?= htmlspecialchars($agent['id']) ?>-cache" class="btn btn-secondary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="load cache" onclick="loadCache('<?= htmlspecialchars($agent['id']) ?>')">load cache</button>
<button style="display: none" disabled id="agent<?= htmlspecialchars($agent['id']) ?>-clear" class="btn btn-danger" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="clear cache" onclick="clearCache('<?= htmlspecialchars($agent['id']) ?>')">clear cache</button>
<span style="display: none" id="cacheInfo<?= htmlspecialchars($agent['id']) ?>" style="margin: 5px 0;"></span>
<?php } ?>
</p>
<pre class="results" id="result<?= htmlspecialchars($agent['id']) ?>">click a button to display data from the agent.</pre>
<?php } ?>

View File

@ -1,7 +1,7 @@
<?php if (isset($error)) { ?>
<div class="error"><?= $error ?></div>
<div class="error"><?php echo $error; ?></div>
<?php } ?>
<?php if (isset($notice)) { ?>
<div class="notice"><?= $notice ?></div>
<div class="notice"><?php echo $notice; ?></div>
<?php } ?>

View File

@ -1,15 +1,15 @@
<!-- Results filter -->
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
<form method="POST" id="filter_form" class="filter-results" action="?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($page) ?>">
<form method="POST" id="filter_form" action="?platform=<?= $platform_id?>&page=<?= $page ?>">
<label for="from_time">from</label>
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . $_REQUEST['from_time'] . "\"" ?> />
<label for="until_time">until</label>
<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="ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
<input type="text" name="name" placeholder="name"<?php if (isset($_REQUEST['name'])) echo " value=\"" . htmlspecialchars($_REQUEST['name']) . "\"" ?> />
<input type="date" id="until_time" name="until_time"<?php if (isset($_REQUEST['until_time'])) echo " value=\"" . $_REQUEST['until_time'] . "\"" ?> />
<input type="text" name="id" placeholder="ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . $_REQUEST['id'] . "\"" ?> />
<input type="text" name="name" placeholder="name"<?php if (isset($_REQUEST['name'])) echo " value=\"" . $_REQUEST['name'] . "\"" ?> />
<?php if ($page == 'participants') { ?>
<input type="text" name="ip" placeholder="ip address"<?php if (isset($_REQUEST['ip'])) echo " value=\"" . htmlspecialchars($_REQUEST['ip']) . "\"" ?> maxlength="15" size="15" />
<input type="text" name="ip" placeholder="ip address"<?php if (isset($_REQUEST['ip'])) echo " value=\"" . $_REQUEST['ip'] . "\"" ?> maxlength="15" size="15" />
<?php } ?>
<input type="button" onclick="clearFilter()" value="clear" />
<input type="submit" value="search" />

View File

@ -1,72 +0,0 @@
<!-- widget "agents" -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Add new Jilo Agent to Jitsi platform "<strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong>"</p>
<div class="card-body">
<!--p class="card-text">add new agent:</p-->
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="type" class="form-label">type</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<select class="form-control" type="text" name="type" id="agent_type_id" required>
<option></option>
<?php foreach ($jilo_agent_types as $agent_type) { ?>
<option value="<?= htmlspecialchars($agent_type['id']) ?>"<?php
if (in_array($agent_type['id'], $jilo_agent_types_in_platform)) {
echo 'disabled="disabled"';
} ?>>
<?= htmlspecialchars($agent_type['description']) ?>
</option>
<?php } ?>
</select>
<p class="text-start"><small>type of agent (meet, jvb, jibri, etc.)<br />if a type has already been aded, it's disabled here</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="url" class="form-label">URL</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="url" value="https://" required />
<p class="text-start"><small>URL of the Jilo Agent API (https://example.com:8081)</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="secret_key" class="form-label">secret key</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="secret_key" value="" required />
<p class="text-start"><small>secret key for generating the access JWT token</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="check_period" class="form-label">check period</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="check_period" value="0" required />
<p class="text-start"><small>period in minutes for the automatic agent check (0 disables it)</small></p>
</div>
</div>
<input type="hidden" name="new" value="true" />
<input type="hidden" name="item" value="agent" />
<br />
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>
</div>
<!-- /widget "agents" -->

View File

@ -4,7 +4,7 @@
<p class="h4 card-header">Add new Jitsi platform</p>
<div class="card-body">
<!--p class="card-text">add new platform:</p-->
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<form method="POST" action="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<div class="row mb-3">
<div class="col-md-4 text-end">
@ -12,7 +12,7 @@
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="name" value="" required autofocus />
<input class="form-control" type="text" name="name" value="" required />
<p class="text-start"><small>descriptive name for the platform</small></p>
</div>
</div>
@ -42,7 +42,7 @@
<input type="hidden" name="new" value="true" />
<br />
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config" />Cancel</a>
<a class="btn btn-secondary" href="<?= $app_root ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>

View File

@ -1,32 +0,0 @@
<!-- widget "agents" -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Jilo Agent configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
<div class="card-body">
<p class="card-text">delete an agent:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<?php
foreach ($agentDetails[0] as $key => $value) {
// if ($key === 'id') continue;
?>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="<?= htmlspecialchars($key) ?>" class="form-label"><?= htmlspecialchars($key) ?>:</label>
</div>
<div class="col-md-8">
<div class="text-start"><?= htmlspecialchars($value ?? '') ?></div>
<input type="hidden" name="<?= htmlspecialchars($key) ?>" value="<?= htmlspecialchars($value ?? '') ?>" />
</div>
</div>
<?php } ?>
<br />
<input type="hidden" name="agent" value="<?= htmlspecialchars($agentDetails[0]['id']) ?>" />
<input type="hidden" name="delete" value="true" />
<p class="h5 text-danger">Are you sure you want to delete this agent?</p>
<br />
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>agent<?= htmlspecialchars($agentDetails[0]['id']) ?>" />Cancel</a>
<input type="submit" class="btn btn-danger" value="Delete" />
</form>
</div>
</div>
<!-- /widget "agents" -->

View File

@ -1,21 +1,18 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Jilo web configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
<p class="h4 card-header">Jilo web configuration for Jitsi platform "<?= htmlspecialchars($platform_id) ?>"</p>
<div class="card-body">
<p class="card-text">delete a platform:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<?php
foreach ($platformDetails[0] as $key => $value) {
if ($key === 'id') continue;
?>
<form method="POST" action="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<?php foreach ($config['platforms'][$platform_id] as $config_item => $config_value) { ?>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="<?= htmlspecialchars($key) ?>" class="form-label"><?= htmlspecialchars($key) ?>:</label>
<label for="<?= htmlspecialchars($config_item) ?>" class="form-label"><?= htmlspecialchars($config_item) ?>:</label>
</div>
<div class="col-md-8">
<div class="text-start"><?= htmlspecialchars($value) ?? '' ?></div>
<input type="hidden" name="<?= htmlspecialchars($key) ?>" value="<?= htmlspecialchars($value ?? '')?>" />
<div class="text-start"><?= htmlspecialchars($config_value ?? '')?></div>
<input type="hidden" name="<?= htmlspecialchars($config_item) ?>" value="<?= htmlspecialchars($config_value ?? '')?>" />
</div>
</div>
<?php } ?>
@ -24,7 +21,7 @@ foreach ($platformDetails[0] as $key => $value) {
<input type="hidden" name="delete" value="true" />
<p class="h5 text-danger">Are you sure you want to delete this platform?</p>
<br />
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>" />Cancel</a>
<a class="btn btn-secondary" href="<?= $app_root ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-danger" value="Delete" />
</form>
</div>

View File

@ -1,68 +0,0 @@
<!-- agents -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Jilo Agent configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
<div class="card-body">
<p class="card-text">edit the agent details:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="type_id" class="form-label">type</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<select class="form-control" type="text" name="type" id="agent_type_id" required>
<option></option>
<?php foreach ($jilo_agent_types as $agent_type) { ?>
<option value="<?= htmlspecialchars($agent_type['id']) ?>" <?php if ($agentDetails[0]['agent_type_id'] === $agent_type['id']) echo 'selected'; ?>>
<?= htmlspecialchars($agent_type['description']) ?>
</option>
<?php } ?>
</select>
<p class="text-start"><small>type of agent (meet, jvb, jibri, all)</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="url" class="form-label">URL</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="url" value="<?= htmlspecialchars($agentDetails[0]['url']) ?>" required />
<p class="text-start"><small>URL of the Jilo Agent API (https://example.com:8081)</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="secret_key" class="form-label">secret key</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="secret_key" value="<?= htmlspecialchars($agentDetails[0]['secret_key']) ?>" required />
<p class="text-start"><small>secret key for generating the access JWT token</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="check_period" class="form-label">check period</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="check_period" value="<?= htmlspecialchars($agentDetails[0]['check_period']) ?>" required />
<p class="text-start"><small>period in minutes for the automatic agent check (0 disables it)</small></p>
</div>
</div>
<br />
<input type="hidden" name="agent" value="<?= htmlspecialchars($agentDetails[0]['id']) ?>" />
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>agent<?= htmlspecialchars($agentDetails[0]['id']) ?>" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>
</div>
<!-- /agents -->

View File

@ -1,26 +1,23 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Jilo web configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
<p class="h4 card-header">Jilo web configuration for Jitsi platform "<?= htmlspecialchars($platform_id) ?>"</p>
<div class="card-body">
<p class="card-text">edit the platform details:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<?php
foreach ($platformDetails[0] as $key => $value) {
if ($key === 'id') continue;
?>
<form method="POST" action="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<?php foreach ($config['platforms'][$platform_id] as $config_item => $config_value) { ?>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="<?= htmlspecialchars($config_item) ?>" class="form-label"><?= htmlspecialchars($key) ?></label>
<label for="<?= htmlspecialchars($config_item) ?>" class="form-label"><?= htmlspecialchars($config_item) ?></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') { ?>
<input class="form-control" type="text" name="<?= htmlspecialchars($config_item) ?>" value="<?= htmlspecialchars($config_value ?? '')?>" required />
<?php if ($config_item === 'name') { ?>
<p class="text-start"><small>descriptive name for the platform</small></p>
<?php } elseif ($key === 'jitsi_url') { ?>
<?php } elseif ($config_item === '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') { ?>
<?php } elseif ($config_item === 'jilo_database') { ?>
<p class="text-start"><small>path to the database file (relative to the app root)</small></p>
<?php } ?>
</div>
@ -28,7 +25,7 @@ foreach ($platformDetails[0] as $key => $value) {
<?php } ?>
<br />
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>" />
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>" />Cancel</a>
<a class="btn btn-secondary" href="<?= $app_root ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>

View File

@ -1,18 +1,18 @@
<!-- widget "config" -->
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></p>
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails['name']) ?></strong></p>
<div class="card-body">
<p class="card-text">
<span class="m-3">URL: <?= htmlspecialchars($platformDetails[0]['jitsi_url']) ?></span>
<span class="m-3">URL: <?= htmlspecialchars($platformDetails['jitsi_url']) ?></span>
<span class="m-3">FILE: config.js</span>
<?php if ($mode === 'raw') { ?>
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs">view only active lines</a></span>
<span class="m-3"><a class="btn btn-light" href="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs">view only active lines</a></span>
<?php } else { ?>
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs&mode=raw">view raw file contents</a></span>
<span class="m-3"><a class="btn btn-light" href="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs&mode=raw">view raw file contents</a></span>
<?php } ?>
</p>
<pre class="results">
<pre style="text-align: left;">
<?php
echo htmlspecialchars($platformConfigjs);
?>

View File

@ -1,18 +1,18 @@
<!-- widget "config" -->
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></p>
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails['name']) ?></strong></p>
<div class="card-body">
<p class="card-text">
<span class="m-3">URL: <?= htmlspecialchars($platformDetails[0]['jitsi_url']) ?></span>
<span class="m-3">URL: <?= htmlspecialchars($platformDetails['jitsi_url']) ?></span>
<span class="m-3">FILE: interface_config.js</span>
<?php if ($mode === 'raw') { ?>
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs">view only active lines</a></span>
<span class="m-3"><a class="btn btn-light" href="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs">view only active lines</a></span>
<?php } else { ?>
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs&mode=raw">view raw file contents</a></span>
<span class="m-3"><a class="btn btn-light" href="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs&mode=raw">view raw file contents</a></span>
<?php } ?>
</p>
<pre class="results">
<pre style="text-align: left;">
<?php
echo htmlspecialchars($platformInterfaceConfigjs);
?>

View File

@ -3,125 +3,12 @@
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Jilo web configuration</p>
<div class="card-body">
<p class="card-text">main variables</p>
<p class="card-text">platform variables</p>
<?php
include '../app/helpers/render.php';
renderConfig($config, '0');
echo "\n";
?>
<hr />
<p class="card-text">platforms configuration &nbsp;<a class="btn btn-secondary" style="padding: 0px;" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&action=add">add new</a></p>
<?php foreach ($platformsAll as $platform_array) {
$agents = $agentObject->getAgentDetails($platform_array['id']);
?>
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>"></a>
<div class="row mb-3" style="padding-left: 0px;">
<div class="border bg-light" style="padding-left: 50px; padding-bottom: 0px; padding-top: 0px;">
<a style="text-decoration: none;" data-toggle="collapse" href="#collapsePlatform<?= htmlspecialchars($platform_array['id']) ?>" role="button" aria-expanded="true" aria-controls="collapsePlatform<?= htmlspecialchars($platform_array['id']) ?>">
<div class="border bg-white text-start mb-3 rounded mt-3" data-toggle="tooltip" data-placement="bottom" title="configuration for platform <?= htmlspecialchars($platform_array['id']) ?>">
<i class="fas fa-wrench"></i>
<small>platform <?= htmlspecialchars($platform_array['id']) ?> (<?= htmlspecialchars($platform_array['name']) ?>)</small>
</div>
</a>
<div class="collapse show" id="collapsePlatform<?= htmlspecialchars($platform_array['id']) ?>">
<div class="row mb-1" style="padding-left: 0px;">
<div class="col-md-8 text-start">
<div class="row mb-1">
<div class="col-md-8 text-start">
<a class="btn btn-secondary" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=edit">edit platform</a>
<?php if (count($platformsAll) <= 1) { ?>
<span class="btn btn-light" style="padding: 2px;" href="#" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="can't delete the last platform">delete platform</span>
<?php } else { ?>
<a class="btn btn-danger" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=delete">delete platform</a>
<?php } ?>
</div>
</div>
</div>
<div style="padding-left: 100px; padding-bottom: 20px;">
<?php foreach ($platform_array as $key => $value) {
if ($key === 'id') continue;
?>
<div class="row mb-1" style="padding-left: 100px;">
<div class="col-md-4 text-end">
<?= htmlspecialchars($key) ?>:
</div>
<div class="border col-md-8 text-start">
<?= htmlspecialchars($value) ?>
</div>
</div>
<?php } ?>
</div>
<hr />
<p class="card-text">jilo agents on platform <?= htmlspecialchars($platform_array['id']) ?> (<?= htmlspecialchars($platform_array['name']) ?>)
<br />
total <?= htmlspecialchars(count($agents)) ?> <?= htmlspecialchars(count($agents)) === 1 ? 'jilo agent' : 'jilo agents' ?>&nbsp;
<a class="btn btn-secondary" style="padding: 0px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=add-agent">
add new
</a>
</p>
<?php foreach ($agents as $agent_array) { ?>
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>agent<?= htmlspecialchars($agent_array['id']) ?>"></a>
<div class="row mb-3" style="padding-left: 0px;">
<div class="border rounded bg-light" style="padding-left: 50px; padding-bottom: 20px; padding-top: 20px;">
<div class="row mb-1" style="padding-left: 0px;">
<div class="col-md-4 text-end">
agent id <?= htmlspecialchars($agent_array['id']) ?>:
</div>
<div class="col-md-8 text-start">
<a class="btn btn-secondary" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($agent_array['platform_id']) ?>&agent=<?= htmlspecialchars($agent_array['id']) ?>&action=edit">edit agent</a>
<a class="btn btn-danger" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($agent_array['platform_id']) ?>&agent=<?= htmlspecialchars($agent_array['id']) ?>&action=delete">delete agent</a>
</div>
<div style="padding-left: 100px; padding-bottom: 20px;">
<div class="row mb-1" style="padding-left: 100px;">
<div class="col-md-4 text-end">
agent type:
</div>
<div class="border col-md-8 text-start">
<?= htmlspecialchars($agent_array['agent_description']) ?>
</div>
</div>
<div class="row mb-1" style="padding-left: 100px;">
<div class="col-md-4 text-end">
endpoint:
</div>
<div class="border col-md-8 text-start">
<?= htmlspecialchars($agent_array['url'].$agent_array['agent_endpoint']) ?>
</div>
</div>
<?php if (isset($agent_array['check_period']) && $agent_array['check_period'] !== 0) { ?>
<div class="row mb-1" style="padding-left: 100px;">
<div class="col-md-4 text-end">
check period:
</div>
<div class="border col-md-8 text-start">
<?= htmlspecialchars($agent_array['check_period']) ?> <?= ($agent_array['check_period'] == 1 ? 'minute' : 'minutes') ?>
</div>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<?php } ?>
</div>
</div>
<!-- /widget "config" -->

View File

@ -1,8 +0,0 @@
<div class="text-center">
<div class="mt-3 h5">The page is not found.</div>
<div>
<small>go to <a href="<?= htmlspecialchars($app_root) ?>">front page</a> or to <a href="<?= htmlspecialchars($app_root) ?>?page=profile">your profile</a></small>
</div>
</div>

View File

@ -1,8 +0,0 @@
<div class="text-center">
<div class="mt-3 h5">You have no access to this page.</div>
<div>
<small>go to <a href="<?= htmlspecialchars($app_root) ?>">front page</a> or to <a href="<?= htmlspecialchars($app_root) ?>?page=profile">your profile</a></small>
</div>
</div>

View File

@ -1,76 +0,0 @@
<div class="row">
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<!-- Results filter -->
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
<form method="POST" id="filter_form" class="filter-results" action="?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($page) ?>">
<label for="from_time">from</label>
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
<label for="until_time">until</label>
<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="component ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
<input type="text" name="name" placeholder="component name"<?php if (isset($_REQUEST['name'])) echo " value=\"" . htmlspecialchars($_REQUEST['name']) . "\"" ?> />
<input type="text" name="event" placeholder="event name"<?php if (isset($_REQUEST['event'])) echo " value=\"" . htmlspecialchars($_REQUEST['event']) . "\"" ?> />
<input type="button" onclick="clearFilter()" value="clear" />
<input type="submit" value="search" />
</form>
<script>
function clearFilter() {
document.getElementById("filter_form").reset();
const filterFields = document.querySelectorAll("#filter_form input");
filterFields.forEach(input => {
if (input.type === 'text' ||input.type === 'date') {
input.value = '';
}
});
}
</script>
</div>
<!-- /Results filter -->
</div>
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
<?php if ($time_range_specified) { ?>
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
<?php } ?>
<div class="mb-5">
<?php if ($widget['full'] === true) { ?>
<table class="table table-results table-striped table-hover table-bordered">
<thead class="thead-dark">
<tr>
<?php foreach ($widget['table_headers'] as $header) { ?>
<th scope="col"><?= htmlspecialchars($header) ?></th>
<?php } ?>
</tr>
</thead>
<tbody>
<?php foreach ($widget['table_records'] as $row) { ?>
<tr>
<?php foreach ($row as $key => $column) { ?>
<?php if ($key === 'component ID') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'component') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } else { ?>
<td><?= htmlspecialchars($column ?? '') ?></td>
<?php }
} ?>
</tr>
<?php } ?>
</tbody>
</table>
<?php
if ($widget['pagination'] && $item_count > $items_per_page) {
$url = "$app_root?platform=$platform_id&page=$page";
include '../app/helpers/pagination.php';
}
?>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->

View File

@ -1,94 +0,0 @@
<div class="row">
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<!-- Results filter -->
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
<form method="POST" id="filter_form" class="filter-results" action="?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($page) ?>">
<label for="from_time">from</label>
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
<label for="until_time">until</label>
<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="conference ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
<input type="text" name="name" placeholder="conference name"<?php if (isset($_REQUEST['name'])) echo " value=\"" . htmlspecialchars($_REQUEST['name']) . "\"" ?> />
<input type="button" onclick="clearFilter()" value="clear" />
<input type="submit" value="search" />
</form>
<script>
function clearFilter() {
document.getElementById("filter_form").reset();
const filterFields = document.querySelectorAll("#filter_form input");
filterFields.forEach(input => {
if (input.type === 'text' ||input.type === 'date') {
input.value = '';
}
});
}
</script>
</div>
<!-- /Results filter -->
</div>
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
<?php if ($time_range_specified) { ?>
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
<?php } ?>
<div class="mb-5">
<?php if ($widget['full'] === true) { ?>
<table class="table table-results table-striped table-hover table-bordered">
<thead class="thead-dark">
<tr>
<?php foreach ($widget['table_headers'] as $header) { ?>
<th scope="col"><?= htmlspecialchars($header) ?></th>
<?php } ?>
</tr>
</thead>
<tbody>
<?php foreach ($widget['table_records'] as $row) { ?>
<tr>
<?php $stats_id = false;
$participant_ip = false;
if (isset($row['event']) && $row['event'] === 'stats_id') $stats_id = true;
if (isset($row['event']) && $row['event'] === 'pair selected') $participant_ip = true;
foreach ($row as $key => $column) {
if ($key === 'conference ID' && isset($conferenceId) && $conferenceId === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } elseif ($key === 'conference ID') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'conference name' && isset($conferenceName) && $conferenceName === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } elseif ($key === 'conference name') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'participant ID') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($stats_id && $key === 'parameter') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($participant_ip && $key === 'parameter') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&ip=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php
// in general listings we don't show seconds and miliseconds
} elseif ($key === 'start' || $key === 'end') { ?>
<td><?= htmlspecialchars(substr($column ?? '', 0, -7)) ?></td>
<?php } else { ?>
<td><?= htmlspecialchars($column ?? '') ?></td>
<?php }
} ?>
</tr>
<?php } ?>
</tbody>
</table>
<?php
if ($widget['pagination'] && $item_count > $items_per_page) {
$url = "$app_root?platform=$platform_id&page=$page";
include '../app/helpers/pagination.php';
}
?>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->

View File

@ -3,8 +3,8 @@
<h2 class="card-header">Login</h2>
<div class="card-body">
<p class="card-text"><strong>Welcome to JILO!</strong><br />Please enter login credentials:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=login">
<input type="text" name="username" placeholder="Username" required autofocus />
<form method="POST" action="<?= $app_root ?>?page=login">
<input type="text" name="username" placeholder="Username" required />
<br />
<input type="password" name="password" placeholder="Password" required />
<br />

View File

@ -3,8 +3,8 @@
<h2 class="card-header">Register</h2>
<div class="card-body">
<p class="card-text">Enter credentials for registration:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
<input type="text" name="username" placeholder="Username" required autofocus />
<form method="POST" action="<?php= $app_root ?>?page=register">
<input type="text" name="username" placeholder="Username" required />
<br />
<input type="password" name="password" placeholder="Password" required />
<br />&nbsp;<br />

View File

@ -1,140 +0,0 @@
<hr /><p class="m-3">NB: This functionality is still under development. The data is just an example.</p><hr /><!-- FIXME remove when implemented -->
<div class="row">
<div class="card w-auto bg-light border-light card-body filter-results">
<div class="btn-group" role="group">
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('today'); setActive(this)" value="today" />
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('last2days'); setActive(this)" value="last 2 days" />
<input type="button" class="button active" style="margin-right: 3px;" onclick="setTimeRange('last7days'); setActive(this)" value="last 7 days" />
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('thisMonth'); setActive(this)" value="month" />
<input type="button" class="button" style="margin-right: 18px;" onclick="setTimeRange('thisYear'); setActive(this)" value="year" />
<input type="date" style="margin-right: 3px;" id="start-date">
<input type="date" style="margin-right: 3px;" id="end-date">
<input type="button" id="custom_range" class="button" onclick="setCustomTimeRange(); setActive(this)" value="custom range" />
</div>
</div>
</div>
<script>
// Define an array to store all graph instances
var graphs = [];
</script>
<?php
foreach ($graph as $data) {
include '../app/helpers/graph.php';
}
?>
<script>
// Function to update the label and propagate zoom across charts
function propagateZoom(chart) {
var startDate = chart.scales.x.min;
var endDate = chart.scales.x.max;
// Update the datetime input fields
document.getElementById('start-date').value = new Date(startDate).toISOString().slice(0, 10);
document.getElementById('end-date').value = new Date(endDate).toISOString().slice(0, 10);
// Update all charts with the new date range
graphs.forEach(function(graphObj) {
if (graphObj.graph !== chart) {
graphObj.graph.options.scales.x.min = startDate;
graphObj.graph.options.scales.x.max = endDate;
graphObj.graph.update(); // Redraw chart with new range
}
updatePeriodLabel(graphObj.graph, graphObj.label); // Update period label
});
}
// Predefined time range buttons
function setTimeRange(range) {
var startDate, endDate;
var now = new Date();
switch (range) {
case 'today':
startDate = new Date(now.setHours(0, 0, 0, 0));
endDate = new Date(now.setHours(23, 59, 59, 999));
timeRangeName = 'today';
break;
case 'last2days':
startDate = new Date(now.setDate(now.getDate() - 2));
endDate = new Date();
timeRangeName = 'last 2 days';
break;
case 'last7days':
startDate = new Date(now.setDate(now.getDate() - 7));
endDate = new Date();
timeRangeName = 'last 7 days';
break;
case 'thisMonth':
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
endDate = new Date();
timeRangeName = 'this month so far';
break;
case 'thisYear':
startDate = new Date(now.getFullYear(), 0, 1);
endDate = new Date();
timeRangeName = 'this year so far';
break;
default:
return;
}
// We set the date input fields to match the selected period
document.getElementById('start-date').value = startDate.toISOString().slice(0, 10);
document.getElementById('end-date').value = endDate.toISOString().slice(0, 10);
// Loop through all graphs and update their time range and label
graphs.forEach(function(graphObj) {
graphObj.graph.options.scales.x.min = startDate;
graphObj.graph.options.scales.x.max = endDate;
graphObj.graph.update();
updatePeriodLabel(graphObj.graph, graphObj.label); // Update the period label
});
}
// Custom date range
function setCustomTimeRange() {
var startDate = document.getElementById('start-date').value;
var endDate = document.getElementById('end-date').value;
if (!startDate || !endDate) return;
// Convert the input dates to JavaScript Date objects
startDate = new Date(startDate);
endDate = new Date(endDate);
timeRangeName = 'custom range';
// Loop through all graphs and update the custom time range
graphs.forEach(function(graphObj) {
graphObj.graph.options.scales.x.min = startDate;
graphObj.graph.options.scales.x.max = endDate;
graphObj.graph.update();
updatePeriodLabel(graphObj.graph, graphObj.label); // Update the period label
});
}
// Set the clicked button state to active
function setActive(element) {
// Remove 'active' class from all buttons
var buttons = document.querySelectorAll('.button');
buttons.forEach(function(btn) {
btn.classList.remove('active');
});
// Add 'active' class only to the clicked button
element.classList.add('active');
}
// Call setTimeRange('last7days') on page load to pre-load last 7 days by default
window.onload = function() {
setTimeRange('last7days');
};
</script>

View File

@ -1,53 +0,0 @@
<!-- help -->
<div class="card text-center w-100 mx-auto">
<p class="h4 card-header">Jilo Help</p>
<div class="card-body">
<div style="text-align: left; font-family: monospace; font-size: 0.75em; white-space: pre-wrap;">
<a href="https://lindeas.com/jilo">Jilo</a> is a software tools suite developed by <a href="https://lindeas.com">Lindeas Ltd.</a> designed to help in maintaining a Jitsi Meet platform.
It consists of several parts meant to run together, although some of them can be used separately.
<hr /><strong>"JILO"</strong>
This is the command-line tool for extracting information about important events from the Jitsi Meet log files, storing them in a database and searching through that database.
Jilo is written in Bash and has very minimal external dependencies. That means that you can run it on almost any Linux system with jitsi log files.
It can either:
- show the data directly in the terminal,
- provide it to an instance of the web application "Jilo Web" for displaying statistics (the Jilo Web server needs to have access to the sqlite database file from Jilo),
- or send the data output to a "Jilo Agent" that can then allow a remote Jilo Web access it.
This way Jilo is always available on each host in the Jitsi Meet platform for quick command-line search, while also providing data for the statistics on a central Jilo Web server, without any need to put additional software on each server.
<hr /><strong>"Jilo Agent"</strong>
The Jilo Agent is a small program, written in Go. It runs on remote servers in the Jitsi Meet platform, and provides info about the operation of the different components of the platform.
It listens for connections from the central Jilo Web app on a special port and the connection is secured with JWT tokens authentication. It can be configured to use HTTPS. In a firewall you only need to allow the agent's port for incoming connections from the Jilo Web server.
All information about the different services (jvb, jicofo, jigasi, nginx, prosody) is transmitted over that single port. This, plus the authentication and the fact that Jilo Agent doesn't need any additional external programs or libraries to operate, make it very easy for deploying and automating the deployment in a large Jitsi Meet setup.
<hr /><strong>"Jilo Web"</strong>
Jilo Web is the web app that combines all the information received from Jilo and Jilo Agents and shows statistics and graphs of the usage, the events and the issues.
It's a multi-user web tool with user levels and access rights integrated into it, making it suitable for the different levels in an enterprise.
The current website you are looking at is running a Jilo Web instance.
<hr /><strong>"Jilo Server"</strong>
Jilo Server is a server component written in Go, meant to work alongside Jilo Web. It is responsible for all automated tasks - health checks, periodic retrieval of data from the remote Jilo Agents, etc.
It generally works on the same machine as the web interface Jilo Web and shares its database, although if needed it could be deployed on a separate machine.
Jilo Web checks for the Jilo Server availability and displays a warning if there is a problem with the server.
</div>
</div>
</div>
<!-- /help -->

View File

@ -1,45 +0,0 @@
<div class="row">
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
</div>
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
<div class="mb-5">
<hr /><p class="m-3">NB: This functionality is still under development. The data is just an example.</p><hr /><!-- FIXME remove when implemented -->
<?php if ($widget['full'] === true) { ?>
<table class="table table-results table-striped table-hover table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col"></th>
<?php foreach ($widget['records'] as $record) { ?>
<th scope="col"><?= htmlspecialchars($record['table_headers']) ?></th>
<?php } ?>
</tr>
</thead>
<tbody>
<tr>
<td>conferences</td>
<?php foreach ($widget['records'] as $record) { ?>
<td><?php if (!empty($record['conferences'])) { ?>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&from_time=<?= htmlspecialchars($record['from_time']) ?>&until_time=<?= htmlspecialchars($record['until_time']) ?>"><?= htmlspecialchars($record['conferences']) ?></a> <?php } else { ?>
0<?php } ?>
</td>
<?php } ?>
</tr>
<tr>
<td>participants</td>
<?php foreach ($widget['records'] as $record) { ?>
<td><?php if (!empty($record['participants'])) { ?>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&from_time=<?= htmlspecialchars($record['from_time']) ?>&until_time=<?= htmlspecialchars($record['until_time']) ?>"><?= htmlspecialchars($record['participants']) ?></a> <?php } else { ?>
0<?php } ?>
</td>
<?php } ?>
</tr>
</tbody>
</table>
<?php } else { ?>
<p class="m-3">No records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->

View File

@ -1,26 +0,0 @@
<!-- Logs filter -->
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
<form method="POST" id="filter_form" class="filter-results" action="?page=logs">
<label for="from_time">from</label>
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
<label for="until_time">until</label>
<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']) . "\"" ?> />
<input type="text" name="message" placeholder="message"<?php if (isset($_REQUEST['message'])) echo " value=\"" . htmlspecialchars($_REQUEST['message']) . "\"" ?> />
<input type="button" onclick="clearFilter()" value="clear" />
<input type="submit" value="search" />
</form>
<script>
function clearFilter() {
document.getElementById("filter_form").reset();
const filterFields = document.querySelectorAll("#filter_form input");
filterFields.forEach(input => {
if (input.type === 'text' ||input.type === 'date') {
input.value = '';
}
});
}
</script>
</div>
<!-- /Logs filter -->

View File

@ -1,57 +0,0 @@
<div class="row">
<?php if ($widget['collapsible'] === true) { ?>
<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="card w-auto bg-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div>
<?php } else { ?>
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div>
<?php } ?>
<?php if ($widget['filter'] === true) {
include '../app/templates/logs-filter.php'; } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a>
<?php } ?>
</div>
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
<?php if ($time_range_specified) { ?>
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
<?php } ?>
<div class="mb-5">
<?php if ($widget['full'] === true) { ?>
<table class="table table-results table-striped table-hover table-bordered">
<thead class="thead-dark">
<tr>
<?php foreach ($widget['table_headers'] as $header) { ?>
<th scope="col" class="th-<?= htmlspecialchars($header) ?>"><?= htmlspecialchars($header) ?></th>
<?php } ?>
</tr>
</thead>
<tbody>
<?php foreach ($widget['table_records'] as $row) { ?>
<tr>
<?php
foreach ($row as $key => $column) {
if ($key === 'user ID' && isset($user_id) && $user_id === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } else { ?>
<td><?= htmlspecialchars($column ?? '') ?></td>
<?php }
} ?>
</tr>
<?php } ?>
</tbody>
</table>
<?php
if ($widget['pagination'] && $item_count > $items_per_page) {
$url = "$app_root?platform=$platform_id&page=$page";
include '../app/helpers/pagination.php';
}
?>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->

View File

@ -7,7 +7,7 @@
<?php } ?>
<!-- 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 <?= $config['version'] ?> &copy;2024 - web interface for <a href="https://lindeas.com/jilo">Jilo</a></div>
<!-- /Footer -->
</div>

View File

@ -2,14 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/bootstrap/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/main.css">
<?php if ($page === 'logs') { ?>
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/logs.css">
<?php } ?>
<?php if ($page === 'profile') { ?>
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/profile.css">
<?php } ?>
<link rel="stylesheet" type="text/css" href="<?= $app_root ?>static/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="<?= $app_root ?>static/all.css">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
@ -23,17 +17,9 @@
}
})();
</script>
<?php if ($page === 'agents') { ?>
<script src="<?= htmlspecialchars($app_root) ?>static/agents.js"></script>
<?php } ?>
<?php if ($page === 'graphs') { ?>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.0"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@1.2.1/dist/chartjs-plugin-zoom.min.js"></script>
<?php } ?>
<title>Jilo Web</title>
<link rel="icon" type="image/x-icon" href="<?= htmlspecialchars($app_root) ?>static/favicon.ico">
</head>
<body>
<div class="container-fluid">

View File

@ -1,32 +1,22 @@
<div class="container-fluid">
<!-- Menu -->
<div class="menu-container">
<ul class="menu-left">
<div class="container">
<div class="row">
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>" class="logo-link"><div class="col-4"><img class="logo" src="<?= htmlspecialchars($app_root) ?>static/jilo-logo.png" alt="JILO"/></div></a>
<a href="<?= $app_root ?>?platform=<?= $platform_id?>" class="logo-link"><div class="col-4"><img class="logo" src="<?= $app_root ?>static/jilo-logo.png" alt="JILO"/></div></a>
</div>
</div>
<li class="font-weight-light text-uppercase" style="font-size: 0.5em; color: whitesmoke; margin-right: 70px; align-content: center;">version&nbsp;<?= htmlspecialchars($config['version']) ?></li>
<li class="font-weight-light text-uppercase" style="font-size: 0.5em; color: whitesmoke; margin-right: 70px; align-content: center;">version&nbsp;<?php echo $config['version']; ?></li>
<?php if ( isset($_SESSION['username']) ) { ?>
<?php foreach ($platformsAll as $platform) {
$platform_switch_url = switchPlatform($platform['id']);
?>
<?php foreach ($config['platforms'] as $index => $platform) { ?>
<li style="margin-right: 3px;">
<?php if ((isset($_REQUEST['platform']) || empty($_SERVER['QUERY_STRING'])) && $platform['id'] == $platform_id) { ?>
<span style="background-color: #fff; border: 1px solid #111; color: #111; border-bottom-color: #fff; padding-bottom: 12px;">
<?= htmlspecialchars($platform['name']) ?>
</span>
<?php } else { ?>
<a href="<?= htmlspecialchars($platform_switch_url) ?>">
<a style="background-color: #111;" href="?platform=<?= htmlspecialchars($index) ?>&page=front">
<?= htmlspecialchars($platform['name']) ?>
</a>
<?php } ?>
</li>
<?php } ?>
@ -35,11 +25,11 @@
<ul class="menu-right">
<?php if ( isset($_SESSION['username']) ) { ?>
<li><a href="<?= htmlspecialchars($app_root) ?>?page=profile"><?= htmlspecialchars($currentUser) ?></a></li>
<li><a href="<?= htmlspecialchars($app_root) ?>?page=logout">logout</a></li>
<li><a href="<?= $app_root ?>?page=profile"><?= $user ?></a></li>
<li><a href="<?= $app_root ?>?page=logout">logout</a></li>
<?php } else { ?>
<li><a href="<?= htmlspecialchars($app_root) ?>?page=login">login</a></li>
<li><a href="<?= htmlspecialchars($app_root) ?>?page=register">register</a></li>
<li><a href="<?= $app_root ?>?page=login">login</a></li>
<li><a href="<?= $app_root ?>?page=register">register</a></li>
<?php } ?>
</ul>
</div>

View File

@ -2,100 +2,58 @@
<!-- Sidebar -->
<div class="col-md-3 sidebar-wrapper bg-light" id="sidebar">
<div class="text-center" style="border: 1px solid #0dcaf0; height: 22px;" id="time_now">
<?php
$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>
</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">
<ul class="list-group">
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=dashboard">
<li class="list-group-item<?php if ($page === 'dashboard') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>statistics</small></p></li>
<a href="<?= $app_root ?>?platform=<?= $platform_id ?>&page=front">
<li class="list-group-item<?php if ($page === 'front') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-chart-line" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="general jitsi stats"></i>general stats
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>logs statistics</small></p></li>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences">
<a href="<?= $app_root ?>?platform=<?= $platform_id ?>&page=conferences">
<li class="list-group-item<?php if ($page === 'conferences') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-video" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="conferences"></i>conferences
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants">
<a href="<?= $app_root ?>?platform=<?= $platform_id ?>&page=participants">
<li class="list-group-item<?php if ($page === 'participants') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-users" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="participants"></i>participants
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components">
<a href="<?= $app_root ?>?platform=<?= $platform_id ?>&page=components">
<li class="list-group-item<?php if ($page === 'components') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-puzzle-piece" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="components"></i>components
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>graphs</small></p></li>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>jilo-web config</small></p></li>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=graphs">
<li class="list-group-item<?php if ($page === 'graphs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-chart-bar" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="combined graphs"></i>combined graphs
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=latest">
<li class="list-group-item<?php if ($page === 'latest') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-list" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="latest data"></i>latest data
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>live data</small></p></li>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs">
<li class="list-group-item<?php if ($page === 'config' && $item === 'configjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-tv" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="config.js"></i>config.js
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs">
<li class="list-group-item<?php if ($page === 'config' && $item === 'interfaceconfigjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-th" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="interface_config.js"></i>interface_config.js
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=agents">
<li class="list-group-item<?php if ($page === 'agents') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-mask" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="jilo agents"></i>jilo agents
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>system</small></p></li>
<?php if ($userObject->hasRight($user_id, 'view config file')) {?>
<a href="<?= htmlspecialchars($app_root) ?>?page=config">
<a href="<?= $app_root ?>?page=config">
<li class="list-group-item<?php if ($page === 'config' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-wrench" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>config
</li>
</a>
<?php } ?>
<a href="<?= htmlspecialchars($app_root) ?>?page=status">
<li class="list-group-item<?php if ($page === 'status' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-heartbeat" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="status"></i>status
</li>
</a>
<?php if ($userObject->hasRight($user_id, 'view app logs')) {?>
<a href="<?= htmlspecialchars($app_root) ?>?page=logs">
<a href="<?= $app_root ?>?page=logs">
<li class="list-group-item<?php if ($page === 'logs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-shoe-prints" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="logs"></i>logs
</li>
</a>
<?php } ?>
<a href="<?= htmlspecialchars($app_root) ?>?page=help">
<li class="list-group-item<?php if ($page === 'help') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-question-circle" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="help"></i>help
<i class="fas fa-list" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="logs"></i>logs
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>current Jitsi platform</small></p></li>
<a href="<?= $app_root ?>?platform=<?= $platform_id ?>&page=config&item=configjs">
<li class="list-group-item<?php if ($page === 'config' && $item === 'configjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-tv" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>config.js
</li>
</a>
<a href="<?= $app_root ?>?platform=<?= $platform_id ?>&page=config&item=interfaceconfigjs">
<li class="list-group-item<?php if ($page === 'config' && $item === 'interfaceconfigjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-th" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>interface_config.js
</li>
</a>
</ul>
</div>
</div>

View File

@ -1,201 +0,0 @@
<!-- user profile -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Profile of <?= htmlspecialchars($userDetails[0]['username']) ?></p>
<div class="card-body">
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=profile" enctype="multipart/form-data">
<div class="row">
<p class="border rounded bg-light mb-4"><small>edit the profile fields</small></p>
<div class="col-md-4 avatar-container">
<div class="avatar-wrapper">
<img class="avatar-img" src="<?= htmlspecialchars($app_root) . htmlspecialchars($avatar) ?>" alt="avatar" />
<div class="avatar-btn-container">
<label for="avatar-upload" class="avatar-btn avatar-btn-select btn btn-primary">
<i class="fas fa-folder" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="select new avatar"></i>
</label>
<input type="file" id="avatar-upload" name="avatar_file" accept="image/*" style="display:none;">
<?php if ($default_avatar) { ?>
<button type="button" class="avatar-btn avatar-btn-remove btn btn-secondary" data-toggle="modal" data-target="#confirmDeleteModal" disabled>
<?php } else { ?>
<button type="button" class="avatar-btn avatar-btn-remove btn btn-danger" data-toggle="modal" data-target="#confirmDeleteModal">
<?php } ?>
<i class="fas fa-trash" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="remove current avatar"></i>
</button>
</div>
</div>
</div>
<div class="col-md-8">
<!--div class="row mb-3">
<div class="col-md-4 text-end">
<label for="username" class="form-label"><small>username:</small></label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8 text-start bg-light">
<input class="form-control" type="text" name="username" value="<?= htmlspecialchars($userDetails[0]['username']) ?>" required />
</div>
</div-->
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="name" class="form-label"><small>name:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<input class="form-control" type="text" name="name" value="<?= htmlspecialchars($userDetails[0]['name']) ?>" autofocus />
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="email" class="form-label"><small>email:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<input class="form-control" type="text" name="email" value="<?= htmlspecialchars($userDetails[0]['email']) ?>" />
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="timezone" class="form-label"><small>timezone:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<select class="form-control" name="timezone" id="timezone">
<?php foreach ($allTimezones as $timezone) { ?>
<option value="<?= htmlspecialchars($timezone) ?>" <?= $timezone === $userTimezone ? 'selected' : '' ?>>
<?= htmlspecialchars($timezone) ?>&nbsp;&nbsp;(<?= htmlspecialchars(getUTCOffset($timezone)) ?>)
</option>
<?php } ?>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="bio" class="form-label"><small>bio:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<textarea class="form-control" name="bio" rows="10"><?= htmlspecialchars($userDetails[0]['bio'] ?? '') ?></textarea>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="rights" class="form-label"><small>rights:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?php foreach ($allRights as $right) {
// Check if the current right exists in $userRights
$isChecked = false;
foreach ($userRights as $userRight) {
if ($userRight['right_id'] === $right['right_id']) {
$isChecked = true;
break;
}
} ?>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="rights[]" value="<?= htmlspecialchars($right['right_id']) ?>" id="right_<?= htmlspecialchars($right['right_id']) ?>" <?= $isChecked ? 'checked' : '' ?> />
<label class="form-check-label" for="right_<?= htmlspecialchars($right['right_id']) ?>"><?= htmlspecialchars($right['right_name']) ?></label>
</div>
<?php } ?>
</div>
</div>
</div>
<p>
<a href="<?= htmlspecialchars($app_root) ?>?page=profile" class="btn btn-secondary">Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</p>
</div>
</form>
<!-- avatar removal modal confirmation -->
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmDeleteModalLabel">Confirm Avatar Deletion</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img class="avatar-img" src="<?= htmlspecialchars($app_root) . htmlspecialchars($avatar) ?>" alt="avatar" />
<br />
Are you sure you want to delete your avatar?
<br />
This action cannot be undone.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<form id="remove-avatar-form" data-action="remove-avatar" method="POST" action="<?= htmlspecialchars($app_root) ?>?page=profile&action=remove&item=avatar">
<button type="button" class="btn btn-danger" id="confirm-delete">Delete Avatar</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /user profile -->
<script>
// Preview the uploaded avatar
document.getElementById('avatar-upload').addEventListener('change', function(event) {
const reader = new FileReader();
reader.onload = function() {
document.querySelector('.avatar-img').src = reader.result;
};
reader.readAsDataURL(event.target.files[0]);
});
// Avatar file size and type control
document.getElementById('avatar-upload').addEventListener('change', function() {
const maxFileSize = 500 * 1024; // 500 KB in bytes
const currentAvatar = '<?= htmlspecialchars($app_root) . htmlspecialchars($avatar) ?>'; // current avatar
const file = this.files[0];
if (file) {
// Check file size
if (file.size > maxFileSize) {
alert('File size exceeds 500 KB. Please select a smaller file.');
this.value = ''; // Clear the file input
document.querySelector('.avatar-img').src = currentAvatar;
}
}
});
// Submitting the avatar deletion confirmation modal form
document.getElementById('confirm-delete').addEventListener('click', function(event) {
event.preventDefault(); // Prevent the outer form from submitting
document.getElementById('remove-avatar-form').submit();
});
// Function to detect user's timezone and select it in the dropdown
function setTimezone() {
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const timezoneSelect = document.getElementById("timezone");
timezoneSelect.className = 'form-control border border-danger';
// Loop through the options to find and select the user's timezone
for (let i = 0; i < timezoneSelect.options.length; i++) {
if (timezoneSelect.options[i].value === userTimezone) {
timezoneSelect.selectedIndex = i;
break;
}
}
}
// Run the function on page load
window.onload = function() {
const isTimezoneSet = <?php echo json_encode($isTimezoneSet); ?>; // Pass PHP flag to JavaScript
// If timezone is not set, run setTimezone()
if (!isTimezoneSet) {
setTimezone();
}
};
</script>

View File

@ -1,87 +0,0 @@
<!-- user profile -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Profile of <?= htmlspecialchars($userDetails[0]['username']) ?></p>
<div class="card-body">
<div class="row">
<div class="col-md-4 avatar-container">
<div>
<img class="avatar-img" src="<?= htmlspecialchars($app_root) . htmlspecialchars($avatar) ?>" alt="avatar" />
</div>
</div>
<div class="col-md-8">
<!--div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>username:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?= htmlspecialchars($userDetails[0]['username']) ?>
</div>
</div-->
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>name:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?= htmlspecialchars($userDetails[0]['name']) ?>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>email:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?= htmlspecialchars($userDetails[0]['email']) ?>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>timezone:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?php if (isset($userDetails[0]['timezone'])) { ?>
<?= htmlspecialchars($userDetails[0]['timezone']) ?>&nbsp;&nbsp;<span style="font-size: 0.66em;">(<?= htmlspecialchars(getUTCOffset($userDetails[0]['timezone'])) ?>)</span>
<?php } ?>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>bio:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<textarea class="scroll-box" rows="10" readonly><?= htmlspecialchars($userDetails[0]['bio'] ?? '') ?></textarea>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>rights:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?php foreach ($userRights as $right) { ?>
<?= htmlspecialchars($right['right_name'] ?? '') ?>
<br />
<?php } ?>
</div>
</div>
</div>
<p>
<a href="<?= htmlspecialchars($app_root) ?>?page=profile&action=edit" class="btn btn-primary">Edit</a>
</p>
</div>
</div>
</div>
<!-- /user profile -->

View File

@ -1,14 +0,0 @@
<!-- jilo agent status -->
<div class="card text-center w-75 mx-lef" style="padding-left: 80px;">
<div class="card-body">
<p class="card-text text-left" style="text-align: left;">
Jilo Agent <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>agent<?= htmlspecialchars($agent['id']) ?>"><?= htmlspecialchars($agent['agent_description']) ?></a>:
<strong><?= $agent_availability ?></strong>
<br />
host: <strong><?= htmlspecialchars($agent_host) ?></strong>,
port: <strong><?= htmlspecialchars($agent_port) ?></strong>,
endpoint: <strong><?= htmlspecialchars($agent['agent_endpoint']) ?></strong>
</p>
</div>
</div>

View File

@ -1,10 +0,0 @@
<!-- jitsi platform status -->
<br />
<div class="card text-center w-75 mx-lef" style="padding-left: 40px;">
<div class="card-body">
<p class="card-text text-left" style="text-align: left;">
Jitsi Meet platform: <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>"><?= htmlspecialchars($platform['name']) ?></a>
</p>
</div>
</div>

View File

@ -1,19 +0,0 @@
<!-- jilo status -->
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Jilo platform status</p>
<div class="card-body">
<p class="card-text text-left" style="text-align: left;">
Jilo Server:
<?php if ($server_status) { ?>
<strong><span class="text-success">running</span></strong>
<?php } else { ?>
<strong><span class="text-danger">not running</span></strong>
<?php } ?>
<br />
host: <strong><?= htmlspecialchars($server_host) ?></strong>,
port: <strong><?= htmlspecialchars($server_port) ?></strong>,
endpoint: <strong><?= htmlspecialchars($server_endpoint) ?></strong>
</p>
</div>
</div>

View File

@ -2,27 +2,27 @@
<div class="row">
<?php if ($widget['collapsible'] === true) { ?>
<a style="text-decoration: none;" data-toggle="collapse" href="#collapse<?= htmlspecialchars($widget['name']) ?>" role="button" aria-expanded="true" aria-controls="collapse<?= htmlspecialchars($widget['name']) ?>">
<a style="text-decoration: none;" data-toggle="collapse" href="#collapse<?= $widget['name'] ?>" role="button" aria-expanded="true" aria-controls="collapse<?= $widget['name'] ?>">
<div class="card w-auto bg-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<?php } else { ?>
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<?php } ?>
<?php if ($widget['filter'] === true) {
include '../app/templates/block-results-filter.php'; } ?>
include('../app/templates/block-results-filter.php'); } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a>
<?php } ?>
</div>
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
<!-- widget "<?= $widget['name']; ?>" -->
<div class="collapse show" id="collapse<?= $widget['name'] ?>">
<?php if ($time_range_specified) { ?>
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
<p class="m-3">time period: <strong><?= $from_time ?> - <?= $until_time ?></strong></p>
<?php } ?>
<div class="mb-5">
<?php if ($widget['full'] === true) { ?>
<table class="table table-results table-striped table-hover table-bordered">
<table class="table table-striped table-hover table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col"></th>
@ -36,7 +36,7 @@
<td>conferences</td>
<?php foreach ($widget['records'] as $record) { ?>
<td><?php if (!empty($record['conferences'])) { ?>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&from_time=<?= htmlspecialchars($record['from_time']) ?>&until_time=<?= htmlspecialchars($record['until_time']) ?>"><?= htmlspecialchars($record['conferences']) ?></a> <?php } else { ?>
<a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=conferences&from_time=<?= $record['from_time'] ?>&until_time=<?= $record['until_time'] ?>"><?= htmlspecialchars($record['conferences']) ?></a> <?php } else { ?>
0<?php } ?>
</td>
<?php } ?>
@ -45,7 +45,7 @@
<td>participants</td>
<?php foreach ($widget['records'] as $record) { ?>
<td><?php if (!empty($record['participants'])) { ?>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&from_time=<?= htmlspecialchars($record['from_time']) ?>&until_time=<?= htmlspecialchars($record['until_time']) ?>"><?= htmlspecialchars($record['participants']) ?></a> <?php } else { ?>
<a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=participants&from_time=<?= $record['from_time'] ?>&until_time=<?= $record['until_time'] ?>"><?= htmlspecialchars($record['participants']) ?></a> <?php } else { ?>
0<?php } ?>
</td>
<?php } ?>
@ -57,4 +57,4 @@
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->
<!-- /widget "<?= $widget['name']; ?>" -->

View File

@ -0,0 +1,9 @@
<!-- widget "user profile" -->
<div>
<p>Profile of <?= $user ?></p>
<ul>
<li>username: <?= $_SESSION['username'] ?></li>
</ul>
</div>
<!-- /widget "user profile" -->

View File

@ -1,26 +1,26 @@
<div class="row">
<?php if ($widget['collapsible'] === true) { ?>
<a style="text-decoration: none;" data-toggle="collapse" href="#collapse<?= htmlspecialchars($widget['name']) ?>" role="button" aria-expanded="true" aria-controls="collapse<?= htmlspecialchars($widget['name']) ?>">
<a style="text-decoration: none;" data-toggle="collapse" href="#collapse<?= $widget['name'] ?>" role="button" aria-expanded="true" aria-controls="collapse<?= $widget['name'] ?>">
<div class="card w-auto bg-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<?php } else { ?>
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<?php } ?>
<?php if ($widget['filter'] === true) {
include '../app/templates/block-results-filter.php'; } ?>
include('../app/templates/block-results-filter.php'); } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a>
<?php } ?>
</div>
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
<!-- widget "<?= $widget['name']; ?>" -->
<div class="collapse show" id="collapse<?= $widget['name'] ?>">
<?php if ($time_range_specified) { ?>
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
<p class="m-3">time period: <strong><?= $from_time ?> - <?= $until_time ?></strong></p>
<?php } ?>
<div class="mb-5">
<?php if ($widget['full'] === true) { ?>
<table class="table table-results table-striped table-hover table-bordered">
<table class="table table-striped table-hover table-bordered">
<thead class="thead-dark">
<tr>
<?php foreach ($widget['table_headers'] as $header) { ?>
@ -39,31 +39,27 @@
if ($key === 'conference ID' && isset($conferenceId) && $conferenceId === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } elseif ($key === 'conference ID') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'conference name' && isset($conferenceName) && $conferenceName === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } elseif ($key === 'conference name') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'participant ID' && isset($participantId) && $participantId === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } elseif ($key === 'participant ID') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'component ID') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=components&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($stats_id && $key === 'parameter' && isset($participantName) && $participantName === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } elseif ($stats_id && $key === 'parameter') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=participants&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($participant_ip && $key === 'parameter' && isset($participantIp) && $participantIp === $column) { ?>
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
<?php } elseif ($participant_ip && $key === 'parameter') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&ip=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=participants&ip=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'component') { ?>
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php
// in general listings we don't show seconds and miliseconds
} elseif ($key === 'start' || $key === 'end') { ?>
<td><?= htmlspecialchars(substr($column ?? '', 0, -7)) ?></td>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } else { ?>
<td><?= htmlspecialchars($column ?? '') ?></td>
<?php }
@ -72,15 +68,9 @@
<?php } ?>
</tbody>
</table>
<?php
if ($widget['pagination'] && $item_count > $items_per_page) {
$url = "$app_root?platform=$platform_id&page=$page";
include '../app/helpers/pagination.php';
}
?>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->
<!-- /widget "<?= $widget['name']; ?>" -->

View File

@ -1,5 +0,0 @@
INSERT INTO jilo_agent_types VALUES(1,'jvb','/jvb');
INSERT INTO jilo_agent_types VALUES(2,'jicofo','/jicofo');
INSERT INTO jilo_agent_types VALUES(3,'prosody','/prosody');
INSERT INTO jilo_agent_types VALUES(4,'nginx','/nginx');
INSERT INTO jilo_agent_types VALUES(5,'jibri','/jibri');

View File

@ -1,13 +0,0 @@
INSERT INTO rights VALUES(1,'superuser');
INSERT INTO rights VALUES(2,'edit users');
INSERT INTO rights VALUES(3,'view config file');
INSERT INTO rights VALUES(4,'edit config file');
INSERT INTO rights VALUES(5,'view own profile');
INSERT INTO rights VALUES(6,'edit own profile');
INSERT INTO rights VALUES(7,'view all profiles');
INSERT INTO rights VALUES(8,'edit all profiles');
INSERT INTO rights VALUES(9,'view app logs');
INSERT INTO rights VALUES(10,'view all platforms');
INSERT INTO rights VALUES(11,'edit all platforms');
INSERT INTO rights VALUES(12,'view all agents');
INSERT INTO rights VALUES(13,'edit all agents');

View File

@ -1,65 +1,5 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
);
CREATE TABLE users_meta (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT,
email TEXT,
timezone TEXT,
avatar TEXT,
bio TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE users_rights (
user_id INTEGER,
right_id INTEGER,
PRIMARY KEY (user_id, right_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (right_id) REFERENCES rights(id)
);
CREATE TABLE rights (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE platforms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
jitsi_url TEXT NOT NULL,
jilo_database TEXT NOT NULL
);
CREATE TABLE jilo_agents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform_id INTEGER NOT NULL,
agent_type_id INTEGER NOT NULL,
url TEXT NOT NULL,
secret_key TEXT,
check_period INTEGER DEFAULT 0,
FOREIGN KEY (platform_id) REFERENCES platforms(id),
FOREIGN KEY (agent_type_id) REFERENCES jilo_agent_types(id)
);
CREATE TABLE jilo_agent_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
description TEXT,
endpoint TEXT
);
CREATE TABLE jilo_agent_checks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
status_code INTEGER,
response_time_ms INTEGER,
response_content TEXT,
FOREIGN KEY (agent_id) REFERENCES jilo_agents(id)
);
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGET NOT NULL,
time TEXT DEFAULT (DATETIME('now')),
scope TEXT NOT NULL,
message TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);

View File

@ -1,5 +1,5 @@
Package: jilo-web
Version: 0.2.1
Version: 0.2
Section: web
Priority: optional
Architecture: all

View File

@ -1,4 +1,4 @@
.TH JILO-WEB "8" "October 2024" "jilo-web 0.2.1"
.TH JILO-WEB "8" "August 2024" "jilo-web 0.2"
.SH NAME
jilo-web \- PHP frontent to jilo (jitsi logs observer) database.
.SH DESCRIPTION
@ -17,7 +17,7 @@ Written and maintained by Yasen Pramatarov <yasen@lindeas.com>
https://lindeas.com/jilo
.SH VERSION
0.2.1
0.2
.SH SEE ALSO
jilo(8), jilo-cli(8)

View File

@ -1,5 +1,5 @@
Name: jilo-web
Version: 0.2.1
Version: 0.2
Release: 1%{?dist}
Summary: Jitsi logs web observer
@ -54,8 +54,6 @@ cp %{sourcedir}/man-jilo.8 %{buildroot}/usr/share/man/man8/%{name}.8
/usr/share/man/man8/%{name}.8.gz
%changelog
* Thu Oct 17 2024 Yasen Pramatarov <yasen@lindeas.com> 0.2.1
- Build of upstream v0.2.1
* Sat Aug 31 2024 Yasen Pramatarov <yasen@lindeas.com> 0.2
- Build of upstream v0.2
* Thu Jul 25 2024 Yasen Pramatarov <yasen@lindeas.com> 0.1.1

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014-2024 Chart.js Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -8,18 +8,13 @@
* License: GPLv2
* Project URL: https://lindeas.com/jilo
* Year: 2024
* Version: 0.2.1
* Version: 0.2
*/
// we start output buffering and.
// flush it later only when there is no redirect
ob_start();
// sanitize all input vars that may end up in URLs or forms
require '../app/helpers/sanitize.php';
require '../app/helpers/errors.php';
// error reporting, comment out in production
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
@ -28,26 +23,15 @@ error_reporting(E_ALL);
// list of available pages
// edit accordingly, add 'pages/PAGE.php'
$allowed_urls = [
'dashboard',
'conferences',
'participants',
'components',
'graphs',
'latest',
'agents',
'profile',
'config',
'status',
'logs',
'help',
'front',
'login',
'logout',
'register',
'profile',
'config',
'conferences',
'participants',
'components',
];
// cnfig file
@ -78,49 +62,45 @@ $app_root = $config['folder'];
session_name('jilo');
session_start();
if (isset($_REQUEST['page'])) {
$page = $_REQUEST['page'];
} else {
$page = 'front';
}
if (isset($_REQUEST['item'])) {
$item = $_REQUEST['item'];
} else {
$item = '';
}
// check if logged in
unset($currentUser);
unset($user);
if (isset($_COOKIE['username'])) {
if ( !isset($_SESSION['username']) ) {
$_SESSION['username'] = $_COOKIE['username'];
}
$currentUser = htmlspecialchars($_SESSION['username']);
$user = htmlspecialchars($_SESSION['username']);
}
// redirect to login
if ( !isset($_COOKIE['username']) && ($page !== 'login' && $page !== 'register') ) {
header('Location: ' . htmlspecialchars($app_root) . '?page=login');
header('Location: index.php?page=login');
exit();
}
// connect to db of Jilo Web
require '../app/classes/database.php';
require '../app/helpers/database.php';
$dbWeb = connectDB($config);
// start logging
require '../app/classes/log.php';
include '../app/helpers/logs.php';
$logObject = new Log($dbWeb);
$user_IP = getUserIP();
// get platforms details
require '../app/classes/platform.php';
$platformObject = new Platform($dbWeb);
$platformsAll = $platformObject->getPlatformDetails();
// by default we connect ot the first configured platform
if ($platform_id == '') {
$platform_id = $platformsAll[0]['id'];
// we use 'notice' for all non-critical messages and 'error' for errors
if (isset($_SESSION['notice'])) {
$notice = $_SESSION['notice'];
}
if (isset($_SESSION['error'])) {
$error = $_SESSION['error'];
}
$platformDetails = $platformObject->getPlatformDetails($platform_id);
// init user functions
require '../app/classes/user.php';
include '../app/helpers/profile.php';
$userObject = new User($dbWeb);
// by default we connect ot the first configured platform
$platform_id = $_REQUEST['platform'] ?? '0';
// page building
if (in_array($page, $allowed_urls)) {
// logout is a special case, as we can't use session vars for notices
if ($page == 'logout') {
@ -130,58 +110,33 @@ if ($page == 'logout') {
setcookie('username', "", time() - 100, $config['folder'], $config['domain'], isset($_SERVER['HTTPS']), true);
$notice = "You were logged out.<br />You can log in again.";
$user_id = $userObject->getUserId($currentUser)[0]['id'];
$logObject->insertLog($user_id, "Logout: User \"$currentUser\" logged out. IP: $user_IP", 'user');
include '../app/templates/page-header.php';
include '../app/templates/page-menu.php';
include '../app/templates/block-message.php';
include '../app/pages/login.php';
// all other normal pages
} else {
// if user is logged in, we need user details and rights
if (isset($currentUser)) {
$user_id = $userObject->getUserId($currentUser)[0]['id'];
$userDetails = $userObject->getUserDetails($user_id);
$userRights = $userObject->getUserRights($user_id);
$userTimezone = isset($userDetails[0]['timezone']) ? $userDetails[0]['timezone'] : 'UTC'; // Default to UTC if no timezone is set
// If by error a logged in user requests the login page
if ($page === 'login') {
header('Location: ' . htmlspecialchars($app_root));
exit();
}
// check if the Jilo Server is running
require '../app/classes/server.php';
$serverObject = new Server($dbWeb);
$server_host = '127.0.0.1';
$server_port = '8080';
$server_endpoint = '/health';
$server_status = $serverObject->getServerStatus($server_host, $server_port, $server_endpoint);
if (!$server_status) {
$error = 'The Jilo Server is not running. Some data may be old and incorrect.';
}
}
// page building
include '../app/templates/page-header.php';
include '../app/templates/page-menu.php';
include '../app/templates/block-message.php';
if (isset($currentUser)) {
if (isset($user)) {
include '../app/templates/page-sidebar.php';
}
if (in_array($page, $allowed_urls)) {
// all normal pages
include "../app/pages/{$page}.php";
}
// the page is not in allowed urls, loading front page
} else {
// the page is not in allowed urls, loading "not found" page
include '../app/templates/error-notfound.php';
$error = 'The page "' . $page . '" is not found';
include '../app/templates/page-header.php';
include '../app/templates/page-menu.php';
include '../app/templates/block-message.php';
if (isset($user)) {
include '../app/templates/page-sidebar.php';
}
include '../app/pages/front.php';
}
// end with the footer
include '../app/templates/page-footer.php';
// flush the output buffer and show the page

View File

@ -1,3 +0,0 @@
<?php
phpinfo();
?>

View File

@ -1,218 +0,0 @@
function fetchData(agent_id, url, endpoint, jwtToken, force = false) {
// FIXME make use of force variable
let counter = 0;
const loadCacheButton = document.getElementById('agent' + agent_id + '-cache');
const clearCacheButton = document.getElementById('agent' + agent_id + '-clear');
const resultElement = document.getElementById("result" + agent_id);
const cacheInfoElement = document.getElementById("cacheInfo" + agent_id);
// Show loading text
resultElement.innerHTML = "Loading... (0 seconds)";
// Create an interval to update the counter every second
const intervalId = setInterval(() => {
counter++;
resultElement.innerHTML = `Loading... (${counter} seconds)`;
}, 1000);
// Create an AJAX request
var xhr = new XMLHttpRequest();
const agentUrl = url + endpoint;
// DEBUG show the requested URL for debugging purpose
//console.log("Requesting URL:", agentUrl);
// Handle invalid URL error
try {
xhr.open("POST", agentUrl, true);
} catch (e) {
clearInterval(intervalId); // Stop the counter on error
resultElement.innerHTML = `Error: Invalid URL ${agentUrl}<br />` + e.message;
return; // Exit the function early
}
// send the token
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.setRequestHeader("Authorization", "Bearer " + jwtToken);
// Set a timeout in milliseconds (10 seconds = 10000 ms)
xhr.timeout = 10000;
// Handle the request state change
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
clearInterval(intervalId); // Stop the counter when the request completes
clearTimeout(requestTimeout); // Clear the timeout if response is received
if (xhr.status === 200) {
try {
// Parse and display the result
let result = JSON.parse(xhr.responseText);
// DEBUG display the result
//console.log(xhr.responseText);
if (result.error) {
resultElement.innerHTML = "Error: " + result.error;
} else {
// show the result in the html
resultElement.innerHTML = JSON.stringify(result, null, 2);
// we don't cache the /status
if (endpoint !== '/status') {
// get the cache timestamp from the session
const now = Date.now();
const cacheTimestamp = new Date(now);
// display the cache retrieval date and time
const formattedDate = cacheTimestamp.toLocaleDateString();
const formattedTime = cacheTimestamp.toLocaleTimeString();
cacheInfoElement.style.display = '';
cacheInfoElement.innerHTML = `cache refreshed on ${formattedDate} at ${formattedTime}`;
// show the cache buttons
loadCacheButton.disabled = false;
loadCacheButton.style.display = '';
clearCacheButton.disabled = false;
clearCacheButton.style.display = '';
// send the result to PHP to store in session
saveResultToSession(result, agent_id);
}
}
} catch (e) {
// Display the error
resultElement.innerHTML = "Error: Response is not a valid JSON.<br />Response: " + xhr.responseText;
console.error("error:", e);
}
} else {
resultElement.innerHTML = `Error: Unable to fetch data from ${agentUrl}<br />Status Code: ${xhr.status}<br />Status Text: ${xhr.statusText}<br />Response: ${xhr.responseText}`;
}
}
};
// Handle network-level errors (e.g., connection refused)
xhr.onerror = function() {
clearInterval(intervalId); // Stop the counter on error
resultElement.innerHTML = `Network Error:<br />Unable to connect to ${agentUrl}<br />Check network connection or try again later.`;
};
// Handle the timeout event
xhr.ontimeout = function() {
clearInterval(intervalId); // Stop the counter on timeout
resultElement.innerHTML = "Request timed out. Please try again.";
};
// Additional manual timeout
var requestTimeout = setTimeout(function() {
if (xhr.readyState !== 4) {
xhr.abort(); // Abort the request if still pending after timeout
clearInterval(intervalId); // Stop the counter
resultElement.innerHTML = "Request timed out.";
}
}, 10000); // 10 seconds
// Send the AJAX request, with force flag
xhr.send("action=fetch&agent_id=" + agent_id + "&force=" + (force ? 'true' : 'false'));
// // If the request finishes quickly, set this up to show at least 1 second delay
// setTimeout(function() {
// if (counter === 0) {
// counter++;
// resultElement.innerHTML = `Loading... (${counter} seconds)`;
// }
// }, 1000); // Simulate a minimum 1 second delay for testing
}
// load the result from cache
function loadCache(agent_id) {
const resultElement = document.getElementById("result" + agent_id);
const cacheInfoElement = document.getElementById("cacheInfo" + agent_id);
resultElement.innerHTML = "Loading cached data...";
// Fetch the cached data from PHP
var xhr = new XMLHttpRequest();
xhr.open("GET", "static/loadcache.php?agent="+agent_id, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
let response = JSON.parse(xhr.responseText);
if (response.status === 'success') {
// Display the cached data
resultElement.innerHTML = JSON.stringify(response.data, null, 2);
// Get the cache timestamp from the session
const cacheTimestamp = new Date(response.cache_time * 1000);
// Display the cache retrieval date and time
const formattedDate = cacheTimestamp.toLocaleDateString();
const formattedTime = cacheTimestamp.toLocaleTimeString();
cacheInfoElement.innerHTML = `cache retrieved on ${formattedDate} at ${formattedTime}`;
} else {
resultElement.innerHTML = "No cached data found.";
cacheInfoElement.innerHTML = "";
}
} catch (e) {
resultElement.innerHTML = "Error loading cached data.";
console.error("error:", e);
}
} else {
resultElement.innerHTML = `Error: Unable to load cache. Status code: ${xhr.status}`;
}
}
};
xhr.onerror = function() {
resultElement.innerHTML = "Network error occurred while fetching the cached data.";
};
xhr.send();
}
// clear cache
function clearCache(agent_id) {
const loadCacheButton = document.getElementById('agent' + agent_id + '-cache');
const clearCacheButton = document.getElementById('agent' + agent_id + '-clear');
const cacheInfoElement = document.getElementById('cacheInfo' + agent_id);
const resultElement = document.getElementById("result" + agent_id);
saveResultToSession(null, agent_id);
// hides the cache buttons
// FIXME add a check whether the saveResult function was successful
loadCacheButton.disabled = true;
loadCacheButton.style.display = 'none';
clearCacheButton.disabled = true;
clearCacheButton.style.display = 'none';
cacheInfoElement.innerHTML = '';
cacheInfoElement.style.display = 'none';
resultElement.innerHTML = 'click a button to to display data from the agent.';
}
// we send result to PHP session, to be available to the whole app
function saveResultToSession(result, agent_id) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "?page=agents&agent="+agent_id, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("Data saved to session successfully.");
}
};
xhr.onerror = function() {
console.error("Error saving data to session.");
};
xhr.send(JSON.stringify(result));
}

View File

@ -1,10 +1,3 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.menu-container {
display: flex;
justify-content: space-between;
@ -32,7 +25,7 @@ html, body {
color: white;
}
.menu-left li a, .menu-left li span, .menu-right li a {
.menu-left li a, .menu-right li a {
display: block;
color: white;
text-align: center;
@ -140,8 +133,7 @@ html, body {
.main-content {
flex-grow: 1;
transition: width 0.5s ease;
margin-bottom: 50px;
/* width: 80%;*/
width: 80%;
}
.main-content.expanded {
width: calc(70% + 250px);
@ -151,7 +143,7 @@ html, body {
}
.logo {
height: 36px;
height: 35px;
margin-top: 10px;
}
@ -165,39 +157,3 @@ html, body {
.sidebar-content a {
text-decoration: none;
}
.pagination {
font-size: 0.66em;
text-align: center;
display: block;
}
.pagination span {
margin-left: 5px;
margin-right: 5px;
}
.th-time {
width: 200px;
}
.results {
text-align: left;
font-family: monospace;
white-space: pre-wrap;
background-color: #f4f4f4;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
overflow-x: auto;
}
.table-results, .filter-results {
font-size: 0.85em;
}
.button.active {
background-color: lightgray;
border: 1px solid gray;
border-radius: 4px;
}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
.th-time {
width: 200px;
}

View File

@ -1,71 +0,0 @@
/* profile */
.scroll-box {
width: 100%;
height: 250px;
padding: 10px;
border: 0;
background-color: #f8f9fa;
white-space: pre-wrap;
overflow-y: scroll;
resize: none;
font-family: inherit;
font-size: 14px;
user-select: text;
cursor: default;
outline: none;
box-shadow: none;
}
/* avatars */
.avatar-container {
position: relative;
text-align: center;
}
.avatar-img {
width: 200px;
height: 200px;
border-radius: 50%;
object-fit: cover; /* Ensures proper cropping of image */
border: 3px solid #ccc;
}
.avatar-wrapper {
position: relative;
display: inline-block;
}
.avatar-btn {
position: absolute;
bottom: 10px; /* Adjust this value as needed */
/* background: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
color: white;
border: none;
border-radius: 4px;
padding: 10px;
}
.avatar-btn-container {
position: absolute;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px; /* Space between buttons */
}
.avatar-btn-select,
.avatar-btn-remove {
/* background: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
color: white;
border: none;
border-radius: 4px;
padding: 10px;
}
.avatar-btn-select {
width: 50px;
left: -55px;
}
.avatar-btn-remove {
width: 50px;
left: 5px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

View File

@ -1,24 +0,0 @@
<?php
session_name('jilo');
session_start();
$agent = $_GET['agent'];
// Check if cached data exists in session
if (isset($_SESSION["agent{$agent}_cache"])) {
// return status, the data, and caching time - in JSON
echo json_encode([
'status' => 'success',
'data' => $_SESSION["agent{$agent}_cache"],
// we store cache time in the session
// FIXME may need to move to file cache
'cache_time' => $_SESSION["agent{$agent}_cache_time"] ?? time()
]);
} else {
// If no cached data exists
echo json_encode(['status' => 'error', 'message' => 'No cached data found']);
}
?>

View File

@ -3,7 +3,6 @@ document.addEventListener('DOMContentLoaded', function () {
var sidebar = document.getElementById('sidebar');
var mainContent = document.getElementById('mainContent');
var toggleButton = document.getElementById('toggleSidebarButton');
var timeNow = document.getElementById('time_now');
// update localStorage based on the current state
function updateStorage() {
@ -19,13 +18,11 @@ document.addEventListener('DOMContentLoaded', function () {
toggleButton.textContent = ">>";
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
timeNow.style.display = 'none';
} else {
toggleButton.value = "<<";
toggleButton.textContent = "<<";
sidebar.classList.remove('collapsed');
mainContent.classList.remove('expanded');
timeNow.style.display = 'block';
}
}
@ -41,11 +38,9 @@ document.addEventListener('DOMContentLoaded', function () {
if (toggleButton.value === ">>") {
toggleButton.value = "<<";
toggleButton.textContent = "<<";
timeNow.style.display = 'block';
} else {
toggleButton.value = ">>";
toggleButton.textContent = ">>";
timeNow.style.display = 'none';
}
// Update with the new state