Compare commits
130 Commits
Author | SHA1 | Date |
---|---|---|
|
aa530c20d2 | |
|
69ce646bad | |
|
08c20fa2b9 | |
|
e2daf22ad7 | |
|
921f310ac1 | |
|
d9bee210d4 | |
|
2fc6940c11 | |
|
ecad8e2801 | |
|
4a18c344c8 | |
|
58633313e1 | |
|
0f6dda44b8 | |
|
b4b5a7ac8f | |
|
a45e064c18 | |
|
ecb4e0fab4 | |
|
035681ab28 | |
|
34779bb891 | |
|
c61f42792f | |
|
788167e251 | |
|
019f31cc05 | |
|
91aca75138 | |
|
66fb6bf576 | |
|
ad6ca25493 | |
|
4b4cac7cec | |
|
487c23da3e | |
|
4182ba6c1b | |
|
20094b5e42 | |
|
9d5f87d86f | |
|
f0b487ca36 | |
|
5327bde032 | |
|
c2f63f6121 | |
|
9d0056f0a6 | |
|
a399103305 | |
|
b7f8fce86e | |
|
c77b07b8a2 | |
|
6fc3629014 | |
|
2da13af04c | |
|
363fbf2a6b | |
|
3953546ace | |
|
b7e10363d0 | |
|
f53a3eef05 | |
|
ae8d84012b | |
|
ddb86eae51 | |
|
144dd6e742 | |
|
c465fbfdf4 | |
|
beafdf29fb | |
|
00e2a38087 | |
|
80bf3ee2ed | |
|
c32bbd518b | |
|
730a5c153e | |
|
3a9916e63b | |
|
3e9eb0d822 | |
|
ef97dda39b | |
|
31f4a99d20 | |
|
759059baad | |
|
cca0eb63a6 | |
|
6c37a082bf | |
|
d2a9280d7d | |
|
64d19f61f2 | |
|
cadc7b7750 | |
|
d84c015787 | |
|
27a4dca7c6 | |
|
9c9a306f55 | |
|
be77376d85 | |
|
eecd74cc0f | |
|
b4df4b785a | |
|
9b00e3d42c | |
|
170e885251 | |
|
a96b203021 | |
|
057cc6dca5 | |
|
11d4118e71 | |
|
f13cad57d8 | |
|
b552a80203 | |
|
b971a76662 | |
|
25da7331f0 | |
|
50b89f92ea | |
|
676e145349 | |
|
f952257c20 | |
|
e6e91b19d0 | |
|
26c7660bfa | |
|
e50ac96b50 | |
|
20a39f5c29 | |
|
6e4657e90f | |
|
779d3e0bf6 | |
|
a288d311c0 | |
|
f87c42a746 | |
|
299327cf29 | |
|
eb512c4c1b | |
|
7dfd50e19a | |
|
dfdb24a550 | |
|
e13bb7fc42 | |
|
828020d689 | |
|
4a8185839d | |
|
71d0984e9d | |
|
4e79b76377 | |
|
fc16bea465 | |
|
df200aae64 | |
|
06cc20fb2a | |
|
5a451115f4 | |
|
fc71cdd7f8 | |
|
e59920cfd0 | |
|
6e6f4f6694 | |
|
752f519ccc | |
|
ffe08f913b | |
|
1f75f81297 | |
|
b9e85c65bd | |
|
e3b8cccba3 | |
|
5f9702848e | |
|
5dc419b7a7 | |
|
aa2dcc027d | |
|
f0b98d3063 | |
|
5b24d098e4 | |
|
53b3965a32 | |
|
d0fa120202 | |
|
fc1ed97499 | |
|
e3f839bc56 | |
|
d45ba62805 | |
|
5321942da8 | |
|
405f58124d | |
|
1e4ebae652 | |
|
e932e4899c | |
|
7c8335d3e7 | |
|
81287a2c95 | |
|
9c3964da20 | |
|
3c9cce2c8b | |
|
35020a0108 | |
|
e85292b58f | |
|
9fd2af6538 | |
|
949ce27f63 | |
|
81b66db3c6 | |
|
b2fcaf6793 |
|
@ -0,0 +1,30 @@
|
|||
name: PHP Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: pdo, pdo_sqlite
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd tests
|
||||
composer install
|
||||
|
||||
- name: Run test suite
|
||||
run: |
|
||||
cd tests
|
||||
./vendor/bin/phpunit
|
34
CHANGELOG.md
34
CHANGELOG.md
|
@ -4,6 +4,40 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
---
|
||||
|
||||
## Unreleased
|
||||
|
||||
#### Links
|
||||
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.3...HEAD
|
||||
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.3...HEAD
|
||||
- github: https://github.com/lindeas/jilo-web/compare/v0.3...HEAD
|
||||
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.3...HEAD
|
||||
|
||||
### Added
|
||||
- Added proper pagination
|
||||
- Added agents managemet pages
|
||||
- Added javascript-based feedback messages
|
||||
- Added description to each page
|
||||
- Added CSRF checks
|
||||
- Added validator class for all forms
|
||||
- Added rate limiting to all pages
|
||||
- Added authentication rate limiting to login and registration
|
||||
- Added unit tests
|
||||
- Added integration/feature tests
|
||||
- Added testing workflow for github
|
||||
|
||||
### Changed
|
||||
- Made the config editing in-place with AJAX
|
||||
- Redesigned the help page
|
||||
- Moved graphs and latest data to their own pages
|
||||
- Moved live config.js to its own page
|
||||
- Redesigned the messages system and renamed them to feedback messages
|
||||
|
||||
### Fixed
|
||||
- Fixed logs search
|
||||
- Removed hardcoded messages, changed to feedback messages
|
||||
|
||||
---
|
||||
|
||||
## 0.3 - 2025-01-15
|
||||
|
||||
#### Links
|
||||
|
|
|
@ -24,29 +24,32 @@ class Agent {
|
|||
|
||||
|
||||
/**
|
||||
* Retrieves details of a specified agent ID (or all agents) in a specified platform.
|
||||
* Retrieves details of agents for a specified host.
|
||||
*
|
||||
* @param int $platform_id The platform ID to filter agents by.
|
||||
* @param int $agent_id The agent ID to filter by. If empty, all agents are returned.
|
||||
* @param int $host_id The host ID to filter agents by.
|
||||
* @param int $agent_id Optional agent ID to filter by.
|
||||
*
|
||||
* @return array The list of agent details.
|
||||
*/
|
||||
public function getAgentDetails($platform_id, $agent_id = '') {
|
||||
public function getAgentDetails($host_id, $agent_id = '') {
|
||||
$sql = 'SELECT
|
||||
ja.id,
|
||||
ja.platform_id,
|
||||
ja.host_id,
|
||||
ja.agent_type_id,
|
||||
ja.url,
|
||||
ja.secret_key,
|
||||
ja.check_period,
|
||||
jat.description AS agent_description,
|
||||
jat.endpoint AS agent_endpoint
|
||||
jat.endpoint AS agent_endpoint,
|
||||
h.platform_id
|
||||
FROM
|
||||
jilo_agents ja
|
||||
JOIN
|
||||
jilo_agent_types jat ON ja.agent_type_id = jat.id
|
||||
JOIN
|
||||
hosts h ON ja.host_id = h.id
|
||||
WHERE
|
||||
platform_id = :platform_id';
|
||||
ja.host_id = :host_id';
|
||||
|
||||
if ($agent_id !== '') {
|
||||
$sql .= ' AND ja.id = :agent_id';
|
||||
|
@ -54,7 +57,7 @@ class Agent {
|
|||
|
||||
$query = $this->db->prepare($sql);
|
||||
|
||||
$query->bindParam(':platform_id', $platform_id);
|
||||
$query->bindParam(':host_id', $host_id);
|
||||
if ($agent_id !== '') {
|
||||
$query->bindParam(':agent_id', $agent_id);
|
||||
}
|
||||
|
@ -75,17 +78,20 @@ class Agent {
|
|||
public function getAgentIDDetails($agent_id) {
|
||||
$sql = 'SELECT
|
||||
ja.id,
|
||||
ja.platform_id,
|
||||
ja.host_id,
|
||||
ja.agent_type_id,
|
||||
ja.url,
|
||||
ja.secret_key,
|
||||
ja.check_period,
|
||||
jat.description AS agent_description,
|
||||
jat.endpoint AS agent_endpoint
|
||||
jat.endpoint AS agent_endpoint,
|
||||
h.platform_id
|
||||
FROM
|
||||
jilo_agents ja
|
||||
JOIN
|
||||
jilo_agent_types jat ON ja.agent_type_id = jat.id
|
||||
JOIN
|
||||
hosts h ON ja.host_id = h.id
|
||||
WHERE
|
||||
ja.id = :agent_id';
|
||||
|
||||
|
@ -114,22 +120,22 @@ class Agent {
|
|||
|
||||
|
||||
/**
|
||||
* Retrieves agent types already configured for a specific platform.
|
||||
* Retrieves agent types already configured for a specific host.
|
||||
*
|
||||
* @param int $platform_id The platform ID to filter agents by.
|
||||
* @param int $host_id The host ID to filter agents by.
|
||||
*
|
||||
* @return array List of agent types configured for the platform.
|
||||
* @return array List of agent types configured for the host.
|
||||
*/
|
||||
public function getPlatformAgentTypes($platform_id) {
|
||||
public function getHostAgentTypes($host_id) {
|
||||
$sql = 'SELECT
|
||||
id,
|
||||
agent_type_id
|
||||
FROM
|
||||
jilo_agents
|
||||
WHERE
|
||||
platform_id = :platform_id';
|
||||
host_id = :host_id';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindParam(':platform_id', $platform_id);
|
||||
$query->bindParam(':host_id', $host_id);
|
||||
$query->execute();
|
||||
|
||||
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
@ -137,27 +143,27 @@ class Agent {
|
|||
|
||||
|
||||
/**
|
||||
* Adds a new agent to the platform.
|
||||
* Add a new agent to the database.
|
||||
*
|
||||
* @param int $platform_id The platform ID where the agent is to be added.
|
||||
* @param array $newAgent The new agent details to add.
|
||||
* @param int $host_id The host ID to add the agent to.
|
||||
* @param array $newAgent An associative array containing the details of the agent to be added.
|
||||
*
|
||||
* @return bool|string Returns true on success or an error message on failure.
|
||||
* @return bool|string True if the agent was added successfully, otherwise error message.
|
||||
*/
|
||||
public function addAgent($platform_id, $newAgent) {
|
||||
public function addAgent($host_id, $newAgent) {
|
||||
try {
|
||||
$sql = 'INSERT INTO jilo_agents
|
||||
(platform_id, agent_type_id, url, secret_key, check_period)
|
||||
(host_id, agent_type_id, url, secret_key, check_period)
|
||||
VALUES
|
||||
(:platform_id, :agent_type_id, :url, :secret_key, :check_period)';
|
||||
(:host_id, :agent_type_id, :url, :secret_key, :check_period)';
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':platform_id' => $platform_id,
|
||||
':agent_type_id' => $newAgent['type_id'],
|
||||
':url' => $newAgent['url'],
|
||||
':secret_key' => $newAgent['secret_key'],
|
||||
':check_period' => $newAgent['check_period'],
|
||||
':host_id' => $host_id,
|
||||
':agent_type_id' => $newAgent['type_id'],
|
||||
':url' => $newAgent['url'],
|
||||
':secret_key' => $newAgent['secret_key'],
|
||||
':check_period' => $newAgent['check_period'],
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
@ -169,33 +175,34 @@ class Agent {
|
|||
|
||||
|
||||
/**
|
||||
* Edits an existing agent's details.
|
||||
* Edit an existing agent in the database.
|
||||
*
|
||||
* @param int $platform_id The platform ID where the agent exists.
|
||||
* @param array $updatedAgent The updated agent details.
|
||||
* @param int $agent_id The ID of the agent to edit.
|
||||
* @param array $updatedAgent An associative array containing the updated details of the agent.
|
||||
*
|
||||
* @return bool|string Returns true on success or an error message on failure.
|
||||
* @return bool|string True if the agent was updated successfully, otherwise error message.
|
||||
*/
|
||||
public function editAgent($platform_id, $updatedAgent) {
|
||||
public function editAgent($agent_id, $updatedAgent) {
|
||||
try {
|
||||
$sql = 'UPDATE jilo_agents SET
|
||||
$sql = 'UPDATE jilo_agents
|
||||
SET
|
||||
agent_type_id = :agent_type_id,
|
||||
url = :url,
|
||||
secret_key = :secret_key,
|
||||
check_period = :check_period
|
||||
WHERE
|
||||
id = :agent_id
|
||||
AND
|
||||
platform_id = :platform_id';
|
||||
id = :agent_id';
|
||||
|
||||
// Convert empty secret key to NULL
|
||||
$secretKey = !empty($updatedAgent['secret_key']) ? $updatedAgent['secret_key'] : null;
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':agent_type_id' => $updatedAgent['agent_type_id'],
|
||||
':url' => $updatedAgent['url'],
|
||||
':secret_key' => $updatedAgent['secret_key'],
|
||||
':check_period' => $updatedAgent['check_period'],
|
||||
':agent_id' => $updatedAgent['id'],
|
||||
':platform_id' => $platform_id,
|
||||
':agent_id' => $agent_id,
|
||||
':agent_type_id' => $updatedAgent['agent_type_id'],
|
||||
':url' => $updatedAgent['url'],
|
||||
':secret_key' => $secretKey,
|
||||
':check_period' => $updatedAgent['check_period'],
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
@ -207,7 +214,7 @@ class Agent {
|
|||
|
||||
|
||||
/**
|
||||
* Deletes an agent from the platform.
|
||||
* Deletes an agent from the database.
|
||||
*
|
||||
* @param int $agent_id The agent ID to delete.
|
||||
*
|
||||
|
@ -279,7 +286,7 @@ class Agent {
|
|||
$base64Url_payload = $this->base64UrlEncode($payload);
|
||||
|
||||
// signature
|
||||
$signature = hash_hmac('sha256', $base64Url_header . "." . $base64Url_payload, $secret_key, true);
|
||||
$signature = hash_hmac('sha256', $base64Url_header . "." . $base64Url_payload, $secret_key ?? '', true);
|
||||
$base64Url_signature = $this->base64UrlEncode($signature);
|
||||
|
||||
// build the JWT
|
||||
|
@ -398,15 +405,15 @@ class Agent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves the latest stored data for a specific platform, agent type, and metric type.
|
||||
* Retrieves the latest stored data for a specific host, agent type, and metric type.
|
||||
*
|
||||
* @param int $platform_id The platform ID.
|
||||
* @param int $host_id The host ID.
|
||||
* @param string $agent_type The agent type.
|
||||
* @param string $metric_type The metric type to filter by.
|
||||
*
|
||||
* @return mixed The latest stored data.
|
||||
*/
|
||||
public function getLatestData($platform_id, $agent_type, $metric_type) {
|
||||
public function getLatestData($host_id, $agent_type, $metric_type) {
|
||||
$sql = 'SELECT
|
||||
jac.timestamp,
|
||||
jac.response_content,
|
||||
|
@ -418,8 +425,10 @@ class Agent {
|
|||
jilo_agents ja ON jac.agent_id = ja.id
|
||||
JOIN
|
||||
jilo_agent_types jat ON ja.agent_type_id = jat.id
|
||||
JOIN
|
||||
hosts h ON ja.host_id = h.id
|
||||
WHERE
|
||||
ja.platform_id = :platform_id
|
||||
h.id = :host_id
|
||||
AND jat.description = :agent_type
|
||||
AND jac.status_code = 200
|
||||
ORDER BY
|
||||
|
@ -428,7 +437,7 @@ class Agent {
|
|||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':platform_id' => $platform_id,
|
||||
':host_id' => $host_id,
|
||||
':agent_type' => $agent_type
|
||||
]);
|
||||
|
||||
|
@ -497,14 +506,14 @@ class Agent {
|
|||
/**
|
||||
* Gets historical data for a specific metric from agent checks
|
||||
*
|
||||
* @param int $platform_id The platform ID
|
||||
* @param int $host_id The host ID
|
||||
* @param string $agent_type The type of agent (e.g., 'jvb', 'jicofo')
|
||||
* @param string $metric_type The type of metric to retrieve
|
||||
* @param string $from_time Start time in Y-m-d format
|
||||
* @param string $until_time End time in Y-m-d format
|
||||
* @return array Array with the dataset from agent checks
|
||||
*/
|
||||
public function getHistoricalData($platform_id, $agent_type, $metric_type, $from_time, $until_time) {
|
||||
public function getHistoricalData($host_id, $agent_type, $metric_type, $from_time, $until_time) {
|
||||
// Get data from agent checks
|
||||
$sql = 'SELECT
|
||||
DATE(jac.timestamp) as date,
|
||||
|
@ -516,8 +525,10 @@ class Agent {
|
|||
jilo_agents ja ON jac.agent_id = ja.id
|
||||
JOIN
|
||||
jilo_agent_types jat ON ja.agent_type_id = jat.id
|
||||
JOIN
|
||||
hosts h ON ja.host_id = h.id
|
||||
WHERE
|
||||
ja.platform_id = :platform_id
|
||||
h.id = :host_id
|
||||
AND jat.description = :agent_type
|
||||
AND jac.status_code = 200
|
||||
AND DATE(jac.timestamp) BETWEEN :from_time AND :until_time
|
||||
|
@ -528,7 +539,7 @@ class Agent {
|
|||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':platform_id' => $platform_id,
|
||||
':host_id' => $host_id,
|
||||
':agent_type' => $agent_type,
|
||||
':from_time' => $from_time,
|
||||
':until_time' => $until_time
|
||||
|
@ -565,6 +576,72 @@ class Agent {
|
|||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
/**
|
||||
* Gets the previous record for a specific metric
|
||||
*
|
||||
* @param int $host_id The host ID
|
||||
* @param string $agent_type The type of agent (e.g., 'jvb', 'jicofo')
|
||||
* @param string $metric_type The type of metric to retrieve
|
||||
* @param string $current_timestamp Current record's timestamp to get data before this
|
||||
* @return array|null Previous record data or null if not found
|
||||
*/
|
||||
public function getPreviousRecord($host_id, $agent_type, $metric_type, $current_timestamp) {
|
||||
$sql = 'SELECT
|
||||
jac.timestamp,
|
||||
jac.response_content
|
||||
FROM
|
||||
jilo_agent_checks jac
|
||||
JOIN
|
||||
jilo_agents ja ON jac.agent_id = ja.id
|
||||
JOIN
|
||||
jilo_agent_types jat ON ja.agent_type_id = jat.id
|
||||
JOIN
|
||||
hosts h ON ja.host_id = h.id
|
||||
WHERE
|
||||
h.id = :host_id
|
||||
AND jat.description = :agent_type
|
||||
AND jac.status_code = 200
|
||||
AND jac.timestamp < :current_timestamp
|
||||
ORDER BY
|
||||
jac.timestamp DESC
|
||||
LIMIT 1';
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':host_id' => $host_id,
|
||||
':agent_type' => $agent_type,
|
||||
':current_timestamp' => $current_timestamp
|
||||
]);
|
||||
|
||||
$result = $query->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result) {
|
||||
$json_data = json_decode($result['response_content'], true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$api_data = [];
|
||||
if ($agent_type === 'jvb') {
|
||||
$api_data = $json_data['jvb_api_data'] ?? [];
|
||||
} elseif ($agent_type === 'jicofo') {
|
||||
$api_data = $json_data['jicofo_api_data'] ?? [];
|
||||
} elseif ($agent_type === 'jigasi') {
|
||||
$api_data = $json_data['jigasi_api_data'] ?? [];
|
||||
} elseif ($agent_type === 'prosody') {
|
||||
$api_data = $json_data['prosody_api_data'] ?? [];
|
||||
} elseif ($agent_type === 'nginx') {
|
||||
$api_data = $json_data['nginx_api_data'] ?? [];
|
||||
}
|
||||
|
||||
$value = $this->getNestedValue($api_data, $metric_type);
|
||||
if ($value !== null) {
|
||||
return [
|
||||
'value' => $value,
|
||||
'timestamp' => $result['timestamp']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,59 +36,134 @@ class Component {
|
|||
* @return array The list of Jitsi component events or an empty array if no results.
|
||||
*/
|
||||
public function jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time, $offset=0, $items_per_page='') {
|
||||
global $logObject;
|
||||
try {
|
||||
// Add time part to dates if not present
|
||||
if (strlen($from_time) <= 10) {
|
||||
$from_time .= ' 00:00:00';
|
||||
}
|
||||
if (strlen($until_time) <= 10) {
|
||||
$until_time .= ' 23:59:59';
|
||||
}
|
||||
|
||||
// time period drill-down
|
||||
// FIXME make it similar to the bash version
|
||||
if (empty($from_time)) {
|
||||
$from_time = '0000-01-01';
|
||||
// list of jitsi component events
|
||||
$sql = "SELECT jitsi_component, loglevel, time, component_id, event_type, event_param
|
||||
FROM jitsi_components
|
||||
WHERE time >= :from_time
|
||||
AND time <= :until_time";
|
||||
|
||||
// Only add component and event filters if they're not the default values
|
||||
if ($jitsi_component !== 'jitsi_component') {
|
||||
$sql .= " AND LOWER(jitsi_component) = LOWER(:jitsi_component)";
|
||||
}
|
||||
if ($component_id !== 'component_id') {
|
||||
$sql .= " AND component_id = :component_id";
|
||||
}
|
||||
if ($event_type !== 'event_type') {
|
||||
$sql .= " AND event_type LIKE :event_type";
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY time";
|
||||
|
||||
if ($items_per_page) {
|
||||
$sql .= ' LIMIT :offset, :items_per_page';
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
|
||||
// Bind parameters only if they're not default values
|
||||
if ($jitsi_component !== 'jitsi_component') {
|
||||
$stmt->bindValue(':jitsi_component', trim($jitsi_component, "'"));
|
||||
}
|
||||
if ($component_id !== 'component_id') {
|
||||
$stmt->bindValue(':component_id', trim($component_id, "'"));
|
||||
}
|
||||
if ($event_type !== 'event_type') {
|
||||
$stmt->bindValue(':event_type', '%' . trim($event_type, "'") . '%');
|
||||
}
|
||||
|
||||
$stmt->bindParam(':from_time', $from_time);
|
||||
$stmt->bindParam(':until_time', $until_time);
|
||||
|
||||
if ($items_per_page) {
|
||||
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':items_per_page', $items_per_page, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!empty($result)) {
|
||||
$logObject->insertLog(0, "Retrieved " . count($result) . " Jitsi component events");
|
||||
}
|
||||
return $result;
|
||||
} catch (PDOException $e) {
|
||||
$logObject->insertLog(0, "Failed to retrieve Jitsi component events: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
if (empty($until_time)) {
|
||||
$until_time = '9999-12-31';
|
||||
}
|
||||
$from_time = htmlspecialchars(strip_tags($from_time));
|
||||
$until_time = htmlspecialchars(strip_tags($until_time));
|
||||
|
||||
// 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";
|
||||
if ($event_type != '' && $event_type != 'event_type') {
|
||||
$sql .= "
|
||||
AND
|
||||
event_type LIKE '%%%s%%'";
|
||||
}
|
||||
$sql .= "
|
||||
AND
|
||||
(time >= '%s 00:00:00' AND time <= '%s 23:59:59')
|
||||
ORDER BY
|
||||
time";
|
||||
|
||||
if ($items_per_page) {
|
||||
$items_per_page = (int)$items_per_page;
|
||||
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
|
||||
}
|
||||
|
||||
// FIXME this needs to be done with bound params instead of sprintf
|
||||
if ($event_type != '' && $event_type != 'event_type') {
|
||||
$sql = sprintf($sql, $jitsi_component, $component_id, $event_type, $from_time, $until_time);
|
||||
$sql = str_replace("LIKE '%'", "LIKE '%", $sql);
|
||||
$sql = str_replace("'%'\nAND", "%' AND", $sql);
|
||||
} else {
|
||||
$sql = sprintf($sql, $jitsi_component, $component_id, $from_time, $until_time);
|
||||
}
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute();
|
||||
|
||||
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Gets the total count of components events matching the filter criteria
|
||||
*
|
||||
* @param string $jitsi_component The Jitsi component name.
|
||||
* @param int $component_id The component ID.
|
||||
* @param string $event_type The type of event to filter by.
|
||||
* @param string $from_time The start date in 'YYYY-MM-DD' format.
|
||||
* @param string $until_time The end date in 'YYYY-MM-DD' format.
|
||||
*
|
||||
* @return int The total count of matching components
|
||||
*/
|
||||
public function getComponentEventsCount($jitsi_component, $component_id, $event_type, $from_time, $until_time) {
|
||||
global $logObject;
|
||||
try {
|
||||
// Add time part to dates if not present
|
||||
if (strlen($from_time) <= 10) {
|
||||
$from_time .= ' 00:00:00';
|
||||
}
|
||||
if (strlen($until_time) <= 10) {
|
||||
$until_time .= ' 23:59:59';
|
||||
}
|
||||
|
||||
?>
|
||||
// Build the query
|
||||
$sql = "SELECT COUNT(*) as total
|
||||
FROM jitsi_components
|
||||
WHERE time >= :from_time
|
||||
AND time <= :until_time";
|
||||
|
||||
// Only add component and event filters if they're not the default values
|
||||
if ($jitsi_component !== 'jitsi_component') {
|
||||
$sql .= " AND LOWER(jitsi_component) = LOWER(:jitsi_component)";
|
||||
}
|
||||
if ($component_id !== 'component_id') {
|
||||
$sql .= " AND component_id = :component_id";
|
||||
}
|
||||
if ($event_type !== 'event_type') {
|
||||
$sql .= " AND event_type LIKE :event_type";
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
|
||||
// Bind parameters only if they're not default values
|
||||
if ($jitsi_component !== 'jitsi_component') {
|
||||
$stmt->bindValue(':jitsi_component', trim($jitsi_component, "'"));
|
||||
}
|
||||
if ($component_id !== 'component_id') {
|
||||
$stmt->bindValue(':component_id', trim($component_id, "'"));
|
||||
}
|
||||
if ($event_type !== 'event_type') {
|
||||
$stmt->bindValue(':event_type', '%' . trim($event_type, "'") . '%');
|
||||
}
|
||||
|
||||
$stmt->bindParam(':from_time', $from_time);
|
||||
$stmt->bindParam(':until_time', $until_time);
|
||||
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return (int)$result['total'];
|
||||
} catch (PDOException $e) {
|
||||
$logObject->insertLog(0, "Failed to retrieve component events count: " . $e->getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -382,5 +382,3 @@ SELECT COUNT(*) AS conferences
|
|||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
/**
|
||||
* class Config
|
||||
*
|
||||
* Handles editing and fetching configuration files.
|
||||
* Handles editing and fetching ot the config files.
|
||||
*/
|
||||
class Config {
|
||||
|
||||
/**
|
||||
* Edits a configuration file by updating specified options.
|
||||
* Edits a config file by updating specified options.
|
||||
*
|
||||
* @param array $updatedConfig Key-value pairs of configuration options to update.
|
||||
* @param string $config_file Path to the configuration file.
|
||||
* @param array $updatedConfig Key-value pairs of config options to update.
|
||||
* @param string $config_file Path to the config file.
|
||||
*
|
||||
* @return mixed Returns true on success, or an error message on failure.
|
||||
*/
|
||||
|
@ -50,104 +50,4 @@ class Config {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the config.js file from the Jitsi server.
|
||||
*
|
||||
* @param string $jitsiUrl The base URL of the Jitsi server.
|
||||
* @param bool $raw Whether to return the full file (true) or only uncommented values (false).
|
||||
*
|
||||
* @return string The content of the config.js file or an error message.
|
||||
*/
|
||||
public function getPlatformConfigjs($jitsiUrl, $raw = false) {
|
||||
// constructing the URL
|
||||
$configjsFile = $jitsiUrl . '/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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the interface_config.js file from the Jitsi server.
|
||||
*
|
||||
* @param string $jitsiUrl The base URL of the Jitsi server.
|
||||
* @param bool $raw Whether to return the full file (true) or only uncommented values (false).
|
||||
*
|
||||
* @return string The content of the interface_config.js file or an error message.
|
||||
*/
|
||||
public function getPlatformInterfaceConfigjs($jitsiUrl, $raw = false) {
|
||||
// constructing the URL
|
||||
$interfaceConfigjsFile = $jitsiUrl . '/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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -28,13 +28,13 @@ class Database {
|
|||
*/
|
||||
public function __construct($options) {
|
||||
// check if PDO extension is loaded
|
||||
if ( !extension_loaded('pdo') ) {
|
||||
$error = getError('PDO extension not loaded.');
|
||||
if (!extension_loaded('pdo')) {
|
||||
throw new Exception('PDO extension not loaded.');
|
||||
}
|
||||
|
||||
// options check
|
||||
if (empty($options['type'])) {
|
||||
$error = getError('Database type is not set.');
|
||||
throw new Exception('Database type is not set.');
|
||||
}
|
||||
|
||||
// connect based on database type
|
||||
|
@ -42,15 +42,15 @@ class Database {
|
|||
case 'sqlite':
|
||||
$this->connectSqlite($options);
|
||||
break;
|
||||
case 'mysql' || 'mariadb':
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
$this->connectMysql($options);
|
||||
break;
|
||||
default:
|
||||
$error = getError("Database type \"{$options['type']}\" is not supported.");
|
||||
$this->pdo = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes a connection to a SQLite database.
|
||||
*
|
||||
|
@ -62,12 +62,17 @@ class Database {
|
|||
private function connectSqlite($options) {
|
||||
// pdo_sqlite extension is needed
|
||||
if (!extension_loaded('pdo_sqlite')) {
|
||||
$error = getError('PDO extension for SQLite not loaded.');
|
||||
throw new Exception('PDO extension for SQLite not loaded.');
|
||||
}
|
||||
|
||||
// SQLite options
|
||||
if (empty($options['dbFile']) || !file_exists($options['dbFile'])) {
|
||||
$error = getError("SQLite database file \"{$options['dbFile']}\" not found.");
|
||||
if (empty($options['dbFile'])) {
|
||||
throw new Exception('SQLite database file path is missing.');
|
||||
}
|
||||
|
||||
// For in-memory database (especially for the tests), skip file check
|
||||
if ($options['dbFile'] !== ':memory:' && !file_exists($options['dbFile'])) {
|
||||
throw new Exception("SQLite database file \"{$options['dbFile']}\" not found.");
|
||||
}
|
||||
|
||||
// connect to SQLite
|
||||
|
@ -77,11 +82,10 @@ class Database {
|
|||
// enable foreign key constraints (not ON by default in SQLite3)
|
||||
$this->pdo->exec('PRAGMA foreign_keys = ON;');
|
||||
} catch (PDOException $e) {
|
||||
$error = getError('SQLite connection failed: ', $e->getMessage());
|
||||
throw new Exception('SQLite connection failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes a connection to a MySQL (or MariaDB) database.
|
||||
*
|
||||
|
@ -97,25 +101,25 @@ class Database {
|
|||
private function connectMysql($options) {
|
||||
// pdo_mysql extension is needed
|
||||
if (!extension_loaded('pdo_mysql')) {
|
||||
$error = getError('PDO extension for MySQL not loaded.');
|
||||
throw new Exception('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.');
|
||||
throw new Exception('MySQL connection data is missing.');
|
||||
}
|
||||
|
||||
// Connect to MySQL
|
||||
try {
|
||||
$dsn = "mysql:host={$options['host']};port={$options['port']};dbname={$options['dbname']};charset=utf8";
|
||||
$port = $options['port'] ?? 3306;
|
||||
$dsn = "mysql:host={$options['host']};port={$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: ', $e->getMessage());
|
||||
$this->pdo = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the current PDO connection instance.
|
||||
*
|
||||
|
@ -125,6 +129,95 @@ class Database {
|
|||
return $this->pdo;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Executes an SQL query with optional parameters.
|
||||
*
|
||||
* @param string $query The SQL query to execute
|
||||
* @param array $params Optional parameters for the query
|
||||
* @return PDOStatement|false The result of the query execution
|
||||
* @throws Exception If the query fails
|
||||
*/
|
||||
public function execute($query, $params = []) {
|
||||
if (!$this->pdo) {
|
||||
throw new Exception('No database connection.');
|
||||
}
|
||||
|
||||
?>
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute($params);
|
||||
return $stmt;
|
||||
} catch (PDOException $e) {
|
||||
throw new Exception('Query execution failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an SQL statement for execution.
|
||||
*
|
||||
* @param string $query The SQL query to prepare
|
||||
* @return PDOStatement The prepared statement
|
||||
* @throws Exception If the preparation fails
|
||||
*/
|
||||
public function prepare($query) {
|
||||
if (!$this->pdo) {
|
||||
throw new Exception('No database connection.');
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->pdo->prepare($query);
|
||||
} catch (PDOException $e) {
|
||||
throw new Exception('Statement preparation failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins a database transaction.
|
||||
*
|
||||
* @throws Exception If starting the transaction fails
|
||||
*/
|
||||
public function beginTransaction() {
|
||||
if (!$this->pdo) {
|
||||
throw new Exception('No database connection.');
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->pdo->beginTransaction();
|
||||
} catch (PDOException $e) {
|
||||
throw new Exception('Failed to start transaction: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the current database transaction.
|
||||
*
|
||||
* @throws Exception If committing the transaction fails
|
||||
*/
|
||||
public function commit() {
|
||||
if (!$this->pdo) {
|
||||
throw new Exception('No database connection.');
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->pdo->commit();
|
||||
} catch (PDOException $e) {
|
||||
throw new Exception('Failed to commit transaction: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls back the current database transaction.
|
||||
*
|
||||
* @throws Exception If rolling back the transaction fails
|
||||
*/
|
||||
public function rollBack() {
|
||||
if (!$this->pdo) {
|
||||
throw new Exception('No database connection.');
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->pdo->rollBack();
|
||||
} catch (PDOException $e) {
|
||||
throw new Exception('Failed to rollback transaction: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
class Messages {
|
||||
// Message types
|
||||
class Feedback {
|
||||
// Feedback types
|
||||
const TYPE_SUCCESS = 'success';
|
||||
const TYPE_ERROR = 'danger';
|
||||
const TYPE_INFO = 'info';
|
||||
const TYPE_WARNING = 'warning';
|
||||
|
||||
// Default message configurations
|
||||
// Default feedback message configurations
|
||||
const NOTICE = [
|
||||
'DEFAULT' => [
|
||||
'type' => self::TYPE_INFO,
|
||||
|
@ -96,20 +96,50 @@ class Messages {
|
|||
]
|
||||
];
|
||||
|
||||
const REGISTER = [
|
||||
'SUCCESS' => [
|
||||
'type' => self::TYPE_SUCCESS,
|
||||
'dismissible' => true
|
||||
],
|
||||
'FAILED' => [
|
||||
'type' => self::TYPE_ERROR,
|
||||
'dismissible' => true
|
||||
],
|
||||
'DISABLED' => [
|
||||
'type' => self::TYPE_ERROR,
|
||||
'dismissible' => false
|
||||
],
|
||||
];
|
||||
|
||||
const SYSTEM = [
|
||||
'DB_ERROR' => [
|
||||
'type' => self::TYPE_ERROR,
|
||||
'dismissible' => false
|
||||
],
|
||||
'DB_CONNECT_ERROR' => [
|
||||
'type' => self::TYPE_ERROR,
|
||||
'dismissible' => false
|
||||
],
|
||||
'DB_UNKNOWN_TYPE' => [
|
||||
'type' => self::TYPE_ERROR,
|
||||
'dismissible' => false
|
||||
],
|
||||
];
|
||||
|
||||
private static $strings = null;
|
||||
|
||||
/**
|
||||
* Get message strings
|
||||
* Get feedback message strings
|
||||
*/
|
||||
private static function getStrings() {
|
||||
if (self::$strings === null) {
|
||||
self::$strings = require __DIR__ . '/../includes/messages-strings.php';
|
||||
self::$strings = require __DIR__ . '/../includes/strings.php';
|
||||
}
|
||||
return self::$strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message configuration by key
|
||||
* Get feedback message configuration by key
|
||||
*/
|
||||
public static function get($category, $key) {
|
||||
$config = constant("self::$category")[$key] ?? null;
|
||||
|
@ -122,9 +152,9 @@ class Messages {
|
|||
}
|
||||
|
||||
/**
|
||||
* Render message HTML
|
||||
* Render feedback message HTML
|
||||
*/
|
||||
// Usage: echo Messages::render('LOGIN', 'LOGIN_SUCCESS', 'custom message [or null]', true [for dismissible; or null], true [for small; or omit]);
|
||||
// Usage: echo Feedback::render('LOGIN', 'LOGIN_SUCCESS', 'custom message [or null]', true [for dismissible; or null], true [for small; or omit]);
|
||||
public static function render($category, $key, $customMessage = null, $dismissible = null, $small = false, $sanitize = true) {
|
||||
$config = self::get($category, $key);
|
||||
if (!$config) return '';
|
||||
|
@ -146,15 +176,30 @@ class Messages {
|
|||
}
|
||||
|
||||
/**
|
||||
* Store message in session for display after redirect
|
||||
* Get feedback message data for JavaScript
|
||||
*/
|
||||
// Usage: Messages::flash('LOGIN', 'LOGIN_SUCCESS', 'custom message [or null]', true [for dismissible; or null], true [for small; or omit]);
|
||||
public static function getMessageData($category, $key, $customMessage = null, $dismissible = null, $small = false) {
|
||||
$config = self::get($category, $key);
|
||||
if (!$config) return null;
|
||||
|
||||
return [
|
||||
'type' => $config['type'],
|
||||
'message' => $customMessage ?? $config['message'],
|
||||
'dismissible' => $dismissible ?? $config['dismissible'] ?? false,
|
||||
'small' => $small
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store feedback message in session for display after redirect
|
||||
*/
|
||||
// Usage: Feedback::flash('LOGIN', 'LOGIN_SUCCESS', 'custom message [or null]', true [for dismissible; or null], true [for small; or omit]);
|
||||
public static function flash($category, $key, $customMessage = null, $dismissible = null, $small = false) {
|
||||
if (!isset($_SESSION['flash_messages'])) {
|
||||
$_SESSION['flash_messages'] = [];
|
||||
}
|
||||
|
||||
// Get the message configuration
|
||||
// Get the feedback message configuration
|
||||
$config = self::get($category, $key);
|
||||
$isDismissible = $dismissible ?? $config['dismissible'] ?? false;
|
||||
|
||||
|
@ -168,11 +213,11 @@ class Messages {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get and clear all flash messages
|
||||
* Get and clear all flash feedback messages
|
||||
*/
|
||||
public static function getFlash() {
|
||||
$messages = $_SESSION['flash_messages'] ?? [];
|
||||
$system_messages = $_SESSION['flash_messages'] ?? [];
|
||||
unset($_SESSION['flash_messages']);
|
||||
return $messages;
|
||||
return $system_messages;
|
||||
}
|
||||
}
|
|
@ -34,7 +34,6 @@ class Host {
|
|||
$sql = 'SELECT
|
||||
id,
|
||||
address,
|
||||
port,
|
||||
platform_id,
|
||||
name
|
||||
FROM
|
||||
|
@ -73,14 +72,13 @@ class Host {
|
|||
public function addHost($newHost) {
|
||||
try {
|
||||
$sql = 'INSERT INTO hosts
|
||||
(address, port, platform_id, name)
|
||||
(address, platform_id, name)
|
||||
VALUES
|
||||
(:address, :port, :platform_id, :name)';
|
||||
(:address, :platform_id, :name)';
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':address' => $newHost['address'],
|
||||
':port' => $newHost['port'],
|
||||
':platform_id' => $newHost['platform_id'],
|
||||
':name' => $newHost['name'],
|
||||
]);
|
||||
|
@ -99,25 +97,28 @@ class Host {
|
|||
* @param string $platform_id The platform ID to which the host belongs.
|
||||
* @param array $updatedHost An associative array containing the updated details of the host.
|
||||
*
|
||||
* @return bool True if the host was updated successfully, otherwise false.
|
||||
* @return bool|string True if the host was updated successfully, otherwise error message.
|
||||
*/
|
||||
public function editHost($platform_id, $updatedHost) {
|
||||
try {
|
||||
$sql = 'UPDATE hosts SET
|
||||
address = :address,
|
||||
port = :port,
|
||||
name = :name
|
||||
WHERE
|
||||
id = :id';
|
||||
id = :id AND platform_id = :platform_id';
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':id' => $updatedHost['id'],
|
||||
':address' => $updatedHost['address'],
|
||||
':port' => $updatedHost['port'],
|
||||
':name' => $updatedHost['name'],
|
||||
':id' => $updatedHost['id'],
|
||||
':platform_id' => $platform_id,
|
||||
':address' => $updatedHost['address'],
|
||||
':name' => $updatedHost['name']
|
||||
]);
|
||||
|
||||
if ($query->rowCount() === 0) {
|
||||
return "No host found with ID {$updatedHost['id']} in platform $platform_id";
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
@ -135,21 +136,30 @@ class Host {
|
|||
*/
|
||||
public function deleteHost($host_id) {
|
||||
try {
|
||||
$sql = 'DELETE FROM hosts
|
||||
WHERE
|
||||
id = :host_id';
|
||||
// Start transaction
|
||||
$this->db->beginTransaction();
|
||||
|
||||
// First delete all agents associated with this host
|
||||
$sql = 'DELETE FROM jilo_agents WHERE host_id = :host_id';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindParam(':host_id', $host_id);
|
||||
|
||||
$query->execute();
|
||||
|
||||
// Then delete the host
|
||||
$sql = 'DELETE FROM hosts WHERE id = :host_id';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindParam(':host_id', $host_id);
|
||||
$query->execute();
|
||||
|
||||
// Commit transaction
|
||||
$this->db->commit();
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Rollback transaction on error
|
||||
$this->db->rollBack();
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -21,7 +21,6 @@ class Log {
|
|||
$this->db = $database->getConnection();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert a log event into the database.
|
||||
*
|
||||
|
@ -40,9 +39,9 @@ class Log {
|
|||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':user_id' => $user_id,
|
||||
':scope' => $scope,
|
||||
':message' => $message,
|
||||
':user_id' => $user_id,
|
||||
':scope' => $scope,
|
||||
':message' => $message,
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
@ -52,7 +51,6 @@ class Log {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve log entries from the database.
|
||||
*
|
||||
|
@ -60,36 +58,65 @@ class Log {
|
|||
* @param string $scope The scope of the logs ('user' or 'system').
|
||||
* @param int $offset The offset for pagination. Default is 0.
|
||||
* @param int $items_per_page The number of log entries to retrieve per page. Default is no limit.
|
||||
* @param array $filters Optional array of filters (from_time, until_time, message, id)
|
||||
*
|
||||
* @return array An array of log entries.
|
||||
*/
|
||||
public function readLog($user_id, $scope, $offset=0, $items_per_page='') {
|
||||
public function readLog($user_id, $scope, $offset=0, $items_per_page='', $filters=[]) {
|
||||
$params = [];
|
||||
$where_clauses = [];
|
||||
|
||||
// Base query with user join
|
||||
$base_sql = 'SELECT l.*, u.username
|
||||
FROM logs l
|
||||
LEFT JOIN users u ON l.user_id = u.id';
|
||||
|
||||
// Add scope condition
|
||||
if ($scope === 'user') {
|
||||
$sql = 'SELECT * FROM logs WHERE user_id = :user_id ORDER BY time DESC';
|
||||
if ($items_per_page) {
|
||||
$items_per_page = (int)$items_per_page;
|
||||
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
|
||||
}
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([
|
||||
':user_id' => $user_id,
|
||||
]);
|
||||
$where_clauses[] = 'l.user_id = :user_id';
|
||||
$params[':user_id'] = $user_id;
|
||||
}
|
||||
if ($scope === 'system') {
|
||||
$sql = 'SELECT * FROM logs ORDER BY time DESC';
|
||||
if ($items_per_page) {
|
||||
$items_per_page = (int)$items_per_page;
|
||||
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
|
||||
}
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute();
|
||||
// Add time range filters if specified
|
||||
if (!empty($filters['from_time'])) {
|
||||
$where_clauses[] = 'l.time >= :from_time';
|
||||
$params[':from_time'] = $filters['from_time'] . ' 00:00:00';
|
||||
}
|
||||
if (!empty($filters['until_time'])) {
|
||||
$where_clauses[] = 'l.time <= :until_time';
|
||||
$params[':until_time'] = $filters['until_time'] . ' 23:59:59';
|
||||
}
|
||||
|
||||
// Add message search if specified
|
||||
if (!empty($filters['message'])) {
|
||||
$where_clauses[] = 'l.message LIKE :message';
|
||||
$params[':message'] = '%' . $filters['message'] . '%';
|
||||
}
|
||||
|
||||
// Add user ID search if specified
|
||||
if (!empty($filters['id'])) {
|
||||
$where_clauses[] = 'l.user_id = :search_user_id';
|
||||
$params[':search_user_id'] = $filters['id'];
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
$sql = $base_sql;
|
||||
if (!empty($where_clauses)) {
|
||||
$sql .= ' WHERE ' . implode(' AND ', $where_clauses);
|
||||
}
|
||||
|
||||
// Add ordering
|
||||
$sql .= ' ORDER BY l.time DESC';
|
||||
|
||||
// Add pagination
|
||||
if ($items_per_page) {
|
||||
$items_per_page = (int)$items_per_page;
|
||||
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
|
||||
}
|
||||
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute($params);
|
||||
|
||||
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -372,5 +372,3 @@ AND pe.event_type = 'participant joining'";
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -122,21 +122,42 @@ class Platform {
|
|||
*/
|
||||
public function deletePlatform($platform_id) {
|
||||
try {
|
||||
$sql = 'DELETE FROM platforms
|
||||
WHERE
|
||||
id = :platform_id';
|
||||
$this->db->beginTransaction();
|
||||
|
||||
// First, get all hosts in this platform
|
||||
$sql = 'SELECT id FROM hosts WHERE platform_id = :platform_id';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindParam(':platform_id', $platform_id);
|
||||
|
||||
$query->execute();
|
||||
$hosts = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Delete all agents for each host
|
||||
foreach ($hosts as $host) {
|
||||
$sql = 'DELETE FROM jilo_agents WHERE host_id = :host_id';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindParam(':host_id', $host['id']);
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
// Delete all hosts in this platform
|
||||
$sql = 'DELETE FROM hosts WHERE platform_id = :platform_id';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindParam(':platform_id', $platform_id);
|
||||
$query->execute();
|
||||
|
||||
// Finally, delete the platform
|
||||
$sql = 'DELETE FROM platforms WHERE id = :platform_id';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindParam(':platform_id', $platform_id);
|
||||
$query->execute();
|
||||
|
||||
$this->db->commit();
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->db->rollBack();
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -7,9 +7,20 @@ class RateLimiter {
|
|||
public $decayMinutes = 15; // Time window in minutes
|
||||
public $autoBlacklistThreshold = 10; // Attempts before auto-blacklist
|
||||
public $autoBlacklistDuration = 24; // Hours to blacklist for
|
||||
public $ratelimitTable = 'login_attempts';
|
||||
public $authRatelimitTable = 'login_attempts'; // For username/password attempts
|
||||
public $pagesRatelimitTable = 'pages_rate_limits'; // For page requests
|
||||
public $whitelistTable = 'ip_whitelist';
|
||||
public $blacklistTable = 'ip_blacklist';
|
||||
private $pageLimits = [
|
||||
// Default rate limits per minute
|
||||
'default' => 60,
|
||||
'admin' => 120,
|
||||
'message' => 20,
|
||||
'contact' => 30,
|
||||
'call' => 30,
|
||||
'register' => 5,
|
||||
'config' => 10
|
||||
];
|
||||
|
||||
public function __construct($database) {
|
||||
$this->db = $database->getConnection();
|
||||
|
@ -19,15 +30,24 @@ class RateLimiter {
|
|||
|
||||
// Database preparation
|
||||
private function createTablesIfNotExist() {
|
||||
// Login attempts table
|
||||
$sql = "CREATE TABLE IF NOT EXISTS {$this->ratelimitTable} (
|
||||
// Authentication attempts table
|
||||
$sql = "CREATE TABLE IF NOT EXISTS {$this->authRatelimitTable} (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ip_address TEXT NOT NULL UNIQUE,
|
||||
ip_address TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
attempted_at TEXT DEFAULT (DATETIME('now'))
|
||||
)";
|
||||
$this->db->exec($sql);
|
||||
|
||||
// Pages rate limits table
|
||||
$sql = "CREATE TABLE IF NOT EXISTS {$this->pagesRatelimitTable} (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ip_address TEXT NOT NULL,
|
||||
endpoint TEXT NOT NULL,
|
||||
request_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)";
|
||||
$this->db->exec($sql);
|
||||
|
||||
// IP whitelist table
|
||||
$sql = "CREATE TABLE IF NOT EXISTS {$this->whitelistTable} (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
@ -91,7 +111,7 @@ class RateLimiter {
|
|||
* Get number of recent login attempts for an IP
|
||||
*/
|
||||
public function getRecentAttempts($ip) {
|
||||
$stmt = $this->db->prepare("SELECT COUNT(*) as attempts FROM {$this->ratelimitTable}
|
||||
$stmt = $this->db->prepare("SELECT COUNT(*) as attempts FROM {$this->authRatelimitTable}
|
||||
WHERE ip_address = ? AND attempted_at > datetime('now', '-' || :minutes || ' minutes')");
|
||||
$stmt->execute([$ip, $this->decayMinutes]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
@ -103,7 +123,20 @@ class RateLimiter {
|
|||
*/
|
||||
public function isIpBlacklisted($ip) {
|
||||
// First check if IP is explicitly blacklisted or in a blacklisted range
|
||||
$stmt = $this->db->prepare("SELECT ip_address, is_network, expiry_time FROM {$this->blacklistTable}");
|
||||
$stmt = $this->db->prepare("SELECT ip_address, is_network, expiry_time FROM {$this->blacklistTable} WHERE ip_address = ?");
|
||||
$stmt->execute([$ip]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
// Skip expired entries
|
||||
if ($row['expiry_time'] !== null && strtotime($row['expiry_time']) < time()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check network ranges
|
||||
$stmt = $this->db->prepare("SELECT ip_address, expiry_time FROM {$this->blacklistTable} WHERE is_network = 1");
|
||||
$stmt->execute();
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
@ -112,14 +145,8 @@ class RateLimiter {
|
|||
continue;
|
||||
}
|
||||
|
||||
if ($row['is_network']) {
|
||||
if ($this->ipInRange($ip, $row['ip_address'])) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ($ip === $row['ip_address']) {
|
||||
return true;
|
||||
}
|
||||
if ($this->ipInRange($ip, $row['ip_address'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,19 +157,24 @@ class RateLimiter {
|
|||
* Check if an IP is whitelisted
|
||||
*/
|
||||
public function isIpWhitelisted($ip) {
|
||||
// Check exact IP match and CIDR ranges
|
||||
$stmt = $this->db->prepare("SELECT ip_address, is_network FROM {$this->whitelistTable}");
|
||||
$stmt->execute();
|
||||
// Check exact IP match first
|
||||
$stmt = $this->db->prepare("SELECT ip_address FROM {$this->whitelistTable} WHERE ip_address = ?");
|
||||
$stmt->execute([$ip]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if ($row['is_network']) {
|
||||
// Only check ranges for IPv4 addresses
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
// Check network ranges
|
||||
$stmt = $this->db->prepare("SELECT ip_address FROM {$this->whitelistTable} WHERE is_network = 1");
|
||||
$stmt->execute();
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if ($this->ipInRange($ip, $row['ip_address'])) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ($ip === $row['ip_address']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,8 +182,18 @@ class RateLimiter {
|
|||
}
|
||||
|
||||
private function ipInRange($ip, $cidr) {
|
||||
// Only work with IPv4 addresses
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($subnet, $bits) = explode('/', $cidr);
|
||||
|
||||
// Make sure subnet is IPv4
|
||||
if (!filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ip = ip2long($ip);
|
||||
$subnet = ip2long($subnet);
|
||||
$mask = -1 << (32 - $bits);
|
||||
|
@ -168,7 +210,7 @@ class RateLimiter {
|
|||
$message = "Cannot whitelist {$ip} - IP is currently blacklisted";
|
||||
if ($userId) {
|
||||
$this->log->insertLog($userId, "IP Whitelist: {$message}", 'system');
|
||||
Messages::flash('ERROR', 'DEFAULT', $message);
|
||||
Feedback::flash('ERROR', 'DEFAULT', $message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -195,7 +237,7 @@ class RateLimiter {
|
|||
} catch (Exception $e) {
|
||||
if ($userId) {
|
||||
$this->log->insertLog($userId, "IP Whitelist: Failed to add {$ip}: " . $e->getMessage(), 'system');
|
||||
Messages::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to add {$ip}: " . $e->getMessage());
|
||||
Feedback::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to add {$ip}: " . $e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -230,7 +272,7 @@ class RateLimiter {
|
|||
} catch (Exception $e) {
|
||||
if ($userId) {
|
||||
$this->log->insertLog($userId, "IP Whitelist: Failed to remove {$ip}: " . $e->getMessage(), 'system');
|
||||
Messages::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to remove {$ip}: " . $e->getMessage());
|
||||
Feedback::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to remove {$ip}: " . $e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -243,7 +285,7 @@ class RateLimiter {
|
|||
$message = "Cannot blacklist {$ip} - IP is currently whitelisted";
|
||||
if ($userId) {
|
||||
$this->log->insertLog($userId, "IP Blacklist: {$message}", 'system');
|
||||
Messages::flash('ERROR', 'DEFAULT', $message);
|
||||
Feedback::flash('ERROR', 'DEFAULT', $message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -272,7 +314,7 @@ class RateLimiter {
|
|||
} catch (Exception $e) {
|
||||
if ($userId) {
|
||||
$this->log->insertLog($userId, "IP Blacklist: Failed to add {$ip}: " . $e->getMessage(), 'system');
|
||||
Messages::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to add {$ip}: " . $e->getMessage());
|
||||
Feedback::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to add {$ip}: " . $e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -287,6 +329,7 @@ class RateLimiter {
|
|||
|
||||
// Remove the IP
|
||||
$stmt = $this->db->prepare("DELETE FROM {$this->blacklistTable} WHERE ip_address = ?");
|
||||
|
||||
$result = $stmt->execute([$ip]);
|
||||
|
||||
if ($result && $ipDetails) {
|
||||
|
@ -305,7 +348,7 @@ class RateLimiter {
|
|||
} catch (Exception $e) {
|
||||
if ($userId) {
|
||||
$this->log->insertLog($userId, "IP Blacklist: Failed to remove {$ip}: " . $e->getMessage(), 'system');
|
||||
Messages::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to remove {$ip}: " . $e->getMessage());
|
||||
Feedback::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to remove {$ip}: " . $e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -333,14 +376,14 @@ class RateLimiter {
|
|||
$stmt->execute();
|
||||
|
||||
// Clean old login attempts
|
||||
$stmt = $this->db->prepare("DELETE FROM {$this->ratelimitTable}
|
||||
$stmt = $this->db->prepare("DELETE FROM {$this->authRatelimitTable}
|
||||
WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')");
|
||||
$stmt->execute([':minutes' => $this->decayMinutes]);
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->log->insertLog(0, "Failed to cleanup expired entries: " . $e->getMessage(), 'system');
|
||||
Messages::flash('ERROR', 'DEFAULT', "Failed to cleanup expired entries: " . $e->getMessage());
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Failed to cleanup expired entries: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -366,7 +409,7 @@ class RateLimiter {
|
|||
|
||||
// Check total attempts across all usernames from this IP
|
||||
$sql = "SELECT COUNT(*) as total_attempts
|
||||
FROM {$this->ratelimitTable}
|
||||
FROM {$this->authRatelimitTable}
|
||||
WHERE ip_address = :ip
|
||||
AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
|
@ -382,23 +425,14 @@ class RateLimiter {
|
|||
|
||||
public function attempt($username, $ipAddress) {
|
||||
// Record this attempt
|
||||
$sql = "INSERT INTO {$this->ratelimitTable} (ip_address, username) VALUES (:ip, :username)";
|
||||
$sql = "INSERT INTO {$this->authRatelimitTable} (ip_address, username) VALUES (:ip, :username)";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
':ip' => $ipAddress,
|
||||
':username' => $username
|
||||
]);
|
||||
|
||||
// Auto-blacklist if too many attempts
|
||||
if (!$this->isAllowed($username, $ipAddress)) {
|
||||
$this->addToBlacklist(
|
||||
$ipAddress,
|
||||
false,
|
||||
'Auto-blacklisted due to excessive login attempts',
|
||||
'system',
|
||||
null,
|
||||
$this->autoBlacklistDuration
|
||||
);
|
||||
try {
|
||||
$stmt->execute([
|
||||
':ip' => $ipAddress,
|
||||
':username' => $username
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -407,7 +441,7 @@ class RateLimiter {
|
|||
|
||||
public function tooManyAttempts($username, $ipAddress) {
|
||||
$sql = "SELECT COUNT(*) as attempts
|
||||
FROM {$this->ratelimitTable}
|
||||
FROM {$this->authRatelimitTable}
|
||||
WHERE ip_address = :ip
|
||||
AND username = :username
|
||||
AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
|
||||
|
@ -420,11 +454,32 @@ class RateLimiter {
|
|||
]);
|
||||
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $result['attempts'] >= $this->maxAttempts;
|
||||
|
||||
// Also check what's in the table
|
||||
$sql = "SELECT * FROM {$this->authRatelimitTable} WHERE ip_address = :ip";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([':ip' => $ipAddress]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$tooMany = $result['attempts'] >= $this->maxAttempts;
|
||||
|
||||
// Auto-blacklist if too many attempts
|
||||
if ($tooMany) {
|
||||
$this->addToBlacklist(
|
||||
$ipAddress,
|
||||
false,
|
||||
'Auto-blacklisted due to excessive login attempts',
|
||||
'system',
|
||||
null,
|
||||
$this->autoBlacklistDuration
|
||||
);
|
||||
}
|
||||
|
||||
return $tooMany;
|
||||
}
|
||||
|
||||
public function clearOldAttempts() {
|
||||
$sql = "DELETE FROM {$this->ratelimitTable}
|
||||
$sql = "DELETE FROM {$this->authRatelimitTable}
|
||||
WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
|
@ -435,7 +490,7 @@ class RateLimiter {
|
|||
|
||||
public function getRemainingAttempts($username, $ipAddress) {
|
||||
$sql = "SELECT COUNT(*) as attempts
|
||||
FROM {$this->ratelimitTable}
|
||||
FROM {$this->authRatelimitTable}
|
||||
WHERE ip_address = :ip
|
||||
AND username = :username
|
||||
AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
|
||||
|
@ -454,4 +509,121 @@ class RateLimiter {
|
|||
public function getDecayMinutes() {
|
||||
return $this->decayMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a page request is allowed
|
||||
*/
|
||||
public function isPageRequestAllowed($ipAddress, $endpoint, $userId = null) {
|
||||
// First check if IP is blacklisted
|
||||
if ($this->isIpBlacklisted($ipAddress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then check if IP is whitelisted
|
||||
if ($this->isIpWhitelisted($ipAddress)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clean old requests
|
||||
$this->cleanOldPageRequests();
|
||||
|
||||
// Get limit based on endpoint type and user role
|
||||
$limit = $this->getPageLimitForEndpoint($endpoint, $userId);
|
||||
|
||||
// Count recent requests, including this one
|
||||
$sql = "SELECT COUNT(*) as request_count
|
||||
FROM {$this->pagesRatelimitTable}
|
||||
WHERE ip_address = :ip
|
||||
AND endpoint = :endpoint
|
||||
AND request_time >= DATETIME('now', '-1 minute')";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
':ip' => $ipAddress,
|
||||
':endpoint' => $endpoint
|
||||
]);
|
||||
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $result['request_count'] < $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a page request
|
||||
*/
|
||||
public function recordPageRequest($ipAddress, $endpoint) {
|
||||
$sql = "INSERT INTO {$this->pagesRatelimitTable} (ip_address, endpoint)
|
||||
VALUES (:ip, :endpoint)";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
return $stmt->execute([
|
||||
':ip' => $ipAddress,
|
||||
':endpoint' => $endpoint
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean old page requests
|
||||
*/
|
||||
private function cleanOldPageRequests() {
|
||||
$sql = "DELETE FROM {$this->pagesRatelimitTable}
|
||||
WHERE request_time < DATETIME('now', '-1 minute')";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page rate limit for endpoint
|
||||
*/
|
||||
private function getPageLimitForEndpoint($endpoint, $userId = null) {
|
||||
// Admin users get higher limits
|
||||
if ($userId) {
|
||||
$userObj = new User($this->db);
|
||||
if ($userObj->hasRight($userId, 'admin')) {
|
||||
return $this->pageLimits['admin'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get endpoint type from the endpoint path
|
||||
$endpointType = $this->getEndpointType($endpoint);
|
||||
|
||||
// Return specific limit if exists, otherwise default
|
||||
return isset($this->pageLimits[$endpointType])
|
||||
? $this->pageLimits[$endpointType]
|
||||
: $this->pageLimits['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get endpoint type from path
|
||||
*/
|
||||
private function getEndpointType($endpoint) {
|
||||
if (strpos($endpoint, 'message') !== false) return 'message';
|
||||
if (strpos($endpoint, 'contact') !== false) return 'contact';
|
||||
if (strpos($endpoint, 'call') !== false) return 'call';
|
||||
if (strpos($endpoint, 'register') !== false) return 'register';
|
||||
if (strpos($endpoint, 'config') !== false) return 'config';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remaining page requests
|
||||
*/
|
||||
public function getRemainingPageRequests($ipAddress, $endpoint, $userId = null) {
|
||||
$limit = $this->getPageLimitForEndpoint($endpoint, $userId);
|
||||
|
||||
$sql = "SELECT COUNT(*) as request_count
|
||||
FROM {$this->pagesRatelimitTable}
|
||||
WHERE ip_address = :ip
|
||||
AND endpoint = :endpoint
|
||||
AND request_time > DATETIME('now', '-1 minute')";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
':ip' => $ipAddress,
|
||||
':endpoint' => $endpoint
|
||||
]);
|
||||
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return max(0, $limit - $result['request_count']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,5 +83,3 @@ class Router {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -52,5 +52,3 @@ class Server {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* class Settings
|
||||
*
|
||||
* Handles editing and fetching jilo configuration.
|
||||
*/
|
||||
class Settings {
|
||||
|
||||
/**
|
||||
* Loads javascript file the Jitsi server.
|
||||
*
|
||||
* @param string $jitsiUrl The base URL of the Jitsi server.
|
||||
* @param string $livejsFile The name of the remote js file to load.
|
||||
* @param bool $raw Whether to return the full file (true) or only uncommented values (false).
|
||||
*
|
||||
* @return string The content of the interface_config.js file or an error message.
|
||||
*/
|
||||
public function getPlatformJsFile($jitsiUrl, $livejsFile, $raw = false) {
|
||||
// constructing the URL
|
||||
$jsFile = $jitsiUrl . '/' . $livejsFile;
|
||||
|
||||
// default content, if we can't get the file contents
|
||||
$jsFileContent = "The file $livejsFile can't be loaded.";
|
||||
|
||||
// Check if URL is valid
|
||||
if (!filter_var($jsFile, FILTER_VALIDATE_URL)) {
|
||||
return "Invalid URL: $jsFile";
|
||||
}
|
||||
|
||||
// ssl options
|
||||
$contextOptions = [
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
];
|
||||
$context = stream_context_create($contextOptions);
|
||||
|
||||
// Try to get headers first to check if file exists and wasn't redirected
|
||||
$headers = @get_headers($jsFile, 1); // 1 to get headers as array
|
||||
if ($headers === false) {
|
||||
return "The file $livejsFile can't be loaded (connection error).";
|
||||
}
|
||||
|
||||
// Check for redirects
|
||||
$statusLine = $headers[0];
|
||||
if (strpos($statusLine, '301') !== false || strpos($statusLine, '302') !== false) {
|
||||
return "The file $livejsFile was redirected - this might indicate the file doesn't exist.";
|
||||
}
|
||||
|
||||
// Check if we got 200 OK
|
||||
if (strpos($statusLine, '200') === false) {
|
||||
return "The file $livejsFile can't be loaded (HTTP error: $statusLine).";
|
||||
}
|
||||
|
||||
// Check content type
|
||||
$contentType = isset($headers['Content-Type']) ? $headers['Content-Type'] : '';
|
||||
if (is_array($contentType)) {
|
||||
$contentType = end($contentType); // get last content-type in case of redirects
|
||||
}
|
||||
if (stripos($contentType, 'javascript') === false && stripos($contentType, 'text/plain') === false) {
|
||||
return "The file $livejsFile doesn't appear to be a JavaScript file (got $contentType).";
|
||||
}
|
||||
|
||||
// get the file
|
||||
$fileContent = @file_get_contents($jsFile, false, $context);
|
||||
|
||||
if ($fileContent !== false) {
|
||||
// Quick validation of content
|
||||
$firstLine = strtolower(trim(substr($fileContent, 0, 100)));
|
||||
if (strpos($firstLine, '<!doctype html>') !== false ||
|
||||
strpos($firstLine, '<html') !== false ||
|
||||
strpos($firstLine, '<?xml') !== false) {
|
||||
return "The file $livejsFile appears to be HTML/XML content instead of JavaScript.";
|
||||
}
|
||||
|
||||
// when we need only uncommented values
|
||||
if ($raw === false) {
|
||||
// remove block comments
|
||||
$jsFileContent = preg_replace('!/\*.*?\*/!s', '', $fileContent);
|
||||
// remove single-line comments
|
||||
$jsFileContent = preg_replace('/\/\/[^\n]*/', '', $jsFileContent);
|
||||
// remove empty lines
|
||||
$jsFileContent = preg_replace('/^\s*[\r\n]/m', '', $jsFileContent);
|
||||
|
||||
// when we need the full file as it is
|
||||
} else {
|
||||
$jsFileContent = $fileContent;
|
||||
}
|
||||
}
|
||||
|
||||
return $jsFileContent;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -90,8 +90,9 @@ class User {
|
|||
* @return bool True if login is successful, false otherwise.
|
||||
*/
|
||||
public function login($username, $password) {
|
||||
// get client IP address
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
// Get user's IP address
|
||||
require_once __DIR__ . '/../helpers/logs.php';
|
||||
$ipAddress = getUserIP();
|
||||
|
||||
// Record attempt
|
||||
$this->rateLimiter->attempt($username, $ipAddress);
|
||||
|
@ -450,5 +451,3 @@ class User {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
class Validator {
|
||||
private $errors = [];
|
||||
private $data = [];
|
||||
|
||||
public function __construct(array $data) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function validate(array $rules) {
|
||||
foreach ($rules as $field => $fieldRules) {
|
||||
foreach ($fieldRules as $rule => $parameter) {
|
||||
$this->applyRule($field, $rule, $parameter);
|
||||
}
|
||||
}
|
||||
return empty($this->errors);
|
||||
}
|
||||
|
||||
private function applyRule($field, $rule, $parameter) {
|
||||
$value = $this->data[$field] ?? null;
|
||||
|
||||
switch ($rule) {
|
||||
case 'required':
|
||||
if ($parameter && empty($value)) {
|
||||
$this->addError($field, "Field is required");
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
if (!empty($value) && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->addError($field, "Invalid email format");
|
||||
}
|
||||
break;
|
||||
case 'min':
|
||||
if (!empty($value) && strlen($value) < $parameter) {
|
||||
$this->addError($field, "Minimum length is $parameter characters");
|
||||
}
|
||||
break;
|
||||
case 'max':
|
||||
if (!empty($value) && strlen($value) > $parameter) {
|
||||
$this->addError($field, "Maximum length is $parameter characters");
|
||||
}
|
||||
break;
|
||||
case 'numeric':
|
||||
if (!empty($value) && !is_numeric($value)) {
|
||||
$this->addError($field, "Must be a number");
|
||||
}
|
||||
break;
|
||||
case 'phone':
|
||||
if (!empty($value) && !preg_match('/^[+]?[\d\s-()]{7,}$/', $value)) {
|
||||
$this->addError($field, "Invalid phone number format");
|
||||
}
|
||||
break;
|
||||
case 'url':
|
||||
if (!empty($value) && !filter_var($value, FILTER_VALIDATE_URL)) {
|
||||
$this->addError($field, "Invalid URL format");
|
||||
}
|
||||
break;
|
||||
case 'date':
|
||||
if (!empty($value)) {
|
||||
$date = date_parse($value);
|
||||
if ($date['error_count'] > 0) {
|
||||
$this->addError($field, "Invalid date format");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'in':
|
||||
if (!empty($value) && !in_array($value, $parameter)) {
|
||||
$this->addError($field, "Invalid option selected");
|
||||
}
|
||||
break;
|
||||
case 'matches':
|
||||
if ($value !== ($this->data[$parameter] ?? null)) {
|
||||
$this->addError($field, "Does not match $parameter field");
|
||||
}
|
||||
break;
|
||||
case 'ip':
|
||||
if (!empty($value)) {
|
||||
// Support both IPv4 and IPv6
|
||||
if (!filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {
|
||||
$this->addError($field, "Invalid IP address format");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function addError($field, $message) {
|
||||
if (!isset($this->errors[$field])) {
|
||||
$this->errors[$field] = [];
|
||||
}
|
||||
$this->errors[$field][] = $message;
|
||||
}
|
||||
|
||||
public function getErrors() {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function hasErrors() {
|
||||
return !empty($this->errors);
|
||||
}
|
||||
|
||||
public function getFirstError() {
|
||||
if (!$this->hasErrors()) {
|
||||
return null;
|
||||
}
|
||||
$firstField = array_key_first($this->errors);
|
||||
return $this->errors[$firstField][0];
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ return [
|
|||
// subfolder for the web app, if any
|
||||
'folder' => '/jilo-web/',
|
||||
// set to false to disable new registrations
|
||||
'registration_enabled' => true,
|
||||
'registration_enabled' => '1',
|
||||
// will be displayed on login screen
|
||||
'login_message' => '',
|
||||
|
||||
|
@ -36,5 +36,3 @@ return [
|
|||
'environment' => 'development',
|
||||
|
||||
];
|
||||
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Feedback Helper
|
||||
*
|
||||
* Combines functionality to handle retrieving and displaying feedback messages.
|
||||
*/
|
||||
|
||||
// Get any flash messages from previous request
|
||||
$flash_messages = Feedback::getFlash();
|
||||
if (!empty($flash_messages)) {
|
||||
$system_messages = array_merge($system_messages ?? [], array_map(function($flash) {
|
||||
return [
|
||||
'category' => $flash['category'],
|
||||
'key' => $flash['key'],
|
||||
'custom_message' => $flash['custom_message'] ?? null,
|
||||
'dismissible' => $flash['dismissible'] ?? false,
|
||||
'small' => $flash['small'] ?? false
|
||||
];
|
||||
}, $flash_messages));
|
||||
}
|
||||
|
||||
// Show feedback messages
|
||||
if (isset($system_messages) && is_array($system_messages)) {
|
||||
foreach ($system_messages as $msg) {
|
||||
echo Feedback::render(
|
||||
$msg['category'],
|
||||
$msg['key'],
|
||||
$msg['custom_message'] ?? null,
|
||||
$msg['dismissible'] ?? false,
|
||||
$msg['small'] ?? false
|
||||
);
|
||||
}
|
||||
}
|
|
@ -18,5 +18,3 @@ function getUserIP() {
|
|||
|
||||
return trim($ip);
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -44,5 +44,3 @@ function switchPlatform($platform_id) {
|
|||
// return the new URL with the new platform_id
|
||||
return $new_url;
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Security Helper
|
||||
*
|
||||
* Security helper, to be used with all the forms in the app.
|
||||
* Implements singleton pattern for consistent state management.
|
||||
*/
|
||||
class SecurityHelper {
|
||||
private static $instance = null;
|
||||
private $session;
|
||||
|
||||
private function __construct() {
|
||||
// Don't start a new session, just reference the existing one
|
||||
$this->session = &$_SESSION;
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new SecurityHelper();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
// Generate CSRF token
|
||||
public function generateCsrfToken() {
|
||||
if (empty($this->session['csrf_token'])) {
|
||||
$this->session['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $this->session['csrf_token'];
|
||||
}
|
||||
|
||||
// Verify CSRF token
|
||||
public function verifyCsrfToken($token) {
|
||||
if (empty($this->session['csrf_token']) || empty($token)) {
|
||||
return false;
|
||||
}
|
||||
return hash_equals($this->session['csrf_token'], $token);
|
||||
}
|
||||
|
||||
// Sanitize string input
|
||||
public function sanitizeString($input) {
|
||||
if (is_string($input)) {
|
||||
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Validate email
|
||||
public function validateEmail($email) {
|
||||
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
// Validate integer
|
||||
public function validateInt($input) {
|
||||
return filter_var($input, FILTER_VALIDATE_INT) !== false;
|
||||
}
|
||||
|
||||
// Validate URL
|
||||
public function validateUrl($url) {
|
||||
return filter_var($url, FILTER_VALIDATE_URL) !== false;
|
||||
}
|
||||
|
||||
// Sanitize array of inputs
|
||||
public function sanitizeArray($array, $allowedKeys = []) {
|
||||
$sanitized = [];
|
||||
foreach ($array as $key => $value) {
|
||||
if (empty($allowedKeys) || in_array($key, $allowedKeys)) {
|
||||
if (is_array($value)) {
|
||||
$sanitized[$key] = $this->sanitizeArray($value);
|
||||
} else {
|
||||
$sanitized[$key] = $this->sanitizeString($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
// Validate form data based on rules
|
||||
public function validateFormData($data, $rules) {
|
||||
$errors = [];
|
||||
foreach ($rules as $field => $rule) {
|
||||
if (!isset($data[$field]) && $rule['required']) {
|
||||
$errors[$field] = "Field is required";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($data[$field])) {
|
||||
$value = $data[$field];
|
||||
switch ($rule['type']) {
|
||||
case 'email':
|
||||
if (!$this->validateEmail($value)) {
|
||||
$errors[$field] = "Invalid email format";
|
||||
}
|
||||
break;
|
||||
case 'integer':
|
||||
if (!$this->validateInt($value)) {
|
||||
$errors[$field] = "Must be a valid integer";
|
||||
}
|
||||
break;
|
||||
case 'url':
|
||||
if (!$this->validateUrl($value)) {
|
||||
$errors[$field] = "Invalid URL format";
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
if (isset($rule['min']) && strlen($value) < $rule['min']) {
|
||||
$errors[$field] = "Minimum length is {$rule['min']} characters";
|
||||
}
|
||||
if (isset($rule['max']) && strlen($value) > $rule['max']) {
|
||||
$errors[$field] = "Maximum length is {$rule['max']} characters";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
}
|
|
@ -11,5 +11,3 @@ if (!isset($until_time) || (isset($until_time) && $until_time == '')) {
|
|||
} else {
|
||||
$time_range_specified = true;
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/../helpers/security.php';
|
||||
require_once __DIR__ . '/../helpers/logs.php';
|
||||
|
||||
function applyCsrfMiddleware() {
|
||||
$security = SecurityHelper::getInstance();
|
||||
|
||||
// Skip CSRF check for GET requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip CSRF check for initial login and registration attempts
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' &&
|
||||
isset($_GET['page']) &&
|
||||
in_array($_GET['page'], ['login', 'register']) &&
|
||||
!isset($_SESSION['username'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check CSRF token for all other POST requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$token = $_POST['csrf_token'] ?? '';
|
||||
if (!$security->verifyCsrfToken($token)) {
|
||||
// Log CSRF attempt
|
||||
$ipAddress = getUserIP();
|
||||
$logMessage = sprintf(
|
||||
"CSRF attempt detected - IP: %s, Page: %s, User: %s",
|
||||
$ipAddress,
|
||||
$_GET['page'] ?? 'unknown',
|
||||
$_SESSION['username'] ?? 'anonymous'
|
||||
);
|
||||
$logObject->insertLog(0, $logMessage, 'system');
|
||||
|
||||
// Return error message
|
||||
http_response_code(403);
|
||||
die('Invalid CSRF token. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -52,11 +52,10 @@ function connectDB($config, $database = '', $dbFile = '', $platformId = '') {
|
|||
// unknown database
|
||||
} else {
|
||||
$error = "Error: unknow database type \"{$config['db']['db_type']}\"";
|
||||
Messages::flash('ERROR', 'DEFAULT', $error);
|
||||
Feedback::flash('ERROR', 'DEFAULT', $error);
|
||||
exit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -38,5 +38,3 @@ function renderMessage(&$message, $type, $unset = false) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
if (isset($messages) && is_array($messages)) {
|
||||
foreach ($messages as $msg) {
|
||||
echo Messages::render($msg['category'], $msg['key'], $msg['custom_message'] ?? null, $msg['dismissible'] ?? false, $msg['small'] ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
// Get any flash messages from previous request
|
||||
$flash_messages = Messages::getFlash();
|
||||
if (!empty($flash_messages)) {
|
||||
$messages = array_merge($messages, array_map(function($flash) {
|
||||
return [
|
||||
'category' => $flash['category'],
|
||||
'key' => $flash['key'],
|
||||
'custom_message' => $flash['custom_message'] ?? null,
|
||||
'dismissible' => $flash['dismissible'] ?? false,
|
||||
'small' => $flash['small'] ?? false
|
||||
];
|
||||
}, $flash_messages));
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/../classes/ratelimiter.php';
|
||||
require_once __DIR__ . '/../helpers/logs.php';
|
||||
|
||||
/**
|
||||
* Rate limit middleware for page requests
|
||||
*
|
||||
* @param Database $database Database connection
|
||||
* @param string $endpoint The endpoint being accessed
|
||||
* @param int|null $userId Current user ID if authenticated
|
||||
* @param RateLimiter|null $existingRateLimiter Optional existing RateLimiter instance
|
||||
* @return bool True if request is allowed, false if rate limited
|
||||
*/
|
||||
function checkRateLimit($database, $endpoint, $userId = null, $existingRateLimiter = null) {
|
||||
global $app_root;
|
||||
$isTest = defined('PHPUNIT_RUNNING');
|
||||
$rateLimiter = $existingRateLimiter ?? new RateLimiter($database);
|
||||
$ipAddress = getUserIP();
|
||||
|
||||
// Check if request is allowed
|
||||
if (!$rateLimiter->isPageRequestAllowed($ipAddress, $endpoint, $userId)) {
|
||||
// Get remaining requests for error message
|
||||
$remaining = $rateLimiter->getRemainingPageRequests($ipAddress, $endpoint, $userId);
|
||||
|
||||
if (!$isTest) {
|
||||
// Set rate limit headers
|
||||
header('X-RateLimit-Remaining: ' . $remaining);
|
||||
header('X-RateLimit-Reset: ' . (time() + 60)); // Reset in 1 minute
|
||||
|
||||
// Return 429 Too Many Requests
|
||||
http_response_code(429);
|
||||
|
||||
// If AJAX request, return JSON
|
||||
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Too many requests. Please try again in a minute.',
|
||||
'messageData' => Feedback::getMessageData('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true)
|
||||
]);
|
||||
} else {
|
||||
// For regular requests, set flash message and redirect
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true);
|
||||
header('Location: ' . htmlspecialchars($app_root));
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// In test mode, just set the flash message
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Record this request
|
||||
$rateLimiter->recordPageRequest($ipAddress, $endpoint);
|
||||
return true;
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// sanitize all input vars that may end up in URLs or forms
|
||||
|
||||
$platform_id = htmlspecialchars($_REQUEST['platform']);
|
||||
$platform_id = htmlspecialchars($_REQUEST['platform'] ?? '');
|
||||
if (isset($_REQUEST['page'])) {
|
||||
$page = htmlspecialchars($_REQUEST['page']);
|
||||
} else {
|
||||
|
@ -59,6 +59,3 @@ if (isset($_POST['check_period'])) {
|
|||
if (isset($_POST['name'])) {
|
||||
$name = htmlspecialchars($_POST['name']);
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Security Headers Middleware
|
||||
*
|
||||
* Sets various security headers to protect against common web vulnerabilities:
|
||||
* - HSTS: Force HTTPS connections
|
||||
* - CSP: Content Security Policy to prevent XSS and other injection attacks
|
||||
* - X-Frame-Options: Prevent clickjacking
|
||||
* - X-Content-Type-Options: Prevent MIME-type sniffing
|
||||
* - Referrer-Policy: Control referrer information
|
||||
* - Permissions-Policy: Control browser features
|
||||
*/
|
||||
|
||||
function applySecurityHeaders($testMode = false) {
|
||||
$headers = [];
|
||||
|
||||
// Get current page
|
||||
$current_page = $_GET['page'] ?? 'dashboard';
|
||||
|
||||
// Define pages that need media access
|
||||
$media_enabled_pages = [
|
||||
// 'conference' => ['camera', 'microphone'],
|
||||
// 'call' => ['microphone'],
|
||||
// Add more pages and their required permissions as needed
|
||||
];
|
||||
|
||||
// Strict Transport Security (HSTS)
|
||||
// Only enable if HTTPS is properly configured
|
||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
||||
$headers[] = 'Strict-Transport-Security: max-age=31536000; includeSubDomains; preload';
|
||||
}
|
||||
|
||||
// Content Security Policy (CSP)
|
||||
$csp = [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Required for Bootstrap and jQuery
|
||||
"style-src 'self' 'unsafe-inline' https://use.fontawesome.com", // Allow FontAwesome CSS
|
||||
"img-src 'self' data:", // Allow data: URLs for images
|
||||
"font-src 'self' https://use.fontawesome.com", // Allow FontAwesome fonts
|
||||
"connect-src 'self'",
|
||||
"frame-ancestors 'none'", // Equivalent to X-Frame-Options: DENY
|
||||
"form-action 'self'",
|
||||
"base-uri 'self'",
|
||||
"upgrade-insecure-requests" // Force HTTPS for all requests
|
||||
];
|
||||
$headers[] = "Content-Security-Policy: " . implode('; ', $csp);
|
||||
|
||||
// X-Frame-Options (legacy support)
|
||||
$headers[] = 'X-Frame-Options: DENY';
|
||||
|
||||
// X-Content-Type-Options
|
||||
$headers[] = 'X-Content-Type-Options: nosniff';
|
||||
|
||||
// X-XSS-Protection
|
||||
$headers[] = 'X-XSS-Protection: 1; mode=block';
|
||||
|
||||
// Referrer-Policy
|
||||
$headers[] = 'Referrer-Policy: strict-origin-when-cross-origin';
|
||||
|
||||
// Permissions-Policy
|
||||
$permissions = [
|
||||
'geolocation=()',
|
||||
'payment=()',
|
||||
'usb=()',
|
||||
'accelerometer=()',
|
||||
'autoplay=()',
|
||||
'document-domain=()',
|
||||
'encrypted-media=()',
|
||||
'fullscreen=(self)',
|
||||
'magnetometer=()',
|
||||
'midi=()',
|
||||
'sync-xhr=(self)',
|
||||
'usb=()'
|
||||
];
|
||||
|
||||
// Add camera/microphone permissions based on current page
|
||||
$camera_allowed = false;
|
||||
$microphone_allowed = false;
|
||||
|
||||
if (isset($media_enabled_pages[$current_page])) {
|
||||
$allowed_media = $media_enabled_pages[$current_page];
|
||||
if (in_array('camera', $allowed_media)) {
|
||||
$camera_allowed = true;
|
||||
}
|
||||
if (in_array('microphone', $allowed_media)) {
|
||||
$microphone_allowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add media permissions
|
||||
$permissions[] = $camera_allowed ? 'camera=(self)' : 'camera=()';
|
||||
$permissions[] = $microphone_allowed ? 'microphone=(self)' : 'microphone=()';
|
||||
|
||||
$headers[] = 'Permissions-Policy: ' . implode(', ', $permissions);
|
||||
|
||||
// Clear PHP version
|
||||
if (!$testMode) {
|
||||
header_remove('X-Powered-By');
|
||||
}
|
||||
|
||||
// Prevent caching of sensitive pages
|
||||
if (in_array($current_page, ['login', 'register', 'profile', 'security'])) {
|
||||
$headers[] = 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0';
|
||||
$headers[] = 'Pragma: no-cache';
|
||||
$headers[] = 'Expires: ' . gmdate('D, d M Y H:i:s', time() - 3600) . ' GMT';
|
||||
}
|
||||
|
||||
if ($testMode) {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Apply headers in production
|
||||
foreach ($headers as $header) {
|
||||
header($header);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Session Middleware
|
||||
*
|
||||
* Validates session status and handles session timeout.
|
||||
* This middleware should be included in all protected pages.
|
||||
*/
|
||||
|
||||
function applySessionMiddleware($config, $app_root) {
|
||||
$isTest = defined('PHPUNIT_RUNNING');
|
||||
|
||||
// Access $_SESSION directly in test mode
|
||||
if (!$isTest) {
|
||||
// Start session if not already started
|
||||
if (session_status() !== PHP_SESSION_ACTIVE && !headers_sent()) {
|
||||
session_start([
|
||||
'cookie_httponly' => 1,
|
||||
'cookie_secure' => 1,
|
||||
'cookie_samesite' => 'Strict',
|
||||
'gc_maxlifetime' => 1440 // 24 minutes
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user is logged in
|
||||
if (!isset($_SESSION['USER_ID'])) {
|
||||
if (!$isTest) {
|
||||
header('Location: ' . $app_root . '?page=login');
|
||||
exit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check session timeout
|
||||
$session_timeout = isset($_SESSION['REMEMBER_ME']) ? (30 * 24 * 60 * 60) : 1440; // 30 days or 24 minutes
|
||||
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $session_timeout)) {
|
||||
// Session has expired
|
||||
$oldSessionData = $_SESSION;
|
||||
$_SESSION = array();
|
||||
|
||||
if (!$isTest && session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
|
||||
// Start a new session to prevent errors
|
||||
if (!headers_sent()) {
|
||||
session_start([
|
||||
'cookie_httponly' => 1,
|
||||
'cookie_secure' => 1,
|
||||
'cookie_samesite' => 'Strict',
|
||||
'gc_maxlifetime' => 1440
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isTest && !headers_sent()) {
|
||||
setcookie('username', '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => $config['folder'],
|
||||
'domain' => $config['domain'],
|
||||
'secure' => isset($_SERVER['HTTPS']),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict'
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$isTest) {
|
||||
header('Location: ' . $app_root . '?page=login&timeout=1');
|
||||
exit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update last activity time
|
||||
$_SESSION['LAST_ACTIVITY'] = time();
|
||||
|
||||
// Regenerate session ID periodically (every 30 minutes)
|
||||
if (!isset($_SESSION['CREATED'])) {
|
||||
$_SESSION['CREATED'] = time();
|
||||
} else if (time() - $_SESSION['CREATED'] > 1800) {
|
||||
// Regenerate session ID and update creation time
|
||||
if (!$isTest && !headers_sent() && session_status() === PHP_SESSION_ACTIVE) {
|
||||
$oldData = $_SESSION;
|
||||
session_regenerate_id(true);
|
||||
$_SESSION = $oldData;
|
||||
$_SESSION['CREATED'] = time();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
// Message strings for translation
|
||||
return [
|
||||
'ERROR' => [
|
||||
'CSRF_INVALID' => 'Invalid security token. Please try again.',
|
||||
'INVALID_ACTION' => 'Invalid action requested.',
|
||||
'DEFAULT' => 'An error occurred. Please try again.',
|
||||
],
|
||||
'LOGIN' => [
|
||||
'LOGIN_SUCCESS' => 'Login successful.',
|
||||
'LOGIN_FAILED' => 'Login failed. Please check your credentials.',
|
||||
|
@ -12,13 +17,15 @@ return [
|
|||
],
|
||||
'SECURITY' => [
|
||||
'WHITELIST_ADD_SUCCESS' => 'IP address successfully added to whitelist.',
|
||||
'WHITELIST_ADD_ERROR' => 'Failed to add IP to whitelist. Please check the IP format.',
|
||||
'WHITELIST_ADD_FAILED' => 'Failed to add IP to whitelist.',
|
||||
'WHITELIST_ADD_ERROR_IP' => 'Failed to add IP to whitelist. Please check the IP format.',
|
||||
'WHITELIST_REMOVE_SUCCESS' => 'IP address successfully removed from whitelist.',
|
||||
'WHITELIST_REMOVE_ERROR' => 'Failed to remove IP from whitelist.',
|
||||
'WHITELIST_REMOVE_FAILED' => 'Failed to remove IP from whitelist.',
|
||||
'BLACKLIST_ADD_SUCCESS' => 'IP address successfully added to blacklist.',
|
||||
'BLACKLIST_ADD_ERROR' => 'Failed to add IP to blacklist. Please check the IP format.',
|
||||
'BLACKLIST_ADD_FAILED' => 'Failed to add IP to blacklist.',
|
||||
'BLACKLIST_ADD_ERROR_IP' => 'Failed to add IP to blacklist. Please check the IP format.',
|
||||
'BLACKLIST_REMOVE_SUCCESS' => 'IP address successfully removed from blacklist.',
|
||||
'BLACKLIST_REMOVE_ERROR' => 'Failed to remove IP from blacklist.',
|
||||
'BLACKLIST_REMOVE_FAILED' => 'Failed to remove IP from blacklist.',
|
||||
'RATE_LIMIT_INFO' => 'Rate limiting is active. This helps protect against brute force attacks.',
|
||||
'PERMISSION_DENIED' => 'Permission denied. You do not have the required rights.',
|
||||
'IP_REQUIRED' => 'IP address is required.',
|
|
@ -8,61 +8,167 @@
|
|||
* to allow time-based invalidation if needed.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Constants for session keys and cache settings
|
||||
define('SESSION_CACHE_SUFFIX', '_cache');
|
||||
define('SESSION_CACHE_TIME_SUFFIX', '_cache_time');
|
||||
define('CACHE_EXPIRY_TIME', 3600); // 1 hour in seconds
|
||||
|
||||
// Input validation
|
||||
$action = isset($_GET['action']) ? htmlspecialchars(trim($_GET['action']), ENT_QUOTES, 'UTF-8') : '';
|
||||
$agentId = filter_input(INPUT_GET, 'agent', FILTER_VALIDATE_INT);
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$agent = $_REQUEST['agent'] ?? '';
|
||||
require '../app/classes/agent.php';
|
||||
|
||||
require '../app/classes/host.php';
|
||||
$agentObject = new Agent($dbWeb);
|
||||
$hostObject = new Host($dbWeb);
|
||||
|
||||
// if it's a POST request, it's saving to cache
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
|
||||
// read the JSON sent from javascript
|
||||
$data = file_get_contents("php://input");
|
||||
$result = json_decode($data, true);
|
||||
|
||||
// store the data in the session
|
||||
if ($result) {
|
||||
$_SESSION["agent{$agent}_cache"] = $result;
|
||||
$_SESSION["agent{$agent}_cache_time"] = time(); // store the cache time
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'message' => "Cache for agent {$agent} is stored."
|
||||
]);
|
||||
} elseif ($result === null && !empty($agent)) {
|
||||
unset($_SESSION["agent{$agent}_cache"]);
|
||||
unset($_SESSION["agent{$agent}_cache_time"]);
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'message' => "Cache for agent {$agent} is cleared."
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid data'
|
||||
]);
|
||||
}
|
||||
|
||||
//// if it's a GET request, it's read/load from cache
|
||||
//} elseif ($loadcache === true) {
|
||||
//
|
||||
// // check if cached data exists in session
|
||||
// if (isset($_SESSION["agent{$agent}_cache"])) {
|
||||
// // return the cached data in JSON format
|
||||
// echo json_encode(['status' => 'success', 'data' => $_SESSION["agent{$agent}_cache"]]);
|
||||
// } else {
|
||||
// // if no cached data exists
|
||||
// echo json_encode(['status' => 'error', 'message' => 'No cached data found']);
|
||||
// }
|
||||
|
||||
// no form submitted, show the templates
|
||||
} else {
|
||||
$agentDetails = $agentObject->getAgentDetails($platform_id);
|
||||
include '../app/templates/agent-list.php';
|
||||
/**
|
||||
* Get the cache key for an agent
|
||||
* @param int $agentId The agent ID
|
||||
* @param string $suffix The suffix to append (_cache or _cache_time)
|
||||
* @return string The cache key
|
||||
*/
|
||||
function getAgentCacheKey($agentId, $suffix) {
|
||||
return "agent{$agentId}{$suffix}";
|
||||
}
|
||||
|
||||
?>
|
||||
/**
|
||||
* Check if cache is expired
|
||||
* @param int $agentId The agent ID
|
||||
* @return bool True if cache is expired or doesn't exist
|
||||
*/
|
||||
function isCacheExpired($agentId) {
|
||||
$timeKey = getAgentCacheKey($agentId, SESSION_CACHE_TIME_SUFFIX);
|
||||
if (!isset($_SESSION[$timeKey])) {
|
||||
return true;
|
||||
}
|
||||
return (time() - $_SESSION[$timeKey]) > CACHE_EXPIRY_TIME;
|
||||
}
|
||||
|
||||
// Handle POST request (saving to cache)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
// Apply rate limiting for adding new contacts
|
||||
require '../app/includes/rate_limit_middleware.php';
|
||||
checkRateLimit($dbWeb, 'contact', $user_id);
|
||||
|
||||
// Validate agent ID for POST operations
|
||||
if ($agentId === false || $agentId === null) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Invalid agent ID format');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid agent ID format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Read and validate JSON data
|
||||
$jsonData = file_get_contents("php://input");
|
||||
if ($jsonData === false) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Failed to read input data');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Failed to read input data']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode($jsonData, true);
|
||||
|
||||
// Handle cache clearing
|
||||
if ($data === null && !empty($agentId)) {
|
||||
$cacheKey = getAgentCacheKey($agentId, SESSION_CACHE_SUFFIX);
|
||||
$timeKey = getAgentCacheKey($agentId, SESSION_CACHE_TIME_SUFFIX);
|
||||
|
||||
unset($_SESSION[$cacheKey]);
|
||||
unset($_SESSION[$timeKey]);
|
||||
|
||||
Feedback::flash('SUCCESS', 'DEFAULT', "Cache for agent {$agentId} is cleared.");
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'message' => "Cache for agent {$agentId} is cleared."
|
||||
]);
|
||||
}
|
||||
// Handle cache storing
|
||||
elseif ($data) {
|
||||
$cacheKey = getAgentCacheKey($agentId, SESSION_CACHE_SUFFIX);
|
||||
$timeKey = getAgentCacheKey($agentId, SESSION_CACHE_TIME_SUFFIX);
|
||||
|
||||
$_SESSION[$cacheKey] = $data;
|
||||
$_SESSION[$timeKey] = time();
|
||||
|
||||
Feedback::flash('SUCCESS', 'DEFAULT', "Cache for agent {$agentId} is stored.");
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'message' => "Cache for agent {$agentId} is stored."
|
||||
]);
|
||||
}
|
||||
else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Invalid data format');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid data format']);
|
||||
}
|
||||
|
||||
// Handle AJAX requests
|
||||
} elseif (isset($_GET['action'])) {
|
||||
$action = $_GET['action'];
|
||||
$agentId = filter_input(INPUT_GET, 'agent', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($action === 'fetch') {
|
||||
$response = ['status' => 'success', 'data' => $data];
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'status') {
|
||||
$response = ['status' => 'success', 'data' => $statusData];
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle template display
|
||||
} else {
|
||||
|
||||
// Validate platform_id is set
|
||||
if (!isset($platform_id)) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Platform ID is not set');
|
||||
}
|
||||
|
||||
// Get host details for this platform
|
||||
$hostDetails = $hostObject->getHostDetails($platform_id);
|
||||
|
||||
// Group agents by host
|
||||
$agentsByHost = [];
|
||||
foreach ($hostDetails as $host) {
|
||||
$hostId = $host['id'];
|
||||
$agentsByHost[$hostId] = [
|
||||
'host_name' => $host['name'],
|
||||
'agents' => []
|
||||
];
|
||||
|
||||
// Get agents for this host
|
||||
$hostAgents = $agentObject->getAgentDetails($hostId);
|
||||
if ($hostAgents) {
|
||||
$agentsByHost[$hostId]['agents'] = $hostAgents;
|
||||
}
|
||||
|
||||
// Generate JWT tokens for each agent beforehand
|
||||
$agentTokens = [];
|
||||
foreach ($agentsByHost[$hostId]['agents'] as $agent) {
|
||||
$payload = [
|
||||
'iss' => 'Jilo Web',
|
||||
'aud' => $config['domain'],
|
||||
'iat' => time(),
|
||||
'exp' => time() + 3600,
|
||||
'agent_id' => $agent['id']
|
||||
];
|
||||
$agentTokens[$agent['id']] = $agentObject->generateAgentToken($payload, $agent['secret_key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Now we have:
|
||||
* $hostDetails - hosts in this platform
|
||||
* $agentsByHost[$hostId]['agents'] - agents details by hostId
|
||||
* $agentTokens[$agent['id']] - tokens for the agentsIds
|
||||
*/
|
||||
}
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/agents.php';
|
||||
}
|
||||
|
|
|
@ -8,26 +8,46 @@
|
|||
* Supports pagination.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
|
||||
require '../app/classes/component.php';
|
||||
|
||||
// connect to database
|
||||
$response = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
|
||||
|
||||
// if DB connection has error, display it and stop here
|
||||
if ($response['db'] === null) {
|
||||
Messages::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
Feedback::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
|
||||
// otherwise if DB connection is OK, go on
|
||||
} else {
|
||||
$db = $response['db'];
|
||||
|
||||
// Get current page for pagination
|
||||
$currentPage = $_REQUEST['page_num'] ?? 1;
|
||||
$currentPage = (int)$currentPage;
|
||||
|
||||
// specify time range
|
||||
include '../app/helpers/time_range.php';
|
||||
|
||||
// Build params for pagination
|
||||
$params = '';
|
||||
if (!empty($_REQUEST['from_time'])) {
|
||||
$params .= '&from_time=' . urlencode($_REQUEST['from_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['until_time'])) {
|
||||
$params .= '&until_time=' . urlencode($_REQUEST['until_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['name'])) {
|
||||
$params .= '&name=' . urlencode($_REQUEST['name']);
|
||||
}
|
||||
if (!empty($_REQUEST['id'])) {
|
||||
$params .= '&id=' . urlencode($_REQUEST['id']);
|
||||
}
|
||||
if (isset($_REQUEST['event'])) {
|
||||
$params .= '&event=' . urlencode($_REQUEST['event']);
|
||||
}
|
||||
|
||||
// pagination variables
|
||||
$items_per_page = 20;
|
||||
$offset = ($currentPage -1) * $items_per_page;
|
||||
|
||||
// jitsi component events list
|
||||
// we use $_REQUEST, so that both links and forms work
|
||||
// if it's there, but empty, we make it same as the field name; otherwise assign the value
|
||||
|
@ -40,15 +60,9 @@ if ($response['db'] === null) {
|
|||
// Component events listings
|
||||
//
|
||||
|
||||
|
||||
// list of all component events (default)
|
||||
require '../app/classes/component.php';
|
||||
$componentObject = new Component($db);
|
||||
|
||||
// pagination variables
|
||||
$items_per_page = 15;
|
||||
$browse_page = $_REQUEST['p'] ?? 1;
|
||||
$browse_page = (int)$browse_page;
|
||||
$offset = ($browse_page -1) * $items_per_page;
|
||||
|
||||
// prepare the result
|
||||
$search = $componentObject->jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time, $offset, $items_per_page);
|
||||
|
@ -57,7 +71,7 @@ if ($response['db'] === null) {
|
|||
if (!empty($search)) {
|
||||
// we get total items and number of pages
|
||||
$item_count = count($search_all);
|
||||
$page_count = ceil($item_count / $items_per_page);
|
||||
$totalPages = ceil($item_count / $items_per_page);
|
||||
|
||||
$components = array();
|
||||
$components['records'] = array();
|
||||
|
@ -78,30 +92,18 @@ if ($response['db'] === null) {
|
|||
}
|
||||
}
|
||||
|
||||
// prepare the widget
|
||||
$widget['full'] = false;
|
||||
$widget['name'] = 'AllComponents';
|
||||
$widget['filter'] = true;
|
||||
$widget['pagination'] = true;
|
||||
|
||||
// widget title
|
||||
// filter message
|
||||
$filterMessage = array();
|
||||
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
|
||||
$widget['title'] = 'Jitsi events for component <strong>' . $_REQUEST['name'] . '</strong>';
|
||||
array_push($filterMessage, 'Jitsi events for component "<strong>' . $_REQUEST['name'] . '</strong>"');
|
||||
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
|
||||
$widget['title'] = 'Jitsi events for component ID <strong>' . $_REQUEST['id'] . '</strong>';
|
||||
} else {
|
||||
$widget['title'] = 'Jitsi events for <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'];
|
||||
array_push($filterMessage, 'Jitsi events for component ID "<strong>' . $_REQUEST['id'] . '</strong>"');
|
||||
}
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// display the widget
|
||||
include '../app/templates/event-list-components.php';
|
||||
include '../app/templates/components.php';
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -8,18 +8,12 @@
|
|||
* Supports pagination.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
|
||||
require '../app/classes/conference.php';
|
||||
|
||||
// connect to database
|
||||
$response = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
|
||||
|
||||
// if DB connection has error, display it and stop here
|
||||
if ($response['db'] === null) {
|
||||
Messages::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
Feedback::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
|
||||
// otherwise if DB connection is OK, go on
|
||||
} else {
|
||||
|
@ -52,13 +46,31 @@ if ($response['db'] === null) {
|
|||
// Conference listings
|
||||
//
|
||||
|
||||
require '../app/classes/conference.php';
|
||||
$conferenceObject = new Conference($db);
|
||||
|
||||
// get current page for pagination
|
||||
$currentPage = $_REQUEST['page_num'] ?? 1;
|
||||
$currentPage = (int)$currentPage;
|
||||
|
||||
// pagination variables
|
||||
$items_per_page = 15;
|
||||
$browse_page = $_REQUEST['p'] ?? 1;
|
||||
$browse_page = (int)$browse_page;
|
||||
$offset = ($browse_page -1) * $items_per_page;
|
||||
$items_per_page = 20;
|
||||
$offset = ($currentPage -1) * $items_per_page;
|
||||
|
||||
// Build params for pagination
|
||||
$params = '';
|
||||
if (!empty($_REQUEST['from_time'])) {
|
||||
$params .= '&from_time=' . urlencode($_REQUEST['from_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['until_time'])) {
|
||||
$params .= '&until_time=' . urlencode($_REQUEST['until_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['name'])) {
|
||||
$params .= '&name=' . urlencode($_REQUEST['name']);
|
||||
}
|
||||
if (!empty($_REQUEST['id'])) {
|
||||
$params .= '&id=' . urlencode($_REQUEST['id']);
|
||||
}
|
||||
|
||||
// search and list specific conference ID
|
||||
if (isset($conferenceId)) {
|
||||
|
@ -77,7 +89,7 @@ if ($response['db'] === null) {
|
|||
if (!empty($search)) {
|
||||
// we get total items and number of pages
|
||||
$item_count = count($search_all);
|
||||
$page_count = ceil($item_count / $items_per_page);
|
||||
$totalPages = ceil($item_count / $items_per_page);
|
||||
|
||||
$conferences = array();
|
||||
$conferences['records'] = array();
|
||||
|
@ -139,32 +151,18 @@ if ($response['db'] === null) {
|
|||
}
|
||||
}
|
||||
|
||||
// prepare the widget
|
||||
$widget['full'] = false;
|
||||
$widget['name'] = 'Conferences';
|
||||
$widget['collapsible'] = false;
|
||||
$widget['collapsed'] = false;
|
||||
$widget['filter'] = true;
|
||||
$widget['pagination'] = true;
|
||||
|
||||
// widget title
|
||||
// filter message
|
||||
$filterMessage = array();
|
||||
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
|
||||
$widget['title'] = 'Conferences with name matching "<strong>' . $_REQUEST['name'] . '"</strong>';
|
||||
array_push($filterMessage, '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'];
|
||||
array_push($filterMessage, 'Conference with ID "<strong>' . $_REQUEST['id'] . '</strong>"');
|
||||
}
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// display the widget
|
||||
include '../app/templates/event-list-conferences.php';
|
||||
include '../app/templates/conferences.php';
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -1,257 +1,99 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Configuration management.
|
||||
* Config management.
|
||||
*
|
||||
* This page ("config") handles configuration by adding, editing, and deleting platforms,
|
||||
* hosts, agents, and the configuration file itself.
|
||||
* This page handles the config file.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$agent = $_REQUEST['agent'] ?? '';
|
||||
$host = $_REQUEST['host'] ?? '';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
require '../app/classes/config.php';
|
||||
require '../app/classes/host.php';
|
||||
require '../app/classes/agent.php';
|
||||
|
||||
$configObject = new Config();
|
||||
$hostObject = new Host($dbWeb);
|
||||
$agentObject = new Agent($dbWeb);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
/**
|
||||
* Handles form submissions from editing page
|
||||
*/
|
||||
require '../app/includes/rate_limit_middleware.php';
|
||||
|
||||
// editing the config file
|
||||
if (isset($_POST['item']) && $_POST['item'] === 'config_file') {
|
||||
// check if file is writable
|
||||
if (!is_writable($config_file)) {
|
||||
$_SESSION['error'] = "Configuration file is not writable.";
|
||||
} else {
|
||||
$result = $configObject->editConfigFile($_POST, $config_file);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "The config file is edited.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Editing the config file failed. Error: $result";
|
||||
}
|
||||
// For AJAX requests
|
||||
$isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
|
||||
|
||||
// Check if file is writable
|
||||
$isWritable = is_writable($config_file);
|
||||
$configMessage = '';
|
||||
if (!$isWritable) {
|
||||
$configMessage = Feedback::render('ERROR', 'DEFAULT', 'Config file is not writable', false);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Apply rate limiting
|
||||
checkRateLimit($dbWeb, 'config', $user_id);
|
||||
|
||||
// Ensure no output before this point
|
||||
ob_clean();
|
||||
|
||||
// For AJAX requests, get JSON data
|
||||
if ($isAjax) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Get raw input
|
||||
$jsonData = file_get_contents('php://input');
|
||||
|
||||
$postData = json_decode($jsonData, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$error = json_last_error_msg();
|
||||
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Invalid JSON data received: ' . $error, true);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Invalid JSON data received: ' . $error
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// new host adding
|
||||
} elseif (isset($_POST['new']) && isset($_POST['item']) && $_POST['new'] === 'true' && $_POST['item'] === 'host') {
|
||||
$newHost = [
|
||||
'address' => $address,
|
||||
'port' => $port,
|
||||
'platform_id' => $platform_id,
|
||||
'name' => $name,
|
||||
];
|
||||
$result = $hostObject->addHost($newHost);
|
||||
// Try to update config file
|
||||
$result = $configObject->editConfigFile($postData, $config_file);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "New Jilo host added.";
|
||||
$messageData = Feedback::getMessageData('NOTICE', 'DEFAULT', 'Config file updated successfully', true);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Config file updated successfully',
|
||||
'messageData' => $messageData
|
||||
]);
|
||||
} else {
|
||||
$_SESSION['error'] = "Adding the host failed. Error: $result";
|
||||
$messageData = Feedback::getMessageData('ERROR', 'DEFAULT', "Error updating config file: $result", true);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => "Error updating config file: $result",
|
||||
'messageData' => $messageData
|
||||
]);
|
||||
}
|
||||
|
||||
// new agent adding
|
||||
} elseif (isset($_POST['new']) && isset($_POST['item']) && $_POST['new'] === 'true' && $_POST['item'] === 'agent') {
|
||||
$newAgent = [
|
||||
'type_id' => $type,
|
||||
'url' => $url,
|
||||
'secret_key' => $secret_key,
|
||||
'check_period' => $check_period,
|
||||
];
|
||||
$result = $agentObject->addAgent($platform_id, $newAgent);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "New Jilo Agent added.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Adding the agent failed. Error: $result";
|
||||
}
|
||||
|
||||
// new platform adding
|
||||
} elseif (isset($_POST['new']) && $_POST['new'] === 'true') {
|
||||
$newPlatform = [
|
||||
'name' => $name,
|
||||
'jitsi_url' => $_POST['jitsi_url'],
|
||||
'jilo_database' => $_POST['jilo_database'],
|
||||
];
|
||||
$result = $platformObject->addPlatform($newPlatform);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "New Jitsi platform added.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Adding the platform failed. Error: $result";
|
||||
}
|
||||
|
||||
// deleting a host
|
||||
} elseif (isset($_POST['delete']) && isset($_POST['host']) && $_POST['delete'] === 'true') {
|
||||
$result = $hostObject->deleteHost($host);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "Host id \"{$_REQUEST['host']}\" deleted.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Deleting the host failed. Error: $result";
|
||||
}
|
||||
|
||||
// deleting an agent
|
||||
} elseif (isset($_POST['delete']) && isset($_POST['agent']) && $_POST['delete'] === 'true') {
|
||||
$result = $agentObject->deleteAgent($agent);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "Agent id \"{$_REQUEST['agent']}\" deleted.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Deleting the agent failed. Error: $result";
|
||||
}
|
||||
|
||||
// deleting a platform
|
||||
} elseif (isset($_POST['delete']) && $_POST['delete'] === 'true') {
|
||||
$platform = $_POST['platform'];
|
||||
$result = $platformObject->deletePlatform($platform);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "Platform \"{$platformObject['name']}\" added.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Adding the platform failed. Error: $result";
|
||||
}
|
||||
|
||||
// an update to an existing host
|
||||
} elseif (isset($_POST['host'])) {
|
||||
$updatedHost = [
|
||||
'id' => $host,
|
||||
'address' => $address,
|
||||
'port' => $port,
|
||||
'name' => $name,
|
||||
];
|
||||
$result = $hostObject->editHost($platform_id, $updatedHost);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "Host \"{$_REQUEST['address']}:{$_REQUEST['port']}\" edited.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Editing the host failed. Error: $result";
|
||||
}
|
||||
|
||||
// an update to an existing agent
|
||||
} elseif (isset($_POST['agent'])) {
|
||||
$updatedAgent = [
|
||||
'id' => $agent,
|
||||
'agent_type_id' => $type,
|
||||
'url' => $url,
|
||||
'secret_key' => $secret_key,
|
||||
'check_period' => $check_period,
|
||||
];
|
||||
$result = $agentObject->editAgent($platform_id, $updatedAgent);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "Agent id \"{$_REQUEST['agent']}\" edited.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Editing the agent failed. Error: $result";
|
||||
}
|
||||
|
||||
// an update to an existing platform
|
||||
} else {
|
||||
$platform = $_POST['platform'];
|
||||
$updatedPlatform = [
|
||||
'name' => $name,
|
||||
'jitsi_url' => $_POST['jitsi_url'],
|
||||
'jilo_database' => $_POST['jilo_database'],
|
||||
];
|
||||
$result = $platformObject->editPlatform($platform, $updatedPlatform);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] = "Platform \"{$_REQUEST['name']}\" edited.";
|
||||
} else {
|
||||
$_SESSION['error'] = "Editing the platform failed. Error: $result";
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// FIXME the new file is not loaded on first page load
|
||||
unset($config);
|
||||
header("Location: $app_root?page=config&item=$item");
|
||||
exit();
|
||||
// Handle non-AJAX POST
|
||||
$result = $configObject->editConfigFile($_POST, $config_file);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', 'Config file updated successfully', true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Error updating config file: $result", true);
|
||||
}
|
||||
|
||||
} else {
|
||||
header('Location: ' . htmlspecialchars($app_root) . '?page=config');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Only include template for non-AJAX requests
|
||||
if (!$isAjax) {
|
||||
/**
|
||||
* Handles GET requests to display templates.
|
||||
*/
|
||||
|
||||
switch ($item) {
|
||||
|
||||
case 'platform':
|
||||
if (isset($action) && $action === 'add') {
|
||||
include '../app/templates/config-platform-add.php';
|
||||
} elseif (isset($action) && $action === 'edit') {
|
||||
include '../app/templates/config-platform-edit.php';
|
||||
} elseif (isset($action) && $action === 'delete') {
|
||||
include '../app/templates/config-platform-delete.php';
|
||||
} else {
|
||||
if ($userObject->hasRight($user_id, 'view config file')) {
|
||||
include '../app/templates/config-platform.php';
|
||||
} else {
|
||||
include '../app/templates/error-unauthorized.php';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'host':
|
||||
if (isset($action) && $action === 'add') {
|
||||
include '../app/templates/config-host-add.php';
|
||||
} elseif (isset($action) && $action === 'edit') {
|
||||
$hostDetails = $hostObject->getHostDetails($platform_id, $agent);
|
||||
include '../app/templates/config-host-edit.php';
|
||||
} elseif (isset($action) && $action === 'delete') {
|
||||
$hostDetails = $hostObject->getHostDetails($platform_id, $agent);
|
||||
include '../app/templates/config-host-delete.php';
|
||||
} else {
|
||||
if ($userObject->hasRight($user_id, 'view config file')) {
|
||||
$hostDetails = $hostObject->getHostDetails();
|
||||
include '../app/templates/config-host.php';
|
||||
} else {
|
||||
include '../app/templates/error-unauthorized.php';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'endpoint':
|
||||
// TODO ad here endpoints options
|
||||
echo 'under construction';
|
||||
// switch ($action) {
|
||||
// case 'add-agent':
|
||||
// $jilo_agent_types = $agentObject->getAgentTypes();
|
||||
// $jilo_agents_in_platform = $agentObject->getPlatformAgentTypes($platform_id);
|
||||
// $jilo_agent_types_in_platform = array_column($jilo_agents_in_platform, 'agent_type_id');
|
||||
// include '../app/templates/config-add-agent.php';
|
||||
// break;
|
||||
// case 'edit':
|
||||
// if (isset($_GET['agent'])) {
|
||||
// $agentDetails = $agentObject->getAgentDetails($platform_id, $agent);
|
||||
// $jilo_agent_types = $agentObject->getAgentTypes();
|
||||
// include '../app/templates/config-edit-agent.php';
|
||||
// }
|
||||
// break;
|
||||
// case 'delete':
|
||||
// if (isset($_GET['agent'])) {
|
||||
// $agentDetails = $agentObject->getAgentDetails($platform_id, $agent);
|
||||
// include '../app/templates/config-delete-agent.php';
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
break;
|
||||
|
||||
case 'config_file':
|
||||
if (isset($action) && $action === 'edit') {
|
||||
include '../app/templates/config-configfile-edit.php';
|
||||
} else {
|
||||
if ($userObject->hasRight($user_id, 'view config file')) {
|
||||
include '../app/templates/config-configfile.php';
|
||||
} else {
|
||||
include '../app/templates/error-unauthorized.php';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// the default config page is the platforms page
|
||||
header("Location: $app_root?page=config&item=platform");
|
||||
exit();
|
||||
if ($userObject->hasRight($user_id, 'view config file')) {
|
||||
include '../app/templates/config.php';
|
||||
} else {
|
||||
$logObject->insertLog($user_id, "Unauthorized: User \"$currentUser\" tried to access \"config\" page. IP: $user_IP", 'system');
|
||||
include '../app/templates/error-unauthorized.php';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -9,9 +9,8 @@
|
|||
* 3. The most recent 10 conferences.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
require '../app/classes/conference.php';
|
||||
require '../app/classes/participant.php';
|
||||
|
@ -21,7 +20,7 @@ $response = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $pl
|
|||
|
||||
// if DB connection has error, display it and stop here
|
||||
if ($response['db'] === null) {
|
||||
Messages::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
Feedback::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
|
||||
// otherwise if DB connection is OK, go on
|
||||
} else {
|
||||
|
@ -228,5 +227,3 @@ if ($response['db'] === null) {
|
|||
include '../app/templates/widget.php';
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -1,207 +0,0 @@
|
|||
<?php
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$agent = $_REQUEST['agent'] ?? '';
|
||||
|
||||
require '../app/classes/config.php';
|
||||
require '../app/classes/agent.php';
|
||||
require '../app/classes/conference.php';
|
||||
|
||||
$configObject = new Config();
|
||||
$agentObject = new Agent($dbWeb);
|
||||
|
||||
// connect to Jilo database
|
||||
$response = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
|
||||
|
||||
// if DB connection has error, display it and stop here
|
||||
if ($response['db'] === null) {
|
||||
Messages::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
|
||||
// otherwise if DB connection is OK, go on
|
||||
} else {
|
||||
$db = $response['db'];
|
||||
|
||||
$conferenceObject = new Conference($db);
|
||||
|
||||
switch ($item) {
|
||||
|
||||
case 'graphs':
|
||||
// Connect to Jilo database for log data
|
||||
$jilo_response = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
|
||||
if ($jilo_response['db'] === null) {
|
||||
Messages::flash('ERROR', 'DEFAULT', $jilo_response['error']);
|
||||
break;
|
||||
}
|
||||
$jilo_db = $jilo_response['db'];
|
||||
|
||||
// Get date range for the last 7 days
|
||||
$from_time = date('Y-m-d', strtotime('-7 days'));
|
||||
$until_time = date('Y-m-d');
|
||||
|
||||
// Define graphs to show
|
||||
$graphs = [
|
||||
[
|
||||
'graph_name' => 'conferences',
|
||||
'graph_title' => 'Conferences in "' . htmlspecialchars($platformDetails[0]['name']) . '" over time',
|
||||
'datasets' => []
|
||||
],
|
||||
[
|
||||
'graph_name' => 'participants',
|
||||
'graph_title' => 'Participants in "' . htmlspecialchars($platformDetails[0]['name']) . '" over time',
|
||||
'datasets' => []
|
||||
]
|
||||
];
|
||||
|
||||
// Get Jitsi API data
|
||||
$conferences_api = $agentObject->getHistoricalData(
|
||||
$platform_id,
|
||||
'jicofo',
|
||||
'conferences',
|
||||
$from_time,
|
||||
$until_time
|
||||
);
|
||||
$graphs[0]['datasets'][] = [
|
||||
'data' => $conferences_api,
|
||||
'label' => 'Conferences from Jitsi API',
|
||||
'color' => 'rgba(75, 192, 192, 1)'
|
||||
];
|
||||
|
||||
// Get conference data from logs
|
||||
$conferences_logs = $conferenceObject->conferenceNumber(
|
||||
$from_time,
|
||||
$until_time
|
||||
);
|
||||
$graphs[0]['datasets'][] = [
|
||||
'data' => $conferences_logs,
|
||||
'label' => 'Conferences from Logs',
|
||||
'color' => 'rgba(255, 99, 132, 1)'
|
||||
];
|
||||
|
||||
// Get participants data
|
||||
$participants_api = $agentObject->getHistoricalData(
|
||||
$platform_id,
|
||||
'jicofo',
|
||||
'participants',
|
||||
$from_time,
|
||||
$until_time
|
||||
);
|
||||
$graphs[1]['datasets'][] = [
|
||||
'data' => $participants_api,
|
||||
'label' => 'Participants from Jitsi API',
|
||||
'color' => 'rgba(75, 192, 192, 1)'
|
||||
];
|
||||
|
||||
// Prepare data for template
|
||||
$graph = $graphs;
|
||||
|
||||
// prepare the widget
|
||||
$widget['full'] = false;
|
||||
$widget['name'] = 'Graphs';
|
||||
$widget['title'] = 'Jitsi graphs';
|
||||
|
||||
include '../app/templates/graphs-combined.php';
|
||||
break;
|
||||
|
||||
case 'latest':
|
||||
// Define metrics to display
|
||||
$metrics = [
|
||||
'Basic stats' => [
|
||||
'conferences' => ['label' => 'Current conferences', 'link' => 'conferences'],
|
||||
'participants' => ['label' => 'Current participants', 'link' => 'participants'],
|
||||
'total_conferences_created' => ['label' => 'Total conferences created'],
|
||||
'total_participants' => ['label' => 'Total participants']
|
||||
],
|
||||
'Bridge stats' => [
|
||||
'bridge_selector.bridge_count' => ['label' => 'Bridge count'],
|
||||
'bridge_selector.operational_bridge_count' => ['label' => 'Operational bridges'],
|
||||
'bridge_selector.in_shutdown_bridge_count' => ['label' => 'Bridges in shutdown']
|
||||
],
|
||||
'Jibri stats' => [
|
||||
'jibri_detector.count' => ['label' => 'Jibri count'],
|
||||
'jibri_detector.available' => ['label' => 'Jibri idle'],
|
||||
'jibri.live_streaming_active' => ['label' => 'Jibri active streaming'],
|
||||
'jibri.recording_active' => ['label' => 'Jibri active recording'],
|
||||
|
||||
],
|
||||
'System stats' => [
|
||||
'threads' => ['label' => 'Threads'],
|
||||
'stress_level' => ['label' => 'Stress level'],
|
||||
'version' => ['label' => 'Version']
|
||||
]
|
||||
];
|
||||
|
||||
// Get latest data for all the agents
|
||||
$agents = ['jvb', 'jicofo', 'jibri', 'prosody', 'nginx'];
|
||||
$widget['records'] = [];
|
||||
|
||||
// Initialize records for each agent
|
||||
foreach ($agents as $agent) {
|
||||
$record = [
|
||||
'table_headers' => strtoupper($agent),
|
||||
'metrics' => [],
|
||||
'timestamp' => null
|
||||
];
|
||||
|
||||
// Fetch all metrics for this agent
|
||||
foreach ($metrics as $section => $section_metrics) {
|
||||
foreach ($section_metrics as $metric => $metricConfig) {
|
||||
$data = $agentObject->getLatestData($platform_id, $agent, $metric);
|
||||
if ($data !== null) {
|
||||
$record['metrics'][$section][$metric] = [
|
||||
'value' => $data['value'],
|
||||
'label' => $metricConfig['label'],
|
||||
'link' => isset($metricConfig['link']) ? $metricConfig['link'] : null
|
||||
];
|
||||
// Use the most recent timestamp
|
||||
if ($record['timestamp'] === null || strtotime($data['timestamp']) > strtotime($record['timestamp'])) {
|
||||
$record['timestamp'] = $data['timestamp'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($record['metrics'])) {
|
||||
$widget['records'][] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// prepare the widget
|
||||
$widget['full'] = false;
|
||||
$widget['name'] = 'LatestData';
|
||||
$widget['title'] = 'Latest data from Jilo Agents';
|
||||
$widget['collapsible'] = false;
|
||||
$widget['collapsed'] = false;
|
||||
$widget['filter'] = false;
|
||||
$widget['metrics'] = $metrics; // Pass metrics configuration to template
|
||||
if (!empty($widget['records'])) {
|
||||
$widget['full'] = true;
|
||||
}
|
||||
$widget['pagination'] = false;
|
||||
|
||||
include '../app/templates/latest-data.php';
|
||||
break;
|
||||
|
||||
case 'configjs':
|
||||
$mode = $_REQUEST['mode'] ?? '';
|
||||
$raw = ($mode === 'raw');
|
||||
$platformConfigjs = $configObject->getPlatformConfigjs($platformDetails[0]['jitsi_url'], $raw);
|
||||
include '../app/templates/data-configjs.php';
|
||||
break;
|
||||
|
||||
case 'interfaceconfigjs':
|
||||
$mode = $_REQUEST['mode'] ?? '';
|
||||
$raw = ($mode === 'raw');
|
||||
$platformInterfaceConfigjs = $configObject->getPlatformInterfaceConfigjs($platformDetails[0]['jitsi_url'], $raw);
|
||||
include '../app/templates/data-interfaceconfigjs.php';
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$agent = $_REQUEST['agent'] ?? '';
|
||||
|
||||
require '../app/classes/agent.php';
|
||||
require '../app/classes/conference.php';
|
||||
require '../app/classes/host.php';
|
||||
|
||||
$agentObject = new Agent($dbWeb);
|
||||
$hostObject = new Host($dbWeb);
|
||||
|
||||
// Connect to Jilo database for log data
|
||||
$response = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
|
||||
if ($response['db'] === null) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
} else {
|
||||
$db = $response['db'];
|
||||
}
|
||||
$conferenceObject = new Conference($db);
|
||||
|
||||
// Get date range for the last 7 days
|
||||
$from_time = date('Y-m-d', strtotime('-7 days'));
|
||||
$until_time = date('Y-m-d');
|
||||
|
||||
// Define graphs to show
|
||||
$graphs = [
|
||||
[
|
||||
'graph_name' => 'conferences',
|
||||
'graph_title' => 'Conferences in "' . htmlspecialchars($platformDetails[0]['name']) . '" over time',
|
||||
'datasets' => []
|
||||
],
|
||||
[
|
||||
'graph_name' => 'participants',
|
||||
'graph_title' => 'Participants in "' . htmlspecialchars($platformDetails[0]['name']) . '" over time',
|
||||
'datasets' => []
|
||||
]
|
||||
];
|
||||
|
||||
// Get Jitsi API data
|
||||
$conferences_api = $agentObject->getHistoricalData(
|
||||
$platform_id,
|
||||
'jicofo',
|
||||
'conferences',
|
||||
$from_time,
|
||||
$until_time
|
||||
);
|
||||
$graphs[0]['datasets'][] = [
|
||||
'data' => $conferences_api,
|
||||
'label' => 'Conferences from Jitsi API',
|
||||
'color' => 'rgba(75, 192, 192, 1)'
|
||||
];
|
||||
|
||||
// Get conference data from logs
|
||||
$conferences_logs = $conferenceObject->conferenceNumber(
|
||||
$from_time,
|
||||
$until_time
|
||||
);
|
||||
$graphs[0]['datasets'][] = [
|
||||
'data' => $conferences_logs,
|
||||
'label' => 'Conferences from Logs',
|
||||
'color' => 'rgba(255, 99, 132, 1)'
|
||||
];
|
||||
|
||||
// Get participants data
|
||||
$participants_api = $agentObject->getHistoricalData(
|
||||
$platform_id,
|
||||
'jicofo',
|
||||
'participants',
|
||||
$from_time,
|
||||
$until_time
|
||||
);
|
||||
$graphs[1]['datasets'][] = [
|
||||
'data' => $participants_api,
|
||||
'label' => 'Participants from Jitsi API',
|
||||
'color' => 'rgba(75, 192, 192, 1)'
|
||||
];
|
||||
|
||||
// Prepare data for template
|
||||
$graph = $graphs;
|
||||
|
||||
// prepare the widget
|
||||
$widget['full'] = false;
|
||||
$widget['name'] = 'Graphs';
|
||||
$widget['title'] = 'Jitsi graphs';
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/graphs.php';
|
|
@ -1,9 +1,6 @@
|
|||
<?php
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
include '../app/templates/help-main.php';
|
||||
|
||||
?>
|
||||
include '../app/templates/help.php';
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
require '../app/classes/agent.php';
|
||||
require '../app/classes/host.php';
|
||||
|
||||
$agentObject = new Agent($dbWeb);
|
||||
$hostObject = new Host($dbWeb);
|
||||
|
||||
// Define metrics to display
|
||||
$metrics = [
|
||||
'Basic stats' => [
|
||||
'conferences' => ['label' => 'Current conferences', 'link' => 'conferences'],
|
||||
'participants' => ['label' => 'Current participants', 'link' => 'participants'],
|
||||
'total_conferences_created' => ['label' => 'Total conferences created'],
|
||||
'total_participants' => ['label' => 'Total participants']
|
||||
],
|
||||
'Bridge stats' => [
|
||||
'bridge_selector.bridge_count' => ['label' => 'Bridge count'],
|
||||
'bridge_selector.operational_bridge_count' => ['label' => 'Operational bridges'],
|
||||
'bridge_selector.in_shutdown_bridge_count' => ['label' => 'Bridges in shutdown']
|
||||
],
|
||||
'Jibri stats' => [
|
||||
'jibri_detector.count' => ['label' => 'Jibri count'],
|
||||
'jibri_detector.available' => ['label' => 'Jibri idle'],
|
||||
'jibri.live_streaming_active' => ['label' => 'Jibri active streaming'],
|
||||
'jibri.recording_active' => ['label' => 'Jibri active recording'],
|
||||
],
|
||||
'System stats' => [
|
||||
'threads' => ['label' => 'Threads'],
|
||||
'stress_level' => ['label' => 'Stress level'],
|
||||
'version' => ['label' => 'Version']
|
||||
]
|
||||
];
|
||||
|
||||
// Get all hosts for this platform
|
||||
$hosts = $hostObject->getHostDetails($platform_id);
|
||||
$hostsData = [];
|
||||
|
||||
// For each host, get its agents and their metrics
|
||||
foreach ($hosts as $host) {
|
||||
$hostData = [
|
||||
'id' => $host['id'],
|
||||
'name' => $host['name'] ?: $host['address'],
|
||||
'address' => $host['address'],
|
||||
'agents' => []
|
||||
];
|
||||
|
||||
// Get agents for this host
|
||||
$hostAgents = $agentObject->getAgentDetails($host['id']);
|
||||
foreach ($hostAgents as $agent) {
|
||||
$agentData = [
|
||||
'id' => $agent['id'],
|
||||
'type' => $agent['agent_description'],
|
||||
'name' => strtoupper($agent['agent_description']),
|
||||
'metrics' => [],
|
||||
'timestamp' => null
|
||||
];
|
||||
|
||||
// Fetch all metrics for this agent
|
||||
foreach ($metrics as $section => $section_metrics) {
|
||||
foreach ($section_metrics as $metric => $metricConfig) {
|
||||
// Get latest data
|
||||
$latestData = $agentObject->getLatestData($host['id'], $agent['agent_description'], $metric);
|
||||
|
||||
if ($latestData !== null) {
|
||||
// Get the previous record
|
||||
$previousData = $agentObject->getPreviousRecord(
|
||||
$host['id'],
|
||||
$agent['agent_description'],
|
||||
$metric,
|
||||
$latestData['timestamp']
|
||||
);
|
||||
|
||||
$agentData['metrics'][$section][$metric] = [
|
||||
'latest' => [
|
||||
'value' => $latestData['value'],
|
||||
'timestamp' => $latestData['timestamp']
|
||||
],
|
||||
'previous' => $previousData,
|
||||
'label' => $metricConfig['label'],
|
||||
'link' => isset($metricConfig['link']) ? $metricConfig['link'] : null
|
||||
];
|
||||
|
||||
// Use the most recent timestamp for the agent
|
||||
if ($agentData['timestamp'] === null || strtotime($latestData['timestamp']) > strtotime($agentData['timestamp'])) {
|
||||
$agentData['timestamp'] = $latestData['timestamp'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($agentData['metrics'])) {
|
||||
$hostData['agents'][] = $agentData;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($hostData['agents'])) {
|
||||
$hostsData[] = $hostData;
|
||||
}
|
||||
}
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/latest.php';
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
$mode = $_REQUEST['mode'] ?? '';
|
||||
$raw = ($mode === 'raw');
|
||||
$livejsFile = $_REQUEST['item'] ?? '';
|
||||
|
||||
require '../app/classes/settings.php';
|
||||
$settingsObject = new Settings();
|
||||
|
||||
$livejsData = $settingsObject->getPlatformJsFile($platformDetails[0]['jitsi_url'], $item, $raw);
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/livejs.php';
|
|
@ -19,46 +19,75 @@ unset($error);
|
|||
try {
|
||||
|
||||
// connect to database
|
||||
$dbWeb = connectDB($config);
|
||||
$dbWeb = connectDB($config)['db'];
|
||||
|
||||
// Initialize RateLimiter
|
||||
require_once '../app/classes/ratelimiter.php';
|
||||
$rateLimiter = new RateLimiter($dbWeb['db']);
|
||||
$rateLimiter = new RateLimiter($dbWeb);
|
||||
|
||||
// Get user IP
|
||||
$user_IP = getUserIP();
|
||||
|
||||
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
||||
try {
|
||||
$username = $_POST['username'];
|
||||
$password = $_POST['password'];
|
||||
// Validate form data
|
||||
$security = SecurityHelper::getInstance();
|
||||
$formData = $security->sanitizeArray($_POST, ['username', 'password', 'remember_me', 'csrf_token']);
|
||||
|
||||
// Check if IP is blacklisted
|
||||
if ($rateLimiter->isIpBlacklisted($user_IP)) {
|
||||
throw new Exception(Messages::get('LOGIN', 'IP_BLACKLISTED')['message']);
|
||||
$validationRules = [
|
||||
'username' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'min' => 3,
|
||||
'max' => 20
|
||||
],
|
||||
'password' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'min' => 2
|
||||
]
|
||||
];
|
||||
|
||||
$errors = $security->validateFormData($formData, $validationRules);
|
||||
if (!empty($errors)) {
|
||||
throw new Exception("Invalid input: " . implode(", ", $errors));
|
||||
}
|
||||
|
||||
// Check rate limiting (but skip if IP is whitelisted)
|
||||
$username = $formData['username'];
|
||||
$password = $formData['password'];
|
||||
|
||||
// Skip all checks if IP is whitelisted
|
||||
if (!$rateLimiter->isIpWhitelisted($user_IP)) {
|
||||
$attempts = $rateLimiter->getRecentAttempts($user_IP);
|
||||
if ($attempts >= $rateLimiter->maxAttempts) {
|
||||
throw new Exception(Messages::get('LOGIN', 'LOGIN_BLOCKED')['message']);
|
||||
// Check if IP is blacklisted
|
||||
if ($rateLimiter->isIpBlacklisted($user_IP)) {
|
||||
throw new Exception(Feedback::get('LOGIN', 'IP_BLACKLISTED')['message']);
|
||||
}
|
||||
|
||||
// Check rate limiting before recording attempt
|
||||
if ($rateLimiter->tooManyAttempts($username, $user_IP)) {
|
||||
throw new Exception(Feedback::get('LOGIN', 'TOO_MANY_ATTEMPTS')['message']);
|
||||
}
|
||||
|
||||
// Record this attempt
|
||||
$rateLimiter->attempt($username, $user_IP);
|
||||
}
|
||||
|
||||
// login successful
|
||||
if ( $userObject->login($username, $password) ) {
|
||||
// if remember_me is checked, max out the session
|
||||
if (isset($_POST['remember_me'])) {
|
||||
if (isset($formData['remember_me'])) {
|
||||
// 30*24*60*60 = 30 days
|
||||
$cookie_lifetime = 30 * 24 * 60 * 60;
|
||||
$setcookie_lifetime = time() + 30 * 24 * 60 * 60;
|
||||
$gc_maxlifetime = 30 * 24 * 60 * 60;
|
||||
} else {
|
||||
// 0 - session end on browser close
|
||||
// 1440 - 24 minutes (default)
|
||||
$cookie_lifetime = 0;
|
||||
$setcookie_lifetime = 0;
|
||||
$gc_maxlifetime = 1440;
|
||||
}
|
||||
|
||||
// Regenerate session ID to prevent session fixation
|
||||
session_regenerate_id(true);
|
||||
|
||||
// set session lifetime and cookies
|
||||
setcookie('username', $username, [
|
||||
'expires' => $setcookie_lifetime,
|
||||
|
@ -69,20 +98,28 @@ try {
|
|||
'samesite' => 'Strict'
|
||||
]);
|
||||
|
||||
// Set session variables
|
||||
$_SESSION['USER_ID'] = $userObject->getUserId($username)[0]['id'];
|
||||
$_SESSION['USERNAME'] = $username;
|
||||
$_SESSION['LAST_ACTIVITY'] = time();
|
||||
if (isset($formData['remember_me'])) {
|
||||
$_SESSION['REMEMBER_ME'] = true;
|
||||
}
|
||||
|
||||
// Log successful login
|
||||
$user_id = $userObject->getUserId($username)[0]['id'];
|
||||
$logObject->insertLog($user_id, "Login: User \"$username\" logged in. IP: $user_IP", 'user');
|
||||
|
||||
// Set success message and redirect
|
||||
Messages::flash('LOGIN', 'LOGIN_SUCCESS', null, true);
|
||||
Feedback::flash('LOGIN', 'LOGIN_SUCCESS');
|
||||
header('Location: ' . htmlspecialchars($app_root));
|
||||
exit();
|
||||
} else {
|
||||
throw new Exception(Messages::get('LOGIN', 'LOGIN_FAILED')['message']);
|
||||
throw new Exception(Feedback::get('LOGIN', 'LOGIN_FAILED')['message']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Log the failed attempt
|
||||
Messages::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||
if (isset($username)) {
|
||||
$user_id = $userObject->getUserId($username)[0]['id'] ?? 0;
|
||||
$logObject->insertLog($user_id, "Login: Failed login attempt for user \"$username\". IP: $user_IP. Reason: {$e->getMessage()}", 'user');
|
||||
|
@ -90,19 +127,16 @@ try {
|
|||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Messages::flash('ERROR', 'DEFAULT', 'There was an unexpected error. Please try again.');
|
||||
Feedback::flash('ERROR', 'DEFAULT');
|
||||
}
|
||||
|
||||
// Show configured login message if any
|
||||
if (!empty($config['login_message'])) {
|
||||
echo Messages::render('NOTICE', 'DEFAULT', $config['login_message'], false, false, false);
|
||||
echo Feedback::render('NOTICE', 'DEFAULT', $config['login_message'], false);
|
||||
}
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/form-login.php';
|
||||
|
||||
?>
|
||||
|
|
|
@ -3,58 +3,98 @@
|
|||
/**
|
||||
* Logs listings
|
||||
*
|
||||
* This page ("logs") retrieves and displays logs for a specified user within a time range.
|
||||
* It supports pagination and filtering, and generates a widget to display the logs.
|
||||
* This page ("logs") retrieves and displays logs within a time range
|
||||
* either for a specified user or for all users.
|
||||
* It supports pagination and filtering.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Check for rights; user or system
|
||||
if (($userObject->hasRight($user_id, 'superuser') ||
|
||||
$userObject->hasRight($user_id, 'view app logs'))) {
|
||||
$scope = 'system';
|
||||
} else {
|
||||
$scope = 'user';
|
||||
$has_system_access = ($userObject->hasRight($user_id, 'superuser') ||
|
||||
$userObject->hasRight($user_id, 'view app logs'));
|
||||
|
||||
// Get current page for pagination
|
||||
$currentPage = $_REQUEST['page_num'] ?? 1;
|
||||
$currentPage = (int)$currentPage;
|
||||
|
||||
// Get selected tab
|
||||
$selected_tab = $_REQUEST['tab'] ?? 'user';
|
||||
if ($selected_tab === 'system' && !$has_system_access) {
|
||||
$selected_tab = 'user';
|
||||
}
|
||||
|
||||
// Set scope based on selected tab
|
||||
$scope = ($selected_tab === 'system') ? 'system' : 'user';
|
||||
|
||||
// specify time range
|
||||
include '../app/helpers/time_range.php';
|
||||
|
||||
// Prepare search filters
|
||||
$filters = [];
|
||||
if (isset($_REQUEST['from_time']) && !empty($_REQUEST['from_time'])) {
|
||||
$filters['from_time'] = $_REQUEST['from_time'];
|
||||
}
|
||||
if (isset($_REQUEST['until_time']) && !empty($_REQUEST['until_time'])) {
|
||||
$filters['until_time'] = $_REQUEST['until_time'];
|
||||
}
|
||||
if (isset($_REQUEST['message']) && !empty($_REQUEST['message'])) {
|
||||
$filters['message'] = $_REQUEST['message'];
|
||||
}
|
||||
if ($scope === 'system' && isset($_REQUEST['id']) && !empty($_REQUEST['id'])) {
|
||||
$filters['id'] = $_REQUEST['id'];
|
||||
}
|
||||
|
||||
// pagination variables
|
||||
$items_per_page = 15;
|
||||
$browse_page = $_REQUEST['p'] ?? 1;
|
||||
$browse_page = (int)$browse_page;
|
||||
$offset = ($browse_page -1) * $items_per_page;
|
||||
$offset = ($currentPage - 1) * $items_per_page;
|
||||
|
||||
// Build params for pagination
|
||||
$params = '';
|
||||
if (!empty($_REQUEST['from_time'])) {
|
||||
$params .= '&from_time=' . urlencode($_REQUEST['from_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['until_time'])) {
|
||||
$params .= '&until_time=' . urlencode($_REQUEST['until_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['message'])) {
|
||||
$params .= '&message=' . urlencode($_REQUEST['message']);
|
||||
}
|
||||
if (!empty($_REQUEST['id'])) {
|
||||
$params .= '&id=' . urlencode($_REQUEST['id']);
|
||||
}
|
||||
if (isset($_REQUEST['tab'])) {
|
||||
$params .= '&tab=' . urlencode($_REQUEST['tab']);
|
||||
}
|
||||
|
||||
// prepare the result
|
||||
$search = $logObject->readLog($user_id, $scope, $offset, $items_per_page);
|
||||
$search_all = $logObject->readLog($user_id, $scope);
|
||||
$search = $logObject->readLog($user_id, $scope, $offset, $items_per_page, $filters);
|
||||
$search_all = $logObject->readLog($user_id, $scope, 0, 0, $filters);
|
||||
|
||||
if (!empty($search)) {
|
||||
// we get total items and number of pages
|
||||
$item_count = count($search_all);
|
||||
$page_count = ceil($item_count / $items_per_page);
|
||||
$totalPages = ceil($item_count / $items_per_page);
|
||||
|
||||
$logs = array();
|
||||
$logs['records'] = array();
|
||||
|
||||
foreach ($search as $item) {
|
||||
|
||||
// when we show only user's logs, omit user_id column
|
||||
if ($scope === 'user') {
|
||||
$log_record = array(
|
||||
// assign title to the field in the array record
|
||||
'time' => $item['time'],
|
||||
'log message' => $item['message']
|
||||
'time' => $item['time'],
|
||||
'log message' => $item['message']
|
||||
);
|
||||
} else {
|
||||
$log_record = array(
|
||||
// assign title to the field in the array record
|
||||
'userID' => $item['user_id'],
|
||||
'time' => $item['time'],
|
||||
'log message' => $item['message']
|
||||
'userID' => $item['user_id'],
|
||||
'username' => $item['username'],
|
||||
'time' => $item['time'],
|
||||
'log message' => $item['message']
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -63,21 +103,7 @@ if (!empty($search)) {
|
|||
}
|
||||
}
|
||||
|
||||
// prepare the widget
|
||||
$widget['full'] = false;
|
||||
$widget['collapsible'] = false;
|
||||
$widget['name'] = 'Logs';
|
||||
$username = $userObject->getUserDetails($user_id)[0]['username'];
|
||||
$widget['title'] = "Log events for user \"$username\"";
|
||||
$widget['filter'] = true;
|
||||
if (!empty($logs['records'])) {
|
||||
$widget['full'] = true;
|
||||
$widget['table_headers'] = array_keys($logs['records'][0]);
|
||||
$widget['table_records'] = $logs['records'];
|
||||
}
|
||||
$widget['pagination'] = true;
|
||||
|
||||
// display the widget
|
||||
include '../app/templates/logs-list.php';
|
||||
|
||||
?>
|
||||
// Load the template
|
||||
include '../app/templates/logs.php';
|
||||
|
|
|
@ -8,18 +8,12 @@
|
|||
* Supports pagination.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
|
||||
require '../app/classes/participant.php';
|
||||
|
||||
// connect to database
|
||||
$response = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
|
||||
|
||||
// if DB connection has error, display it and stop here
|
||||
if ($response['db'] === null) {
|
||||
Messages::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
Feedback::flash('ERROR', 'DEFAULT', $response['error']);
|
||||
|
||||
// otherwise if DB connection is OK, go on
|
||||
} else {
|
||||
|
@ -52,13 +46,34 @@ if ($response['db'] === null) {
|
|||
// Participant listings
|
||||
//
|
||||
|
||||
require '../app/classes/participant.php';
|
||||
$participantObject = new Participant($db);
|
||||
|
||||
// get current page for pagination
|
||||
$currentPage = $_REQUEST['page_num'] ?? 1;
|
||||
$currentPage = (int)$currentPage;
|
||||
|
||||
// pagination variables
|
||||
$items_per_page = 15;
|
||||
$browse_page = $_REQUEST['p'] ?? 1;
|
||||
$browse_page = (int)$browse_page;
|
||||
$offset = ($browse_page -1) * $items_per_page;
|
||||
$items_per_page = 20;
|
||||
$offset = ($currentPage -1) * $items_per_page;
|
||||
|
||||
// Build params for pagination
|
||||
$params = '';
|
||||
if (!empty($_REQUEST['from_time'])) {
|
||||
$params .= '&from_time=' . urlencode($_REQUEST['from_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['until_time'])) {
|
||||
$params .= '&until_time=' . urlencode($_REQUEST['until_time']);
|
||||
}
|
||||
if (!empty($_REQUEST['name'])) {
|
||||
$params .= '&name=' . urlencode($_REQUEST['name']);
|
||||
}
|
||||
if (!empty($_REQUEST['id'])) {
|
||||
$params .= '&id=' . urlencode($_REQUEST['id']);
|
||||
}
|
||||
if (isset($_REQUEST['event'])) {
|
||||
$params .= '&ip=' . urlencode($_REQUEST['ip']);
|
||||
}
|
||||
|
||||
// search and list specific participant ID
|
||||
if (isset($participantId)) {
|
||||
|
@ -82,7 +97,7 @@ if ($response['db'] === null) {
|
|||
if (!empty($search)) {
|
||||
// we get total items and number of pages
|
||||
$item_count = count($search_all);
|
||||
$page_count = ceil($item_count / $items_per_page);
|
||||
$totalPages = ceil($item_count / $items_per_page);
|
||||
|
||||
$participants = array();
|
||||
$participants['records'] = array();
|
||||
|
@ -144,34 +159,20 @@ if ($response['db'] === null) {
|
|||
}
|
||||
}
|
||||
|
||||
// prepare the widget
|
||||
$widget['full'] = false;
|
||||
$widget['name'] = 'Participants';
|
||||
$widget['collapsible'] = false;
|
||||
$widget['collapsed'] = false;
|
||||
$widget['filter'] = true;
|
||||
$widget['pagination'] = true;
|
||||
|
||||
// widget title
|
||||
// filter message
|
||||
$filterMessage = array();
|
||||
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
|
||||
$widget['title'] = 'Conferences with participant name (stats_id) matching "<strong>' . $_REQUEST['name'] . '"</strong>';
|
||||
array_push($filterMessage, '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>';
|
||||
array_push($filterMessage, 'Conferences 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'];
|
||||
array_push($filterMessage, 'Conferences with participant IP matching "<strong>' . $participantIp . '</strong>"');
|
||||
}
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// display the widget
|
||||
include '../app/templates/widget.php';
|
||||
include '../app/templates/participants.php';
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -12,24 +12,40 @@
|
|||
* - `edit`: Edit user profile details, rights, or avatar.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
|
||||
// if a form is submitted, it's from the edit page
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
|
||||
require_once '../app/classes/validator.php';
|
||||
|
||||
// Apply rate limiting for profile operations
|
||||
require_once '../app/includes/rate_limit_middleware.php';
|
||||
checkRateLimit($dbWeb, 'profile', $user_id);
|
||||
|
||||
$item = $_REQUEST['item'] ?? '';
|
||||
|
||||
// avatar removal
|
||||
if ($item === 'avatar' && $action === 'remove') {
|
||||
$validator = new Validator(['user_id' => $user_id]);
|
||||
$rules = [
|
||||
'user_id' => [
|
||||
'required' => true,
|
||||
'numeric' => true
|
||||
]
|
||||
];
|
||||
|
||||
if (!$validator->validate($rules)) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
|
||||
header("Location: $app_root?page=profile");
|
||||
exit();
|
||||
}
|
||||
|
||||
$result = $userObject->removeAvatar($user_id, $config['avatars_path'].$userDetails[0]['avatar']);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] .= "Avatar for user \"{$userDetails[0]['username']}\" is removed. ";
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Avatar for user \"{$userDetails[0]['username']}\" is removed.");
|
||||
} else {
|
||||
$_SESSION['error'] .= "Removing the avatar failed. Error: $result ";
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Removing the avatar failed. Error: $result");
|
||||
}
|
||||
|
||||
header("Location: $app_root?page=profile");
|
||||
|
@ -37,35 +53,73 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
|||
}
|
||||
|
||||
// update the profile
|
||||
$validator = new Validator($_POST);
|
||||
$rules = [
|
||||
'name' => [
|
||||
'max' => 100
|
||||
],
|
||||
'email' => [
|
||||
'email' => true,
|
||||
'max' => 100
|
||||
],
|
||||
'timezone' => [
|
||||
'max' => 50
|
||||
],
|
||||
'bio' => [
|
||||
'max' => 1000
|
||||
]
|
||||
];
|
||||
|
||||
if (!$validator->validate($rules)) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
|
||||
header("Location: $app_root?page=profile");
|
||||
exit();
|
||||
}
|
||||
|
||||
$updatedUser = [
|
||||
'name' => $_POST['name'] ?? '',
|
||||
'email' => $_POST['email'] ?? '',
|
||||
'timezone' => $_POST['timezone'] ?? '',
|
||||
'bio' => $_POST['bio'] ?? '',
|
||||
];
|
||||
'name' => htmlspecialchars($_POST['name'] ?? ''),
|
||||
'email' => filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL),
|
||||
'timezone' => htmlspecialchars($_POST['timezone'] ?? ''),
|
||||
'bio' => htmlspecialchars($_POST['bio'] ?? ''),
|
||||
];
|
||||
$result = $userObject->editUser($user_id, $updatedUser);
|
||||
if ($result === true) {
|
||||
$_SESSION['notice'] .= "User details for \"{$updatedUser['name']}\" are edited. ";
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "User details for \"{$updatedUser['name']}\" are edited.");
|
||||
} else {
|
||||
$_SESSION['error'] .= "Editing the user details failed. Error: $result ";
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Editing the user details failed. Error: $result");
|
||||
}
|
||||
|
||||
// update the rights
|
||||
$newRights = $_POST['rights'] ?? array();
|
||||
// extract the new right_ids
|
||||
$userRightsIds = array_column($userRights, 'right_id');
|
||||
// what rights we need to add
|
||||
$rightsToAdd = array_diff($newRights, $userRightsIds);
|
||||
if (!empty($rightsToAdd)) {
|
||||
foreach ($rightsToAdd as $rightId) {
|
||||
$userObject->addUserRight($user_id, $rightId);
|
||||
if (isset($_POST['rights'])) {
|
||||
$validator = new Validator(['rights' => $_POST['rights']]);
|
||||
$rules = [
|
||||
'rights' => [
|
||||
'array' => true
|
||||
]
|
||||
];
|
||||
|
||||
if (!$validator->validate($rules)) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
|
||||
header("Location: $app_root?page=profile");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
// what rights we need to remove
|
||||
$rightsToRemove = array_diff($userRightsIds, $newRights);
|
||||
if (!empty($rightsToRemove)) {
|
||||
foreach ($rightsToRemove as $rightId) {
|
||||
$userObject->removeUserRight($user_id, $rightId);
|
||||
|
||||
$newRights = $_POST['rights'];
|
||||
// extract the new right_ids
|
||||
$userRightsIds = array_column($userRights, 'right_id');
|
||||
// what rights we need to add
|
||||
$rightsToAdd = array_diff($newRights, $userRightsIds);
|
||||
if (!empty($rightsToAdd)) {
|
||||
foreach ($rightsToAdd as $rightId) {
|
||||
$userObject->addUserRight($user_id, $rightId);
|
||||
}
|
||||
}
|
||||
// what rights we need to remove
|
||||
$rightsToRemove = array_diff($userRightsIds, $newRights);
|
||||
if (!empty($rightsToRemove)) {
|
||||
foreach ($rightsToRemove as $rightId) {
|
||||
$userObject->removeUserRight($user_id, $rightId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,12 +143,19 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
|||
$allTimezones = timezone_identifiers_list();
|
||||
// if timezone is already set, we pass a flag for JS to not autodetect browser timezone
|
||||
$isTimezoneSet = !empty($userDetails[0]['timezone']);
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/profile-edit.php';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/profile.php';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -9,46 +9,90 @@
|
|||
*/
|
||||
|
||||
// registration is allowed, go on
|
||||
if ($config['registration_enabled'] === true) {
|
||||
if ($config['registration_enabled'] == true) {
|
||||
|
||||
try {
|
||||
|
||||
// connect to database
|
||||
$dbWeb = connectDB($config);
|
||||
global $dbWeb, $logObject, $userObject;
|
||||
|
||||
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
||||
$username = $_POST['username'];
|
||||
$password = $_POST['password'];
|
||||
|
||||
// registering
|
||||
$result = $userObject->register($username, $password);
|
||||
// Apply rate limiting
|
||||
require '../app/includes/rate_limit_middleware.php';
|
||||
checkRateLimit($dbWeb, 'register');
|
||||
|
||||
// redirect to login
|
||||
if ($result === true) {
|
||||
Messages::flash('NOTICE', 'DEFAULT', "Registration successful.<br />You can log in now.");
|
||||
header('Location: ' . htmlspecialchars($app_root));
|
||||
exit();
|
||||
// registration fail, redirect to login
|
||||
require_once '../app/classes/validator.php';
|
||||
require_once '../app/helpers/security.php';
|
||||
$security = SecurityHelper::getInstance();
|
||||
|
||||
// Sanitize input
|
||||
$formData = $security->sanitizeArray($_POST, ['username', 'password', 'confirm_password', 'csrf_token']);
|
||||
|
||||
// Validate CSRF token
|
||||
if (!$security->verifyCsrfToken($formData['csrf_token'] ?? '')) {
|
||||
throw new Exception(Feedback::get('ERROR', 'CSRF_INVALID')['message']);
|
||||
}
|
||||
|
||||
$validator = new Validator($formData);
|
||||
$rules = [
|
||||
'username' => [
|
||||
'required' => true,
|
||||
'min' => 3,
|
||||
'max' => 20
|
||||
],
|
||||
'password' => [
|
||||
'required' => true,
|
||||
'min' => 8,
|
||||
'max' => 100
|
||||
],
|
||||
'confirm_password' => [
|
||||
'required' => true,
|
||||
'matches' => 'password'
|
||||
]
|
||||
];
|
||||
|
||||
$username = $formData['username'] ?? 'unknown';
|
||||
|
||||
if ($validator->validate($rules)) {
|
||||
$password = $formData['password'];
|
||||
|
||||
// registering
|
||||
$result = $userObject->register($username, $password);
|
||||
|
||||
// redirect to login
|
||||
if ($result === true) {
|
||||
// Get the new user's ID for logging
|
||||
$user_id = $userObject->getUserId($username)[0]['id'];
|
||||
$logObject->insertLog($user_id, "Registration: New user \"$username\" registered successfully. IP: $user_IP", 'user');
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Registration successful. You can log in now.");
|
||||
header('Location: ' . htmlspecialchars($app_root));
|
||||
exit();
|
||||
// registration fail, redirect to login
|
||||
} else {
|
||||
$logObject->insertLog(0, "Registration: Failed registration attempt for user \"$username\". IP: $user_IP. Reason: $result", 'system');
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
||||
header('Location: ' . htmlspecialchars($app_root));
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
Messages::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
||||
header('Location: ' . htmlspecialchars($app_root));
|
||||
$error = $validator->getFirstError();
|
||||
$logObject->insertLog(0, "Registration: Failed validation for user \"" . ($username ?? 'unknown') . "\". IP: $user_IP. Reason: $error", 'system');
|
||||
Feedback::flash('ERROR', 'DEFAULT', $error);
|
||||
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Messages::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||
$logObject->insertLog(0, "Registration: System error. IP: $user_IP. Error: " . $e->getMessage(), 'system');
|
||||
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||
}
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/form-register.php';
|
||||
|
||||
// registration disabled
|
||||
} else {
|
||||
echo Messages::render('NOTICE', 'DEFAULT', 'Registration is disabled', false);
|
||||
echo Feedback::render('NOTICE', 'DEFAULT', 'Registration is disabled', false);
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -23,91 +23,157 @@ $rateLimiter = new RateLimiter($dbWeb);
|
|||
|
||||
// Handle form submissions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
require_once '../app/classes/validator.php';
|
||||
|
||||
// Apply rate limiting for security operations
|
||||
require_once '../app/includes/rate_limit_middleware.php';
|
||||
checkRateLimit($dbWeb, 'security', $user_id);
|
||||
|
||||
$action = $_POST['action'];
|
||||
$validator = new Validator($_POST);
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
case 'add_whitelist':
|
||||
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) {
|
||||
throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
|
||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||
break;
|
||||
}
|
||||
if (empty($_POST['ip_address'])) {
|
||||
throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
|
||||
|
||||
$rules = [
|
||||
'ip_address' => [
|
||||
'required' => true,
|
||||
'max' => 45, // Max length for IPv6
|
||||
'ip' => true
|
||||
],
|
||||
'description' => [
|
||||
'required' => true,
|
||||
'max' => 255
|
||||
]
|
||||
];
|
||||
|
||||
if ($validator->validate($rules)) {
|
||||
$is_network = isset($_POST['is_network']) && $_POST['is_network'] === 'on';
|
||||
if (!$rateLimiter->addToWhitelist($_POST['ip_address'], $is_network, $_POST['description'] ?? '', $currentUser, $user_id)) {
|
||||
Feedback::flash('SECURITY', 'WHITELIST_ADD_FAILED');
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'WHITELIST_ADD_SUCCESS');
|
||||
}
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'WHITELIST_ADD_ERROR_IP', $validator->getFirstError());
|
||||
}
|
||||
$is_network = isset($_POST['is_network']) ? 1 : 0;
|
||||
if (!$rateLimiter->addToWhitelist($_POST['ip_address'], $is_network, $_POST['description'] ?? '', $currentUser, $user_id)) {
|
||||
throw new Exception(Messages::get('SECURITY', 'WHITELIST_ADD_ERROR')['message']);
|
||||
}
|
||||
Messages::flash('SECURITY', 'WHITELIST_ADD_SUCCESS');
|
||||
break;
|
||||
|
||||
case 'remove_whitelist':
|
||||
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) {
|
||||
throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
|
||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||
break;
|
||||
}
|
||||
if (empty($_POST['ip_address'])) {
|
||||
throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
|
||||
|
||||
$rules = [
|
||||
'ip_address' => [
|
||||
'required' => true,
|
||||
'max' => 45,
|
||||
'ip' => true
|
||||
]
|
||||
];
|
||||
|
||||
if ($validator->validate($rules)) {
|
||||
if (!$rateLimiter->removeFromWhitelist($_POST['ip_address'], $currentUser, $user_id)) {
|
||||
Feedback::flash('SECURITY', 'WHITELIST_REMOVE_FAILED');
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'WHITELIST_REMOVE_SUCCESS');
|
||||
}
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'WHITELIST_REMOVE_FAILED', $validator->getFirstError());
|
||||
}
|
||||
if (!$rateLimiter->removeFromWhitelist($_POST['ip_address'], $currentUser, $user_id)) {
|
||||
throw new Exception(Messages::get('SECURITY', 'WHITELIST_REMOVE_ERROR')['message']);
|
||||
}
|
||||
Messages::flash('SECURITY', 'WHITELIST_REMOVE_SUCCESS');
|
||||
break;
|
||||
|
||||
case 'add_blacklist':
|
||||
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) {
|
||||
throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
|
||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||
break;
|
||||
}
|
||||
if (empty($_POST['ip_address'])) {
|
||||
throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
|
||||
|
||||
$rules = [
|
||||
'ip_address' => [
|
||||
'required' => true,
|
||||
'max' => 45,
|
||||
'ip' => true
|
||||
],
|
||||
'reason' => [
|
||||
'required' => true,
|
||||
'max' => 255
|
||||
],
|
||||
'expiry_hours' => [
|
||||
'numeric' => true,
|
||||
'min' => 0,
|
||||
'max' => 8760 // 1 year in hours
|
||||
]
|
||||
];
|
||||
|
||||
if ($validator->validate($rules)) {
|
||||
$is_network = isset($_POST['is_network']) && $_POST['is_network'] === 'on';
|
||||
$expiry_hours = !empty($_POST['expiry_hours']) ? (int)$_POST['expiry_hours'] : null;
|
||||
|
||||
if (!$rateLimiter->addToBlacklist($_POST['ip_address'], $is_network, $_POST['reason'], $currentUser, $user_id, $expiry_hours)) {
|
||||
Feedback::flash('SECURITY', 'BLACKLIST_ADD_FAILED');
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'BLACKLIST_ADD_SUCCESS');
|
||||
}
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'BLACKLIST_ADD_ERROR_IP', $validator->getFirstError());
|
||||
}
|
||||
$is_network = isset($_POST['is_network']) ? 1 : 0;
|
||||
$expiry_hours = !empty($_POST['expiry_hours']) ? intval($_POST['expiry_hours']) : null;
|
||||
if (!$rateLimiter->addToBlacklist($_POST['ip_address'], $is_network, $_POST['reason'] ?? '', $currentUser, $user_id, $expiry_hours)) {
|
||||
throw new Exception(Messages::get('SECURITY', 'BLACKLIST_ADD_ERROR')['message']);
|
||||
}
|
||||
Messages::flash('SECURITY', 'BLACKLIST_ADD_SUCCESS');
|
||||
break;
|
||||
|
||||
case 'remove_blacklist':
|
||||
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) {
|
||||
throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
|
||||
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
||||
break;
|
||||
}
|
||||
if (empty($_POST['ip_address'])) {
|
||||
throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
|
||||
|
||||
$rules = [
|
||||
'ip_address' => [
|
||||
'required' => true,
|
||||
'max' => 45,
|
||||
'ip' => true
|
||||
]
|
||||
];
|
||||
|
||||
if ($validator->validate($rules)) {
|
||||
if (!$rateLimiter->removeFromBlacklist($_POST['ip_address'], $currentUser, $user_id)) {
|
||||
Feedback::flash('SECURITY', 'BLACKLIST_REMOVE_FAILED');
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'BLACKLIST_REMOVE_SUCCESS');
|
||||
}
|
||||
} else {
|
||||
Feedback::flash('SECURITY', 'BLACKLIST_REMOVE_FAILED', $validator->getFirstError());
|
||||
}
|
||||
if (!$rateLimiter->removeFromBlacklist($_POST['ip_address'], $currentUser, $user_id)) {
|
||||
throw new Exception(Messages::get('SECURITY', 'BLACKLIST_REMOVE_ERROR')['message']);
|
||||
}
|
||||
Messages::flash('SECURITY', 'BLACKLIST_REMOVE_SUCCESS');
|
||||
break;
|
||||
|
||||
default:
|
||||
Feedback::flash('ERROR', 'INVALID_ACTION');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$messages[] = ['category' => 'SECURITY', 'key' => 'CUSTOM_ERROR', 'custom_message' => $e->getMessage()];
|
||||
Messages::flash('SECURITY', 'CUSTOM_ERROR', 'custom_message');
|
||||
Feedback::flash('ERROR', $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($messages)) {
|
||||
// Only redirect if there were no errors
|
||||
header("Location: {$app_root}?page=security§ion={$section}");
|
||||
exit;
|
||||
}
|
||||
// Redirect back to the appropriate section
|
||||
header("Location: $app_root?page=security§ion=" . urlencode($section));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Always show rate limit info message for rate limiting section
|
||||
if ($section === 'ratelimit') {
|
||||
$messages[] = ['category' => 'SECURITY', 'key' => 'RATE_LIMIT_INFO'];
|
||||
$system_messages[] = ['category' => 'SECURITY', 'key' => 'RATE_LIMIT_INFO'];
|
||||
}
|
||||
|
||||
// Get current lists
|
||||
$whitelisted = $rateLimiter->getWhitelistedIps();
|
||||
$blacklisted = $rateLimiter->getBlacklistedIps();
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include '../app/templates/security.php';
|
||||
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Jilo settings management.
|
||||
*
|
||||
* This page ("settings") handles Jilo settings by
|
||||
* adding, editing, and deleting platforms, hosts, agents.
|
||||
*/
|
||||
|
||||
// Check if this is an AJAX request
|
||||
$isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
|
||||
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$agent = $_REQUEST['agent'] ?? '';
|
||||
$host = $_REQUEST['host'] ?? '';
|
||||
|
||||
require '../app/classes/host.php';
|
||||
require '../app/classes/agent.php';
|
||||
|
||||
$hostObject = new Host($dbWeb);
|
||||
$agentObject = new Agent($dbWeb);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
/**
|
||||
* Handles form submissions from editing
|
||||
*/
|
||||
|
||||
// Apply rate limiting for profile operations
|
||||
require_once '../app/includes/rate_limit_middleware.php';
|
||||
checkRateLimit($dbWeb, 'profile', $user_id);
|
||||
|
||||
// Get hash from URL if present
|
||||
$hash = parse_url($_SERVER['REQUEST_URI'], PHP_URL_FRAGMENT) ?? '';
|
||||
$redirectUrl = htmlspecialchars($app_root) . '?page=settings';
|
||||
if ($hash) {
|
||||
$redirectUrl .= '#' . $hash;
|
||||
}
|
||||
|
||||
// host operations
|
||||
if (isset($_POST['item']) && $_POST['item'] === 'host') {
|
||||
if (isset($_POST['delete']) && $_POST['delete'] === 'true') { // This is a host delete
|
||||
$host_id = $_POST['host'];
|
||||
$result = $hostObject->deleteHost($host_id);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Host deleted successfully.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Deleting the host failed. Error: $result", true);
|
||||
}
|
||||
} else if (!isset($_POST['host'])) { // This is a new host
|
||||
$newHost = [
|
||||
'address' => $_POST['address'],
|
||||
'platform_id' => $_POST['platform'],
|
||||
'name' => empty($_POST['name']) ? $_POST['address'] : $_POST['name'],
|
||||
];
|
||||
$result = $hostObject->addHost($newHost);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "New Jilo host added.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Adding the host failed. Error: $result", true);
|
||||
}
|
||||
} else { // This is an edit of existing host
|
||||
$host_id = $_POST['host'];
|
||||
$platform_id = $_POST['platform'];
|
||||
$updatedHost = [
|
||||
'id' => $host_id,
|
||||
'address' => $_POST['address'],
|
||||
'name' => empty($_POST['name']) ? $_POST['address'] : $_POST['name'],
|
||||
];
|
||||
$result = $hostObject->editHost($platform_id, $updatedHost);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Host edited.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Editing the host failed. Error: $result", true);
|
||||
}
|
||||
}
|
||||
if (!$isAjax) {
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
// agent operations
|
||||
} elseif (isset($_POST['item']) && $_POST['item'] === 'agent') {
|
||||
if (isset($_POST['delete']) && $_POST['delete'] === 'true') { // This is an agent delete
|
||||
$agent_id = $_POST['agent'];
|
||||
$result = $agentObject->deleteAgent($agent_id);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Agent deleted successfully.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Deleting the agent failed. Error: $result", true);
|
||||
}
|
||||
} else if (isset($_POST['new']) && $_POST['new'] === 'true') { // This is a new agent
|
||||
$newAgent = [
|
||||
'type_id' => $_POST['type'],
|
||||
'url' => $_POST['url'],
|
||||
'secret_key' => empty($_POST['secret_key']) ? null : $_POST['secret_key'],
|
||||
'check_period' => empty($_POST['check_period']) ? 0 : $_POST['check_period'],
|
||||
];
|
||||
$result = $agentObject->addAgent($_POST['host'], $newAgent);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "New Jilo agent added.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Adding the agent failed. Error: $result", true);
|
||||
}
|
||||
} else { // This is an edit of existing agent
|
||||
$agent_id = $_POST['agent'];
|
||||
$updatedAgent = [
|
||||
'agent_type_id' => $_POST['agent_type_id'],
|
||||
'url' => $_POST['url'],
|
||||
'secret_key' => empty($_POST['secret_key']) ? null : $_POST['secret_key'],
|
||||
'check_period' => empty($_POST['check_period']) ? 0 : $_POST['check_period'],
|
||||
];
|
||||
$result = $agentObject->editAgent($agent_id, $updatedAgent);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Agent edited.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Editing the agent failed. Error: $result", true);
|
||||
}
|
||||
}
|
||||
if (!$isAjax) {
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
// platform operations
|
||||
} elseif (isset($_POST['item']) && $_POST['item'] === 'platform') {
|
||||
if (isset($_POST['delete']) && $_POST['delete'] === 'true') { // This is a platform delete
|
||||
$platform_id = $_POST['platform'];
|
||||
$result = $platformObject->deletePlatform($platform_id);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Platform deleted successfully.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Deleting the platform failed. Error: $result", true);
|
||||
}
|
||||
} else if (!isset($_POST['platform'])) { // This is a new platform
|
||||
$newPlatform = [
|
||||
'name' => $_POST['name'],
|
||||
'jitsi_url' => $_POST['jitsi_url'],
|
||||
'jilo_database' => $_POST['jilo_database'],
|
||||
];
|
||||
$result = $platformObject->addPlatform($newPlatform);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "New Jitsi platform added.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Adding the platform failed. Error: $result", true);
|
||||
}
|
||||
} else { // This is an edit of existing platform
|
||||
$platform_id = $_POST['platform'];
|
||||
$updatedPlatform = [
|
||||
'name' => $_POST['name'],
|
||||
'jitsi_url' => $_POST['jitsi_url'],
|
||||
'jilo_database' => $_POST['jilo_database'],
|
||||
];
|
||||
$result = $platformObject->editPlatform($platform_id, $updatedPlatform);
|
||||
if ($result === true) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Platform edited.", true);
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Editing the platform failed. Error: $result", true);
|
||||
}
|
||||
}
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Handles GET requests to display templates.
|
||||
*/
|
||||
|
||||
if ($userObject->hasRight($user_id, 'view settings')) {
|
||||
$jilo_agent_types = $agentObject->getAgentTypes();
|
||||
include '../app/templates/settings.php';
|
||||
} else {
|
||||
include '../app/templates/error-unauthorized.php';
|
||||
}
|
||||
}
|
|
@ -8,12 +8,13 @@
|
|||
* It generates output for each platform and agent.
|
||||
*/
|
||||
|
||||
// Get any new messages
|
||||
include '../app/includes/messages.php';
|
||||
include '../app/includes/messages-show.php';
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
require '../app/classes/agent.php';
|
||||
require '../app/classes/host.php';
|
||||
$agentObject = new Agent($dbWeb);
|
||||
$hostObject = new Host($dbWeb);
|
||||
|
||||
include '../app/templates/status-server.php';
|
||||
|
||||
|
@ -23,50 +24,58 @@ foreach ($platformsAll as $platform) {
|
|||
// check if we can connect to the jilo database
|
||||
$response = connectDB($config, 'jilo', $platform['jilo_database'], $platform['id']);
|
||||
if ($response['error'] !== null) {
|
||||
$jilo_database_status = '<span class="text-danger">' . htmlspecialchars($response['error']) . '</span>';
|
||||
$jilo_database_status = $response['error'];
|
||||
} else {
|
||||
$jilo_database_status = '<span class="text-success">OK</span>';
|
||||
$jilo_database_status = 'Connected';
|
||||
}
|
||||
|
||||
include '../app/templates/status-platform.php';
|
||||
|
||||
// fetch agent details for the current platform
|
||||
$agentDetails = $agentObject->getAgentDetails($platform['id']);
|
||||
foreach ($agentDetails as $agent) {
|
||||
$agent_url = parse_url($agent['url']);
|
||||
$agent_protocol = isset($agent_url['scheme']) ? $agent_url['scheme']: '';
|
||||
$agent_host = isset($agent_url['host']) ? $agent_url['host']: '';
|
||||
$agent_port = isset($agent_url['port']) ? $agent_url['port']: '';
|
||||
// fetch hosts for the current platform
|
||||
$hostDetails = $hostObject->getHostDetails($platform['id']);
|
||||
foreach ($hostDetails as $host) {
|
||||
// fetch agent details for the current host
|
||||
$agentDetails = $agentObject->getAgentDetails($host['id']);
|
||||
foreach ($agentDetails as $agent) {
|
||||
// we try to parse the URL to scheme:/host:port
|
||||
$agent_url = parse_url($agent['url']);
|
||||
$agent_protocol = isset($agent_url['scheme']) ? $agent_url['scheme']: '';
|
||||
// on failure we keep the full value for displaying purpose
|
||||
$agent_host = isset($agent_url['host']) ? $agent_url['host']: $agent['url'];
|
||||
$agent_port = isset($agent_url['port']) ? $agent_url['port']: '';
|
||||
|
||||
// we get agent data to check availability
|
||||
$agent_response = $agentObject->fetchAgent($agent['id'], true);
|
||||
$agent_data = json_decode($agent_response);
|
||||
// we get agent data to check availability
|
||||
$agent_response = $agentObject->fetchAgent($agent['id'], true);
|
||||
$agent_data = json_decode($agent_response);
|
||||
|
||||
// determine agent availability based on response data
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$agent_availability = '<span class="text-warning">unknown</span>';
|
||||
foreach ($agent_data as $key => $value) {
|
||||
if ($key === 'error') {
|
||||
$agent_availability = '<span class="text-danger">' . htmlspecialchars($value) . '</span>';
|
||||
break;
|
||||
}
|
||||
if (preg_match('/_state$/', $key)) {
|
||||
if ($value === 'error') {
|
||||
$agent_availability = '<span class="text-danger">not running</span>';
|
||||
// determine agent availability based on response data
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$agent_availability = 'unknown';
|
||||
foreach ($agent_data as $key => $value) {
|
||||
if ($key === 'error') {
|
||||
$agent_availability = $value;
|
||||
break;
|
||||
}
|
||||
if ($value === 'running') {
|
||||
$agent_availability = '<span class="text-success">running</span>';
|
||||
break;
|
||||
if (preg_match('/_state$/', $key)) {
|
||||
if ($value === 'error') {
|
||||
$agent_availability = 'not running';
|
||||
break;
|
||||
}
|
||||
if ($value === 'running') {
|
||||
$agent_availability = 'running';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$agent_availability = 'json error';
|
||||
}
|
||||
} else {
|
||||
$agent_availability = 'json error';
|
||||
|
||||
include '../app/templates/status-agent.php';
|
||||
}
|
||||
|
||||
include '../app/templates/status-agent.php';
|
||||
}
|
||||
echo "\n\t\t\t\t\t\t\t</div>\n";
|
||||
}
|
||||
|
||||
?>
|
||||
echo "\n\t\t\t\t\t\t</div>";
|
||||
echo "\n\t\t\t\t\t</div>";
|
||||
echo "\n\t\t\t\t</div>";
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
|
||||
<!-- jilo agents -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Jilo Agents on platform <?= htmlspecialchars($platform_id) ?> (<?= htmlspecialchars($platformDetails[0]['name']) ?>)</p>
|
||||
<div class="card-body">
|
||||
<?php foreach ($agentDetails as $agent) { ?>
|
||||
<p class="card-text text-left" style="text-align: left;">
|
||||
agent id: <strong><?= htmlspecialchars($agent['id']) ?></strong>
|
||||
agent type: <?= htmlspecialchars($agent['agent_type_id']) ?> (<strong><?= htmlspecialchars($agent['agent_description']) ?></strong>)
|
||||
<br />
|
||||
endpoint: <strong><?= htmlspecialchars($agent['url']) ?><?= htmlspecialchars($agent['agent_endpoint']) ?></strong>
|
||||
<br />
|
||||
<?php
|
||||
$payload = [
|
||||
'iss' => 'Jilo Web',
|
||||
'aud' => $config['domain'],
|
||||
'iat' => time(),
|
||||
'exp' => time() + 3600,
|
||||
'agent_id' => $agent['id']
|
||||
];
|
||||
$jwt = $agentObject->generateAgentToken($payload, $agent['secret_key']);
|
||||
// print_r($_SESSION);
|
||||
?>
|
||||
<?php if (isset($_SESSION["agent{$agent['id']}_cache"])) { ?>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-status" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get the agent status" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '/status', '<?= htmlspecialchars($jwt) ?>', true)">get status</button>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-fetch" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get data from the agent" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '<?= htmlspecialchars($agent['agent_endpoint']) ?>', '<?= htmlspecialchars($jwt) ?>', true)">fetch data</button>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-cache" class="btn btn-secondary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="load cache" onclick="loadCache('<?= htmlspecialchars($agent['id']) ?>')">load cache</button>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-clear" class="btn btn-danger" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="clear cache" onclick="clearCache('<?= htmlspecialchars($agent['id']) ?>')">clear cache</button>
|
||||
<span id="cacheInfo<?= htmlspecialchars($agent['id']) ?>" style="margin: 5px 0;"></span>
|
||||
<?php } else { ?>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-status" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get the agent status" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '/status', '<?= htmlspecialchars($jwt) ?>', true)">get status</button>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-fetch" class="btn btn-primary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="get data from the agent" onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '<?= htmlspecialchars($agent['agent_endpoint']) ?>', '<?= htmlspecialchars($jwt) ?>')">fetch data</button>
|
||||
<button style="display: none" disabled id="agent<?= htmlspecialchars($agent['id']) ?>-cache" class="btn btn-secondary" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="load cache" onclick="loadCache('<?= htmlspecialchars($agent['id']) ?>')">load cache</button>
|
||||
<button style="display: none" disabled id="agent<?= htmlspecialchars($agent['id']) ?>-clear" class="btn btn-danger" data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="clear cache" onclick="clearCache('<?= htmlspecialchars($agent['id']) ?>')">clear cache</button>
|
||||
<span style="display: none" id="cacheInfo<?= htmlspecialchars($agent['id']) ?>" style="margin: 5px 0;"></span>
|
||||
<?php } ?>
|
||||
</p>
|
||||
<pre class="results" id="result<?= htmlspecialchars($agent['id']) ?>">click a button to display data from the agent.</pre>
|
||||
<?php } ?>
|
|
@ -0,0 +1,94 @@
|
|||
|
||||
<!-- agents live data -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 mb-4">
|
||||
<h2 class="mb-0">Jilo Agents status</h2>
|
||||
<small>manage and monitor agents on platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- hosts and their agents -->
|
||||
<div class="row">
|
||||
<?php foreach ($agentsByHost as $hostId => $hostData): ?>
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-network-wired me-2 text-secondary"></i>
|
||||
Host: <?= htmlspecialchars($hostData['host_name']) ?>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=settings#platform-<?= htmlspecialchars($platform_id) ?>host-<?= htmlspecialchars($hostId) ?>" class="text-decoration-none">
|
||||
<i class="fas fa-edit ms-2"></i>
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($hostData['agents'])): ?>
|
||||
<p class="text-muted">No agents on this host.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($hostData['agents'] as $agent): ?>
|
||||
<div class="agent-item mb-4 pb-3 border-bottom">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="flex-grow-1">
|
||||
<i class="fas fa-robot me-2 text-secondary"></i>
|
||||
<strong>Agent ID:</strong> <?= htmlspecialchars($agent['id']) ?> |
|
||||
<strong>Type:</strong> <?= htmlspecialchars($agent['agent_type_id']) ?> (<?= htmlspecialchars($agent['agent_description']) ?>) |
|
||||
<strong>Endpoint:</strong> <?= htmlspecialchars($agent['url']) ?><?= htmlspecialchars($agent['agent_endpoint']) ?>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=settings#platform-<?= htmlspecialchars($platform_id) ?>agent-<?= htmlspecialchars($agent['id']) ?>" class="text-decoration-none">
|
||||
<i class="fas fa-edit ms-2"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" role="group">
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-status"
|
||||
class="btn btn-primary"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover"
|
||||
data-placement="bottom"
|
||||
title="Get the agent status"
|
||||
onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '/status', '<?= htmlspecialchars($agentTokens[$agent['id']]) ?>', true)">
|
||||
Get Status
|
||||
</button>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-fetch"
|
||||
class="btn btn-primary"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover"
|
||||
data-placement="bottom"
|
||||
title="Get data from the agent"
|
||||
onclick="fetchData('<?= htmlspecialchars($agent['id']) ?>', '<?= htmlspecialchars($agent['url']) ?>', '<?= htmlspecialchars($agent['agent_endpoint']) ?>', '<?= htmlspecialchars($agentTokens[$agent['id']]) ?>', <?= isset($_SESSION["agent{$agent['id']}_cache"]) ? 'true' : 'false' ?>)">
|
||||
Fetch Data
|
||||
</button>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-cache"
|
||||
<?= !isset($_SESSION["agent{$agent['id']}_cache"]) ? 'style="display:none;" ' : '' ?>
|
||||
class="btn btn-secondary"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover"
|
||||
data-placement="bottom"
|
||||
title="Load cache"
|
||||
onclick="loadCache('<?= htmlspecialchars($agent['id']) ?>')">
|
||||
Load Cache
|
||||
</button>
|
||||
<button id="agent<?= htmlspecialchars($agent['id']) ?>-clear"
|
||||
<?= !isset($_SESSION["agent{$agent['id']}_cache"]) ? 'style="display:none;" ' : '' ?>
|
||||
class="btn btn-danger"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover"
|
||||
data-placement="bottom"
|
||||
title="Clear cache"
|
||||
onclick="clearCache('<?= htmlspecialchars($agent['id']) ?>')">
|
||||
Clear Cache
|
||||
</button>
|
||||
</div>
|
||||
<span id="cacheInfo<?= htmlspecialchars($agent['id']) ?>" class="ms-2 <?= isset($_SESSION["agent{$agent['id']}_cache"]) ? '' : 'd-none' ?>"></span>
|
||||
<pre class="results mt-3" id="result<?= htmlspecialchars($agent['id']) ?>">Click a button to display data from the agent.</pre>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- agents live data -->
|
|
@ -0,0 +1,110 @@
|
|||
|
||||
<!-- jitsi components events -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 mb-5">
|
||||
<h2 class="mb-0">Jitsi components events</h2>
|
||||
<small>log events related to Jitsi Meet components like Jicofo, Videobridge, Jigasi, etc.</small>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
|
||||
<!-- component events filter -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" action="" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="page" value="components">
|
||||
<div class="col-md-auto">
|
||||
<label for="from_time" class="form-label">From date</label>
|
||||
<input type="date" class="form-control" id="from_time" name="from_time" value="<?= htmlspecialchars($_REQUEST['from_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<label for="until_time" class="form-label">Until date</label>
|
||||
<input type="date" class="form-control" id="until_time" name="until_time" value="<?= htmlspecialchars($_REQUEST['until_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="name" class="form-label">Component name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="<?= htmlspecialchars($_REQUEST['name'] ?? '') ?>" placeholder="Component name">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" id="id" name="id" value="<?= htmlspecialchars($_REQUEST['id'] ?? '') ?>" placeholder="Search in component IDs">
|
||||
<input type="text" class="form-control" id="event" name="event" value="<?= htmlspecialchars($_REQUEST['event'] ?? '') ?>" placeholder="Search in event messages">
|
||||
</div>
|
||||
<div class="col-md-auto align-middle">
|
||||
<button type="submit" class="btn btn-primary me-2">
|
||||
<i class="fas fa-search me-2"></i>Search
|
||||
</button>
|
||||
<a href="?page=components" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-2"></i>Clear
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /component events filter -->
|
||||
|
||||
<!-- component events -->
|
||||
<?php if ($time_range_specified || count($filterMessage)) { ?>
|
||||
<div class="alert alert-info m-0 mb-3 small">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="mb-0"><i class="fas fa-calendar-alt me-2"></i>Time period:
|
||||
<strong>
|
||||
<?= $from_time == '0000-01-01' ? 'beginning' : date('d M Y', strtotime($from_time)) ?> - <?= $until_time == '9999-12-31' ? 'now' : date('d M Y', strtotime($until_time)) ?>
|
||||
</strong>
|
||||
</p>
|
||||
<?php } ?>
|
||||
<?php if (count($filterMessage)) {
|
||||
foreach ($filterMessage as $message) { ?>
|
||||
<p class="mb-0"><i class="fas fa-users me-2"></i><?= $message ?></strong></p>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
|
||||
<div class="mb-5">
|
||||
<?php if (!empty($components['records'])) { ?>
|
||||
<div class="table-responsive border">
|
||||
<table class="table table-results table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>component</th>
|
||||
<th>log level</th>
|
||||
<th>time</th>
|
||||
<th>component ID</th>
|
||||
<th>event</th>
|
||||
<th>parameter</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($components['records'] as $row) { ?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&name=<?= htmlspecialchars($row['component'] ?? '') ?>">
|
||||
<?= htmlspecialchars($row['component'] ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($row['loglevel']) ?></td>
|
||||
<td><span class="text-muted"><?= date('d M Y H:i:s', strtotime($row['time'])) ?></span></td>
|
||||
<td>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&id=<?= htmlspecialchars($row['component ID'] ?? '') ?>">
|
||||
<?= htmlspecialchars($row['component ID'] ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($row['event']) ?></td>
|
||||
<td><?= htmlspecialchars($row['param']) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php include '../app/templates/pagination.php'; ?>
|
||||
<?php } else { ?>
|
||||
<div class="alert alert-danger m-0">
|
||||
<i class="fas fa-info-circle me-2"></i>No component events found for the specified criteria.
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /jitsi components events -->
|
|
@ -0,0 +1,145 @@
|
|||
|
||||
<!-- jitsi conferences events -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 mb-5">
|
||||
<h2 class="mb-0">Jitsi conferences events</h2>
|
||||
<small>log events related to conferences in Jitsi Meet</small>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
|
||||
<!-- conference events filter -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" action="" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="page" value="conferences">
|
||||
<div class="col-md-auto">
|
||||
<label for="from_time" class="form-label">From date</label>
|
||||
<input type="date" class="form-control" id="from_time" name="from_time" value="<?= htmlspecialchars($_REQUEST['from_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<label for="until_time" class="form-label">Until date</label>
|
||||
<input type="date" class="form-control" id="until_time" name="until_time" value="<?= htmlspecialchars($_REQUEST['until_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="name" class="form-label">Conference ID</label>
|
||||
<input type="text" class="form-control" id="id" name="name" value="<?= htmlspecialchars($_REQUEST['id'] ?? '') ?>" placeholder="Conference ID">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="name" class="form-label">Conference name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="<?= htmlspecialchars($_REQUEST['name'] ?? '') ?>" placeholder="Search in conference names">
|
||||
</div>
|
||||
<div class="col-md-auto align-middle">
|
||||
<button type="submit" class="btn btn-primary me-2">
|
||||
<i class="fas fa-search me-2"></i>Search
|
||||
</button>
|
||||
<a href="?page=conferences" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-2"></i>Clear
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /conference events filter -->
|
||||
|
||||
<!-- conference events -->
|
||||
<?php if ($time_range_specified || count($filterMessage)) { ?>
|
||||
<div class="alert alert-info m-0 mb-3 small">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="mb-0"><i class="fas fa-calendar-alt me-2"></i>Time period:
|
||||
<strong>
|
||||
<?= $from_time == '0000-01-01' ? 'beginning' : date('d M Y', strtotime($from_time)) ?> - <?= $until_time == '9999-12-31' ? 'now' : date('d M Y', strtotime($until_time)) ?>
|
||||
</strong>
|
||||
</p>
|
||||
<?php } ?>
|
||||
<?php if (count($filterMessage)) {
|
||||
foreach ($filterMessage as $message) { ?>
|
||||
<p class="mb-0"><i class="fas fa-users me-2"></i><?= $message ?></strong></p>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="mb-5">
|
||||
<?php if (!empty($conferences['records'])) { ?>
|
||||
<div class="table-responsive border">
|
||||
<table class="table table-results table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<?php foreach (array_keys($conferences['records'][0]) as $header) { ?>
|
||||
<th scope="col" class="text-nowrap"><?= htmlspecialchars($header) ?></th>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($conferences['records'] as $row) { ?>
|
||||
<tr>
|
||||
<?php foreach ($row as $key => $column) {
|
||||
if ($key === 'conference ID' && isset($conferenceId) && $conferenceId === $column) { ?>
|
||||
<td class="text-nowrap">
|
||||
<strong <?= (strlen($column ?? '') > 20) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 20 ? substr($column, 0, 20) . '...' : $column ?? '') ?>
|
||||
</strong>
|
||||
</td>
|
||||
<?php } elseif ($key === 'conference ID') { ?>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'conference name' && isset($conferenceName) && $conferenceName === $column) { ?>
|
||||
<td class="text-nowrap">
|
||||
<strong <?= (strlen($column ?? '') > 20) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 20 ? substr($column, 0, 20) . '...' : $column ?? '') ?>
|
||||
</strong>
|
||||
</td>
|
||||
<?php } elseif ($key === 'conference name') { ?>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'conference host') { ?>
|
||||
<td class="text-nowrap">
|
||||
<span <?= (strlen($column ?? '') > 30) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 30 ? substr($column, 0, 30) . '...' : $column ?? '') ?>
|
||||
</span>
|
||||
</td>
|
||||
<?php } elseif ($key === 'participant ID') { ?>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'component') { ?>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'time' || $key === 'start' || $key === 'end') { ?>
|
||||
<td class="text-nowrap"><?= !empty($column) ? date('d M Y H:i:s',strtotime($column)) : '<small class="text-muted">n/a</small>' ?></td>
|
||||
<?php } else { ?>
|
||||
<td><?= htmlspecialchars($column ?? '') ?></td>
|
||||
<?php }
|
||||
} ?>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php include '../app/templates/pagination.php'; ?>
|
||||
<?php } else { ?>
|
||||
<div class="alert alert-danger m-0">
|
||||
<i class="fas fa-info-circle me-2"></i>No conference events found for the specified criteria.
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /jitsi conferences events -->
|
|
@ -1,72 +0,0 @@
|
|||
|
||||
<!-- widget "agents" -->
|
||||
<div class="card text-center w-50 mx-auto">
|
||||
<p class="h4 card-header">Add new Jilo Agent to Jitsi platform "<strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong>"</p>
|
||||
<div class="card-body">
|
||||
<!--p class="card-text">add new agent:</p-->
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="type" class="form-label">type</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<select class="form-control" type="text" name="type" id="agent_type_id" required>
|
||||
<option></option>
|
||||
<?php foreach ($jilo_agent_types as $agent_type) { ?>
|
||||
<option value="<?= htmlspecialchars($agent_type['id']) ?>"<?php
|
||||
if (in_array($agent_type['id'], $jilo_agent_types_in_platform)) {
|
||||
echo 'disabled="disabled"';
|
||||
} ?>>
|
||||
<?= htmlspecialchars($agent_type['description']) ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<p class="text-start"><small>type of agent (meet, jvb, jibri, etc.)<br />if a type has already been aded, it's disabled here</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="url" class="form-label">URL</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="url" value="https://" required />
|
||||
<p class="text-start"><small>URL of the Jilo Agent API (https://example.com:8081)</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="secret_key" class="form-label">secret key</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="secret_key" value="" required />
|
||||
<p class="text-start"><small>secret key for generating the access JWT token</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="check_period" class="form-label">check period</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="check_period" value="0" required />
|
||||
<p class="text-start"><small>period in minutes for the automatic agent check (0 disables it)</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="new" value="true" />
|
||||
<input type="hidden" name="item" value="agent" />
|
||||
|
||||
<br />
|
||||
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config" />Cancel</a>
|
||||
<input type="submit" class="btn btn-primary" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "agents" -->
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
<!-- widget "config file" -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration file :: edit</p>
|
||||
<div class="card-body">
|
||||
<div class="card-text">
|
||||
<p class="text-danger"><strong>this may break everything, use with extreme caution</strong></p>
|
||||
</div>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=config&item=config_file">
|
||||
|
||||
<?php
|
||||
include '../app/helpers/render.php';
|
||||
editConfig($config, '0');
|
||||
echo "\n";
|
||||
?>
|
||||
|
||||
<p class="text-danger"><strong>this may break everything, use with extreme caution</strong></p>
|
||||
<br />
|
||||
<input type="hidden" name="item" value="config_file" />
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=config_file" />Cancel</a>
|
||||
|
||||
<input type="submit" class="btn btn-danger btn-sm" value="Save" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "config file" -->
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
<!-- widget "config file" -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration file</p>
|
||||
<div class="card-body">
|
||||
|
||||
<?php
|
||||
include '../app/helpers/render.php';
|
||||
renderConfig($config, '0');
|
||||
echo "\n";
|
||||
?>
|
||||
|
||||
<br />
|
||||
<a class="btn btn-outline-danger btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=config_file&action=edit" />Edit</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "config file" -->
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
<!-- widget "agents" -->
|
||||
<div class="card text-center w-50 mx-auto">
|
||||
<p class="h4 card-header">Jilo Agent configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">delete an agent:</p>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
|
||||
<?php
|
||||
foreach ($agentDetails[0] as $key => $value) {
|
||||
// if ($key === 'id') continue;
|
||||
?>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="<?= htmlspecialchars($key) ?>" class="form-label"><?= htmlspecialchars($key) ?>:</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="text-start"><?= htmlspecialchars($value ?? '') ?></div>
|
||||
<input type="hidden" name="<?= htmlspecialchars($key) ?>" value="<?= htmlspecialchars($value ?? '') ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<br />
|
||||
<input type="hidden" name="agent" value="<?= htmlspecialchars($agentDetails[0]['id']) ?>" />
|
||||
<input type="hidden" name="delete" value="true" />
|
||||
<p class="h5 text-danger">Are you sure you want to delete this agent?</p>
|
||||
<br />
|
||||
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>agent<?= htmlspecialchars($agentDetails[0]['id']) ?>" />Cancel</a>
|
||||
<input type="submit" class="btn btn-danger" value="Delete" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "agents" -->
|
|
@ -1,68 +0,0 @@
|
|||
|
||||
<!-- agents -->
|
||||
<div class="card text-center w-50 mx-auto">
|
||||
<p class="h4 card-header">Jilo Agent configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">edit the agent details:</p>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="type_id" class="form-label">type</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<select class="form-control" type="text" name="type" id="agent_type_id" required>
|
||||
<option></option>
|
||||
<?php foreach ($jilo_agent_types as $agent_type) { ?>
|
||||
<option value="<?= htmlspecialchars($agent_type['id']) ?>" <?php if ($agentDetails[0]['agent_type_id'] === $agent_type['id']) echo 'selected'; ?>>
|
||||
<?= htmlspecialchars($agent_type['description']) ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<p class="text-start"><small>type of agent (meet, jvb, jibri, all)</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="url" class="form-label">URL</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="url" value="<?= htmlspecialchars($agentDetails[0]['url']) ?>" required />
|
||||
<p class="text-start"><small>URL of the Jilo Agent API (https://example.com:8081)</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="secret_key" class="form-label">secret key</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="secret_key" value="<?= htmlspecialchars($agentDetails[0]['secret_key']) ?>" required />
|
||||
<p class="text-start"><small>secret key for generating the access JWT token</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="check_period" class="form-label">check period</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="check_period" value="<?= htmlspecialchars($agentDetails[0]['check_period']) ?>" required />
|
||||
<p class="text-start"><small>period in minutes for the automatic agent check (0 disables it)</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<br />
|
||||
<input type="hidden" name="agent" value="<?= htmlspecialchars($agentDetails[0]['id']) ?>" />
|
||||
<a class="btn btn-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>agent<?= htmlspecialchars($agentDetails[0]['id']) ?>" />Cancel</a>
|
||||
<input type="submit" class="btn btn-primary" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /agents -->
|
|
@ -1,50 +0,0 @@
|
|||
|
||||
<!-- widget "hosts" -->
|
||||
<div class="card text-center w-50 mx-lef">
|
||||
<p class="h4 card-header">Add new host in Jitsi platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></p>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=config&item=host">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="address" class="form-label">address</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="address" value="" required autofocus />
|
||||
<p class="text-start"><small>DNS name or IP address of the machine</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="port" class="form-label">port</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="port" value="" required />
|
||||
<p class="text-start"><small>port on which the Jilo Agent is listening</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="name" class="form-label">name</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="name" value="" />
|
||||
<p class="text-start"><small>description or name of the host (optional)</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="platform" value="<?= htmlspecialchars($platformDetails[0]['id'])?>" />
|
||||
<input type="hidden" name="item" value="host" />
|
||||
<input type="hidden" name="new" value="true" />
|
||||
|
||||
<br />
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_id) ?>&host=<?= htmlspecialchars($host) ?>#platform<?= htmlspecialchars($platform_id) ?>host<?= htmlspecialchars($host) ?>" />Cancel</a>
|
||||
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "hosts" -->
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
<!-- widget "hosts" -->
|
||||
<div class="card text-center w-50 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">delete a host:</p>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=config&item=host">
|
||||
<?php
|
||||
foreach ($hostDetails[0] as $key => $value) {
|
||||
?>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="<?= htmlspecialchars($key) ?>" class="form-label"><?= htmlspecialchars($key) ?>:</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="text-start"><?= htmlspecialchars($value ?? '') ?></div>
|
||||
<input type="hidden" name="<?= htmlspecialchars($key) ?>" value="<?= htmlspecialchars($value ?? '') ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<br />
|
||||
<input type="hidden" name="host" value="<?= htmlspecialchars($hostDetails[0]['id']) ?>" />
|
||||
<input type="hidden" name="delete" value="true" />
|
||||
<p class="h5 text-danger">Are you sure you want to delete this host?</p>
|
||||
<br />
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_id) ?>&host=<?= htmlspecialchars($host) ?>#platform<?= htmlspecialchars($platform_id) ?>host<?= htmlspecialchars($host) ?>" />Cancel</a>
|
||||
|
||||
<input type="submit" class="btn btn-danger btn-sm" value="Delete" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "hosts" -->
|
|
@ -1,52 +0,0 @@
|
|||
|
||||
<!-- widget "hosts" -->
|
||||
<div class="card text-center w-50 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">edit host details:</p>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=config&item=host">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="address" class="form-label">address</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="address" value="<?= htmlspecialchars($hostDetails[0]['address'] ?? '') ?>" required autofocus />
|
||||
<p class="text-start"><small>DNS name or IP address of the machine</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="port" class="form-label">port</label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="port" value="<?= htmlspecialchars($hostDetails[0]['port'] ?? '') ?>" required />
|
||||
<p class="text-start"><small>port on which the Jilo Agent is listening</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="name" class="form-label">name</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="name" value="<?= htmlspecialchars($hostDetails[0]['name'] ?? '') ?>" />
|
||||
<p class="text-start"><small>description or name of the host (optional)</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>" />
|
||||
<input type="hidden" name="item" value="host" />
|
||||
<input type="hidden" name="host" value="<?= htmlspecialchars($hostDetails[0]['id']) ?>" />
|
||||
|
||||
<br />
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_id) ?>&host=<?= htmlspecialchars($host) ?>#platform<?= htmlspecialchars($platform_id) ?>host<?= htmlspecialchars($host) ?>" />Cancel</a>
|
||||
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "hosts" -->
|
|
@ -1,39 +0,0 @@
|
|||
|
||||
<!-- widget "hosts" -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration :: Jitsi Meet hosts</p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Jitsi hosts configuration
|
||||
</p>
|
||||
<?php foreach ($platformsAll as $platform_array) {
|
||||
$hosts = $hostObject->getHostDetails($platform_array['id']);
|
||||
?>
|
||||
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>"></a>
|
||||
<div class="row mb-1 border <?= isset($_REQUEST['platform']) && (int)$platform_array['id'] === (int)$_REQUEST['platform'] ? 'rounded bg-light' : '' ?>" style="padding: 20px; padding-bottom: 0px;">
|
||||
<p class="text-start">
|
||||
platform <strong><?= htmlspecialchars($platform_array['name']) ?></strong>
|
||||
</p>
|
||||
<ul class="text-start" style="padding-left: 50px;">
|
||||
<?php foreach ($hosts as $host_array) { ?>
|
||||
<li style="padding-bottom: 10px;">
|
||||
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>host<?= htmlspecialchars($host_array['id']) ?>"></a>
|
||||
<span class="<?= isset($_REQUEST['platform']) && (int)$platform_array['id'] === (int)$_REQUEST['platform'] && isset($_REQUEST['host']) && (int)$host_array['id'] === (int)$_REQUEST['host'] ? 'border rounded bg-light' : '' ?>" style="padding: 10px;">
|
||||
<?= htmlspecialchars($host_array['address']) ?>:<?= htmlspecialchars($host_array['port']) ?>
|
||||
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($host_array['platform_id']) ?>&host=<?= htmlspecialchars($host_array['id']) ?>&action=edit">edit host</a>
|
||||
<a class="btn btn-outline-danger btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($host_array['platform_id']) ?>&host=<?= htmlspecialchars($host_array['id']) ?>&action=delete">delete host</a>
|
||||
</span>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
<p class="text-start" style="padding-left: 50px;">
|
||||
total <?= htmlspecialchars(count($hosts)) ?> jilo <?= htmlspecialchars(count($hosts)) === '1' ? 'host' : 'hosts' ?>
|
||||
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=add">add new</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "hosts" -->
|
|
@ -1,134 +0,0 @@
|
|||
|
||||
<!-- widget "config" -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration</p>
|
||||
<p class="h6 card-header">
|
||||
<span class="btn btn-outline-primary btn-sm active" aria-pressed="true" style="cursor: default;">platforms</span>
|
||||
<a href="" class="btn btn-outline-primary btn-sm">hosts</a>
|
||||
<a href="" class="btn btn-outline-primary btn-sm">endpoints</a>
|
||||
|
||||
<a href="" class="btn btn-outline-primary btn-sm">config file</a>
|
||||
</p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">main variables</p>
|
||||
<?php
|
||||
include '../app/helpers/render.php';
|
||||
renderConfig($config, '0');
|
||||
echo "\n";
|
||||
?>
|
||||
|
||||
<hr />
|
||||
<p class="card-text">platforms configuration <a class="btn btn-secondary" style="padding: 0px;" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&action=add">add new</a></p>
|
||||
|
||||
<?php foreach ($platformsAll as $platform_array) {
|
||||
$agents = $agentObject->getAgentDetails($platform_array['id']);
|
||||
?>
|
||||
|
||||
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>"></a>
|
||||
<div class="row mb-3" style="padding-left: 0px;">
|
||||
<div class="border bg-light" style="padding-left: 50px; padding-bottom: 0px; padding-top: 0px;">
|
||||
<a style="text-decoration: none;" data-toggle="collapse" href="#collapsePlatform<?= htmlspecialchars($platform_array['id']) ?>" role="button" aria-expanded="true" aria-controls="collapsePlatform<?= htmlspecialchars($platform_array['id']) ?>">
|
||||
<div class="border bg-white text-start mb-3 rounded mt-3" data-toggle="tooltip" data-placement="bottom" title="configuration for platform <?= htmlspecialchars($platform_array['id']) ?>">
|
||||
<i class="fas fa-wrench"></i>
|
||||
<small>platform <?= htmlspecialchars($platform_array['id']) ?> (<?= htmlspecialchars($platform_array['name']) ?>)</small>
|
||||
</div>
|
||||
</a>
|
||||
<div class="collapse show" id="collapsePlatform<?= htmlspecialchars($platform_array['id']) ?>">
|
||||
|
||||
<div class="row mb-1" style="padding-left: 0px;">
|
||||
<div class="col-md-8 text-start">
|
||||
|
||||
<div class="row mb-1">
|
||||
<div class="col-md-8 text-start">
|
||||
<a class="btn btn-secondary" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=edit">edit platform</a>
|
||||
<?php if (count($platformsAll) <= 1) { ?>
|
||||
<span class="btn btn-light" style="padding: 2px;" href="#" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="can't delete the last platform">delete platform</span>
|
||||
<?php } else { ?>
|
||||
<a class="btn btn-danger" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=delete">delete platform</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div style="padding-left: 100px; padding-bottom: 20px;">
|
||||
<?php foreach ($platform_array as $key => $value) {
|
||||
if ($key === 'id') continue;
|
||||
?>
|
||||
<div class="row mb-1" style="padding-left: 100px;">
|
||||
<div class="col-md-4 text-end">
|
||||
<?= htmlspecialchars($key) ?>:
|
||||
</div>
|
||||
<div class="border col-md-8 text-start">
|
||||
<?= htmlspecialchars($value) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
<hr />
|
||||
<p class="card-text">jilo agents on platform <?= htmlspecialchars($platform_array['id']) ?> (<?= htmlspecialchars($platform_array['name']) ?>)
|
||||
<br />
|
||||
total <?= htmlspecialchars(count($agents)) ?> <?= htmlspecialchars(count($agents)) === 1 ? 'jilo agent' : 'jilo agents' ?>
|
||||
<a class="btn btn-secondary" style="padding: 0px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=add-agent">
|
||||
add new
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<?php foreach ($agents as $agent_array) { ?>
|
||||
|
||||
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>agent<?= htmlspecialchars($agent_array['id']) ?>"></a>
|
||||
<div class="row mb-3" style="padding-left: 0px;">
|
||||
<div class="border rounded bg-light" style="padding-left: 50px; padding-bottom: 20px; padding-top: 20px;">
|
||||
<div class="row mb-1" style="padding-left: 0px;">
|
||||
<div class="col-md-4 text-end">
|
||||
agent id <?= htmlspecialchars($agent_array['id']) ?>:
|
||||
</div>
|
||||
<div class="col-md-8 text-start">
|
||||
<a class="btn btn-secondary" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($agent_array['platform_id']) ?>&agent=<?= htmlspecialchars($agent_array['id']) ?>&action=edit">edit agent</a>
|
||||
<a class="btn btn-danger" style="padding: 2px;" href="<?= htmlspecialchars($app_root) ?>?page=config&platform=<?= htmlspecialchars($agent_array['platform_id']) ?>&agent=<?= htmlspecialchars($agent_array['id']) ?>&action=delete">delete agent</a>
|
||||
</div>
|
||||
<div style="padding-left: 100px; padding-bottom: 20px;">
|
||||
<div class="row mb-1" style="padding-left: 100px;">
|
||||
<div class="col-md-4 text-end">
|
||||
agent type:
|
||||
</div>
|
||||
<div class="border col-md-8 text-start">
|
||||
<?= htmlspecialchars($agent_array['agent_description']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1" style="padding-left: 100px;">
|
||||
<div class="col-md-4 text-end">
|
||||
endpoint:
|
||||
</div>
|
||||
<div class="border col-md-8 text-start">
|
||||
<?= htmlspecialchars($agent_array['url'].$agent_array['agent_endpoint']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (isset($agent_array['check_period']) && $agent_array['check_period'] !== 0) { ?>
|
||||
<div class="row mb-1" style="padding-left: 100px;">
|
||||
<div class="col-md-4 text-end">
|
||||
check period:
|
||||
</div>
|
||||
<div class="border col-md-8 text-start">
|
||||
<?= htmlspecialchars($agent_array['check_period']) ?> <?= ($agent_array['check_period'] == 1 ? 'minute' : 'minutes') ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "config" -->
|
|
@ -1,51 +0,0 @@
|
|||
|
||||
<!-- widget "platforms" -->
|
||||
<div class="card text-center w-50 mx-lef">
|
||||
<p class="h4 card-header">Add new Jitsi platform</p>
|
||||
<div class="card-body">
|
||||
<!--p class="card-text">add new platform:</p-->
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=platform">
|
||||
|
||||
<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 autofocus />
|
||||
<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-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform" />Cancel</a>
|
||||
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "platforms" -->
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
<!-- widget "platforms" -->
|
||||
<div class="card text-center w-50 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong> :: delete</p>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
|
||||
<?php
|
||||
foreach ($platformDetails[0] as $key => $value) {
|
||||
if ($key === 'id') continue;
|
||||
?>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="<?= htmlspecialchars($key) ?>" class="form-label"><?= htmlspecialchars($key) ?>:</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="text-start"><?= htmlspecialchars($value) ?? '' ?></div>
|
||||
<input type="hidden" name="<?= htmlspecialchars($key) ?>" value="<?= htmlspecialchars($value ?? '')?>" />
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<br />
|
||||
<input type="hidden" name="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-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&platform=<?= htmlspecialchars($platform_id) ?>#platform<?= htmlspecialchars($platform_id) ?>" />Cancel</a>
|
||||
|
||||
<input type="submit" class="btn btn-danger btn-sm" value="Delete" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "platforms" -->
|
|
@ -1,36 +0,0 @@
|
|||
|
||||
<!-- widget "platforms" -->
|
||||
<div class="card text-center w-50 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong> :: edit</p>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=platform">
|
||||
<?php
|
||||
foreach ($platformDetails[0] as $key => $value) {
|
||||
if ($key === 'id') continue;
|
||||
?>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 text-end">
|
||||
<label for="<?= htmlspecialchars($config_item) ?>" class="form-label"><?= htmlspecialchars($key) ?></label>
|
||||
<span class="text-danger" style="margin-right: -12px;">*</span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" name="<?= htmlspecialchars($key) ?>" value="<?= htmlspecialchars($value ?? '') ?>" required autofocus />
|
||||
<?php if ($key === 'name') { ?>
|
||||
<p class="text-start"><small>descriptive name for the platform</small></p>
|
||||
<?php } elseif ($key === 'jitsi_url') { ?>
|
||||
<p class="text-start"><small>URL of the Jitsi Meet (used for checks and for loading config.js)</small></p>
|
||||
<?php } elseif ($key === 'jilo_database') { ?>
|
||||
<p class="text-start"><small>path to the database file (relative to the app root)</small></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<br />
|
||||
<input type="hidden" name="platform" value="<?= htmlspecialchars($platform_id) ?>" />
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&platform=<?= htmlspecialchars($platform_id) ?>#platform<?= htmlspecialchars($platform_id) ?>" />Cancel</a>
|
||||
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "platforms" -->
|
|
@ -1,60 +0,0 @@
|
|||
|
||||
<!-- widget "platforms" -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Jilo configuration :: Jitsi Meet platforms</p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Jitsi platforms configuration <a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&action=add">add new</a></p>
|
||||
<?php foreach ($platformsAll as $platform_array) {
|
||||
$hosts = $hostObject->getHostDetails($platform_array['id']);
|
||||
$agents = $agentObject->getAgentDetails($platform_array['id']);
|
||||
?>
|
||||
<a name="platform<?= htmlspecialchars($platform_array['id']) ?>"></a>
|
||||
<div class="row mb-1 border<?= isset($_REQUEST['platform']) && (int)$platform_array['id'] === (int)$_REQUEST['platform'] ? ' bg-light' : '' ?>" style="padding: 20px; padding-bottom: 0px;">
|
||||
<p>
|
||||
platform id <?= htmlspecialchars($platform_array['id']) ?> - <strong><?= htmlspecialchars($platform_array['name']) ?></strong>
|
||||
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=edit">edit platform</a>
|
||||
<?php if (count($platformsAll) <= 1) { ?>
|
||||
<span class="btn btn-outline-light btn-sm" href="#" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="can't delete the last platform">delete platform</span>
|
||||
<?php } else { ?>
|
||||
<a class="btn btn-outline-danger btn-sm" href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform&platform=<?= htmlspecialchars($platform_array['id']) ?>&action=delete">delete platform</a>
|
||||
<?php } ?>
|
||||
</p>
|
||||
<div style="padding-left: 100px; padding-bottom: 20px;">
|
||||
<?php foreach ($platform_array as $key => $value) {
|
||||
if ($key === 'id') continue;
|
||||
?>
|
||||
<div class="row mb-1" style="padding-left: 100px;">
|
||||
<div class="col-md-4 text-end">
|
||||
<?= htmlspecialchars($key) ?>:
|
||||
</div>
|
||||
<div class="col-md-8 text-start">
|
||||
<?php if ($key === 'jitsi_url') { ?>
|
||||
<a href="<?= htmlspecialchars($value) ?>" target="_blank" rel="noopener noreferrer" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="open the Jitsi Meet platform in a new window">
|
||||
<?= htmlspecialchars($value) ?>
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
<?php } else { ?>
|
||||
<?= htmlspecialchars($value) ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="row mb-1" style="padding-left: 100px;">
|
||||
<div class="col-md-4 text-end"></div>
|
||||
<div class="col-md-8 text-start">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=host&platform=<?= htmlspecialchars($platform_array['id']) ?>#platform<?= htmlspecialchars($platform_array['id']) ?>"><?= htmlspecialchars(count($hosts)) ?> <?= htmlspecialchars(count($hosts)) === '1' ? 'host' : 'hosts' ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1" style="padding-left: 100px;">
|
||||
<div class="col-md-4 text-end"></div>
|
||||
<div class="col-md-8 text-start">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=endpoint&platform=<?= htmlspecialchars($platform_array['id']) ?>#platform<?= htmlspecialchars($platform_array['id']) ?>"><?= htmlspecialchars(count($agents)) ?> <?= htmlspecialchars(count($agents)) === '1' ? 'endpoint' : 'endpoints' ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "platforms" -->
|
|
@ -0,0 +1,208 @@
|
|||
|
||||
<!-- config file -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 mb-4">
|
||||
<h2 class="mb-0">Configuration</h2>
|
||||
<small>Jilo Web configuration file: <em><?= htmlspecialchars($localConfigPath) ?></em></small>
|
||||
<?php if ($configMessage) { ?>
|
||||
<?= $configMessage ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center py-3">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-wrench me-2 text-secondary"></i>
|
||||
Jilo Web app configuration
|
||||
</h5>
|
||||
<?php if ($userObject->hasRight($user_id, 'edit config file')) { ?>
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm toggle-edit" <?= !$isWritable ? 'disabled' : '' ?>>
|
||||
<i class="fas fa-edit me-2"></i>Edit
|
||||
</button>
|
||||
<div class="edit-controls d-none">
|
||||
<button type="button" class="btn btn-danger btn-sm save-config">
|
||||
<i class="fas fa-save me-2"></i>Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm ms-2 cancel-edit">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
<form id="configForm">
|
||||
<?php
|
||||
function renderConfigItem($key, $value, $path = '') {
|
||||
$fullPath = $path ? $path . '[' . $key . ']' : $key;
|
||||
// Only capitalize first letter, not every word
|
||||
$displayName = ucfirst(str_replace('_', ' ', $key));
|
||||
|
||||
if (is_array($value)) {
|
||||
echo "\t\t\t\t\t\t\t\t<div class=\"config-section mb-4\">";
|
||||
echo "\n\t\t\t\t\t\t\t\t\t<h6 class=\"border-bottom pb-2 mb-3\">" . htmlspecialchars($displayName) . '</h6>';
|
||||
echo "\n\t\t\t\t\t\t\t\t\t<div class=\"ps-4\">\n";
|
||||
foreach ($value as $subKey => $subValue) {
|
||||
renderConfigItem($subKey, $subValue, $fullPath);
|
||||
}
|
||||
echo "\t\t\t\t\t\t\t\t\t</div>\n";
|
||||
echo "\t\t\t\t\t\t\t\t</div>\n";
|
||||
} else {
|
||||
?>
|
||||
<div class="config-item row mb-3 align-items-center">
|
||||
<div class="col-md-4 text-end">
|
||||
<label class="form-label mb-0"><?= htmlspecialchars($displayName) ?></label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="view-mode">
|
||||
<?php if (is_bool($value) || $key === 'registration_enabled') { ?>
|
||||
<span class="badge <?= $value ? 'bg-success' : 'bg-secondary' ?>"><?= $value ? 'Enabled' : 'Disabled' ?></span>
|
||||
<?php } elseif ($key === 'environment') { ?>
|
||||
<span class="badge <?= $value === 'production' ? 'bg-danger' : 'bg-info' ?>"><?= htmlspecialchars($value) ?></span>
|
||||
<?php } else {
|
||||
if (empty($value) && $value !== '0') { ?>
|
||||
<span class="text-muted fst-italic">blank</span>
|
||||
<?php } else { ?>
|
||||
<span class="text-body"><?= htmlspecialchars($value) ?></span>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<div class="edit-mode d-none">
|
||||
<?php if (is_bool($value) || $key === 'registration_enabled') { ?>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="<?= htmlspecialchars($fullPath) ?>" <?= $value ? 'checked' : '' ?>>
|
||||
</div>
|
||||
<?php } elseif ($key === 'environment') { ?>
|
||||
<select class="form-select form-select-sm" name="<?= htmlspecialchars($fullPath) ?>">
|
||||
<option value="development" <?= $value === 'development' ? 'selected' : '' ?>>Development</option>
|
||||
<option value="production" <?= $value === 'production' ? 'selected' : '' ?>>Production</option>
|
||||
</select>
|
||||
<?php } else { ?>
|
||||
<input type="text" class="form-control form-control-sm" name="<?= htmlspecialchars($fullPath) ?>" value="<?= htmlspecialchars($value ?? '') ?>">
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php }
|
||||
}
|
||||
foreach ($config as $key => $value) {
|
||||
renderConfigItem($key, $value);
|
||||
} ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
// Toggle edit mode
|
||||
$('.toggle-edit').click(function() {
|
||||
$(this).hide();
|
||||
$('.edit-controls').removeClass('d-none');
|
||||
$('.view-mode').hide();
|
||||
$('.edit-mode').removeClass('d-none');
|
||||
});
|
||||
|
||||
// Cancel edit
|
||||
$('.cancel-edit').click(function() {
|
||||
$('.toggle-edit').show();
|
||||
$('.edit-controls').addClass('d-none');
|
||||
$('.view-mode').show();
|
||||
$('.edit-mode').addClass('d-none');
|
||||
});
|
||||
|
||||
// Save config
|
||||
$('.save-config').click(function() {
|
||||
const $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Saving...');
|
||||
|
||||
// Build form data object
|
||||
const data = {};
|
||||
|
||||
// Handle text inputs
|
||||
$('#configForm input[type="text"]').each(function() {
|
||||
const name = $(this).attr('name');
|
||||
data[name] = $(this).val();
|
||||
});
|
||||
|
||||
// Handle checkboxes
|
||||
$('#configForm input[type="checkbox"]').each(function() {
|
||||
const name = $(this).attr('name');
|
||||
data[name] = $(this).prop('checked') ? '1' : '0';
|
||||
});
|
||||
|
||||
// Handle selects
|
||||
$('#configForm select').each(function() {
|
||||
const name = $(this).attr('name');
|
||||
data[name] = $(this).val();
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '<?= htmlspecialchars($app_root) ?>?page=config',
|
||||
method: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
success: function(response) {
|
||||
// Show message first
|
||||
if (response.messageData) {
|
||||
JsMessages.success(response.messageData['message']);
|
||||
}
|
||||
|
||||
// Only update UI if save was successful
|
||||
if (response.success) {
|
||||
// Update view mode values
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
const $item = $(`[name="${key}"]`).closest('.config-item');
|
||||
const $viewMode = $item.find('.view-mode');
|
||||
|
||||
if ($item.length) {
|
||||
if ($item.find('input[type="checkbox"]').length) {
|
||||
// Boolean value
|
||||
const isEnabled = value === '1';
|
||||
$viewMode.html(`
|
||||
<span class="badge ${isEnabled ? 'bg-success' : 'bg-secondary'}">
|
||||
${isEnabled ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
`);
|
||||
} else if ($item.find('select').length) {
|
||||
// Environment value
|
||||
$viewMode.html(`
|
||||
<span class="badge ${value === 'production' ? 'bg-danger' : 'bg-info'}">
|
||||
${value}
|
||||
</span>
|
||||
`);
|
||||
} else {
|
||||
// Text value
|
||||
if (value === '') {
|
||||
$viewMode.html('<span class="text-muted fst-italic">blank</span>');
|
||||
} else {
|
||||
$viewMode.html(`<span class="text-body">${value}</span>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Finally, exit edit mode
|
||||
$('.toggle-edit').show();
|
||||
$('.edit-controls').addClass('d-none');
|
||||
$('.view-mode').show();
|
||||
$('.edit-mode').addClass('d-none');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
JsMessages.error('Error saving config: ' + error);
|
||||
},
|
||||
complete: function() {
|
||||
$btn.prop('disabled', false).html('<i class="fas fa-save me-2"></i>Save');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- /config file -->
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
$token = SecurityHelper::getInstance()->generateCsrfToken();
|
||||
?>
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token) ?>" />
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
<!-- widget "config.js" -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<span class="m-3">URL: <?= htmlspecialchars($platformDetails[0]['jitsi_url']) ?></span>
|
||||
<span class="m-3">FILE: config.js</span>
|
||||
<?php if ($mode === 'raw') { ?>
|
||||
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=configjs">view only active lines</a></span>
|
||||
<?php } else { ?>
|
||||
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=configjs&mode=raw">view raw file contents</a></span>
|
||||
<?php } ?>
|
||||
</p>
|
||||
<pre class="results">
|
||||
<?php
|
||||
echo htmlspecialchars($platformConfigjs);
|
||||
?>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "config.js" -->
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
<!-- widget "interfaceconfig" -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></p>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<span class="m-3">URL: <?= htmlspecialchars($platformDetails[0]['jitsi_url']) ?></span>
|
||||
<span class="m-3">FILE: interface_config.js</span>
|
||||
<?php if ($mode === 'raw') { ?>
|
||||
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=interfaceconfigjs">view only active lines</a></span>
|
||||
<?php } else { ?>
|
||||
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=interfaceconfigjs&mode=raw">view raw file contents</a></span>
|
||||
<?php } ?>
|
||||
</p>
|
||||
<pre class="results">
|
||||
<?php
|
||||
echo htmlspecialchars($platformInterfaceConfigjs);
|
||||
?>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "interfaceconfig" -->
|
|
@ -1,76 +0,0 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
|
||||
|
||||
<!-- Results filter -->
|
||||
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
|
||||
<form method="POST" id="filter_form" class="filter-results" action="?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($page) ?>">
|
||||
<label for="from_time">from</label>
|
||||
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
|
||||
<label for="until_time">until</label>
|
||||
<input type="date" id="until_time" name="until_time"<?php if (isset($_REQUEST['until_time'])) echo " value=\"" . htmlspecialchars($until_time) . "\"" ?> />
|
||||
<input type="text" name="id" placeholder="component ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
|
||||
<input type="text" name="name" placeholder="component name"<?php if (isset($_REQUEST['name'])) echo " value=\"" . htmlspecialchars($_REQUEST['name']) . "\"" ?> />
|
||||
<input type="text" name="event" placeholder="event name"<?php if (isset($_REQUEST['event'])) echo " value=\"" . htmlspecialchars($_REQUEST['event']) . "\"" ?> />
|
||||
<input type="button" onclick="clearFilter()" value="clear" />
|
||||
<input type="submit" value="search" />
|
||||
</form>
|
||||
<script>
|
||||
function clearFilter() {
|
||||
document.getElementById("filter_form").reset();
|
||||
const filterFields = document.querySelectorAll("#filter_form input");
|
||||
filterFields.forEach(input => {
|
||||
if (input.type === 'text' ||input.type === 'date') {
|
||||
input.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<!-- /Results filter -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
||||
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
|
||||
<?php } ?>
|
||||
<div class="mb-5">
|
||||
<?php if ($widget['full'] === true) { ?>
|
||||
<table class="table table-results table-striped table-hover table-bordered">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<?php foreach ($widget['table_headers'] as $header) { ?>
|
||||
<th scope="col"><?= htmlspecialchars($header) ?></th>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($widget['table_records'] as $row) { ?>
|
||||
<tr>
|
||||
<?php foreach ($row as $key => $column) { ?>
|
||||
<?php if ($key === 'component ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'component') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } else { ?>
|
||||
<td><?= htmlspecialchars($column ?? '') ?></td>
|
||||
<?php }
|
||||
} ?>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
if ($widget['pagination'] && $item_count > $items_per_page) {
|
||||
$url = "$app_root?platform=$platform_id&page=$page";
|
||||
include '../app/helpers/pagination.php';
|
||||
}
|
||||
?>
|
||||
<?php } else { ?>
|
||||
<p class="m-3">No matching records found.</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
|
@ -1,94 +0,0 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
|
||||
|
||||
<!-- Results filter -->
|
||||
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
|
||||
<form method="POST" id="filter_form" class="filter-results" action="?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($page) ?>">
|
||||
<label for="from_time">from</label>
|
||||
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
|
||||
<label for="until_time">until</label>
|
||||
<input type="date" id="until_time" name="until_time"<?php if (isset($_REQUEST['until_time'])) echo " value=\"" . htmlspecialchars($until_time) . "\"" ?> />
|
||||
<input type="text" name="id" placeholder="conference ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
|
||||
<input type="text" name="name" placeholder="conference name"<?php if (isset($_REQUEST['name'])) echo " value=\"" . htmlspecialchars($_REQUEST['name']) . "\"" ?> />
|
||||
<input type="button" onclick="clearFilter()" value="clear" />
|
||||
<input type="submit" value="search" />
|
||||
</form>
|
||||
<script>
|
||||
function clearFilter() {
|
||||
document.getElementById("filter_form").reset();
|
||||
const filterFields = document.querySelectorAll("#filter_form input");
|
||||
filterFields.forEach(input => {
|
||||
if (input.type === 'text' ||input.type === 'date') {
|
||||
input.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<!-- /Results filter -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
||||
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
|
||||
<?php } ?>
|
||||
<div class="mb-5">
|
||||
<?php if ($widget['full'] === true) { ?>
|
||||
<table class="table table-results table-striped table-hover table-bordered">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<?php foreach ($widget['table_headers'] as $header) { ?>
|
||||
<th scope="col"><?= htmlspecialchars($header) ?></th>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($widget['table_records'] as $row) { ?>
|
||||
<tr>
|
||||
<?php $stats_id = false;
|
||||
$participant_ip = false;
|
||||
if (isset($row['event']) && $row['event'] === 'stats_id') $stats_id = true;
|
||||
if (isset($row['event']) && $row['event'] === 'pair selected') $participant_ip = true;
|
||||
|
||||
foreach ($row as $key => $column) {
|
||||
if ($key === 'conference ID' && isset($conferenceId) && $conferenceId === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($key === 'conference ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'conference name' && isset($conferenceName) && $conferenceName === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($key === 'conference name') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'participant ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($stats_id && $key === 'parameter') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($participant_ip && $key === 'parameter') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&ip=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php
|
||||
// in general listings we don't show seconds and miliseconds
|
||||
} elseif ($key === 'start' || $key === 'end') { ?>
|
||||
<td><?= htmlspecialchars(substr($column ?? '', 0, -7)) ?></td>
|
||||
<?php } else { ?>
|
||||
<td><?= htmlspecialchars($column ?? '') ?></td>
|
||||
<?php }
|
||||
} ?>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
if ($widget['pagination'] && $item_count > $items_per_page) {
|
||||
$url = "$app_root?platform=$platform_id&page=$page";
|
||||
include '../app/helpers/pagination.php';
|
||||
}
|
||||
?>
|
||||
<?php } else { ?>
|
||||
<p class="m-3">No matching records found.</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
|
@ -4,15 +4,23 @@
|
|||
<div class="card-body">
|
||||
<p class="card-text"><strong>Welcome to JILO!</strong><br />Please enter login credentials:</p>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=login">
|
||||
<input type="text" name="username" placeholder="Username" required autofocus />
|
||||
<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 /> <br />
|
||||
<?php include 'csrf_token.php'; ?>
|
||||
<div class="form-group mb-3">
|
||||
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
||||
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||
required autofocus />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<input type="password" class="form-control w-50 mx-auto" name="password" placeholder="Password"
|
||||
pattern=".{2,}" title="Eight or more characters"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label for="remember_me">
|
||||
<input type="checkbox" id="remember_me" name="remember_me" />
|
||||
remember me
|
||||
</label>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary" value="Login" />
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -4,10 +4,22 @@
|
|||
<div class="card-body">
|
||||
<p class="card-text">Enter credentials for registration:</p>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
|
||||
<input type="text" name="username" placeholder="Username" required autofocus />
|
||||
<br />
|
||||
<input type="password" name="password" placeholder="Password" required />
|
||||
<br /> <br />
|
||||
<?php include 'csrf_token.php'; ?>
|
||||
<div class="form-group mb-3">
|
||||
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
||||
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||
required autofocus />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<input type="password" class="form-control w-50 mx-auto" name="password" placeholder="Password"
|
||||
pattern=".{8,}" title="Eight or more characters"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<input type="password" class="form-control w-50 mx-auto" name="confirm_password" placeholder="Confirm password"
|
||||
pattern=".{8,}" title="Eight or more characters"
|
||||
required />
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary" value="Register" />
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
|
||||
</div>
|
||||
<!-- jitsi graphs -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 mb-4">
|
||||
<h2 class="mb-0">Jitsi graphs</h2>
|
||||
<small>usage graphs for platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="card w-auto bg-light border-light card-body filter-results">
|
||||
<div class="btn-group" role="group">
|
||||
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('today'); setActive(this)" value="today" />
|
||||
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('last2days'); setActive(this)" value="last 2 days" />
|
||||
<input type="button" class="button active" style="margin-right: 3px;" onclick="setTimeRange('last7days'); setActive(this)" value="last 7 days" />
|
||||
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('thisMonth'); setActive(this)" value="month" />
|
||||
<input type="button" class="button" style="margin-right: 18px;" onclick="setTimeRange('thisYear'); setActive(this)" value="year" />
|
||||
|
||||
<input type="date" style="margin-right: 3px;" id="start-date">
|
||||
|
||||
<input type="date" style="margin-right: 3px;" id="end-date">
|
||||
<input type="button" id="custom_range" class="button" onclick="setCustomTimeRange(); setActive(this)" value="custom range" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card w-auto bg-light border-light card-body filter-results">
|
||||
<div class="btn-group" role="group">
|
||||
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('today'); setActive(this)" value="today" />
|
||||
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('last2days'); setActive(this)" value="last 2 days" />
|
||||
<input type="button" class="button active" style="margin-right: 3px;" onclick="setTimeRange('last7days'); setActive(this)" value="last 7 days" />
|
||||
<input type="button" class="button" style="margin-right: 3px;" onclick="setTimeRange('thisMonth'); setActive(this)" value="month" />
|
||||
<input type="button" class="button" style="margin-right: 18px;" onclick="setTimeRange('thisYear'); setActive(this)" value="year" />
|
||||
<input type="date" style="margin-right: 3px;" id="start-date">
|
||||
<input type="date" style="margin-right: 3px;" id="end-date">
|
||||
<input type="button" id="custom_range" class="button" onclick="setCustomTimeRange(); setActive(this)" value="custom range" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Define an array to store all graph instances
|
||||
var graphs = [];
|
||||
</script>
|
||||
|
||||
<?php
|
||||
|
||||
foreach ($graph as $data) {
|
||||
<?php foreach ($graph as $data) {
|
||||
include '../app/helpers/graph.php';
|
||||
}
|
||||
|
||||
?>
|
||||
} ?>
|
||||
|
||||
<script>
|
||||
// Function to update the label and propagate zoom across charts
|
||||
|
@ -140,3 +140,5 @@ window.onload = function() {
|
|||
setTimeRange('last7days');
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- /jitsi graphs -->
|
|
@ -1,53 +0,0 @@
|
|||
|
||||
<!-- help -->
|
||||
<div class="card text-center w-100 mx-auto">
|
||||
|
||||
<p class="h4 card-header">Jilo Help</p>
|
||||
<div class="card-body">
|
||||
|
||||
<div style="text-align: left; font-family: monospace; font-size: 0.75em; white-space: pre-wrap;">
|
||||
<a href="https://lindeas.com/jilo">Jilo</a> is a software tools suite developed by <a href="https://lindeas.com">Lindeas Ltd.</a> designed to help in maintaining a Jitsi Meet platform.
|
||||
|
||||
It consists of several parts meant to run together, although some of them can be used separately.
|
||||
|
||||
<hr /><strong>"JILO"</strong>
|
||||
|
||||
This is the command-line tool for extracting information about important events from the Jitsi Meet log files, storing them in a database and searching through that database.
|
||||
Jilo is written in Bash and has very minimal external dependencies. That means that you can run it on almost any Linux system with jitsi log files.
|
||||
|
||||
It can either:
|
||||
- show the data directly in the terminal,
|
||||
- provide it to an instance of the web application "Jilo Web" for displaying statistics (the Jilo Web server needs to have access to the sqlite database file from Jilo),
|
||||
- or send the data output to a "Jilo Agent" that can then allow a remote Jilo Web access it.
|
||||
|
||||
This way Jilo is always available on each host in the Jitsi Meet platform for quick command-line search, while also providing data for the statistics on a central Jilo Web server, without any need to put additional software on each server.
|
||||
|
||||
<hr /><strong>"Jilo Agent"</strong>
|
||||
|
||||
The Jilo Agent is a small program, written in Go. It runs on remote servers in the Jitsi Meet platform, and provides info about the operation of the different components of the platform.
|
||||
|
||||
It listens for connections from the central Jilo Web app on a special port and the connection is secured with JWT tokens authentication. It can be configured to use HTTPS. In a firewall you only need to allow the agent's port for incoming connections from the Jilo Web server.
|
||||
|
||||
All information about the different services (jvb, jicofo, jigasi, nginx, prosody) is transmitted over that single port. This, plus the authentication and the fact that Jilo Agent doesn't need any additional external programs or libraries to operate, make it very easy for deploying and automating the deployment in a large Jitsi Meet setup.
|
||||
|
||||
<hr /><strong>"Jilo Web"</strong>
|
||||
|
||||
Jilo Web is the web app that combines all the information received from Jilo and Jilo Agents and shows statistics and graphs of the usage, the events and the issues.
|
||||
|
||||
It's a multi-user web tool with user levels and access rights integrated into it, making it suitable for the different levels in an enterprise.
|
||||
|
||||
The current website you are looking at is running a Jilo Web instance.
|
||||
|
||||
<hr /><strong>"Jilo Server"</strong>
|
||||
|
||||
Jilo Server is a server component written in Go, meant to work alongside Jilo Web. It is responsible for all automated tasks - health checks, periodic retrieval of data from the remote Jilo Agents, etc.
|
||||
|
||||
It generally works on the same machine as the web interface Jilo Web and shares its database, although if needed it could be deployed on a separate machine.
|
||||
|
||||
Jilo Web checks for the Jilo Server availability and displays a warning if there is a problem with the server.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- /help -->
|
|
@ -0,0 +1,128 @@
|
|||
|
||||
<!-- help -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 mb-4">
|
||||
<h2 class="mb-0">Help</h2>
|
||||
<small>about this program</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<!-- Introduction -->
|
||||
<div class="mb-5">
|
||||
<p class="lead">
|
||||
<a href="https://lindeas.com/jilo" class="text-decoration-none">Jilo</a> is a software tools suite developed by
|
||||
<a href="https://lindeas.com" class="text-decoration-none">Lindeas Ltd.</a> designed to help in maintaining a Jitsi Meet platform.
|
||||
</p>
|
||||
<p>It consists of several parts meant to run together, although some of them can be used separately.</p>
|
||||
</div>
|
||||
|
||||
<!-- Components -->
|
||||
<div class="row g-4">
|
||||
<!-- Jilo CLI -->
|
||||
<div class="col-12">
|
||||
<div class="card border bg-light">
|
||||
<div class="card-header bg-light d-flex align-items-center">
|
||||
<i class="fas fa-terminal me-2 text-secondary"></i>
|
||||
<h5 class="card-title mb-0">JILO</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
This is the command-line tool for extracting information about important events from the Jitsi Meet log files,
|
||||
storing them in a database and searching through that database.
|
||||
</p>
|
||||
<p class="card-text">
|
||||
Jilo is written in Bash and has very minimal external dependencies. That means that you can run it on almost
|
||||
any Linux system with jitsi log files.
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
<p class="mb-2">It can either:</p>
|
||||
<ul class="list-unstyled ps-4">
|
||||
<li><i class="fas fa-check text-success me-2"></i>Show the data directly in the terminal</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>Provide it to an instance of "Jilo Web" for displaying statistics</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>Send the data output to a "Jilo Agent"</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jilo Agent -->
|
||||
<div class="col-12">
|
||||
<div class="card border bg-light">
|
||||
<div class="card-header bg-light d-flex align-items-center">
|
||||
<i class="fas fa-robot me-2 text-secondary"></i>
|
||||
<h5 class="card-title mb-0">Jilo Agent</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
The Jilo Agent is a small program, written in Go. It runs on remote servers in the Jitsi Meet platform,
|
||||
and provides info about the operation of the different components of the platform.
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
<h6 class="fw-bold mb-2">Key Features:</h6>
|
||||
<ul class="list-unstyled ps-4">
|
||||
<li><i class="fas fa-shield-alt text-primary me-2"></i>Secured with JWT tokens authentication</li>
|
||||
<li><i class="fas fa-lock text-primary me-2"></i>HTTPS support</li>
|
||||
<li><i class="fas fa-network-wired text-primary me-2"></i>Single port for all services</li>
|
||||
<li><i class="fas fa-box text-primary me-2"></i>No external dependencies</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jilo Web -->
|
||||
<div class="col-12">
|
||||
<div class="card border bg-light">
|
||||
<div class="card-header bg-light d-flex align-items-center">
|
||||
<i class="fas fa-globe me-2 text-secondary"></i>
|
||||
<h5 class="card-title mb-0">Jilo Web</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Jilo Web is the web app that combines all the information received from Jilo and Jilo Agents and shows
|
||||
statistics and graphs of the usage, the events and the issues.
|
||||
</p>
|
||||
<p class="card-text">
|
||||
It is a multi-user web tool with user levels and access rights integrated into it, making it suitable
|
||||
for the different levels in an enterprise.
|
||||
</p>
|
||||
<div class="alert alert-info mt-3 mb-0">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
The current website you are looking at is running a Jilo Web instance.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jilo Server -->
|
||||
<div class="col-12">
|
||||
<div class="card border bg-light">
|
||||
<div class="card-header bg-light d-flex align-items-center">
|
||||
<i class="fas fa-server me-2 text-secondary"></i>
|
||||
<h5 class="card-title mb-0">Jilo Server</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Jilo Server is a server component written in Go, meant to work alongside Jilo Web. It is responsible for
|
||||
all automated tasks - health checks, periodic retrieval of data from the remote Jilo Agents, etc.
|
||||
</p>
|
||||
<p class="card-text">
|
||||
It generally works on the same machine as the web interface Jilo Web and shares its database, although
|
||||
if needed it could be deployed on a separate machine.
|
||||
</p>
|
||||
<div class="alert alert-warning mt-3 mb-0">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
Jilo Web checks for the Jilo Server availability and displays a warning if there is a problem with the server.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /help -->
|
|
@ -1,58 +0,0 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
|
||||
</div>
|
||||
|
||||
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||
<div class="mb-5">
|
||||
<?php if ($widget['full'] === true) { ?>
|
||||
<table class="table table-results table-striped table-hover table-bordered">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Metric</th>
|
||||
<?php foreach ($widget['records'] as $record) { ?>
|
||||
<th scope="col">
|
||||
<?= htmlspecialchars($record['table_headers']) ?>
|
||||
<?php if ($record['timestamp']) { ?>
|
||||
<br>
|
||||
<small class="text-muted">as of <?= date('Y-m-d H:i:s', strtotime($record['timestamp'])) ?></small>
|
||||
<?php } ?>
|
||||
</th>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($widget['metrics'] as $section => $section_metrics) { ?>
|
||||
<tr class="table-secondary">
|
||||
<th colspan="<?= count($widget['records']) + 1 ?>"><?= htmlspecialchars($section) ?></th>
|
||||
</tr>
|
||||
<?php foreach ($section_metrics as $metric => $metricConfig) { ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($metricConfig['label']) ?></td>
|
||||
<?php foreach ($widget['records'] as $record) { ?>
|
||||
<td>
|
||||
<?php if (isset($record['metrics'][$section][$metric])) {
|
||||
$metric_data = $record['metrics'][$section][$metric];
|
||||
if ($metric_data['link']) { ?>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($metric_data['link']) ?>&from_time=<?= htmlspecialchars($record['timestamp']) ?>&until_time=<?= htmlspecialchars($record['timestamp']) ?>"><?= htmlspecialchars($metric_data['value']) ?></a>
|
||||
<?php } else { ?>
|
||||
<?= htmlspecialchars($metric_data['value']) ?>
|
||||
<?php }
|
||||
} else { ?>
|
||||
<span class="text-muted">No data</span>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php } else { ?>
|
||||
<div class="alert alert-info m-3" role="alert">
|
||||
No data available from any agents. Please check agent configuration and connectivity.
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
<!-- latest data -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 mb-4">
|
||||
<h2 class="mb-0">Latest data from Jilo Agents</h2>
|
||||
<small>gathered for platform <strong><?= htmlspecialchars($platformDetails[0]['name']) ?></strong></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="mb-4">
|
||||
<?php if (!empty($hostsData)) { ?>
|
||||
<?php foreach ($hostsData as $host) { ?>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-network-wired me-2 text-secondary"></i>
|
||||
<?= htmlspecialchars($host['name']) ?><small class="text-muted"> (<?= htmlspecialchars($host['address']) ?>)</small>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php foreach ($host['agents'] as $agent) { ?>
|
||||
<div class="mb-4">
|
||||
<h6 class="border-bottom pb-2">
|
||||
<i class="fas fa-robot me-2 text-secondary"></i>
|
||||
<?= htmlspecialchars($agent['name']) ?> agent
|
||||
</h6>
|
||||
<table class="table table-results table-striped table-hover table-bordered">
|
||||
<thead class="align-top">
|
||||
<tr>
|
||||
<th>Metric</th>
|
||||
<th>
|
||||
Latest value
|
||||
<br>
|
||||
<small class="text-muted"><?= date('d M Y H:i:s', strtotime($agent['timestamp'])) ?></small>
|
||||
</th>
|
||||
<th>
|
||||
Previous value
|
||||
<?php
|
||||
// Find first metric with previous data to get timestamp
|
||||
$prevTimestamp = null;
|
||||
foreach ($metrics as $m_section => $m_metrics) {
|
||||
foreach ($m_metrics as $m_metric => $m_config) {
|
||||
if (isset($agent['metrics'][$m_section][$m_metric]['previous'])) {
|
||||
$prevTimestamp = $agent['metrics'][$m_section][$m_metric]['previous']['timestamp'];
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($prevTimestamp) { ?>
|
||||
<br>
|
||||
<small class="text-muted"><?= date('d M Y H:i:s', strtotime($prevTimestamp)) ?></small>
|
||||
<?php } ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($metrics as $section => $section_metrics) { ?>
|
||||
<?php
|
||||
// Check if this section has any data for this agent
|
||||
$hasData = false;
|
||||
foreach ($section_metrics as $metric => $metricConfig) {
|
||||
if (isset($agent['metrics'][$section][$metric])) {
|
||||
$hasData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$hasData) continue;
|
||||
?>
|
||||
<tr class="table-secondary">
|
||||
<th colspan="3"><?= htmlspecialchars($section) ?></th>
|
||||
</tr>
|
||||
<?php foreach ($section_metrics as $metric => $metricConfig) { ?>
|
||||
<?php if (isset($agent['metrics'][$section][$metric])) {
|
||||
$metric_data = $agent['metrics'][$section][$metric];
|
||||
?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($metricConfig['label']) ?></td>
|
||||
<td>
|
||||
<?php if ($metric_data['link']) { ?>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($metric_data['link']) ?>&from_time=<?= htmlspecialchars($metric_data['latest']['timestamp']) ?>&until_time=<?= htmlspecialchars($metric_data['latest']['timestamp']) ?>"><?= htmlspecialchars($metric_data['latest']['value']) ?></a>
|
||||
<?php } else { ?>
|
||||
<?= htmlspecialchars($metric_data['latest']['value']) ?>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($metric_data['previous']) { ?>
|
||||
<?php if ($metric_data['link']) { ?>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=<?= htmlspecialchars($metric_data['link']) ?>&from_time=<?= htmlspecialchars($metric_data['previous']['timestamp']) ?>&until_time=<?= htmlspecialchars($metric_data['previous']['timestamp']) ?>"><?= htmlspecialchars($metric_data['previous']['value']) ?></a>
|
||||
<?php } else { ?>
|
||||
<?= htmlspecialchars($metric_data['previous']['value']) ?>
|
||||
<?php } ?>
|
||||
<?php } else { ?>
|
||||
<span class="text-muted">No previous data</span>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php } else { ?>
|
||||
<div class="alert alert-info m-3" role="alert">
|
||||
No data available from any agents. Please check agent configuration and connectivity.
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /latest data -->
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
<!-- remote config "<?= htmlspecialchars($livejsFile) ?>" -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 mb-4">
|
||||
<h2 class="mb-0">Remote Jitsi config</h2>
|
||||
<small>contents of the file "<strong><?= htmlspecialchars($livejsFile) ?></strong>"</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="mb-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-file me-2 text-secondary"></i>
|
||||
<small><span class="text-muted"><?= htmlspecialchars($platformDetails[0]['jitsi_url']) ?>:</span> <?= htmlspecialchars($livejsFile) ?></small>
|
||||
<span class="card-text">
|
||||
<?php if ($mode === 'raw') { ?>
|
||||
<span class="m-3"><a class="btn border btn-primary" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=livejs&item=<?= htmlspecialchars($livejsFile) ?>">view only active lines</a></span>
|
||||
<?php } else { ?>
|
||||
<span class="m-3"><a class="btn border btn-secondary" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=livejs&item=<?= htmlspecialchars($livejsFile) ?>&mode=raw">view raw file contents</a></span>
|
||||
<?php } ?>
|
||||
</span>
|
||||
</h5>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre class="results">
|
||||
<?php
|
||||
echo htmlspecialchars($livejsData);
|
||||
?>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /remote config "<?= htmlspecialchars($livejsFile) ?>" -->
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
<!-- Logs filter -->
|
||||
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
|
||||
<form method="POST" id="filter_form" class="filter-results" action="?page=logs">
|
||||
<label for="from_time">from</label>
|
||||
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
|
||||
<label for="until_time">until</label>
|
||||
<input type="date" id="until_time" name="until_time"<?php if (isset($_REQUEST['until_time'])) echo " value=\"" . htmlspecialchars($until_time) . "\"" ?> />
|
||||
<input type="text" name="id" placeholder="user ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
|
||||
<input type="text" name="message" placeholder="message"<?php if (isset($_REQUEST['message'])) echo " value=\"" . htmlspecialchars($_REQUEST['message']) . "\"" ?> />
|
||||
<input type="button" onclick="clearFilter()" value="clear" />
|
||||
<input type="submit" value="search" />
|
||||
</form>
|
||||
<script>
|
||||
function clearFilter() {
|
||||
document.getElementById("filter_form").reset();
|
||||
const filterFields = document.querySelectorAll("#filter_form input");
|
||||
filterFields.forEach(input => {
|
||||
if (input.type === 'text' ||input.type === 'date') {
|
||||
input.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<!-- /Logs filter -->
|
|
@ -1,57 +0,0 @@
|
|||
|
||||
<div class="row">
|
||||
<?php if ($widget['collapsible'] === true) { ?>
|
||||
<a style="text-decoration: none;" data-toggle="collapse" href="#collapse<?= htmlspecialchars($widget['name']) ?>" role="button" aria-expanded="true" aria-controls="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||
<div class="card w-auto bg-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div>
|
||||
<?php } else { ?>
|
||||
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div>
|
||||
<?php } ?>
|
||||
<?php if ($widget['filter'] === true) {
|
||||
include '../app/templates/logs-filter.php'; } ?>
|
||||
<?php if ($widget['collapsible'] === true) { ?>
|
||||
</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
||||
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
|
||||
<?php } ?>
|
||||
<div class="mb-5">
|
||||
<?php if ($widget['full'] === true) { ?>
|
||||
<table class="table table-results table-striped table-hover table-bordered">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<?php foreach ($widget['table_headers'] as $header) { ?>
|
||||
<th scope="col" class="th-<?= htmlspecialchars($header) ?>"><?= htmlspecialchars($header) ?></th>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($widget['table_records'] as $row) { ?>
|
||||
<tr>
|
||||
<?php
|
||||
foreach ($row as $key => $column) {
|
||||
if ($key === 'user ID' && isset($user_id) && $user_id === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } else { ?>
|
||||
<td><?= htmlspecialchars($column ?? '') ?></td>
|
||||
<?php }
|
||||
} ?>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
if ($widget['pagination'] && $item_count > $items_per_page) {
|
||||
$url = "$app_root?platform=$platform_id&page=$page";
|
||||
include '../app/helpers/pagination.php';
|
||||
}
|
||||
?>
|
||||
<?php } else { ?>
|
||||
<p class="m-3">No matching records found.</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
|
@ -0,0 +1,109 @@
|
|||
|
||||
<!-- log events -->
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<h2 class="mb-0">Log events</h2>
|
||||
<small>events recorded in the Jilo monitoring platform</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs navigation -->
|
||||
<ul class="nav nav-tabs mb-4">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $scope === 'user' ? 'active' : '' ?>" href="?page=logs&tab=user">
|
||||
Logs for current user
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($has_system_access) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $scope === 'system' ? 'active' : '' ?>" href="?page=logs&tab=system">
|
||||
Logs for all users
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
|
||||
<!-- logs filter -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="GET" action="" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="page" value="logs">
|
||||
<input type="hidden" name="tab" value="<?= htmlspecialchars($scope) ?>">
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="from_time" class="form-label">From date</label>
|
||||
<input type="date" class="form-control" id="from_time" name="from_time" value="<?= htmlspecialchars($_REQUEST['from_time'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="until_time" class="form-label">Until date</label>
|
||||
<input type="date" class="form-control" id="until_time" name="until_time" value="<?= htmlspecialchars($_REQUEST['until_time'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<?php if ($scope === 'system') { ?>
|
||||
<div class="col-md-2">
|
||||
<label for="id" class="form-label">User ID</label>
|
||||
<input type="text" class="form-control" id="id" name="id" value="<?= htmlspecialchars($_REQUEST['id'] ?? '') ?>" placeholder="Enter user ID">
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="col-md">
|
||||
<label for="message" class="form-label">Message</label>
|
||||
<input type="text" class="form-control" id="message" name="message" value="<?= htmlspecialchars($_REQUEST['message'] ?? '') ?>" placeholder="Search in log messages">
|
||||
</div>
|
||||
|
||||
<div class="col-md-auto">
|
||||
<button type="submit" class="btn btn-primary me-2">
|
||||
<i class="fas fa-search me-2"></i>Search
|
||||
</button>
|
||||
<a href="?page=logs&tab=<?= htmlspecialchars($scope) ?>" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-2"></i>Clear
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /logs filter -->
|
||||
|
||||
<!-- logs -->
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<div class="alert alert-info m-3">
|
||||
<i class="fas fa-calendar-alt me-2"></i>Time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="mb-5">
|
||||
<?php if (!empty($logs['records'])) { ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<?php if ($scope === 'system') { ?>
|
||||
<th>Username (id)</th>
|
||||
<?php } ?>
|
||||
<th>Time</th>
|
||||
<th>Log message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($logs['records'] as $row) { ?>
|
||||
<tr>
|
||||
<?php if ($scope === 'system') { ?>
|
||||
<td><strong><?= htmlspecialchars($row['username']) ?> (<?= htmlspecialchars($row['userID']) ?>)</strong></td>
|
||||
<?php } ?>
|
||||
<td><span class="text-muted"><?= date('d M Y H:i', strtotime($row['time'])) ?></span></td>
|
||||
<td><?= htmlspecialchars($row['log message']) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php include '../app/templates/pagination.php'; ?>
|
||||
<?php } else { ?>
|
||||
<div class="alert alert-info m-3">
|
||||
<i class="fas fa-info-circle me-2"></i>No log entries found for the specified criteria.
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /log events -->
|
|
@ -7,12 +7,15 @@
|
|||
|
||||
<?php } ?>
|
||||
<!-- Footer -->
|
||||
<div id="footer">Jilo Web <?= htmlspecialchars($config['version']) ?> ©2024 - web interface for <a href="https://lindeas.com/jilo">Jilo</a></div>
|
||||
<div id="footer">Jilo Web <?= htmlspecialchars($config['version']) ?> ©2024-<?= date('Y') ?> - web interface for <a href="https://lindeas.com/jilo">Jilo</a></div>
|
||||
<!-- /Footer -->
|
||||
|
||||
</div>
|
||||
|
||||
<script src="static/sidebar.js"></script>
|
||||
<?php if (isset($currentUser) && $page !== 'logout') { ?>
|
||||
<script src="static/js/sidebar.js"></script>
|
||||
<?php } ?>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
@ -39,7 +42,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/main.css">
|
||||
<?php if ($page === 'logs') { ?>
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/logs.css">
|
||||
<?php } ?>
|
||||
<?php if ($page === 'profile') { ?>
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/profile.css">
|
||||
<?php } ?>
|
||||
<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" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/libs/bootstrap/bootstrap-5.3.3.min.css">
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/libs/jquery/jquery.min.js"></script>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/libs/bootstrap/popper.min.js"></script>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/libs/bootstrap/bootstrap-4.0.0.min.js"></script>
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css" integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/messages.css">
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/js/messages.js"></script>
|
||||
<?php if (isset($currentUser)) { ?>
|
||||
<script>
|
||||
// restore sidebar state before the page is rendered
|
||||
(function () {
|
||||
|
@ -23,27 +20,36 @@
|
|||
}
|
||||
})();
|
||||
</script>
|
||||
<?php if ($page === 'agents') { ?>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/agents.js"></script>
|
||||
<?php } ?>
|
||||
<?php if ($page === 'data' && $item === 'graphs') { ?>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@1.2.1/dist/chartjs-plugin-zoom.min.js"></script>
|
||||
<?php if ($page === 'logs') { ?>
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/logs.css">
|
||||
<?php } ?>
|
||||
<?php if ($page === 'profile') { ?>
|
||||
<link rel="stylesheet" type="text/css" href="<?= htmlspecialchars($app_root) ?>static/css/profile.css">
|
||||
<?php } ?>
|
||||
<?php if ($page === 'agents') { ?>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/js/agents.js"></script>
|
||||
<?php } ?>
|
||||
<?php if ($page === 'graphs') { ?>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/libs/chartjs/chart.umd.min.js"></script>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/libs/chartjs/moment.min.js"></script>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/libs/chartjs/chartjs-adapter-moment.min.js"></script>
|
||||
<script src="<?= htmlspecialchars($app_root) ?>static/libs/chartjs/chartjs-plugin-zoom.min.js"></script>
|
||||
<?php } ?>
|
||||
<title>Jilo Web</title>
|
||||
<link rel="icon" type="image/x-icon" href="<?= htmlspecialchars($app_root) ?>static/favicon.ico">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<?php if (isset($messages) && is_array($messages)): ?>
|
||||
<?php foreach ($messages as $msg): ?>
|
||||
<?= Messages::render($msg['category'], $msg['key'], $msg['custom_message'] ?? null) ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="messages-container" class="container-fluid mt-2"></div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<?php if (isset($system_messages) && is_array($system_messages)): ?>
|
||||
<?php foreach ($system_messages as $msg): ?>
|
||||
<?= Feedback::render($msg['category'], $msg['key'], $msg['custom_message'] ?? null) ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<li class="font-weight-light text-uppercase" style="font-size: 0.5em; color: whitesmoke; margin-right: 70px; align-content: center;">version <?= htmlspecialchars($config['version']) ?></li>
|
||||
<li class="font-weight-light text-uppercase" style="font-size: 0.5em; color: whitesmoke; margin-right: 70px; align-content: center;">version <?= htmlspecialchars($config['version']) ?></li>
|
||||
|
||||
<?php if ( isset($_SESSION['username']) ) { ?>
|
||||
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-md-3 mb-5 sidebar-wrapper bg-light" id="sidebar">
|
||||
<div class="text-center" style="border: 1px solid #0dcaf0; height: 22px;" id="time_now">
|
||||
<div class="text-center" style="border: 1px solid #0dcaf0; height: 22px;" id="time_now">
|
||||
<?php
|
||||
$timeNow = new DateTime('now', new DateTimeZone($userTimezone));
|
||||
?>
|
||||
<!--span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('d M Y H:i')) ?> <?= htmlspecialchars($userTimezone) ?></span-->
|
||||
<span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('H:i')) ?> <?= htmlspecialchars($userTimezone) ?></span>
|
||||
</div>
|
||||
<span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('H:i')) ?> <?= htmlspecialchars($userTimezone) ?></span>
|
||||
</div>
|
||||
|
||||
<div class="col-4"><button class="btn btn-sm btn-info toggle-sidebar-button" type="button" id="toggleSidebarButton" value=">>"></button></div>
|
||||
<div class="sidebar-content card ml-3 mt-3">
|
||||
|
@ -40,56 +39,46 @@ $timeNow = new DateTime('now', new DateTimeZone($userTimezone));
|
|||
|
||||
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>live data</small></p></li>
|
||||
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=graphs">
|
||||
<li class="list-group-item<?php if ($page === 'data' && $item === 'graphs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=graphs">
|
||||
<li class="list-group-item<?php if ($page === 'graphs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-chart-bar" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="combined graphs"></i>combined graphs
|
||||
</li>
|
||||
</a>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=latest">
|
||||
<li class="list-group-item<?php if ($page === 'data' && $item === 'latest') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=latest">
|
||||
<li class="list-group-item<?php if ($page === 'latest') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-list" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="latest data"></i>latest data
|
||||
</li>
|
||||
</a>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=configjs">
|
||||
<li class="list-group-item<?php if ($page === 'data' && $item === 'configjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=livejs&item=config.js">
|
||||
<li class="list-group-item<?php if ($page === 'livejs' && $item === 'config.js') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-tv" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="config.js"></i>config.js
|
||||
</li>
|
||||
</a>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=data&item=interfaceconfigjs">
|
||||
<li class="list-group-item<?php if ($page === 'data' && $item === 'interfaceconfigjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=livejs&item=interface_config.js">
|
||||
<li class="list-group-item<?php if ($page === 'livejs' && $item === 'interface_config.js') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-th" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="interface_config.js"></i>interface_config.js
|
||||
</li>
|
||||
</a>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=agents">
|
||||
<li class="list-group-item<?php if ($page === 'agents') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-mask" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="jilo agents"></i>jilo agents
|
||||
<i class="fas fa-robot" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="jilo agents"></i>jilo agents
|
||||
</li>
|
||||
</a>
|
||||
|
||||
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>jitsi platforms config</small></p></li>
|
||||
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>jitsi platforms settings</small></p></li>
|
||||
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=platform">
|
||||
<li class="list-group-item<?php if ($page === 'config' && $item === 'platform') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-sitemap" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>platforms
|
||||
</li>
|
||||
</a>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=host">
|
||||
<li class="list-group-item<?php if ($page === 'config' && $item === 'host') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-laptop" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>hosts
|
||||
</li>
|
||||
</a>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=endpoint">
|
||||
<li class="list-group-item<?php if ($page === 'config' && $item === 'endpoint') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-stethoscope" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>endpoints
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=settings">
|
||||
<li class="list-group-item<?php if ($page === 'settings') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
|
||||
<i class="fas fa-cog" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="jilo settings"></i>settings
|
||||
</li>
|
||||
</a>
|
||||
|
||||
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>system</small></p></li>
|
||||
|
||||
<?php if ($userObject->hasRight($user_id, 'view config file')) {?>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=config&item=config_file">
|
||||
<li class="list-group-item<?php if ($page === 'config' && $item === 'config_file') 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 file
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=config">
|
||||
<li class="list-group-item<?php if ($page === 'config') 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="app config"></i>config
|
||||
</li>
|
||||
</a>
|
||||
<?php } ?>
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* Reusable pagination view/template component
|
||||
* Required variables:
|
||||
* $currentPage - Current page number
|
||||
* $totalPages - Total number of pages
|
||||
*/
|
||||
|
||||
// Ensure required variables are set
|
||||
if (!isset($currentPage) || !isset($totalPages)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Number of page links to show before and after current page
|
||||
$range = 2;
|
||||
?>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<nav aria-label="Page navigation" class="mt-4">
|
||||
<ul class="pagination justify-content-center d-flex flex-row gap-1">
|
||||
<!-- First page -->
|
||||
<?php if ($currentPage > 1): ?>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . $params) ?>">First</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . ($currentPage > 1 ? '&page_num=' . ($currentPage - 1) : '') . $params) ?>">«</a>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">First</span>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">«</span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<!-- Page numbers -->
|
||||
<?php
|
||||
for ($i = 1; $i <= $totalPages; $i++) {
|
||||
// Show first, last, current page, 2 pages before and after current, and step pages (10, 20, etc.)
|
||||
if ($i === 1 ||
|
||||
$i === $totalPages ||
|
||||
$i === $currentPage ||
|
||||
$i === $currentPage - 1 ||
|
||||
$i === $currentPage + 1 ||
|
||||
$i === $currentPage - 2 ||
|
||||
$i === $currentPage + 2 ||
|
||||
($i % 10 === 0 && $i > 10)
|
||||
) { ?>
|
||||
<li class="page-item <?= $i === (int)$currentPage ? 'active' : '' ?>">
|
||||
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . ($i > 1 ? '&page_num=' . $i : '') . $params) ?>"><?= $i ?></a>
|
||||
</li>
|
||||
<?php } elseif ($i === $currentPage - 3 || $i === $currentPage + 3) { ?>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">...</span>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<!-- Last page -->
|
||||
<?php if ($currentPage < $totalPages): ?>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . '&page_num=' . ($currentPage + 1) . $params) ?>">»</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?= htmlspecialchars($app_root . '?page=' . $page . '&page_num=' . $totalPages . $params) ?>">Last</a>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">»</span>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Last</span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<?php endif; ?>
|
|
@ -0,0 +1,134 @@
|
|||
|
||||
<!-- jitsi participants events -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 mb-5">
|
||||
<h2 class="mb-0">Jitsi participants events</h2>
|
||||
<small>log events related to participants in Jitsi Meet conferences</small>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
|
||||
<!-- participant events filter -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" action="" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="page" value="participants">
|
||||
<div class="col-md-auto">
|
||||
<label for="from_time" class="form-label">From date</label>
|
||||
<input type="date" class="form-control" id="from_time" name="from_time" value="<?= htmlspecialchars($_REQUEST['from_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<label for="until_time" class="form-label">Until date</label>
|
||||
<input type="date" class="form-control" id="until_time" name="until_time" value="<?= htmlspecialchars($_REQUEST['until_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="name" class="form-label">Participant name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="<?= htmlspecialchars($_REQUEST['name'] ?? '') ?>" placeholder="Participant name">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" id="id" name="id" value="<?= htmlspecialchars($_REQUEST['id'] ?? '') ?>" placeholder="Search in participant IDs">
|
||||
<input type="text" class="form-control" id="ip" name="ip" value="<?= htmlspecialchars($_REQUEST['ip'] ?? '') ?>" placeholder="Search in participant IPs">
|
||||
</div>
|
||||
<div class="col-md-auto align-middle">
|
||||
<button type="submit" class="btn btn-primary me-2">
|
||||
<i class="fas fa-search me-2"></i>Search
|
||||
</button>
|
||||
<a href="?page=participants" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-2"></i>Clear
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /participant events filter -->
|
||||
|
||||
<!-- participant events -->
|
||||
<?php if ($time_range_specified || count($filterMessage)) { ?>
|
||||
<div class="alert alert-info m-0 mb-3 small">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="mb-0"><i class="fas fa-calendar-alt me-2"></i>Time period:
|
||||
<strong>
|
||||
<?= $from_time == '0000-01-01' ? 'beginning' : date('d M Y', strtotime($from_time)) ?> - <?= $until_time == '9999-12-31' ? 'now' : date('d M Y', strtotime($until_time)) ?>
|
||||
</strong>
|
||||
</p>
|
||||
<?php } ?>
|
||||
<?php if (count($filterMessage)) {
|
||||
foreach ($filterMessage as $message) { ?>
|
||||
<p class="mb-0"><i class="fas fa-users me-2"></i><?= $message ?></strong></p>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="mb-5">
|
||||
<?php if (!empty($participants['records'])) { ?>
|
||||
<div class="table-responsive border">
|
||||
<table class="table table-results table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<?php foreach (array_keys($participants['records'][0]) as $header) { ?>
|
||||
<th scope="col" class="text-nowrap"><?= htmlspecialchars($header) ?></th>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($participants['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') { ?>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'conference name') { ?>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'participant ID' && isset($participantId) && $participantId === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($key === 'participant ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'component ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($stats_id && $key === 'parameter' && isset($participantName) && $participantName === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($stats_id && $key === 'parameter') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($participant_ip && $key === 'parameter' && isset($participantIp) && $participantIp === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($participant_ip && $key === 'parameter') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&ip=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'component') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php
|
||||
} elseif ($key === 'time') { ?>
|
||||
<td class="text-nowrap"><?= !empty($column) ? date('d M Y H:i:s',strtotime($column)) : '<small class="text-muted">n/a</small>' ?></td>
|
||||
<?php } else { ?>
|
||||
<td><?= htmlspecialchars($column ?? '') ?></td>
|
||||
<?php }
|
||||
} ?>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php include '../app/templates/pagination.php'; ?>
|
||||
<?php } else { ?>
|
||||
<div class="alert alert-danger m-0">
|
||||
<i class="fas fa-info-circle me-2"></i>No participant events found for the specified criteria.
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /jitsi participants events -->
|
|
@ -1,223 +1,226 @@
|
|||
<!-- Security Settings -->
|
||||
<div class="container">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h2>Security Settings</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $section === 'whitelist' ? 'active' : '' ?>" href="?page=security§ion=whitelist">IP Whitelist</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $section === 'blacklist' ? 'active' : '' ?>" href="?page=security§ion=blacklist">IP Blacklist</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting')) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $section === 'ratelimit' ? 'active' : '' ?>" href="?page=security§ion=ratelimit">Rate Limiting</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($section === 'whitelist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist'))) { ?>
|
||||
<!-- Whitelist Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>IP Whitelist</h3>
|
||||
IP addresses and networks that will always bypass the ratelimiting login checks.
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" class="mb-4">
|
||||
<input type="hidden" name="action" value="add_whitelist">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" name="ip_address" placeholder="IP Address or CIDR" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" name="description" placeholder="Description">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_white">
|
||||
<label class="form-check-label" for="is_network_white">Is Network</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary">Add to Whitelist</button>
|
||||
</div>
|
||||
<!-- security settings -->
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h2 class="mb-0">Security settings</h2>
|
||||
<small>network restrictions to control flooding and brute force attacks</small>
|
||||
<ul class="nav nav-tabs mt-5">
|
||||
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $section === 'whitelist' ? 'active' : '' ?>" href="?page=security§ion=whitelist">IP whitelist</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $section === 'blacklist' ? 'active' : '' ?>" href="?page=security§ion=blacklist">IP blacklist</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting')) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $section === 'ratelimit' ? 'active' : '' ?>" href="?page=security§ion=ratelimit">Rate limiting</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Network</th>
|
||||
<th>Description</th>
|
||||
<th>Added By</th>
|
||||
<th>Added On</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($whitelisted as $ip) { ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($ip['ip_address']) ?></td>
|
||||
<td><?= $ip['is_network'] ? 'Yes' : 'No' ?></td>
|
||||
<td><?= htmlspecialchars($ip['description']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_by']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_at']) ?></td>
|
||||
<td>
|
||||
<form method="POST" style="display: inline;">
|
||||
<input type="hidden" name="action" value="remove_whitelist">
|
||||
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from whitelist?')">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($section === 'blacklist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist'))) { ?>
|
||||
<!-- Blacklist Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>IP Blacklist</h3>
|
||||
IP addresses and networks that will always get blocked at login.
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" class="mb-4">
|
||||
<input type="hidden" name="action" value="add_blacklist">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<input type="text" class="form-control" name="ip_address" placeholder="IP Address or CIDR" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="text" class="form-control" name="reason" placeholder="Reason">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="number" class="form-control" name="expiry_hours" placeholder="Expiry (hours)">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_black">
|
||||
<label class="form-check-label" for="is_network_black">Is Network</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary">Add to Blacklist</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Network</th>
|
||||
<th>Reason</th>
|
||||
<th>Added By</th>
|
||||
<th>Added On</th>
|
||||
<th>Expires</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($blacklisted as $ip) { ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($ip['ip_address']) ?></td>
|
||||
<td><?= $ip['is_network'] ? 'Yes' : 'No' ?></td>
|
||||
<td><?= htmlspecialchars($ip['reason']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_by']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_at']) ?></td>
|
||||
<td><?= $ip['expiry_time'] ? htmlspecialchars($ip['expiry_time']) : 'Never' ?></td>
|
||||
<td>
|
||||
<form method="POST" style="display: inline;">
|
||||
<input type="hidden" name="action" value="remove_blacklist">
|
||||
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from blacklist?')">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($section === 'ratelimit' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting'))) { ?>
|
||||
<!-- Rate Limiting Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Rate Limiting Settings</h3>
|
||||
Rate limiting settings control how many failed login attempts are allowed before blocking an IP address.
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<h4>Current Settings</h4>
|
||||
<ul>
|
||||
<li>Maximum login attempts: <?= $rateLimiter->maxAttempts ?></li>
|
||||
<li>Time window: <?= $rateLimiter->decayMinutes ?> minutes</li>
|
||||
<li>Auto-blacklist threshold: <?= $rateLimiter->autoBlacklistThreshold ?> attempts</li>
|
||||
<li>Auto-blacklist duration: <?= $rateLimiter->autoBlacklistDuration ?> hours</li>
|
||||
</ul>
|
||||
<p class="mb-0">
|
||||
<small>Note: These settings can be modified in the RateLimiter class configuration.</small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h4>Recent Failed Login Attempts</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Username</th>
|
||||
<th>Attempted At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$stmt = $rateLimiter->db->prepare("
|
||||
SELECT ip_address, username, attempted_at
|
||||
FROM {$rateLimiter->ratelimitTable}
|
||||
ORDER BY attempted_at DESC
|
||||
LIMIT 10
|
||||
");
|
||||
$stmt->execute();
|
||||
$attempts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($attempts as $attempt) {
|
||||
?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($attempt['ip_address']) ?></td>
|
||||
<td><?= htmlspecialchars($attempt['username']) ?></td>
|
||||
<td><?= htmlspecialchars($attempt['attempted_at']) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php if ($section === 'whitelist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist'))) { ?>
|
||||
<!-- whitelist section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>IP whitelist</h3>
|
||||
IP addresses and networks that will always bypass the ratelimiting login checks.
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" class="mb-4">
|
||||
<?php include 'csrf_token.php'; ?>
|
||||
<input type="hidden" name="action" value="add_whitelist">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" name="ip_address" placeholder="IP address or CIDR" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" name="description" placeholder="Description">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_white">
|
||||
<label class="form-check-label" for="is_network_white">is network</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary">Add to whitelist</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP address</th>
|
||||
<th>Network</th>
|
||||
<th>Description</th>
|
||||
<th>Added by</th>
|
||||
<th>Added on</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($whitelisted as $ip) { ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($ip['ip_address']) ?></td>
|
||||
<td><?= $ip['is_network'] ? 'Yes' : 'No' ?></td>
|
||||
<td><?= htmlspecialchars($ip['description']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_by']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_at']) ?></td>
|
||||
<td>
|
||||
<form method="POST" style="display: inline;">
|
||||
<?php include 'csrf_token.php'; ?>
|
||||
<input type="hidden" name="action" value="remove_whitelist">
|
||||
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from whitelist?')">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($section === 'blacklist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist'))) { ?>
|
||||
<!-- blacklist section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>IP blacklist</h3>
|
||||
IP addresses and networks that will always get blocked at login.
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" class="mb-4">
|
||||
<?php include 'csrf_token.php'; ?>
|
||||
<input type="hidden" name="action" value="add_blacklist">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<input type="text" class="form-control" name="ip_address" placeholder="IP address or CIDR" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="text" class="form-control" name="reason" placeholder="Reason">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="number" class="form-control" name="expiry_hours" placeholder="Expiry (hours)">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_black">
|
||||
<label class="form-check-label" for="is_network_black">is network</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary">Add to blacklist</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP address</th>
|
||||
<th>Network</th>
|
||||
<th>Reason</th>
|
||||
<th>Added by</th>
|
||||
<th>Added on</th>
|
||||
<th>Expires</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($blacklisted as $ip) { ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($ip['ip_address']) ?></td>
|
||||
<td><?= $ip['is_network'] ? 'Yes' : 'No' ?></td>
|
||||
<td><?= htmlspecialchars($ip['reason']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_by']) ?></td>
|
||||
<td><?= htmlspecialchars($ip['created_at']) ?></td>
|
||||
<td><?= $ip['expiry_time'] ? htmlspecialchars($ip['expiry_time']) : 'Never' ?></td>
|
||||
<td>
|
||||
<form method="POST" style="display: inline;">
|
||||
<?php include 'csrf_token.php'; ?>
|
||||
<input type="hidden" name="action" value="remove_blacklist">
|
||||
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from blacklist?')">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($section === 'ratelimit' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting'))) { ?>
|
||||
<!-- rate limiting section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Rate limiting settings</h3>
|
||||
Rate limiting settings control how many failed login attempts are allowed before blocking an IP address.
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<h4>Current settings</h4>
|
||||
<ul>
|
||||
<li>Maximum login attempts: <?= $rateLimiter->maxAttempts ?></li>
|
||||
<li>Time window: <?= $rateLimiter->decayMinutes ?> minutes</li>
|
||||
<li>Auto-blacklist threshold: <?= $rateLimiter->autoBlacklistThreshold ?> attempts</li>
|
||||
<li>Auto-blacklist duration: <?= $rateLimiter->autoBlacklistDuration ?> hours</li>
|
||||
</ul>
|
||||
<p class="mb-0">
|
||||
<small>Note: These settings can be modified in the RateLimiter class configuration.</small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h4>Recent failed login attempts</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP sddress</th>
|
||||
<th>Username</th>
|
||||
<th>Attempted at</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $stmt = $rateLimiter->db->prepare("
|
||||
SELECT ip_address, username, attempted_at
|
||||
FROM {$rateLimiter->authRatelimitTable}
|
||||
ORDER BY attempted_at DESC
|
||||
LIMIT 10
|
||||
");
|
||||
$stmt->execute();
|
||||
$attempts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($attempts as $attempt) { ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($attempt['ip_address']) ?></td>
|
||||
<td><?= htmlspecialchars($attempt['username']) ?></td>
|
||||
<td><?= htmlspecialchars($attempt['attempted_at']) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<!-- /Security Settings -->
|
||||
<!-- /security settings -->
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,14 +1,20 @@
|
|||
|
||||
<!-- jilo agent status -->
|
||||
<div class="card text-center w-75 mx-lef" style="padding-left: 80px;">
|
||||
<div class="card-body">
|
||||
<p class="card-text text-left" style="text-align: left;">
|
||||
Jilo Agent <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>agent<?= htmlspecialchars($agent['id']) ?>"><?= htmlspecialchars($agent['agent_description']) ?></a>:
|
||||
<strong><?= $agent_availability ?></strong>
|
||||
<br />
|
||||
host: <strong><?= htmlspecialchars($agent_host) ?></strong>,
|
||||
port: <strong><?= htmlspecialchars($agent_port) ?></strong>,
|
||||
endpoint: <strong><?= htmlspecialchars($agent['agent_endpoint']) ?></strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- jilo agent status -->
|
||||
<div class="d-flex align-items-center flex-wrap border-top p-3">
|
||||
<div class="d-flex align-items-center me-4">
|
||||
<i class="fas fa-robot me-2 text-secondary"></i>
|
||||
<span class="me-2">Jilo agent
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=settings#platform-<?= htmlspecialchars($platform['id']) ?>agent-<?= htmlspecialchars($agent['id']) ?>" class="text-decoration-none">
|
||||
<?= htmlspecialchars($agent['agent_description']) ?>
|
||||
</a>:
|
||||
</span>
|
||||
<span class="badge <?= $agent_availability === 'running' ? 'bg-success' : 'bg-danger' ?>" title="<?= $agent_availability !== 'running' ? htmlspecialchars($agent_availability) : '' ?>" data-toggle="tooltip" data-placement="right" data-offset="30.0">
|
||||
<?= $agent_availability === 'running' ? 'Running' : 'Error' ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center me-4">
|
||||
<span class="me-4">Host: <strong><?= htmlspecialchars($agent_host) ?></strong></span>
|
||||
<span class="me-4">Port: <?= isset($agent_port) && $agent_port !== '' ? '<strong>' . htmlspecialchars($agent_port) . '</strong>' : '<em class="small text-muted">not set</em>' ?></span>
|
||||
<span>Endpoint: <strong><?= htmlspecialchars($agent['agent_endpoint']) ?></strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
|
||||
<!-- jitsi platform status -->
|
||||
<br />
|
||||
<div class="card text-center w-75 mx-lef" style="padding-left: 40px;">
|
||||
<div class="card-body">
|
||||
<p class="card-text text-left" style="text-align: left;">
|
||||
Jitsi Meet platform: <a href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform['id']) ?>"><?= htmlspecialchars($platform['name']) ?></a>
|
||||
<br />
|
||||
jilo database: <strong><?= htmlspecialchars($platform['jilo_database']) ?></strong>,
|
||||
status: <strong><?= $jilo_database_status ?></strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- jitsi platform status -->
|
||||
<div class="card mt-3 mb-3">
|
||||
<div class="card-header">
|
||||
<h4>
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?page=settings#platform-<?= htmlspecialchars($platform['id']) ?>" class="text-decoration-none">
|
||||
Jitsi platform "<?= htmlspecialchars($platform['name']) ?>"
|
||||
</a>
|
||||
</h4>
|
||||
<small class="text-muted">Remote Jitsi Meet installation with its database and agents here.</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
<span class="me-4">Jilo database: <strong><?= htmlspecialchars($platform['jilo_database']) ?></strong></span>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-2">Status:</span>
|
||||
<span class="badge <?= $jilo_database_status === 'Connected' ? 'bg-success' : 'bg-danger' ?>"><?= htmlspecialchars($jilo_database_status) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,30 @@
|
|||
|
||||
<!-- jilo status -->
|
||||
<div class="card text-center w-75 mx-lef">
|
||||
<p class="h4 card-header">Jilo platform status</p>
|
||||
<div class="card-body">
|
||||
<p class="card-text text-left" style="text-align: left;">
|
||||
Jilo Server:
|
||||
<?php if ($server_status) { ?>
|
||||
<strong><span class="text-success">running</span></strong>
|
||||
<?php } else { ?>
|
||||
<strong><span class="text-danger">not running</span></strong>
|
||||
<?php } ?>
|
||||
<br />
|
||||
host: <strong><?= htmlspecialchars($server_host) ?></strong>,
|
||||
port: <strong><?= htmlspecialchars($server_port) ?></strong>,
|
||||
endpoint: <strong><?= htmlspecialchars($server_endpoint) ?></strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid mt-2">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 mb-5">
|
||||
<h2 class="mb-0">Jilo status</h2>
|
||||
<small>status checks of the whole Jilo monitoring platform</small>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
|
||||
<!-- jilo server status -->
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h4>Jilo server</h4>
|
||||
<small class="text-muted">Responsible for periodic checks of remote agents and storing received data.</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
<div class="d-flex align-items-center me-4">
|
||||
<span class="me-2">Jilo server:</span>
|
||||
<span class="badge <?= $server_status ? 'bg-success' : 'bg-danger' ?>">
|
||||
<?= $server_status ? 'Running' : 'Not running' ?>
|
||||
</span>
|
||||
</div>
|
||||
<span class="me-4">Host: <strong><?= htmlspecialchars($server_host) ?></strong></span>
|
||||
<span class="me-4">Port: <strong><?= htmlspecialchars($server_port) ?></strong></span>
|
||||
<span>Endpoint: <strong><?= htmlspecialchars($server_endpoint) ?></strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,11 @@
|
|||
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
||||
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
|
||||
<p class="m-3">time period:
|
||||
<strong>
|
||||
<?= $from_time == '0000-01-01' ? 'beginning' : date('d M Y', strtotime($from_time)) ?> - <?= $until_time == '9999-12-31' ? 'now' : date('d M Y', strtotime($until_time)) ?>
|
||||
</strong>
|
||||
</p>
|
||||
<?php } ?>
|
||||
<div class="mb-5">
|
||||
<?php if ($widget['full'] === true) { ?>
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
<?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 } ?>
|
||||
|
@ -16,7 +14,11 @@
|
|||
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
||||
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||
<?php if ($time_range_specified) { ?>
|
||||
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
|
||||
<p class="m-3">time period:
|
||||
<strong>
|
||||
<?= $from_time == '0000-01-01' ? 'beginning' : date('d M Y', strtotime($from_time)) ?> - <?= $until_time == '9999-12-31' ? 'now' : date('d M Y', strtotime($until_time)) ?>
|
||||
</strong>
|
||||
</p>
|
||||
<?php } ?>
|
||||
<div class="mb-5">
|
||||
<?php if ($widget['full'] === true) { ?>
|
||||
|
@ -24,46 +26,41 @@
|
|||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<?php foreach ($widget['table_headers'] as $header) { ?>
|
||||
<th scope="col"><?= htmlspecialchars($header) ?></th>
|
||||
<th scope="col" class="text-nowrap"><?= htmlspecialchars($header) ?></th>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($widget['table_records'] as $row) { ?>
|
||||
<tr>
|
||||
<?php $stats_id = false;
|
||||
$participant_ip = false;
|
||||
if (isset($row['event']) && $row['event'] === 'stats_id') $stats_id = true;
|
||||
if (isset($row['event']) && $row['event'] === 'pair selected') $participant_ip = true;
|
||||
foreach ($row as $key => $column) {
|
||||
if ($key === 'conference ID' && isset($conferenceId) && $conferenceId === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($key === 'conference ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'conference name' && isset($conferenceName) && $conferenceName === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php foreach ($row as $key => $column) {
|
||||
if ($key === 'conference ID') { ?>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&id=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'conference name') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'participant ID' && isset($participantId) && $participantId === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($key === 'participant ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<td class="text-nowrap">
|
||||
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&name=<?= htmlspecialchars($column ?? '') ?>"
|
||||
<?= (strlen($column ?? '') > 16) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 16 ? substr($column, 0, 16) . '...' : $column ?? '') ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php } elseif ($key === 'conference host') { ?>
|
||||
<td class="text-nowrap">
|
||||
<span <?= (strlen($column ?? '') > 30) ? 'data-toggle="tooltip" title="' . htmlspecialchars($column) . '"' : '' ?>>
|
||||
<?= htmlspecialchars(strlen($column ?? '') > 30 ? substr($column, 0, 30) . '...' : $column ?? '') ?>
|
||||
</span>
|
||||
</td>
|
||||
<?php } elseif ($key === 'component ID') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&id=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($stats_id && $key === 'parameter' && isset($participantName) && $participantName === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($stats_id && $key === 'parameter') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($participant_ip && $key === 'parameter' && isset($participantIp) && $participantIp === $column) { ?>
|
||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
||||
<?php } elseif ($participant_ip && $key === 'parameter') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&ip=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php } elseif ($key === 'component') { ?>
|
||||
<td><a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components&name=<?= htmlspecialchars($column ?? '') ?>"><?= htmlspecialchars($column ?? '') ?></a></td>
|
||||
<?php
|
||||
// in general listings we don't show seconds and miliseconds
|
||||
} elseif ($key === 'start' || $key === 'end') { ?>
|
||||
<td><?= htmlspecialchars(substr($column ?? '', 0, -7)) ?></td>
|
||||
<td class="text-nowrap"><?= !empty($column) ? date('d M Y H:i:s',strtotime($column)) : '<small class="text-muted">n/a</small>' ?></td>
|
||||
<?php } else { ?>
|
||||
<td><?= htmlspecialchars($column ?? '') ?></td>
|
||||
<?php }
|
||||
|
@ -72,12 +69,6 @@
|
|||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
if ($widget['pagination'] && $item_count > $items_per_page) {
|
||||
$url = "$app_root?platform=$platform_id&page=$page";
|
||||
include '../app/helpers/pagination.php';
|
||||
}
|
||||
?>
|
||||
<?php } else { ?>
|
||||
<p class="m-3">No matching records found.</p>
|
||||
<?php } ?>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue