Compare commits

...

78 Commits
v0.1.1 ... v0.2

Author SHA1 Message Date
Yasen Pramatarov 7d85c9181d Prepares for releasing v0.2 2024-08-31 20:24:12 +03:00
Yasen Pramatarov f88fc0f819 Adds switching between raw config files and only active config lines 2024-08-31 19:50:58 +03:00
Yasen Pramatarov 54d6ce2ec4 Updates CHANGELOG - config.js & interface_config.js 2024-08-30 13:03:13 +03:00
Yasen Pramatarov 0bab284e99 Adds interface_config.js capability 2024-08-29 13:01:46 +03:00
Yasen Pramatarov cf7d417193 Adds config.js capability 2024-08-28 12:59:13 +03:00
Yasen Pramatarov 1780233778 Adds Jitsi URL for loading the config.js and doing checks 2024-08-27 21:18:27 +03:00
Yasen Pramatarov 666f3ca98b Fixes adding, editing and deleting a platform 2024-08-26 21:19:21 +03:00
Yasen Pramatarov 1ba32d86f7 Move the platform editing to config page 2024-08-25 13:24:48 +03:00
Yasen Pramatarov d2213ca3e9 Adds "platform_id" to all links 2024-08-24 13:06:00 +03:00
Yasen Pramatarov c83baeee2f Moves all SQL into the respective classes 2024-08-23 12:44:31 +03:00
Yasen Pramatarov cd7b78a2e5 Fixe SQL 2024-08-22 11:59:48 +03:00
Yasen Pramatarov d854473a06 Moving SQL into classes 2024-08-21 22:11:28 +03:00
Yasen Pramatarov bf197ae96b Fixes regexps 2024-08-20 12:58:55 +03:00
Yasen Pramatarov 2bbc4af068 Fixes error handling 2024-08-19 13:42:16 +03:00
Yasen Pramatarov 1cda74cd50 Adds platform editing code 2024-08-19 13:25:09 +03:00
Yasen Pramatarov 3abafe2de7 Platforms edit code 2024-08-18 22:12:45 +03:00
Yasen Pramatarov 3a5eed933e Iniial edit platform code 2024-08-18 22:06:24 +03:00
Yasen Pramatarov 909fbc2626 Better error reporting 2024-08-17 11:20:08 +03:00
Yasen Pramatarov 09667920a2 Updates CHANGELOG 2024-08-17 10:02:55 +03:00
Yasen Pramatarov ea4b85ddf7 Initial support for multiple Jitsi platforms 2024-08-17 10:01:48 +03:00
Yasen Pramatarov 8ca9643fc2 Fixes install script 2024-08-16 09:35:21 +03:00
Yasen Pramatarov b02d72b29d Adds app root in links 2024-08-15 10:31:26 +03:00
Yasen Pramatarov e84c880289 Apache fixes 2024-08-14 10:11:36 +03:00
Yasen Pramatarov 1df19d9609 Updates CHANGELOG 2024-08-13 23:01:31 +03:00
Yasen Pramatarov a6cf2fe99b Updates config file 2024-08-13 22:59:11 +03:00
Yasen Pramatarov f796e100f8 Fixes config rendering of nested config arrays 2024-08-13 22:46:56 +03:00
Yasen Pramatarov 8eed6afd46 Fixes directory structure 2024-08-13 17:53:52 +03:00
Yasen Pramatarov 2cdfbe6e86 Updates config file 2024-08-13 14:24:56 +03:00
Yasen Pramatarov 9d3a4557c1 Updates README to match new folder structure 2024-08-13 14:16:15 +03:00
Yasen Pramatarov acd5ff0a23 Separates app code from public files. 2024-08-12 14:12:24 +03:00
Yasen Pramatarov 9a3024cfa6 Fixes error in database selection in helper function 2024-08-11 13:23:06 +03:00
Yasen Pramatarov 20d9dc0e73 Removes old unused cookie code 2024-08-11 13:15:18 +03:00
Yasen Pramatarov 8e123cf5f2 Adds time range helper function 2024-08-11 13:13:59 +03:00
Yasen Pramatarov 8765fe22c0 Updates CHANGELOG 2024-08-10 21:59:15 +03:00
Yasen Pramatarov e6d6a10795 Fixes error in database selection 2024-08-10 21:57:41 +03:00
Yasen Pramatarov aeb837fee5 Adds database helper functions 2024-08-10 21:42:44 +03:00
Yasen Pramatarov 91aac20998 Fixes error in JICOFO conferences stats 2024-08-09 12:17:05 +03:00
Yasen Pramatarov c915bcba9e Monthly widget fixes 2024-08-09 11:53:58 +03:00
Yasen Pramatarov 0f868f07e2 monthly stats widget fixes 2024-08-08 11:49:10 +03:00
Yasen Pramatarov 3943ba49b6 Fixes sidebar collapsing 2024-08-07 17:41:56 +03:00
Yasen Pramatarov 8a4816a6ae Updates CHANGELOG 2024-08-07 12:08:29 +03:00
Yasen Pramatarov 4b783a6b63 Adds sidebar icons 2024-08-07 12:03:54 +03:00
Yasen Pramatarov 6191d44944 HTML fixes 2024-08-07 11:16:54 +03:00
Yasen Pramatarov 838b8ea9f2 HTML fixes 2024-08-07 11:13:55 +03:00
Yasen Pramatarov 15c97859a4 Cleans up login and register pages. 2024-08-06 10:40:52 +03:00
Yasen Pramatarov 6199dad2c6 Design fixes 2024-08-06 09:56:27 +03:00
Yasen Pramatarov cb040ee408 Fixes logo 2024-08-06 09:32:14 +03:00
Yasen Pramatarov 2aaf2ac03f Adds logo 2024-08-05 22:25:32 +03:00
Yasen Pramatarov 27304513d8 Fixes sliding sidebar 2024-08-05 22:24:35 +03:00
Yasen Pramatarov 0cfa9d811e Fixes sidebar sliding 2024-08-05 20:21:34 +03:00
Yasen Pramatarov 54b998a1a8 Fixes sidebar sliding 2024-08-05 19:53:24 +03:00
Yasen Pramatarov 77e674bab5 Fixes sidebar 2024-08-05 17:08:13 +03:00
Yasen Pramatarov b6fc4a995d Fixes sidebar sliding 2024-08-05 17:02:35 +03:00
Yasen Pramatarov ca95f3c27e Fixes sidebar sliding 2024-08-04 16:33:41 +03:00
Yasen Pramatarov 22e502c90c Fixes sidebar 2024-08-03 16:25:42 +03:00
Yasen Pramatarov ae7929b4e1 Initial version of sidebar 2024-08-02 16:07:50 +03:00
Yasen Pramatarov 80454de3f2 UpdateS CHANGELOG 2024-08-01 11:24:44 +03:00
Yasen Pramatarov 8455a98ec5 Searches for default config file 2024-08-01 11:20:31 +03:00
Yasen Pramatarov 5d240b1dd8 Adds login and registration messages and configs 2024-08-01 11:12:54 +03:00
Yasen Pramatarov 06ffde67f4 Fixes SQL 2024-08-01 10:52:39 +03:00
Yasen Pramatarov 8bbe4b6274 Fixes SQL 2024-08-01 10:18:33 +03:00
Yasen Pramatarov 001e6c5be7 Updates CHANGELOG 2024-07-31 21:55:26 +03:00
Yasen Pramatarov 55b53ef30a Adds participants to monthly stats widget 2024-07-31 21:54:30 +03:00
Yasen Pramatarov c011321403 Fixes SQL 2024-07-31 13:18:45 +03:00
Yasen Pramatarov 42f9738128 Adds widget for monthly conferences number 2024-07-30 12:54:29 +03:00
Yasen Pramatarov c0c072884f Cleans up html code from models and controllers. 2024-07-29 16:50:16 +03:00
Yasen Pramatarov 54ddc6577e Adds widgets to participants page 2024-07-29 16:38:44 +03:00
Yasen Pramatarov d9aaf97b81 HTML fixes 2024-07-29 16:10:35 +03:00
Yasen Pramatarov f76fd03794 Adds widgets to conferences page 2024-07-29 15:56:08 +03:00
Yasen Pramatarov 3e0729c6cc Adds widget to components page 2024-07-28 13:35:09 +03:00
Yasen Pramatarov 16b7627b60 Fixes front page widgets 2024-07-27 12:36:49 +03:00
Yasen Pramatarov c6e52df33a Updates CHANGELOG 2024-07-27 12:30:16 +03:00
Yasen Pramatarov 7d459cf508 Implements front page widgets 2024-07-27 12:29:28 +03:00
Yasen Pramatarov 350ba053e8 Initial version of widgets 2024-07-26 11:55:58 +03:00
Yasen Pramatarov efd7cd74a2 Updates README 2024-07-25 20:03:11 +03:00
Yasen Pramatarov c39e53906f Updates README 2024-07-25 19:58:14 +03:00
Yasen Pramatarov 5463ea0cee Updates licenses information. 2024-07-25 19:51:05 +03:00
Yasen Pramatarov ba9d14f5d1 Added jquery license 2024-07-25 19:49:10 +03:00
82 changed files with 2655 additions and 2045 deletions

View File

@ -7,16 +7,44 @@ All notable changes to this project will be documented in this file.
## Unreleased
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.1.1...HEAD
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.1.1...HEAD
- github: https://github.com/lindeas/jilo-web/compare/v0.1.1...HEAD
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.1.1...HEAD
- 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
---
## 0.2 - 2024-08-31
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.1.1...v0.2
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.1.1...v0.2
- github: https://github.com/lindeas/jilo-web/compare/v0.1.1...v0.2
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.1.1...v0.2
### Added
- Added collapsible front page widgets
- Added widgets to conferences, participants and components pages
- Added front page widget for monthly conferences and participants number
- Added login/registration control and messages
- Added default config file locations
- Added left collapsible sidebar
- Added logo
- Added database helper functions
- Added support for multiple Jitsi platforms
- Added app environments "production" and "development"
- Added visualisation of config.js and interface_config.js per Jitsi platform
### Changed
- MVC design - models(classes folder), views(templates folder) and controllers(pages folder)
- Changed menus - categories on sidebar menu, jitsi platforms on top menu
- Moved all the app code outside of the public web folder
- Config file now can have nested arrays
### Fixed
- Fixed SQL when conferences start and end time are not explicitly clear
- Web design fixes
- Fixed install script
---

View File

@ -26,13 +26,15 @@ To see a demo install, go to https://work.lindeas.com/jilo-web-demo/
## version
Current version: **0.1.1** released on **2024-07-25**
Current version: **0.2** released on **2024-08-31**
## license
This project is licensed under the GNU General Public License version 2 (GPL-2.0). See LICENSE file.
Bootstrap is used in this project and is licensed under the MIT License. See bootstrap-license file
Bootstrap is used in this project and is licensed under the MIT License. See license-bootstrap file.
JQuery is used in this project and is licensed under the MIT License. See license-jquery file.
## requirements
@ -45,20 +47,29 @@ Bootstrap is used in this project and is licensed under the MIT License. See boo
You can install it in the following ways:
- download the latest release from the **"Releases"** section here
```bash
tar -xzf jilo-web_*.tgz
cd jilo-web/doc/
./install.sh
```
- clone the **git repo**:
```bash
git clone https://github.com/lindeas/jilo-web.git
cd jilo
cd jilo-web
./install.sh
```
- DEB and RPM packages are planned, but still unavailable
- DEB and RPM packages are planned, but not yet available. There are experimental build scripts in "packaging" folder.
## config
- edit index.php and set the system path to the jilo-web.conf.php
- edit jilo-web.conf.php and set all the variables correctly
- "database" is the sqlite db file for jilo-web itself, create it with `cat jilo-web.schema | sqlite3 jilo-web.db`
- "sqlite_file" is the sqlite db file for jilo-web itself, it goes to `app/jilo-web.db`, create it with
```bash
cd app
cat ../doc/jilo-web.schema | sqlite3 jilo-web.db
```
- "jilo_database" is the sqlite db file for jilo, with data from the Jitsi logs
## database
The database is an SQLite file. You also need the sqlite db from jilo (mysql/mariadb support is planned, but still unavailable).
The database is an SQLite file. You also need the sqlite db from jilo (mysql/mariadb support is planned, but not yet available).

View File

@ -10,13 +10,13 @@
- - ~~add bootstrap template~~
- - clean up the code to follow model-view--controller style
- - ~~clean up the code to follow model-view--controller style~~
- - no HTML inside PHP code
- - ~~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
- - ~~reduce try/catch usage, use it only for critical errors~~
- - move all SQL code in the model classes, one query per method

View File

@ -2,11 +2,9 @@
class Component {
private $db;
private $queries;
public function __construct($database) {
$this->db = $database->getConnection();
$this->queries = include('queries.php');
}
@ -21,11 +19,24 @@ class Component {
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['jitsi_components'];
// list of jitsi component events
$sql = "
SELECT
jitsi_component, loglevel, time, component_id, event_type, event_param
FROM
jitsi_components
WHERE
jitsi_component = %s
AND
component_id = %s
AND
(time >= '%s 00:00:00' AND time <= '%s 23:59:59')
ORDER BY
time";
$sql = sprintf($sql, $jitsi_component, $component_id, $from_time, $until_time);
$query = $this->db->prepare($sql);

View File

@ -0,0 +1,293 @@
<?php
class Conference {
private $db;
public function __construct($database) {
$this->db = $database->getConnection();
}
// search/list specific conference ID
public function conferenceById($conference_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// search for a conference by its ID for a time period (if given)
$sql = "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
c.conference_id = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
c.conference_id = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
$sql = sprintf($sql, $conference_id, $from_time, $until_time, $conference_id, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// search/list specific conference name
public function conferenceByName($conference_name, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// search for a conference by its name for a time period (if given)
$sql = "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
c.conference_name = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
c.conference_name = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
$sql = sprintf($sql, $conference_name, $from_time, $until_time, $conference_name, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// list of all conferences
public function conferencesAllFormatted($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// list of conferences for time period (if given)
// fields: component, duration, conference ID, conference name, number of participants, name count (the conf name is found), conference host
$sql = "
SELECT DISTINCT
c.jitsi_component,
(SELECT COALESCE
(
(SELECT ce.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,
c.conference_id,
c.conference_name,
(SELECT COUNT(pe.participant_id)
FROM participant_events pe
WHERE
pe.event_type = 'participant joining'
AND
pe.event_param = c.conference_id) AS participants,
name_counts.name_count,
c.conference_host
FROM
conferences c
JOIN (
SELECT
conference_name,
COUNT(*) AS name_count
FROM
conferences
GROUP BY
conference_name
) AS name_counts ON c.conference_name = name_counts.conference_name
JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE (ce.time >= '%s 00:00:00' AND ce.time <= '%s 23:59:59')
ORDER BY
c.id";
$sql = sprintf($sql, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// number of conferences
public function conferenceNumber($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// number of conferences for time period (if given)
// 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(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_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
(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);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
}
?>

View File

@ -0,0 +1,96 @@
<?php
class Config {
public function getPlatformDetails($config, $platform_id) {
$platformDetails = $config['platforms'][$platform_id];
return $platformDetails;
}
// loading the config.js
public function getPlatformConfigjs($platformDetails, $raw = false) {
// constructing the URL
$configjsFile = $platformDetails['jitsi_url'] . '/config.js';
// default content, if we can't get the file contents
$platformConfigjs = "The file $configjsFile can't be loaded.";
// ssl options
$contextOptions = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
];
$context = stream_context_create($contextOptions);
// get the file
$fileContent = @file_get_contents($configjsFile, false, $context);
if ($fileContent !== false) {
// when we need only uncommented values
if ($raw === false) {
// remove block comments
$platformConfigjs = preg_replace('!/\*.*?\*/!s', '', $fileContent);
// remove single-line comments
$platformConfigjs = preg_replace('/\/\/[^\n]*/', '', $platformConfigjs);
// remove empty lines
$platformConfigjs = preg_replace('/^\s*[\r\n]/m', '', $platformConfigjs);
// when we need the full file as it is
} else {
$platformConfigjs = $fileContent;
}
}
return $platformConfigjs;
}
// loading the interface_config.js
public function getPlatformInterfaceConfigjs($platformDetails, $raw = false) {
// constructing the URL
$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.";
// ssl options
$contextOptions = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
];
$context = stream_context_create($contextOptions);
// get the file
$fileContent = @file_get_contents($interfaceConfigjsFile, false, $context);
if ($fileContent !== false) {
// when we need only uncommented values
if ($raw === false) {
// remove block comments
$platformInterfaceConfigjs = preg_replace('!/\*.*?\*/!s', '', $fileContent);
// remove single-line comments
$platformInterfaceConfigjs = preg_replace('/\/\/[^\n]*/', '', $platformInterfaceConfigjs);
// remove empty lines
$platformInterfaceConfigjs = preg_replace('/^\s*[\r\n]/m', '', $platformInterfaceConfigjs);
// when we need the full file as it is
} else {
$platformInterfaceConfigjs = $fileContent;
}
}
return $platformInterfaceConfigjs;
}
}
?>

View File

@ -0,0 +1,79 @@
<?php
require '../app/helpers/errors.php';
class Database {
private $pdo;
public function __construct($options) {
// pdo needed
if ( !extension_loaded('pdo') ) {
$error = getError('PDO extension not loaded.');
}
// options check
if (empty($options['type'])) {
$error = getError('Database type is not set.');
}
// database type
switch ($options['type']) {
case 'sqlite':
$this->connectSqlite($options);
break;
case 'mysql' || 'mariadb':
$this->connectMysql($options);
break;
default:
$error = getError("Database type \"{$options['type']}\" is not supported.");
}
}
private function connectSqlite($options) {
// pdo_sqlite extension is needed
if (!extension_loaded('pdo_sqlite')) {
$error = getError('PDO extension for SQLite not loaded.');
}
// SQLite options
if (empty($options['dbFile']) || !file_exists($options['dbFile'])) {
$error = getError("SQLite database file \"{$dbFile}\" not found.");
}
// connect to SQLite
try {
$this->pdo = new PDO("sqlite:" . $options['dbFile']);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
$error = getError('SQLite connection failed: ', $e->getMessage());
}
}
private function connectMysql($options) {
// pdo_mysql extension is needed
if (!extension_loaded('pdo_mysql')) {
$error = getError('PDO extension for MySQL not loaded.');
}
// MySQL options
if (empty($options['host']) || empty($options['dbname']) || empty($options['user'])) {
$error = getError('MySQL connection data is missing.');
}
// Connect to MySQL
try {
$dsn = "mysql:host={$options['host']};port={$options['port']};dbname={$options['dbname']};charset=utf8";
$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: ', $config['environment'], $e->getMessage());
}
}
public function getConnection() {
return $this->pdo;
}
}
?>

View File

@ -0,0 +1,295 @@
<?php
class Participant {
private $db;
public function __construct($database) {
$this->db = $database->getConnection();
}
// search/list specific participant ID
public function conferenceByParticipantId($participant_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// list conferences where participant ID (endpoint_id) is found
$sql = "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
p.endpoint_id = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
participant_id = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
$sql = sprintf($sql, $participant_id, $from_time, $until_time, $participant_id, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// search/list specific participant name (stats_id)
public function conferenceByParticipantName($participant_name, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// list conferences where participant name (stats_id) is found
$sql = "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.event_type = 'stats_id' AND pe.event_param LIKE '%%%s%%'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_type = 'stats_id' AND event_param LIKE '%%%s%%'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
$sql = sprintf($sql, $participant_name, $from_time, $until_time, $participant_name, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// search/list specific participant IP
public function conferenceByParticipantIP($participant_ip, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// list conferences where participant IP is found
$sql = "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.event_type = 'pair selected' AND pe.event_param = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_type = 'pair selected' AND event_param = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time";
$sql = sprintf($sql, $participant_ip, $from_time, $until_time, $participant_ip, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// list of all participants
public function participantsAll($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// list all participants
$sql = "
SELECT DISTINCT
p.jitsi_component, p.endpoint_id, p.conference_id
FROM
participants p
JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59'
ORDER BY p.id";
$sql = sprintf($sql, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// number of participants
public function participantNumber($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
// number of participants for time period (if given)
$sql = "
SELECT COUNT(DISTINCT p.endpoint_id) as participants
FROM
participants p
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
(pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
AND pe.event_type = 'participant joining'";
$sql = sprintf($sql, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
}
?>

View File

@ -0,0 +1,38 @@
<?php
class Router {
private $routes = [];
public function add() {
$this->routes[$pattern] = $callback;
}
public function dispatch($url) {
// remove variables from url
$url = strtok($url, '?');
foreach ($this->routes as $pattern => $callback) {
if (preg_match('#^' . $pattern . '$#', $url, $matches)) {
// move any exact match
array_shift($matches);
return $this->invoke($callback, $matches);
}
}
// if there was no match at all, return 404
http_response_code(404);
echo '404 page not found';
}
private function invoke($callback, $params) {
list($controllerName, $methodName) = explode('@', $callback);
// $controllerClass = "\\App\\Controllers\\$controllerName";
$controllerClass = "../pages/$pageName";
$controller = new $controllerClass();
call_user_func_array([$controller, $methodName], $params);
}
}
?>

View File

@ -0,0 +1,57 @@
<?php
return [
//*******************
// edit to customize
//*******************
// domain for the web app
'domain' => 'localhost',
// subfolder for the web app, if any
'folder' => '/jilo-web/',
// set to false to disable new registrations
'registration_enabled' => true,
// will be displayed on login screen
'login_message' => '',
//*******************************************
// edit only if needed for tests or debugging
//*******************************************
// database
'db' => [
// DB type for the web app, currently only "sqlite" is used
'db_type' => 'sqlite',
// default is ../app/jilo-web.db
'sqlite_file' => '../app/jilo-web.db',
],
// system info
'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

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

@ -0,0 +1,67 @@
<?php
// connect to database
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 \"{$platform_id}\", database file \"{$dbFile}\"not found."));
}
$db = new Database([
'type' => 'sqlite',
'dbFile' => $dbFile,
]);
} catch (Exception $e) {
$error = getError('Error connecting to DB.', $e->getMessage());
include '../app/templates/block-message.php';
exit();
}
// connecting to a jilo-web database of the web app
} else {
// sqlite database file
if ($config['db']['db_type'] === 'sqlite') {
try {
$db = new Database([
'type' => $config['db']['db_type'],
'dbFile' => $config['db']['sqlite_file'],
]);
$pdo = $db->getConnection();
} catch (Exception $e) {
$error = getError('Error connecting to DB.', $e->getMessage());
include '../app/templates/block-message.php';
exit();
}
// mysql/mariadb database
} elseif ($config['db']['db_type'] === 'mysql' || $config['db']['db_type'] === 'mariadb') {
try {
$db = new Database([
'type' => $config['db']['db_type'],
'host' => $config['db']['sql_host'] ?? 'localhost',
'port' => $config['db']['sql_port'] ?? '3306',
'dbname' => $config['db']['sql_database'],
'user' => $config['db']['sql_username'],
'password' => $config['db']['sql_password'],
]);
$pdo = $db->getConnection();
} catch (Exception $e) {
$error = getError('Error connecting to DB.', $e->getMessage());
include '../app/templates/block-message.php';
exit();
}
// unknown database
} else {
$error = "Error: unknow database type \"{$config['db']['db_type']}\"";
include '../app/templates/block-message.php';
exit();
}
}
return $db;
}
?>

View File

@ -0,0 +1,14 @@
<?php
function getError($message, $error = '', $environment = null) {
global $config;
$environment = $config['environment'] ?? 'production';
if ($environment === 'production') {
return 'There was an unexpected error. Please try again.';
} else {
return $error ?: $message;
}
}
?>

View File

@ -0,0 +1,69 @@
<?php
// render config variables array
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) {
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;
if ($parent === 'platforms') {
$indent = 100;
}
if ($config_item === 'platforms') {
renderConfig($config_value, $indent, $platform, 'platforms');
} else {
renderConfig($config_value, $indent, $platform);
}
$indent = 0;
} else {
// if it's not array, just display it
?>
<div class="border col-md-8 text-start">
<?= htmlspecialchars($config_value ?? '')?>
<?= $platform ?>
</div>
<?php
}
?>
</div>
<?php
}
echo '</div>';
}
?>

View File

@ -0,0 +1,17 @@
<?php
$time_range_specified = false;
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($_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

@ -0,0 +1,84 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/component.php';
// connect to database
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 (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';
}
//
// Component events listings
//
// list of all component events (default)
$component = new Component($db);
// prepare the result
$search = $component->jitsiComponents($jitsi_component, $component_id, $from_time, $until_time);
if (!empty($search)) {
$components = array();
$components['records'] = array();
foreach ($search as $item) {
extract($item);
$component_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'loglevel' => $loglevel,
'time' => $time,
'component ID' => $component_id,
'event' => $event_type,
'param' => $event_param,
);
// populate the result array
array_push($components['records'], $component_record);
}
}
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'AllComponents';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
$widget['title'] = 'Jitsi events for component&nbsp;<strong>' . $_REQUEST['name'] . '</strong>';
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$widget['title'] = 'Jitsi events for component ID&nbsp;<strong>' . $_REQUEST['id'] . '</strong>';
} else {
$widget['title'] = 'Jitsi events for&nbsp;<strong>all components</strong>';
}
// widget records
if (!empty($components['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($components['records'][0]);
$widget['table_records'] = $components['records'];
}
// display the widget
include('../app/templates/widget.php');
?>

View File

@ -0,0 +1,133 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/conference.php';
// connect to database
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 (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$conferenceId = $_REQUEST['id'];
unset($_REQUEST['name']);
unset($conferenceName);
} elseif (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
unset($conferenceId);
$conferenceName = $_REQUEST['name'];
} else {
unset($conferenceId);
unset($conferenceName);
}
//
// Conference listings
//
$conference = new Conference($db);
// search and list specific conference ID
if (isset($conferenceId)) {
$search = $conference->conferenceById($conferenceId, $from_time, $until_time);
// search and list specific conference name
} elseif (isset($conferenceName)) {
$search = $conference->conferenceByName($conferenceName, $from_time, $until_time);
// list of all conferences (default)
} else {
$search = $conference->conferencesAllFormatted($from_time, $until_time);
}
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
// we don't have duration field, so we calculate it
if (!empty($start) && !empty($end)) {
$duration = gmdate("H:i:s", abs(strtotime($end) - strtotime($start)));
} else {
$duration = '';
}
// search and list specific conference ID
if (isset($conferenceId)) {
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// search and list specific conference name
} elseif (isset($conferenceName)) {
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// list of all conferences (default)
} else {
$conference_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'start' => $start,
'end' => $end,
'duration' => $duration,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'participants' => $participants,
'name count' => $name_count,
'conference host' => $conference_host
);
}
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'Conferences';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
$widget['title'] = 'Conferences with name matching "<strong>' . $_REQUEST['name'] . '"</strong>';
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$widget['title'] = 'Conference with ID "<strong>' . $_REQUEST['id'] . '"</strong>';
} else {
$widget['title'] = 'All conferences';
}
// widget records
if (!empty($conferences['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($conferences['records'][0]);
$widget['table_records'] = $conferences['records'];
}
// display the widget
include('../app/templates/widget.php');
?>

View File

@ -0,0 +1,140 @@
<?php
$action = $_REQUEST['action'] ?? '';
require_once '../app/classes/config.php';
require '../app/helpers/errors.php';
require '../app/helpers/config.php';
$configure = new Config();
// if a form is submitted, it's from the edit page
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// load the config file and initialize a copy
$content = file_get_contents($config_file);
$updatedContent = $content;
// new platform adding
if (isset($_POST['new']) && $_POST['new'] === 'true') {
$newPlatform = [
'name' => $_POST['name'],
'jitsi_url' => $_POST['jitsi_url'],
'jilo_database' => $_POST['jilo_database'],
];
// 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'];
$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'];
$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
);
}
// 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);
header("Location: $app_root?platform=$platform_id&page=config");
exit();
// no form submitted, show the templates
} else {
// $item - config.js and interface_config.js are special case; remote loaded files
switch ($item) {
case 'configjs':
$mode = $_REQUEST['mode'] ?? '';
$raw = ($mode === 'raw');
$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');
$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 file
default:
switch ($action) {
case 'add':
include('../app/templates/config-add-platform.php');
break;
case 'edit':
include('../app/templates/config-edit-platform.php');
break;
case 'delete':
include('../app/templates/config-delete-platform.php');
break;
default:
include('../app/templates/config-list.php');
}
}
}
?>

204
app/pages/front.php 100644
View File

@ -0,0 +1,204 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/conference.php';
require '../app/classes/participant.php';
// connect to database
require '../app/helpers/database.php';
$db = connectDB($config, 'jilo', $platform_id);
//
// dashboard widget listings
//
////
// monthly usage
$conference = new Conference($db);
$participant = new Participant($db);
// monthly conferences for the last year
$fromMonth = (new DateTime())->sub(new DateInterval('P1Y'));
$fromMonth->modify('first day of this month');
$thisMonth = new DateTime();
$from_time = $fromMonth->format('Y-m-d');
$until_time = $thisMonth->format('Y-m-d');
$widget['records'] = array();
// loop 1 year in the past
$i = 0;
while ($fromMonth < $thisMonth) {
$untilMonth = clone $fromMonth;
$untilMonth->modify('last day of this month');
$from_time = $fromMonth->format('Y-m-d');
$until_time = $untilMonth->format('Y-m-d');
$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');
// populate the records
$widget['records'][$i] = array(
'from_time' => $from_time,
'until_time' => $until_time,
'table_headers' => $month,
'conferences' => $searchConferenceNumber[0]['conferences'],
'participants' => $searchParticipantNumber[0]['participants'],
);
// move everything one month in future
$untilMonth->add(new DateInterval('P1M'));
$fromMonth->add(new DateInterval('P1M'));
$i++;
}
$time_range_specified = true;
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'LastYearMonths';
$widget['title'] = 'Conferences monthly stats for the last year';
$widget['collapsible'] = true;
$widget['collapsed'] = false;
$widget['filter'] = false;
if (!empty($searchConferenceNumber) && !empty($searchParticipantNumber)) {
$widget['full'] = true;
}
// display the widget
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);
$until_time = date('Y-m-d', time());
$time_range_specified = true;
// prepare the result
$search = $conference->conferencesAllFormatted($from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
// we don't have duration field, so we calculate it
if (!empty($start) && !empty($end)) {
$duration = gmdate("H:i:s", abs(strtotime($end) - strtotime($start)));
} else {
$duration = '';
}
$conference_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'start' => $start,
'end' => $end,
'duration' => $duration,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'participants' => $participants,
'name count' => $name_count,
'conference host' => $conference_host
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'LastDays';
$widget['title'] = 'Conferences for the last 2 days';
$widget['collapsible'] = true;
$widget['collapsed'] = false;
$widget['filter'] = false;
if (!empty($conferences['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($conferences['records'][0]);
$widget['table_records'] = $conferences['records'];
}
// display the widget
include('../app/templates/widget.php');
////
// last 10 conferences
$conference = new Conference($db);
// all time
$from_time = '0000-01-01';
$until_time = '9999-12-31';
$time_range_specified = false;
// number of conferences to show
$conference_number = 10;
// prepare the result
$search = $conference->conferencesAllFormatted($from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
$i = 0;
foreach ($search as $item) {
extract($item);
// we don't have duration field, so we calculate it
if (!empty($start) && !empty($end)) {
$duration = gmdate("H:i:s", abs(strtotime($end) - strtotime($start)));
} else {
$duration = '';
}
$conference_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'start' => $start,
'end' => $end,
'duration' => $duration,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'participants' => $participants,
'name count' => $name_count,
'conference host' => $conference_host
);
// populate the result array
array_push($conferences['records'], $conference_record);
// we only take the first 10 results
$i++;
if ($i == 10) break;
}
}
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'LastConferences';
$widget['title'] = 'The last ' . $conference_number . ' conferences';
$widget['collapsible'] = true;
$widget['collapsed'] = false;
$widget['filter'] = false;
if (!empty($conferences['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($conferences['records'][0]);
$widget['table_records'] = $conferences['records'];
}
// display the widget
include('../app/templates/widget.php');
?>

View File

@ -1,13 +1,17 @@
<?php
require_once 'classes/database.php';
require 'classes/user.php';
require_once '../app/classes/database.php';
require '../app/classes/user.php';
// clear the global error var before login
unset($error);
try {
$db = new Database($config['database']);
// connect to database
require '../app/helpers/database.php';
$db = connectDB($config);
$user = new User($db);
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
@ -31,18 +35,6 @@ try {
}
// set session lifetime and cookies
// FIXME: need to set this before session start (otherwise we need the separate cookie)
// ini_set('session.gc_maxlifetime', $gc_maxlifetime);
// session_set_cookie_params([
// 'lifetime' => $setcookie_lifetime,
// 'samesite' => 'Strict',
// 'httponly' => true,
// 'secure' => isset($_SERVER['HTTPS']),
// 'domain' => $config['domain'],
// 'path' => $config['folder']
// ]);
// session_start();
// FIXME we use separate cookie, because the above won't work
setcookie('username', $username, [
'expires' => $setcookie_lifetime,
'path' => $config['folder'],
@ -65,9 +57,14 @@ try {
}
}
} catch (Exception $e) {
$error = $e->getMessage();
$error = getError('There was an unexpected error. Please try again.', $e->getMessage());
}
include 'templates/form-login.php';
if (!empty($config['login_message'])) {
$notice = $config['login_message'];
include '../app/templates/block-message.php';
}
include '../app/templates/form-login.php';
?>

View File

@ -0,0 +1,142 @@
<?php
require_once '../app/classes/database.php';
require '../app/classes/participant.php';
// connect to database
require '../app/helpers/database.php';
$db = connectDB($config, 'jilo', $platform_id);
// specify time range
include '../app/helpers/time_range.php';
// participant id/name/IP are specified when searching specific participant(s)
// participant name - this is 'stats_id' in the db
// either id, name, OR IP - in that order
// we use $_REQUEST, so that both links and forms work
if (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$participantId = $_REQUEST['id'];
unset($_REQUEST['name']);
unset($participantName);
} elseif (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
unset($participantId);
$participantName = $_REQUEST['name'];
} elseif (isset($_REQUEST['ip']) && $_REQUEST['ip'] != '') {
unset($participantId);
$participantIp = $_REQUEST['ip'];
} else {
unset($participantId);
unset($participantName);
}
//
// Participant listings
//
$participant = new Participant($db);
// search and list specific participant ID
if (isset($participantId)) {
$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 = $participant->conferenceByParticipantName($participantName, $from_time, $until_time);
// search and list specific participant IP
} elseif (isset($participantIp)) {
$search = $participant->conferenceByParticipantIP($participantIp, $from_time, $until_time);
// list of all participants (default)
} else {
// prepare the result
$search = $participant->participantsAll($from_time, $until_time);
}
if (!empty($search)) {
$participants = array();
$participants['records'] = array();
foreach ($search as $item) {
extract($item);
// search and list specific participant ID
if (isset($participantId)) {
$participant_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// search and list specific participant name (stats_id)
} elseif (isset($participantName)) {
$participant_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// search and list specific participant IP
} elseif (isset($participantIp)) {
$participant_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// list of all participants (default)
} else {
$participant_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'participant ID' => $endpoint_id,
'conference ID' => $conference_id
);
}
// populate the result array
array_push($participants['records'], $participant_record);
}
}
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'Participants';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
$widget['title'] = 'Conferences with participant name (stats_id) matching "<strong>' . $_REQUEST['name'] . '"</strong>';
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$widget['title'] = 'Conference with participant ID matching "<strong>' . $_REQUEST['id'] . '"</strong>';
} elseif (isset($participantIp)) {
$widget['title'] = 'Conference with participant IP matching "<strong>' . $participantIp . '"</strong>';
} else {
$widget['title'] = 'All participants';
}
// widget records
if (!empty($participants['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($participants['records'][0]);
$widget['table_records'] = $participants['records'];
}
// display the widget
include('../app/templates/widget.php');
?>

View File

@ -0,0 +1,5 @@
<?php
include('../app/templates/widget-profile.php');
?>

View File

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

View File

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

View File

@ -0,0 +1,29 @@
<!-- 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" 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=\"" . $_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=\"" . $_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=\"" . $_REQUEST['ip'] . "\"" ?> maxlength="15" size="15" />
<?php } ?>
<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 -->

View File

@ -0,0 +1,50 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<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="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="name" class="form-label">name</label>
<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 />
<p class="text-start"><small>descriptive name for the platform</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="jitsi_url" class="form-label">Jitsi URL</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="jitsi_url" value="https://" required />
<p class="text-start"><small>URL of the Jitsi Meet (used for checks and for loading config.js)</small></p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="jilo_database" class="form-label">jilo_database</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="jilo_database" value="" required />
<p class="text-start"><small>path to the database file (relative to the app root)</small></p>
</div>
</div>
<input type="hidden" name="new" value="true" />
<br />
<a class="btn btn-secondary" href="<?= $app_root ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,29 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<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="<?= $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($config_item) ?>:</label>
</div>
<div class="col-md-8">
<div class="text-start"><?= htmlspecialchars($config_value ?? '')?></div>
<input type="hidden" name="<?= htmlspecialchars($config_item) ?>" value="<?= htmlspecialchars($config_value ?? '')?>" />
</div>
</div>
<?php } ?>
<br />
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>" />
<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="<?= $app_root ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-danger" value="Delete" />
</form>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,33 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<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="<?= $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($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($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 ($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 ($config_item === 'jilo_database') { ?>
<p class="text-start"><small>path to the database file (relative to the app root)</small></p>
<?php } ?>
</div>
</div>
<?php } ?>
<br />
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>" />
<a class="btn btn-secondary" href="<?= $app_root ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,22 @@
<!-- widget "config" -->
<div class="card text-center w-75 mx-lef">
<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['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="<?= $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="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs&mode=raw">view raw file contents</a></span>
<?php } ?>
</p>
<pre style="text-align: left;">
<?php
echo htmlspecialchars($platformConfigjs);
?>
</pre>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,22 @@
<!-- widget "config" -->
<div class="card text-center w-75 mx-lef">
<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['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="<?= $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="<?= $app_root ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs&mode=raw">view raw file contents</a></span>
<?php } ?>
</p>
<pre style="text-align: left;">
<?php
echo htmlspecialchars($platformInterfaceConfigjs);
?>
</pre>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,14 @@
<!-- widget "config" -->
<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">platform variables</p>
<?php
include '../app/helpers/render.php';
renderConfig($config, '0');
echo "\n";
?>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,20 @@
<!-- login form -->
<div class="card text-center w-50 mx-auto">
<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="<?= $app_root ?>?page=login">
<input type="text" name="username" placeholder="Username" required />
<br />
<input type="password" name="password" placeholder="Password" required />
<br />
<label for="remember_me">
<input type="checkbox" id="remember_me" name="remember_me" />
remember me
</label>
<br />&nbsp;<br />
<input type="submit" class="btn btn-primary" value="Login" />
</form>
</div>
</div>
<!-- /login form -->

View File

@ -0,0 +1,15 @@
<!-- registration form -->
<div class="card text-center w-50 mx-auto">
<h2 class="card-header">Register</h2>
<div class="card-body">
<p class="card-text">Enter credentials for registration:</p>
<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 />
<input type="submit" class="btn btn-primary" value="Register" />
</form>
</div>
</div>
<!-- /registration form -->

View File

@ -0,0 +1,23 @@
<?php if ($page !== 'login' && $page !== 'register') { ?>
<!-- /Main content -->
</div>
</div>
<?php } ?>
<!-- Footer -->
<div id="footer">Jilo Web <?= $config['version'] ?> &copy;2024 - web interface for <a href="https://lindeas.com/jilo">Jilo</a></div>
<!-- /Footer -->
</div>
<script src="static/sidebar.js"></script>
<script>
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});
</script>
</body>
</html>

View File

@ -2,12 +2,21 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="static/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="static/all.css">
<script src="static/bootstrap.min.js"></script>
<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>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css" integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
<script>
// restore sidebar state before the page is rendered
(function () {
var savedState = localStorage.getItem('sidebarState');
if (savedState === 'collapsed') {
document.documentElement.classList.add('sidebar-collapsed');
}
})();
</script>
<title>Jilo Web</title>
</head>

View File

@ -0,0 +1,36 @@
<!-- Menu -->
<div class="menu-container">
<ul class="menu-left">
<div class="container">
<div class="row">
<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;<?php echo $config['version']; ?></li>
<?php if ( isset($_SESSION['username']) ) { ?>
<?php foreach ($config['platforms'] as $index => $platform) { ?>
<li style="margin-right: 3px;">
<a style="background-color: #111;" href="?platform=<?= htmlspecialchars($index) ?>&page=front">
<?= htmlspecialchars($platform['name']) ?>
</a>
</li>
<?php } ?>
<?php } ?>
</ul>
<ul class="menu-right">
<?php if ( isset($_SESSION['username']) ) { ?>
<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="<?= $app_root ?>?page=login">login</a></li>
<li><a href="<?= $app_root ?>?page=register">register</a></li>
<?php } ?>
</ul>
</div>
<!-- /Menu -->

View File

@ -0,0 +1,63 @@
<div class="row">
<!-- Sidebar -->
<div class="col-md-3 sidebar-wrapper bg-light" id="sidebar">
<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">
<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>
<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="<?= $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="<?= $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>jilo-web config</small></p></li>
<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>
<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-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>
<!-- /Sidebar -->
<!-- Main content -->
<div class="col-md-9 main-content" id="mainContent">

View File

@ -0,0 +1,60 @@
<div class="row">
<?php if ($widget['collapsible'] === true) { ?>
<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'); } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a>
<?php } ?>
</div>
<!-- widget "<?= $widget['name']; ?>" -->
<div class="collapse show" id="collapse<?= $widget['name'] ?>">
<?php if ($time_range_specified) { ?>
<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-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="<?= $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 } ?>
</tr>
<tr>
<td>participants</td>
<?php foreach ($widget['records'] as $record) { ?>
<td><?php if (!empty($record['participants'])) { ?>
<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 } ?>
</tr>
</tbody>
</table>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /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

@ -0,0 +1,76 @@
<div class="row">
<?php if ($widget['collapsible'] === true) { ?>
<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'); } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a>
<?php } ?>
</div>
<!-- widget "<?= $widget['name']; ?>" -->
<div class="collapse show" id="collapse<?= $widget['name'] ?>">
<?php if ($time_range_specified) { ?>
<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-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="<?= $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="<?= $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="<?= $app_root ?>?platform=<?= $platform_id?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'component ID') { ?>
<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="<?= $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="<?= $app_root ?>?platform=<?= $platform_id?>&page=participants&ip=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } elseif ($key === 'component') { ?>
<td><a href="<?= $app_root ?>?platform=<?= $platform_id?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
<?php } else { ?>
<td><?= htmlspecialchars($column ?? '') ?></td>
<?php }
} ?>
</tr>
<?php } ?>
</tbody>
</table>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= $widget['name']; ?>" -->

View File

@ -1,15 +0,0 @@
server {
listen 80;
server_name $DOMAIN;
root $INSTALL_DIR;
index index.php;
location / {
try_files \$uri \$uri/ /index.php?\$args;
}
location ~ /\.ht {
deny all;
}
}

14
doc/.htaccess 100644
View File

@ -0,0 +1,14 @@
RewriteEngine On
# limit access to .htaccess
<Files .htaccess>
Order Allow,Deny
Deny from all
</Files>
# don't rewrite CSS, JS, etc.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# all other go to index.php
RewriteRule ^(.*)$ index.php?page=$1 [L,QSA]

View File

@ -4,4 +4,9 @@
CustomLog \${APACHE_LOG_DIR}/jilo-web_access.log combined
ErrorLog \${APACHE_LOG_DIR}/jilo-web_error.log
<Directory $INSTALL_DIR>
AllowOverride All
</Directory>
</VirtualHost>

22
doc/config.nginx 100644
View File

@ -0,0 +1,22 @@
server {
listen 80;
server_name $DOMAIN;
root $INSTALL_DIR;
index index.php;
location / {
try_files \$uri \$uri/ /index.php?\$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}

View File

@ -6,7 +6,7 @@ if [ "$EUID" -ne 0 ] && [ -z "$SUDO_USER" ]; then
exit 1
fi
VERSION=`grep version jilo-web.conf.php | cut -d "'" -f 4`
VERSION=`grep version ../app/config/jilo-web.conf.php | cut -d "'" -f 4`
# main install function
function install() {
@ -20,23 +20,24 @@ function install() {
WEB_DIR=${WEB_DIR:-jilo-web}
INSTALL_DIR="/opt/jilo-web/public_html"
APP_DIR="/opt/jilo-web/app"
DOC_DIR="/opt/jilo-web/doc"
ETC_DIR="/opt/jilo-web/etc"
mkdir -p $INSTALL_DIR
cp -r ./public_html/* $INSTALL_DIR
cp -r ../public_html/* $INSTALL_DIR
mkdir -p $DOC_DIR
cp CHANGELOG.md $DOC_DIR
cp LICENSE $DOC_DIR
cp README.md $DOC_DIR
cp TODO.md $DOC_DIR
cp config.apache $DOC_DIR
cp config.nginx $DOC_DIR
cp ../CHANGELOG.md $DOC_DIR
cp ../LICENSE $DOC_DIR
cp ../README.md $DOC_DIR
cp ../TODO.md $DOC_DIR
cp ../license-bootstrap $DOC_DIR
cp ../license-jquery $DOC_DIR
cp -r ../doc/ $DOC_DIR
mkdir -p $ETC_DIR
cp jilo-web.conf.php $ETC_DIR
cp jilo-web.schema $ETC_DIR
cp ../app/config/jilo-web.conf.php $ETC_DIR
#FIXME
#mkdir -p "jilo-web-$VERSION/usr/share/man/man8"

View File

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

View File

@ -1,4 +1,4 @@
.TH JILO-WEB "8" "July 2024" "jilo-web 0.1.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.1.1
0.2
.SH SEE ALSO
jilo(8), jilo-cli(8)

View File

@ -1,5 +1,5 @@
Name: jilo-web
Version: 0.1.1
Version: 0.2
Release: 1%{?dist}
Summary: Jitsi logs web observer
@ -54,6 +54,8 @@ cp %{sourcedir}/man-jilo.8 %{buildroot}/usr/share/man/man8/%{name}.8
/usr/share/man/man8/%{name}.8.gz
%changelog
* 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
- Build of upstream v0.1.1
* Wed Jul 12 2024 Yasen Pramatarov <yasen@lindeas.com> 0.1

View File

@ -1,11 +0,0 @@
<?php
$config = [
'domain' => 'localhost',
'folder' => '/jilo-web/',
'database' => '/home/yasen/work/code/git/lindeas-code/jilo-web/jilo-web.db',
'jilo_database' => '/home/yasen/work/code/git/lindeas-code/jilo/jilo.db',
'version' => '0.1.1',
];
?>

20
license-jquery 100644
View File

@ -0,0 +1,20 @@
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
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

@ -1,90 +0,0 @@
<?php
class Conference {
private $db;
private $queries;
public function __construct($database) {
$this->db = $database->getConnection();
$this->queries = include('queries.php');
}
// search/list specific conference ID
public function conferenceById($conference_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['conference_by_id'];
$sql = sprintf($sql, $conference_id, $from_time, $until_time, $conference_id, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// search/list specific conference name
public function conferenceByName($conference_name, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['conference_by_name'];
$sql = sprintf($sql, $conference_name, $from_time, $until_time, $conference_name, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// list of all conferences
public function conferencesAllFormatted($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['conferences_all_formatted'];
$sql = sprintf($sql, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
}
?>

View File

@ -1,34 +0,0 @@
<?php
class Database {
private $pdo;
public function __construct($dbFile) {
// pdo and pdo_sqlite needed
if ( !extension_loaded('pdo_sqlite') ) {
throw new Exception('PDO extension for SQLite not loaded.');
}
// database file check
if (empty($dbFile) || !file_exists($dbFile)) {
throw new Exception('Database file is not found.');
}
// connect to database
// FIXME: add mysql/mariadb option
try {
$this->pdo = new PDO("sqlite:" . $dbFile);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
throw new Exception('DB connection failed: ' . $e->getMessage());
}
}
public function getConnection() {
return $this->pdo;
}
}
?>

View File

@ -1,115 +0,0 @@
<?php
class Participant {
private $db;
private $queries;
public function __construct($database) {
$this->db = $database->getConnection();
$this->queries = include('queries.php');
}
// search/list specific participant ID
public function conferenceByParticipantId($participant_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['conference_by_participant_id'];
$sql = sprintf($sql, $participant_id, $from_time, $until_time, $participant_id, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// search/list specific participant name (stats_id)
public function conferenceByParticipantName($participant_name, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['participant_by_stats_id'];
$sql = sprintf($sql, $participant_name, $from_time, $until_time, $participant_name, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// search/list specific participant IP
public function conferenceByParticipantIP($participant_ip, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['participant_by_ip'];
$sql = sprintf($sql, $participant_ip, $from_time, $until_time, $participant_ip, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
// list of all conferences
public function participantsAll($from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['participants_all'];
$sql = sprintf($sql, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
}
?>

View File

@ -1,314 +0,0 @@
<?php
// all sql queries for the jilo database in one place
return [
// list of conferences for time period (if given)
// fields: component, duration, conference ID, conference name, number of participants, name count (the conf name is found), conference host
'conferences_all_formatted' => "
SELECT DISTINCT
c.jitsi_component,
(SELECT ce.time
FROM conference_events ce
WHERE
ce.conference_id = c.conference_id
AND
ce.conference_event = 'conference created')
AS start,
(SELECT ce.time
FROM conference_events ce
WHERE
ce.conference_id = c.conference_id
AND
ce.conference_event = 'conference expired')
AS end,
c.conference_id,
c.conference_name,
(SELECT COUNT(pe.participant_id)
FROM participant_events pe
WHERE
pe.event_type = 'participant joining'
AND
pe.event_param = c.conference_id) AS participants,
name_counts.name_count,
c.conference_host
FROM
conferences c
JOIN (
SELECT
conference_name,
COUNT(*) AS name_count
FROM
conferences
GROUP BY
conference_name
) AS name_counts ON c.conference_name = name_counts.conference_name
JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE (ce.time >= '%s 00:00:00' AND ce.time <= '%s 23:59:59')
ORDER BY
c.id;",
// search for a conference by its ID for a time period (if given)
'conference_by_id' => "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
c.conference_id = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
c.conference_id = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time;",
// search for a conference by its name for a time period (if given)
'conference_by_name' => "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
c.conference_name = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
c.conference_name = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time;",
// list all participants
'participants_all' => "
SELECT DISTINCT
p.jitsi_component, p.endpoint_id, p.conference_id
FROM
participants p
JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59'
ORDER BY p.id;",
// list conferences where participant ID (endpoint_id) is found
'conference_by_participant_id' => "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
p.endpoint_id = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
participant_id = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time;",
// list conferences where participant name (stats_id) is found
'participant_by_stats_id' => "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.event_type = 'stats_id' AND pe.event_param LIKE '%%%s%%'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_type = 'stats_id' AND event_param LIKE '%%%s%%'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time;",
// list conferences where participant IP is found
'participant_by_ip' => "
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.event_type = 'pair selected' AND pe.event_param = '%s'
AND (pe.time >= '%s 00:00:00' AND pe.time <= '%s 23:59:59')
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_type = 'pair selected' AND event_param = '%s'
AND (event_time >= '%s 00:00:00' AND event_time <= '%s 23:59:59')
ORDER BY
pe.time;",
// list of jitsi component events
'jitsi_components' => "
SELECT jitsi_component, loglevel, time, component_id, event_type, event_param
FROM
jitsi_components
WHERE
jitsi_component = %s
AND
component_id = %s
AND
(time >= '%s 00:00:00' AND time <= '%s 23:59:59')
ORDER BY
time;",
];
?>

View File

@ -8,9 +8,13 @@
* License: GPLv2
* Project URL: https://lindeas.com/jilo
* Year: 2024
* Version: 0.1.1
* Version: 0.2
*/
// we start output buffering and.
// flush it later only when there is no redirect
ob_start();
// error reporting, comment out in production
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
@ -31,26 +35,46 @@ $allowed_urls = [
];
// cnfig file
$config_file = '/home/yasen/work/code/git/lindeas-code/jilo-web/jilo-web.conf.php';
if (file_exists($config_file)) {
require_once $config_file;
// possible locations, in order of preference
$config_file_locations = [
__DIR__ . '/../app/config/jilo-web.conf.php',
__DIR__ . '/../jilo-web.conf.php',
'/srv/jilo-web/jilo-web.conf.php',
'/opt/jilo-web/jilo-web.conf.php'
];
$config_file = null;
// try to find the config file
foreach ($config_file_locations as $location) {
if (file_exists($location)) {
$config_file = $location;
break;
}
}
// if found, use it
if ($config_file) {
$config = require $config_file;
} else {
die('Config file not found');
}
$app_root = $config['folder'];
session_name('jilo');
session_start();
if (isset($_GET['page'])) {
$page = $_GET['page'];
} elseif (isset($_POST['page'])) {
$page = $_POST['page'];
if (isset($_REQUEST['page'])) {
$page = $_REQUEST['page'];
} else {
$page = 'front';
}
if (isset($_REQUEST['item'])) {
$item = $_REQUEST['item'];
} else {
$item = '';
}
// check if logged in
unset($user);
if (isset($_COOKIE['username'])) {
if ( !isset($_SESSION['username']) ) {
$_SESSION['username'] = $_COOKIE['username'];
@ -72,6 +96,9 @@ if (isset($_SESSION['error'])) {
$error = $_SESSION['error'];
}
// 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
@ -83,27 +110,37 @@ if (in_array($page, $allowed_urls)) {
setcookie('username', "", time() - 100, $config['folder'], $config['domain'], isset($_SERVER['HTTPS']), true);
$notice = "You were logged out.<br />You can log in again.";
include 'templates/header.php';
include 'templates/menu.php';
include 'templates/message.php';
include 'pages/login.php';
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 {
include 'templates/header.php';
include 'templates/menu.php';
include 'templates/message.php';
include "pages/{$page}.php";
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/{$page}.php";
}
// the page is not in allowed urls, loading front page
} else {
include 'templates/header.php';
include 'templates/menu.php';
include 'templates/message.php';
include 'pages/front.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';
}
include 'templates/footer.php';
include '../app/templates/page-footer.php';
// flush the output buffer and show the page
ob_end_flush();
// clear errors and notices before next page just in case
unset($_SESSION['error']);

View File

@ -1,150 +0,0 @@
<?php
require_once 'classes/database.php';
require 'classes/component.php';
// FIXME move thi sto a special function
$time_range_specified = false;
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($_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;
}
// jitsi component events list
// we use $_REQUEST, so that both links and forms work
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';
}
// connect to database
try {
$db = new Database($config['jilo_database']);
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
//
// Component events listings
//
// list of all component events (default)
//if ($jitsi_component) {
try {
$component = new Component($db);
// prepare the result
$search = $component->jitsiComponents($jitsi_component, $component_id, $from_time, $until_time);
if (!empty($search)) {
$components = array();
$components['records'] = array();
foreach ($search as $item) {
extract($item);
$component_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'loglevel' => $loglevel,
'time' => $time,
'component ID' => $component_id,
'event' => $event_type,
'param' => $event_param,
);
// populate the result array
array_push($components['records'], $component_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
// format the header message
echo "<div class=\"results-header\">\n";
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
echo "<div class=\"results-message\">Jitsi events for component <strong>" . $_REQUEST['name'] . "</strong>";
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
echo "<div class=\"results-message\">Jitsi events for component ID <br /><strong>" . $_REQUEST['id'] . "</strong>";
} else {
echo "<div class=\"results-message\">Jitsi events for <strong>all components</strong>";
}
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($components['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($components['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($components['records'] as $row) {
echo "\t\t\t<tr>\n";
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'component ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=components&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'component') {
echo "\t\t\t\t<td><a href=\"$app_root?page=components&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching Jitsi component events found.</p>';
}
echo "\n</div>\n";
//}
?>

View File

@ -1,359 +0,0 @@
<?php
require_once 'classes/database.php';
require 'classes/conference.php';
// FIXME move thi sto a special function
$time_range_specified = false;
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($_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;
}
// 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 (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$conference_id = $_REQUEST['id'];
unset($_REQUEST['name']);
unset($conference_name);
} elseif (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
unset($conference_id);
$conference_name = $_REQUEST['name'];
} else {
unset($conference_id);
unset($conference_name);
}
// connect to database
try {
$db = new Database($config['jilo_database']);
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
//
// Conference listings
//
// search and list specific conference ID
if (isset($conference_id)) {
try {
$conference = new Conference($db);
// prepare the result
$search = $conference->conferenceById($conference_id, $from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<div class=\"results-header\">\n";
echo "<div class=\"results-message\">Conferences with ID matching \"<strong>$conference_id</strong>\"";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
$stats_id = false;
$participant_ip = false;
if ($row['event'] === 'stats_id') $stats_id = true;
if ($row['event'] === 'pair selected') $participant_ip = true;
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'conference ID' && $column === $conference_id) {
echo "\t\t\t\t<td><strong>" . htmlspecialchars($column ?? '') . "</strong></td>\n";
} elseif ($key === 'conference name') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($stats_id && $key === 'parameter') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($participant_ip && $key === 'parameter') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&ip=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
// search and list specific conference ID
} elseif (isset($conference_name)) {
try {
$conference = new Conference($db);
// prepare the result
$search = $conference->conferenceByName($conference_name, $from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<div class=\"results-header\">\n";
echo "<div class=\"results-message\">Conferences with name matching \"<strong>$conference_name</strong>\"";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
$stats_id = false;
$participant_ip = false;
if ($row['event'] === 'stats_id') $stats_id = true;
if ($row['event'] === 'pair selected') $participant_ip = true;
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'conference name' && $column === $conference_name) {
echo "\t\t\t\t<td><strong>" . htmlspecialchars($column ?? '') . "</strong></td>\n";
} elseif ($key === 'conference ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'participant ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($stats_id && $key === 'parameter') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($participant_ip && $key === 'parameter') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&ip=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
// list of all conferences (default)
} else {
try {
$conference = new Conference($db);
// prepare the result
$search = $conference->conferencesAllFormatted($from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
// we don't have duration field, so we calculate it
if (!empty($start) && !empty($end)) {
$duration = gmdate("H:i:s", abs(strtotime($end) - strtotime($start)));
} else {
$duration = '';
}
$conference_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'start' => $start,
'end' => $end,
'duration' => $duration,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'participants' => $participants,
'name count' => $name_count,
'conference host' => $conference_host
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<div class=\"results-header\">\n";
echo "<div class=\"results-message\">All conferences";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'conference ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'conference name') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
}
?>

View File

@ -1,13 +0,0 @@
<?php ?>
<div>
<p>Jilo web configuration</p>
<ul>
<?php foreach ($config as $config_item=>$config_value) { ?>
<li><?php echo htmlspecialchars($config_item) . ': ' . htmlspecialchars($config_value ?? ''); ?></li>
<?php } ?>
</ul>
</div>

View File

@ -1,241 +0,0 @@
<?php
require_once 'classes/database.php';
require 'classes/conference.php';
// connect to database
try {
$db = new Database($config['jilo_database']);
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
//
// dashboard widget listings
//
// conferences in last 7 days
try {
$conference = new Conference($db);
// conferences for last 2 days
$from_time = date('Y-m-d', time() - 60 * 60 * 24 * 2);
$until_time = date('Y-m-d', time());
$time_range_specified = true;
// prepare the result
$search = $conference->conferencesAllFormatted($from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
// we don't have duration field, so we calculate it
if (!empty($start) && !empty($end)) {
$duration = gmdate("H:i:s", abs(strtotime($end) - strtotime($start)));
} else {
$duration = '';
}
$conference_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'start' => $start,
'end' => $end,
'duration' => $duration,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'participants' => $participants,
'name count' => $name_count,
'conference host' => $conference_host
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<a style=\"text-decoration: none;\" data-toggle=\"collapse\" href=\"#collapseLastDays\" role=\"button\" aria-expanded=\"true\" aria-controls=\"collapseLastDays\">";
echo "<div class=\"card bg-light card-body\">Conferences for the last 2 days</div></a>";
echo "<div class=\"collapse show\" id=\"collapseLastDays\">";
if ($time_range_specified) {
echo "<p class=\"m-3\">time period: <strong>$from_time - $until_time</strong></p>";
}
//// filters - time selection and sorting dropdowns
//include 'templates/results-filter.php';
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"thead-dark\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'conference ID' && $column === $conference_id) {
echo "\t\t\t\t<td><strong>" . htmlspecialchars($column ?? '') . "</strong></td>\n";
} elseif ($key === 'conference name') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
echo "</div>";
echo "<br />";
// last 10 conferences
try {
$conference = new Conference($db);
// all time
$from_time = '0000-01-01';
$until_time = '9999-12-31';
$time_range_specified = false;
// number of conferences to show
$conference_number = 10;
// prepare the result
$search = $conference->conferencesAllFormatted($from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
$i = 0;
foreach ($search as $item) {
extract($item);
// we don't have duration field, so we calculate it
if (!empty($start) && !empty($end)) {
$duration = gmdate("H:i:s", abs(strtotime($end) - strtotime($start)));
} else {
$duration = '';
}
$conference_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'start' => $start,
'end' => $end,
'duration' => $duration,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'participants' => $participants,
'name count' => $name_count,
'conference host' => $conference_host
);
// populate the result array
array_push($conferences['records'], $conference_record);
// we only take the first 10 results
$i++;
if ($i == 10) break;
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<a style=\"text-decoration: none;\" data-toggle=\"collapse\" href=\"#collapseLastConferences\" role=\"button\" aria-expanded=\"true\" aria-controls=\"collapseLastConferences\">";
echo "<div class=\"card bg-light card-body\">The last $conference_number conferences</div></a>";
echo "<div class=\"collapse show\" id=\"collapseLastConferences\">";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
//// filters - time selection and sorting dropdowns
//include 'templates/results-filter.php';
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'conference ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'conference name') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
echo "</div>";
?>

View File

@ -1,444 +0,0 @@
<?php
require_once 'classes/database.php';
require 'classes/participant.php';
// FIXME move thi sto a special function
$time_range_specified = false;
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($_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;
}
// participant id/name/IP are specified when searching specific participant(s)
// participant name - this is 'stats_id' in the db
// either id, name, OR IP - in that order
// we use $_REQUEST, so that both links and forms work
if (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$participant_id = $_REQUEST['id'];
unset($_REQUEST['name']);
unset($participant_name);
} elseif (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
unset($participant_id);
$participant_name = $_REQUEST['name'];
} elseif (isset($_REQUEST['ip']) && $_REQUEST['ip'] != '') {
unset($participant_id);
$participant_ip = $_REQUEST['ip'];
} else {
unset($participant_id);
unset($participant_name);
}
// connect to database
try {
$db = new Database($config['jilo_database']);
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
//
// Participant listings
//
// search and list specific participant ID
if (isset($participant_id)) {
try {
$participant = new Participant($db);
// prepare the result
$search = $participant->conferenceByParticipantId($participant_id, $from_time, $until_time, $participant_id, $from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<div class=\"results-header\">\n";
echo "<div class=\"results-message\">Conferences with participant ID matching \"<strong>$participant_id</strong>\"";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
$stats_id = false;
$participant_ip = false;
if ($row['event'] === 'stats_id') $stats_id = true;
if ($row['event'] === 'pair selected') $participant_ip = true;
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'participant ID' && $column === $participant_id) {
echo "\t\t\t\t<td><strong>" . htmlspecialchars($column ?? '') . "</strong></td>\n";
} elseif ($key === 'conference ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'conference name') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($stats_id && $key === 'parameter') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($participant_ip && $key === 'parameter') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&ip=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
// search and list specific participant name (stats_id)
} elseif (isset($participant_name)) {
try {
$participant = new Participant($db);
// prepare the result
$search = $participant->conferenceByParticipantName($participant_name, $from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<div class=\"results-header\">\n";
echo "<div class=\"results-message\">Conferences with participant name (stats_id) matching \"<strong>$participant_name</strong>\"";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope-\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'parameter' && $column === $participant_name) {
echo "\t\t\t\t<td><strong>" . htmlspecialchars($column ?? '') . "</strong></td>\n";
} elseif ($key === 'conference ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'conference name') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'participant ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
// search and list specific participant IP
} elseif (isset($participant_ip)) {
try {
$participant = new Participant($db);
// prepare the result
$search = $participant->conferenceByParticipantIP($participant_ip, $from_time, $until_time);
if (!empty($search)) {
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<div class=\"results-header\">\n";
echo "<div class=\"results-message\">Conferences with participant IP matching \"<strong>$participant_ip</strong>\"";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($conferences['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($conferences['records'][0]) as $header) {
echo "\t\t\t\t<th scope=\"col\">" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($conferences['records'] as $row) {
echo "\t\t\t<tr>\n";
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'parameter' && $column === $participant_ip) {
echo "\t\t\t\t<td><strong>" . htmlspecialchars($column ?? '') . "</strong></td>\n";
} elseif ($key === 'conference ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'conference name') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&name=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'participant ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching conferences found.</p>';
}
echo "\n</div>\n";
// list of all participants (default)
} else {
try {
$participant = new Participant($db);
// prepare the result
$search = $participant->participantsAll($from_time, $until_time);
if (!empty($search)) {
$participants = array();
$participants['records'] = array();
foreach ($search as $item) {
extract($item);
$participant_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'participant ID' => $endpoint_id,
'conference ID' => $conference_id,
);
// populate the result array
array_push($participants['records'], $participant_record);
}
}
} catch (Exception $e) {
$error = 'Error: ' . $e->getMessage();
include 'templates/message.php';
exit();
}
// display the result
echo "<div class=\"results-header\">\n";
echo "<div class=\"results-message\">All participants";
if ($time_range_specified) {
echo "<br />for the time period <strong>$from_time - $until_time</strong>";
}
echo "</div>\n\n";
// filters - time selection and sorting dropdowns
include 'templates/results-filter.php';
echo "</div>\n\n";
// results table
echo "<div class=\"mb-5\">\n";
if (!empty($participants['records'])) {
echo "\t<table class=\"table table-striped table-hover table-bordered\">\n";
echo "\t\t<thead class=\"table-secondary\">\n";
echo "\t\t\t<tr>\n";
// table headers
foreach (array_keys($participants['records'][0]) as $header) {
echo "\t\t\t\t<th>" . htmlspecialchars($header) . "</th>\n";
}
echo "\t\t\t</tr>\n";
echo "\t\t</thead>\n";
echo "\t\t<tbody>\n";
//table rows
foreach ($participants['records'] as $row) {
echo "\t\t\t<tr>\n";
// sometimes $column is empty, we make it '' then
foreach ($row as $key => $column) {
if ($key === 'participant ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=participants&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} elseif ($key === 'conference ID') {
echo "\t\t\t\t<td><a href=\"$app_root?page=conferences&id=" . htmlspecialchars($column ?? '') . "\">" . htmlspecialchars($column ?? '') . "</a></td>\n";
} else {
echo "\t\t\t\t<td>" . htmlspecialchars($column ?? '') . "</td>\n";
}
}
echo "\t\t\t</tr>\n";
}
echo "\t\t</tbody>\n";
echo "\t</table>\n";
} else {
echo '<p class="m-3">No matching participants found.</p>';
}
echo "\n</div>\n";
}
?>

View File

@ -1,11 +0,0 @@
<?php ?>
<div>
<p>Profile of <?= $user ?></p>
<ul>
<li>username: <?= $_SESSION['username'] ?></li>
</ul>
</div>

View File

@ -1,34 +0,0 @@
<?php
require_once 'classes/database.php';
require 'classes/user.php';
unset($error);
try {
$db = new Database($config['database']);
$user = new User($db);
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
$username = $_POST['username'];
$password = $_POST['password'];
// redirect to login
if ( $user->register($username, $password) ) {
$_SESSION['notice'] = "Registration successful.<br />You can log in now.";
header('Location: index.php');
exit();
// registration fail, redirect to login
} else {
$_SESSION['error'] = "Registration failed.";
header('Location: index.php');
exit();
}
}
} catch (Exception $e) {
$error = $e->getMessage();
}
include 'templates/message.php';
include 'templates/form-register.php';
?>

View File

@ -78,31 +78,6 @@
color: white;
}
/*
.results {
margin-bottom: 50px;
}
.results table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.results table, th, td {
border: 1px solid #ddd;
}
.results th, td {
padding: 8px;
text-align: left;
}
.results th {
background-color: #f2f2f2;
}
.results p {
text-align: center;
}
*/
.results-header {
display: flex;
justify-content: space-between;
@ -120,3 +95,65 @@
.widget {
border: 1px solid gray;
}
/* collapsing sidebar */
.toggle-sidebar-button {
position: absolute;
top: -10px;
right: -10px;
z-index: 100;
margin: 10px;
height: 22px;
width: 22px;
padding-left: 2px;
padding-top: 0px;
}
.sidebar-wrapper {
position: relative;
width: 275px;
transition: width 0.5s ease;
overflow-x: hidden;
}
.sidebar-wrapper.collapsed {
width: 3em;
}
.sidebar-collapsed .sidebar-wrapper {
width: 3em;
}
.sidebar-content {
width: 250px;
border: none;
}
.list-group-item i {
margin-right: 10px;
margin-left: -8px;
}
.main-content {
flex-grow: 1;
transition: width 0.5s ease;
width: 80%;
}
.main-content.expanded {
width: calc(70% + 250px);
}
.sidebar-collapsed .main-content {
width: calc(70% + 3em);
}
.logo {
height: 35px;
margin-top: 10px;
}
.logo-link {
border: 1px solid white;
margin-top: 2px;
margin-left: 2px;
background-color: lightseagreen;
}
.sidebar-content a {
text-decoration: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,49 @@
document.addEventListener('DOMContentLoaded', function () {
var sidebar = document.getElementById('sidebar');
var mainContent = document.getElementById('mainContent');
var toggleButton = document.getElementById('toggleSidebarButton');
// update localStorage based on the current state
function updateStorage() {
var isSidebarCollapsed = sidebar.classList.contains('collapsed');
localStorage.setItem('sidebarState', isSidebarCollapsed ? 'collapsed' : 'expanded');
}
// apply saved state
function applySavedState() {
var savedState = localStorage.getItem('sidebarState');
if (savedState === 'collapsed') {
toggleButton.value = ">>";
toggleButton.textContent = ">>";
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
} else {
toggleButton.value = "<<";
toggleButton.textContent = "<<";
sidebar.classList.remove('collapsed');
mainContent.classList.remove('expanded');
}
}
// Initialize
applySavedState();
toggleButton.addEventListener('click', function () {
// toggle sidebar and main content
sidebar.classList.toggle('collapsed');
document.documentElement.classList.toggle('sidebar-collapsed');
mainContent.classList.toggle('expanded');
// Toggle the value between ">>" and "<<"
if (toggleButton.value === ">>") {
toggleButton.value = "<<";
toggleButton.textContent = "<<";
} else {
toggleButton.value = ">>";
toggleButton.textContent = ">>";
}
// Update with the new state
updateStorage();
});
});

View File

@ -1,8 +0,0 @@
<div id="footer">Jilo Web <?= $config['version'] ?> &copy;2024 - web interface for <a href="https://lindeas.com/jilo">Jilo</a></div>
</div>
</body>
</html>

View File

@ -1,17 +0,0 @@
<h2>Login</h2>
<div class="login-form">
<form method="POST" action="?page=login">
<input type="text" name="username" placeholder="Username" required />
<br />
<input type="password" name="password" placeholder="Password" required />
<br />
<label for="remember_me">
<input type="checkbox" id="remember_me" name="remember_me" />
remember me
</label>
<br />
<input type="submit" value="Login" />
</form>
</div>

View File

@ -1,12 +0,0 @@
<h2>Register</h2>
<div class="register-form">
<form method="POST" action="?page=register">
<input type="text" name="username" placeholder="Username" required />
<br />
<input type="password" name="password" placeholder="Password" required />
<br />
<input type="submit" value="Register" />
</form>
</div>

View File

@ -1,22 +0,0 @@
<div class="menu-container">
<ul class="menu-left">
<li><a href="index.php">home</a></li>
<?php if ( isset($_SESSION['username']) ) { ?>
<li><a href="?page=config">config</a></li>
<li><a href="?page=conferences">conferences</a></li>
<li><a href="?page=participants">participants</a></li>
<li><a href="?page=components">components</a></li>
<?php } ?>
</ul>
<ul class="menu-right">
<?php if ( isset($_SESSION['username']) ) { ?>
<li><a href="?page=profile"><?= $user ?></a></li>
<li><a href="?page=logout">logout</a></li>
<?php } else { ?>
<li><a href="?page=login">login</a></li>
<li><a href="?page=register">register</a></li>
<?php } ?>
</ul>
</div>

View File

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

View File

@ -1,44 +0,0 @@
<div class="results-filter">
<form method="POST" id="filter_form" action="?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=\"" . $_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=\"" . $_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=\"" . $_REQUEST['ip'] . "\"" ?> maxlength="15" size="15" />
<?php } ?>
<br />
<div class="float-end pt-3">
<input type="button" onclick="clearFilter()" value="clear" />
<input type="submit" value="search" />
</div>
</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>