Compare commits

..

221 Commits
v0.1 ... main

Author SHA1 Message Date
Yasen Pramatarov e9325ee57b Fixe html 2024-10-26 15:37:35 +03:00
Yasen Pramatarov 27c2682c3f Add an agent only if such type has not already beed added 2024-10-26 15:35:16 +03:00
Yasen Pramatarov be8f008eb4 Adds check for already added agents in a aplatform 2024-10-26 15:19:45 +03:00
Yasen Pramatarov 9bfb79d036 Plural form fix 2024-10-25 12:32:53 +03:00
Yasen Pramatarov b3e3a78ed9 Adds check period to creating a new agent 2024-10-25 12:19:54 +03:00
Yasen Pramatarov 2bf3a423f4 Adds check period to agents config 2024-10-24 12:11:35 +03:00
Yasen Pramatarov 8840efebdb Fixes bugs in URL redirects 2024-10-23 15:28:45 +03:00
Yasen Pramatarov fee54aa827 Checks jilo server only when logged in 2024-10-23 15:13:01 +03:00
Yasen Pramatarov 36f287e169 Fixes json escaping 2024-10-23 13:22:55 +03:00
Yasen Pramatarov 9b45d5df9e Adds status checks for the agents in status page 2024-10-23 13:06:59 +03:00
Yasen Pramatarov 73d429647c Adds agents to the status page 2024-10-22 18:03:34 +03:00
Yasen Pramatarov ed9ce2bb07 Adds agent status check button 2024-10-22 16:59:55 +03:00
Yasen Pramatarov daf41efa4c UPdates the help page with Jilo Server info 2024-10-19 16:40:00 +03:00
Yasen Pramatarov d5bc6a3c8d updates CHANGELOG 2024-10-19 16:20:25 +03:00
Yasen Pramatarov cada9dd67c Adds Jilo status page 2024-10-19 16:09:16 +03:00
Yasen Pramatarov f3457d5240 Adds a Jilo Server check and notice. 2024-10-18 15:41:15 +03:00
Yasen Pramatarov e76c78fde4 Prepares for release 0.2.1 2024-10-17 12:03:13 +03:00
Yasen Pramatarov 15d5626b20 Adds Jilo agents checks table to the schema 2024-10-15 20:52:16 +03:00
Yasen Pramatarov 202d90d4fc Adds a check period field in agents config 2024-10-15 20:48:59 +03:00
Yasen Pramatarov 7e09815835 Adds example data notice in charts under construction 2024-10-13 11:32:57 +03:00
Yasen Pramatarov 0318670bce Fixes custom range on mouse zoom 2024-10-12 17:02:07 +03:00
Yasen Pramatarov 6b4f344bfd Adds initial "latest data" code 2024-10-11 10:58:08 +03:00
Yasen Pramatarov 1d706803b1 Adds active state to last clicked graph button 2024-10-11 10:22:20 +03:00
Yasen Pramatarov 88a3907d29 Adds Chart.js license 2024-10-11 09:52:40 +03:00
Yasen Pramatarov 71c636f297 Styles the time range buttons 2024-10-10 12:42:32 +03:00
Yasen Pramatarov c04fcb7d42 Fixes graphs and adds mouse selection 2024-10-10 11:31:05 +03:00
Yasen Pramatarov 4b1ab93474 Uses a common graphs helper template 2024-10-09 15:56:51 +03:00
Yasen Pramatarov b81be03c4f Adds graphs page with some temporary example data 2024-10-09 14:53:57 +03:00
Yasen Pramatarov 1756ee71cd Adds graphs and latest data pages 2024-10-08 13:23:38 +03:00
Yasen Pramatarov 537486d591 Design fixes 2024-10-07 10:42:54 +03:00
Yasen Pramatarov 865d21e2aa Adds a help page 2024-10-07 10:00:15 +03:00
Yasen Pramatarov ef698c4fa7 Makes active platform button a span instead of a link 2024-10-06 18:22:05 +03:00
Yasen Pramatarov 9fcaa76644 Fixes the design and left menu 2024-10-05 18:12:15 +03:00
Yasen Pramatarov 0b9e1118b8 updates the changelog 2024-10-04 18:42:32 +03:00
Yasen Pramatarov d82219eea0 bugfixes 2024-10-04 18:13:55 +03:00
Yasen Pramatarov 37c5bdb4b4 Sanitize all output 2024-10-04 14:18:28 +03:00
Yasen Pramatarov f4a64b6887 Centralizes data sanitation 2024-10-04 11:36:45 +03:00
Yasen Pramatarov b3f642c02b Links to participants in conference view 2024-10-03 19:48:29 +03:00
Yasen Pramatarov fbe646823d Fixes front page conferences counting 2024-10-03 19:33:31 +03:00
Yasen Pramatarov 9c187c9550 Adds ellipses to pagination 2024-10-03 18:47:23 +03:00
Yasen Pramatarov eb8104595e Fixes bugs in components view 2024-10-03 17:31:39 +03:00
Yasen Pramatarov 2d8bc53195 Compacts the agent config listing 2024-10-03 11:40:41 +03:00
Yasen Pramatarov 1468843cac Fixes agent types selection on add/edit 2024-10-03 11:28:23 +03:00
Yasen Pramatarov d0ef53a176 Fixes AJAX agent cache buttons 2024-10-03 10:59:32 +03:00
Yasen Pramatarov 0791a4c2c9 CSS fixes 2024-10-03 09:43:37 +03:00
Yasen Pramatarov 4f0601cff7 Fixes timestamps in cache 2024-10-02 22:50:27 +03:00
Yasen Pramatarov 4e19a1c571 Enhances agent data display 2024-10-02 22:33:09 +03:00
Yasen Pramatarov 784532c44d Fixes cache buttons for agents data 2024-10-02 16:35:10 +03:00
Yasen Pramatarov 4e827e62f0 Fixes the calls to agent with JWT 2024-10-02 14:04:45 +03:00
Yasen Pramatarov 8bb2e8c838 Prepares for agents' JWT 2024-10-01 10:18:53 +03:00
Yasen Pramatarov ff2bc61bda Fixes bottons tooltips 2024-09-30 21:56:53 +03:00
Yasen Pramatarov b6c42fd4ec Fixes agent buttons 2024-09-30 19:48:00 +03:00
Yasen Pramatarov 37b0a289b2 Fixes logout 2024-09-30 12:52:06 +03:00
Yasen Pramatarov e110619835 Small bugfixes 2024-09-30 12:22:53 +03:00
Yasen Pramatarov b72d4ea791 Fixes to the agent API calls 2024-09-30 11:55:23 +03:00
Yasen Pramatarov 37398b5986 Fixes agent calls and adds error reporting 2024-09-30 11:09:01 +03:00
Yasen Pramatarov e1888cce8a Typo fix 2024-09-30 10:07:50 +03:00
Yasen Pramatarov a585bdaaff Initial test with agents API calling 2024-09-29 10:07:04 +03:00
Yasen Pramatarov 24c5870d33 Adds jilo agent API call func 2024-09-28 10:04:20 +03:00
Yasen Pramatarov ed2a058c12 Checks agent cache 2024-09-26 09:56:24 +03:00
Yasen Pramatarov ede9ecc7b6 Adds initial ajax call to agents api 2024-09-27 09:49:50 +03:00
Yasen Pramatarov e5d1324126 Fixes platform switcher 2024-09-25 09:51:26 +03:00
Yasen Pramatarov 6cc1efff15 Adds platforms switcher in menu 2024-09-24 09:39:30 +03:00
Yasen Pramatarov ff2a96119e Adds initial agents checks page 2024-09-23 21:39:32 +03:00
Yasen Pramatarov eabbd67b9a Hides time when the sidebar is collapsed 2024-09-23 19:37:58 +03:00
Yasen Pramatarov 950ea8ff95 Adds internal anchor links in the config page 2024-09-23 19:33:10 +03:00
Yasen Pramatarov 1acb7126ec Moving all of agents config to "config" page 2024-09-23 12:39:33 +03:00
Yasen Pramatarov 6d07744ab7 Fixes agent config 2024-09-22 12:26:19 +03:00
Yasen Pramatarov 72b1442b77 Fixes agent config pages 2024-09-21 12:21:46 +03:00
Yasen Pramatarov 88e77f71ef Fixes config pages 2024-09-20 12:13:18 +03:00
Yasen Pramatarov 847dc280d3 Fixes agents config 2024-09-19 11:41:59 +03:00
Yasen Pramatarov 4786b0d1f7 Fixes the agents configuration. 2024-09-18 11:32:24 +03:00
Yasen Pramatarov be0cd48c01 Adds user IP to the logs. 2024-09-17 14:22:43 +03:00
Yasen Pramatarov 527da25cdc Tidying up CSS 2024-09-17 13:07:10 +03:00
Yasen Pramatarov 33dfbcdeea Fixes to the logging system 2024-09-16 19:08:03 +03:00
Yasen Pramatarov 1c710bef35 Updates changelog 2024-09-16 17:10:14 +03:00
Yasen Pramatarov f6362bfdc1 Adds initial support for logs 2024-09-16 17:09:37 +03:00
Yasen Pramatarov 5d06a7222c Fixes the pagination for components 2024-09-15 21:49:20 +03:00
Yasen Pramatarov 76e061e1cb updates changelog 2024-09-15 21:43:44 +03:00
Yasen Pramatarov 2478f84e85 Adds pagination 2024-09-15 21:42:47 +03:00
Yasen Pramatarov 8a2f082b09 Adds missing file 2024-09-13 14:07:36 +03:00
Yasen Pramatarov 42b42e6fd2 Renames front page to "dashboard 2024-09-13 14:04:14 +03:00
Yasen Pramatarov 3170d87934 Adds "not found" page 2024-09-13 14:02:59 +03:00
Yasen Pramatarov 6ec0981b0a Fixes bugs and cleans up the code 2024-09-13 13:49:17 +03:00
Yasen Pramatarov e0eee38726 Bugfixes to the latest changes 2024-09-13 13:04:15 +03:00
Yasen Pramatarov 7cc8da562d Adds access control to pages based on user rights 2024-09-13 12:13:00 +03:00
Yasen Pramatarov 172a545acf Reorganizes the pages loading in index.php 2024-09-13 11:05:11 +03:00
Yasen Pramatarov 318f5356c0 Fixes error in sql schema 2024-09-12 12:34:22 +03:00
Yasen Pramatarov bed55c909d User 1 is always superuser, User 2 is always demo 2024-09-12 12:28:02 +03:00
Yasen Pramatarov 8da45a06d0 Makes minor fixes to the rights management 2024-09-12 11:54:57 +03:00
Yasen Pramatarov d2154fa63c Adds user rights editing feature 2024-09-11 22:51:46 +03:00
Yasen Pramatarov e195b653b1 Adds favicon 2024-09-11 19:34:48 +03:00
Yasen Pramatarov 70163e1c5e Adds initial support for user rights 2024-09-11 16:21:05 +03:00
Yasen Pramatarov 1efb3b6a17 Adds autofocus to forms 2024-09-10 18:29:58 +03:00
Yasen Pramatarov 59c6706505 updates the changelog 2024-09-10 14:07:49 +03:00
Yasen Pramatarov 6a48b54320 Streamlines the avatar management 2024-09-10 14:05:38 +03:00
Yasen Pramatarov da5e5ec4ae Fixes avatar management 2024-09-09 16:35:06 +03:00
Yasen Pramatarov 573df0fe4f Fixes avatar management 2024-09-09 15:54:32 +03:00
Yasen Pramatarov fd949f6f4f Adds user avatar management 2024-09-09 15:20:21 +03:00
Yasen Pramatarov 84664f8b5f Fixes default avatar 2024-09-08 14:17:08 +03:00
Yasen Pramatarov d2f4850d28 Adds default avatar image 2024-09-08 14:15:08 +03:00
Yasen Pramatarov f0f7d5b2d3 Adds basic profile editing 2024-09-08 13:48:21 +03:00
Yasen Pramatarov 06a534c2da Adds profile edit page 2024-09-08 01:36:57 +03:00
Yasen Pramatarov 189d30bad5 Enhances the user profile 2024-09-07 23:05:22 +03:00
Yasen Pramatarov 993474a754 Adds users meta and rights 2024-09-07 21:49:50 +03:00
Yasen Pramatarov d64fc5cf56 Cleans up style 2024-09-06 19:34:03 +03:00
Yasen Pramatarov 2d4205916b Fixes bug in config editing 2024-09-06 19:03:08 +03:00
Yasen Pramatarov de7133be3d Adds support for managing Jilo Agents 2024-09-05 01:06:38 +03:00
Yasen Pramatarov 48a0cf9e86 Fixes error in web path 2024-09-04 13:11:17 +03:00
Yasen Pramatarov dc9462260d Fixes platform config rendering 2024-09-04 12:53:45 +03:00
Yasen Pramatarov b60208bea7 Moves the platforms config from flat file to SQLite DB 2024-09-04 12:53:02 +03:00
Yasen Pramatarov 7d85c9181d Prepares for releasing v0.2 2024-08-31 20:24:12 +03:00
Yasen Pramatarov f88fc0f819 Adds switching between raw config files and only active config lines 2024-08-31 19:50:58 +03:00
Yasen Pramatarov 54d6ce2ec4 Updates CHANGELOG - config.js & interface_config.js 2024-08-30 13:03:13 +03:00
Yasen Pramatarov 0bab284e99 Adds interface_config.js capability 2024-08-29 13:01:46 +03:00
Yasen Pramatarov cf7d417193 Adds config.js capability 2024-08-28 12:59:13 +03:00
Yasen Pramatarov 1780233778 Adds Jitsi URL for loading the config.js and doing checks 2024-08-27 21:18:27 +03:00
Yasen Pramatarov 666f3ca98b Fixes adding, editing and deleting a platform 2024-08-26 21:19:21 +03:00
Yasen Pramatarov 1ba32d86f7 Move the platform editing to config page 2024-08-25 13:24:48 +03:00
Yasen Pramatarov d2213ca3e9 Adds "platform_id" to all links 2024-08-24 13:06:00 +03:00
Yasen Pramatarov c83baeee2f Moves all SQL into the respective classes 2024-08-23 12:44:31 +03:00
Yasen Pramatarov cd7b78a2e5 Fixe SQL 2024-08-22 11:59:48 +03:00
Yasen Pramatarov d854473a06 Moving SQL into classes 2024-08-21 22:11:28 +03:00
Yasen Pramatarov bf197ae96b Fixes regexps 2024-08-20 12:58:55 +03:00
Yasen Pramatarov 2bbc4af068 Fixes error handling 2024-08-19 13:42:16 +03:00
Yasen Pramatarov 1cda74cd50 Adds platform editing code 2024-08-19 13:25:09 +03:00
Yasen Pramatarov 3abafe2de7 Platforms edit code 2024-08-18 22:12:45 +03:00
Yasen Pramatarov 3a5eed933e Iniial edit platform code 2024-08-18 22:06:24 +03:00
Yasen Pramatarov 909fbc2626 Better error reporting 2024-08-17 11:20:08 +03:00
Yasen Pramatarov 09667920a2 Updates CHANGELOG 2024-08-17 10:02:55 +03:00
Yasen Pramatarov ea4b85ddf7 Initial support for multiple Jitsi platforms 2024-08-17 10:01:48 +03:00
Yasen Pramatarov 8ca9643fc2 Fixes install script 2024-08-16 09:35:21 +03:00
Yasen Pramatarov b02d72b29d Adds app root in links 2024-08-15 10:31:26 +03:00
Yasen Pramatarov e84c880289 Apache fixes 2024-08-14 10:11:36 +03:00
Yasen Pramatarov 1df19d9609 Updates CHANGELOG 2024-08-13 23:01:31 +03:00
Yasen Pramatarov a6cf2fe99b Updates config file 2024-08-13 22:59:11 +03:00
Yasen Pramatarov f796e100f8 Fixes config rendering of nested config arrays 2024-08-13 22:46:56 +03:00
Yasen Pramatarov 8eed6afd46 Fixes directory structure 2024-08-13 17:53:52 +03:00
Yasen Pramatarov 2cdfbe6e86 Updates config file 2024-08-13 14:24:56 +03:00
Yasen Pramatarov 9d3a4557c1 Updates README to match new folder structure 2024-08-13 14:16:15 +03:00
Yasen Pramatarov acd5ff0a23 Separates app code from public files. 2024-08-12 14:12:24 +03:00
Yasen Pramatarov 9a3024cfa6 Fixes error in database selection in helper function 2024-08-11 13:23:06 +03:00
Yasen Pramatarov 20d9dc0e73 Removes old unused cookie code 2024-08-11 13:15:18 +03:00
Yasen Pramatarov 8e123cf5f2 Adds time range helper function 2024-08-11 13:13:59 +03:00
Yasen Pramatarov 8765fe22c0 Updates CHANGELOG 2024-08-10 21:59:15 +03:00
Yasen Pramatarov e6d6a10795 Fixes error in database selection 2024-08-10 21:57:41 +03:00
Yasen Pramatarov aeb837fee5 Adds database helper functions 2024-08-10 21:42:44 +03:00
Yasen Pramatarov 91aac20998 Fixes error in JICOFO conferences stats 2024-08-09 12:17:05 +03:00
Yasen Pramatarov c915bcba9e Monthly widget fixes 2024-08-09 11:53:58 +03:00
Yasen Pramatarov 0f868f07e2 monthly stats widget fixes 2024-08-08 11:49:10 +03:00
Yasen Pramatarov 3943ba49b6 Fixes sidebar collapsing 2024-08-07 17:41:56 +03:00
Yasen Pramatarov 8a4816a6ae Updates CHANGELOG 2024-08-07 12:08:29 +03:00
Yasen Pramatarov 4b783a6b63 Adds sidebar icons 2024-08-07 12:03:54 +03:00
Yasen Pramatarov 6191d44944 HTML fixes 2024-08-07 11:16:54 +03:00
Yasen Pramatarov 838b8ea9f2 HTML fixes 2024-08-07 11:13:55 +03:00
Yasen Pramatarov 15c97859a4 Cleans up login and register pages. 2024-08-06 10:40:52 +03:00
Yasen Pramatarov 6199dad2c6 Design fixes 2024-08-06 09:56:27 +03:00
Yasen Pramatarov cb040ee408 Fixes logo 2024-08-06 09:32:14 +03:00
Yasen Pramatarov 2aaf2ac03f Adds logo 2024-08-05 22:25:32 +03:00
Yasen Pramatarov 27304513d8 Fixes sliding sidebar 2024-08-05 22:24:35 +03:00
Yasen Pramatarov 0cfa9d811e Fixes sidebar sliding 2024-08-05 20:21:34 +03:00
Yasen Pramatarov 54b998a1a8 Fixes sidebar sliding 2024-08-05 19:53:24 +03:00
Yasen Pramatarov 77e674bab5 Fixes sidebar 2024-08-05 17:08:13 +03:00
Yasen Pramatarov b6fc4a995d Fixes sidebar sliding 2024-08-05 17:02:35 +03:00
Yasen Pramatarov ca95f3c27e Fixes sidebar sliding 2024-08-04 16:33:41 +03:00
Yasen Pramatarov 22e502c90c Fixes sidebar 2024-08-03 16:25:42 +03:00
Yasen Pramatarov ae7929b4e1 Initial version of sidebar 2024-08-02 16:07:50 +03:00
Yasen Pramatarov 80454de3f2 UpdateS CHANGELOG 2024-08-01 11:24:44 +03:00
Yasen Pramatarov 8455a98ec5 Searches for default config file 2024-08-01 11:20:31 +03:00
Yasen Pramatarov 5d240b1dd8 Adds login and registration messages and configs 2024-08-01 11:12:54 +03:00
Yasen Pramatarov 06ffde67f4 Fixes SQL 2024-08-01 10:52:39 +03:00
Yasen Pramatarov 8bbe4b6274 Fixes SQL 2024-08-01 10:18:33 +03:00
Yasen Pramatarov 001e6c5be7 Updates CHANGELOG 2024-07-31 21:55:26 +03:00
Yasen Pramatarov 55b53ef30a Adds participants to monthly stats widget 2024-07-31 21:54:30 +03:00
Yasen Pramatarov c011321403 Fixes SQL 2024-07-31 13:18:45 +03:00
Yasen Pramatarov 42f9738128 Adds widget for monthly conferences number 2024-07-30 12:54:29 +03:00
Yasen Pramatarov c0c072884f Cleans up html code from models and controllers. 2024-07-29 16:50:16 +03:00
Yasen Pramatarov 54ddc6577e Adds widgets to participants page 2024-07-29 16:38:44 +03:00
Yasen Pramatarov d9aaf97b81 HTML fixes 2024-07-29 16:10:35 +03:00
Yasen Pramatarov f76fd03794 Adds widgets to conferences page 2024-07-29 15:56:08 +03:00
Yasen Pramatarov 3e0729c6cc Adds widget to components page 2024-07-28 13:35:09 +03:00
Yasen Pramatarov 16b7627b60 Fixes front page widgets 2024-07-27 12:36:49 +03:00
Yasen Pramatarov c6e52df33a Updates CHANGELOG 2024-07-27 12:30:16 +03:00
Yasen Pramatarov 7d459cf508 Implements front page widgets 2024-07-27 12:29:28 +03:00
Yasen Pramatarov 350ba053e8 Initial version of widgets 2024-07-26 11:55:58 +03:00
Yasen Pramatarov efd7cd74a2 Updates README 2024-07-25 20:03:11 +03:00
Yasen Pramatarov c39e53906f Updates README 2024-07-25 19:58:14 +03:00
Yasen Pramatarov 5463ea0cee Updates licenses information. 2024-07-25 19:51:05 +03:00
Yasen Pramatarov ba9d14f5d1 Added jquery license 2024-07-25 19:49:10 +03:00
Yasen Pramatarov 793600ccb9 Prepares for release 0.1.1 2024-07-25 19:10:53 +03:00
Yasen Pramatarov ac42b6f591 Cleans up bootstrap CSS and front page collapsing wodgets 2024-07-25 16:28:51 +03:00
Yasen Pramatarov fa581f8fb7 Cleans up HTML 2024-07-25 15:13:12 +03:00
Yasen Pramatarov 54678bc329 Cleaning up HTML 2024-07-25 12:24:07 +03:00
Yasen Pramatarov 6400854316 Cleaning up HTML 2024-07-25 12:15:53 +03:00
Yasen Pramatarov 462e7a45b8 Cleaning up HTML 2024-07-25 10:43:23 +03:00
Yasen Pramatarov fa61eecb15 Adds Bootstrap 2024-07-24 17:08:13 +03:00
Yasen Pramatarov 2eb18f0e84 Install script fixes 2024-07-24 16:50:48 +03:00
Yasen Pramatarov d8a756e68a Install script fixes 2024-07-24 16:46:20 +03:00
Yasen Pramatarov e32815dde0 Version var fix 2024-07-23 15:36:24 +03:00
Yasen Pramatarov 825b84ddc8 Install script fixes 2024-07-22 15:33:42 +03:00
Yasen Pramatarov 13a2f2482a Updates CHANGELOG 2024-07-21 11:29:00 +03:00
Yasen Pramatarov acb314f9fe Install script fixes 2024-07-20 11:27:15 +03:00
Yasen Pramatarov 01a497a780 Install script fixes 2024-07-19 11:24:38 +03:00
Yasen Pramatarov cd83494654 Install script fixes 2024-07-19 11:20:08 +03:00
Yasen Pramatarov 6596e97889 Install script fixes 2024-07-19 11:14:30 +03:00
Yasen Pramatarov 7e5e68dc23 Install script fixes 2024-07-18 20:00:05 +03:00
Yasen Pramatarov 08f69dcf05 Install script fixes 2024-07-17 19:51:49 +03:00
Yasen Pramatarov b9d7ac69f5 packaging fixes 2024-07-16 19:40:24 +03:00
Yasen Pramatarov 05672680e4 packaginf fixes 2024-07-15 19:37:53 +03:00
Yasen Pramatarov d5a628a200 RPM packaging fixes 2024-07-14 15:00:53 +03:00
Yasen Pramatarov 714800b5c0 Adds initial RPM build script 2024-07-13 14:49:31 +03:00
Yasen Pramatarov fefba65e25 RPM initial files 2024-07-12 08:19:41 +03:00
Yasen Pramatarov dc5d074690 DEB packaging fixes 2024-07-11 14:24:45 +03:00
Yasen Pramatarov 91fdba99b2 Updates README 2024-07-10 19:07:31 +03:00
Yasen Pramatarov 4a9ee48705 Updates README 2024-07-10 19:04:50 +03:00
Yasen Pramatarov a4e644261a Update README.md 2024-07-10 13:34:26 +00:00
Yasen Pramatarov 38e15c8cf6 Adds conference duration to front page widgets 2024-07-10 15:55:16 +03:00
Yasen Pramatarov 2496d3fc93 Adds conference duration calculation 2024-07-10 15:52:13 +03:00
Yasen Pramatarov e85e9c577f Packaging fixes 2024-07-10 13:58:44 +03:00
Yasen Pramatarov 3b3a5c205a Initial deb packaging 2024-07-09 13:51:03 +03:00
Yasen Pramatarov 18e215c92f Updates CHANGELOG 2024-07-08 20:37:15 +03:00
133 changed files with 6714 additions and 2179 deletions

View File

@ -7,10 +7,109 @@ All notable changes to this project will be documented in this file.
## Unreleased ## Unreleased
#### Links #### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web - upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.2.1...HEAD
- codeberg: https://codeberg.org/lindeas/jilo-web - codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.2.1...HEAD
- github: https://github.com/lindeas/jilo-web - github: https://github.com/lindeas/jilo-web/compare/v0.2.1...HEAD
- gitlab: https://gitlab.com/lindeas/jilo-web - gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.2.1...HEAD
### Added
- Added Jilo Server check and notice on error
- Added status page
---
## 0.2.1 - 2024-10-17
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.2...v0.2.1
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.2...v0.2.1
- github: https://github.com/lindeas/jilo-web/compare/v0.2...v0.2.1
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.2...v0.2.1
### Added
- Added support for managing Jilo Agents
- Authenticating to Jilo Agents with JWT tokens with a shared secret key
- Added Jilo Agent functionality to fetch data, cache it and manage the cache
- Added more fields and avatar image to user profile
- Added pagination (with ellipses) for the longer listings
- Added initial support for application logs
- Added help page
- Added support for graphs by Chart.js
- Added "graphs" section in sidebar with graphs and latest data pages
### Changed
- Jitsi platforms config moved from file to SQLite database
- Left sidebar menu items reordered
### Fixed
- All output HTML sanitized
- Sanitized input forms data
- Fixed error in calculation of monthly total conferences on front page
---
## 0.2 - 2024-08-31
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.1.1...v0.2
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.1.1...v0.2
- github: https://github.com/lindeas/jilo-web/compare/v0.1.1...v0.2
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.1.1...v0.2
### Added
- Added collapsible front page widgets
- Added widgets to conferences, participants and components pages
- Added front page widget for monthly conferences and participants number
- Added login/registration control and messages
- Added default config file locations
- Added left collapsible sidebar
- Added logo
- Added database helper functions
- Added support for multiple Jitsi platforms
- Added app environments "production" and "development"
- Added visualisation of config.js and interface_config.js per Jitsi platform
### Changed
- MVC design - models(classes folder), views(templates folder) and controllers(pages folder)
- Changed menus - categories on sidebar menu, jitsi platforms on top menu
- Moved all the app code outside of the public web folder
- Config file now can have nested arrays
### Fixed
- Fixed SQL when conferences start and end time are not explicitly clear
- Web design fixes
- Fixed install script
---
## 0.1.1 - 2024-07-25
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/compare/v0.1...v0.1.1
- codeberg: https://codeberg.org/lindeas/jilo-web/compare/v0.1...v0.1.1
- github: https://github.com/lindeas/jilo-web/compare/v0.1...v0.1.1
- gitlab: https://gitlab.com/lindeas/jilo-web/-/compare/v0.1...v0.1.1
### Added
- Added duration calculation in conferences listing
- Added manual install script
- Added DEB and RPM build files
- Added Bootstrap (licensed under MIT)
### Changed
- Changed the layout with bootstrap CSS classes
### Fixed
---
## 0.1 - 2024-07-08
#### Links
- upstream: https://code.lindeas.com/lindeas/jilo-web/releases/tag/v0.1
- codeberg: https://codeberg.org/lindeas/jilo-web/releases/tag/v0.1
- github: https://github.com/lindeas/jilo-web/releases/tag/v0.1
- gitlab: https://gitlab.com/lindeas/jilo-web/-/releases/v0.1
### Added ### Added
- Initial version - Initial version
@ -23,9 +122,3 @@ All notable changes to this project will be documented in this file.
- Added demo installation on https://work.lindeas.com/jilo-web-demo/ - Added demo installation on https://work.lindeas.com/jilo-web-demo/
- Added participant search page - Added participant search page
- Added component events search page - Added component events search page
### Changed
### Fixed
---

View File

@ -1,25 +1,78 @@
# Jilo Web # Jilo Web
Jilo Web is a PHP web interface to Jilo (JItsi Logs Observer). ## overview
Jilo Web is a PHP web interface to **[Jilo](https://work.lindeas.com/redirect.php?url=jilo)** (JItsi Logs Observer).
To have a working installation, in addition to the code here you need a jilo database file, generated by Jilo. To have a working installation, in addition to the code here you need a jilo database file, generated by Jilo.
The webpage for this project is https://lindeas.com/jilo. There you will find information about both Jilo and Jilo Web.
The main git repo of **Jilo Web** is:
- https://code.lindeas.com/lindeas/jilo-web
It is mirrored at:
- https://codeberg.org/lindeas/jilo-web
- https://github.com/lindeas/jilo-web
- https://gitlab.com/lindeas/jilo-web
You can use any of these git repos to get the program.
You are welcome to send feedback with issues, comments and pull requests to a git mirror you prefer.
## demo ## demo
To see a demo install, go to https://work.lindeas.com/jilo-web-demo/ To see a demo install, go to https://work.lindeas.com/jilo-web-demo/
## version
Current version: **0.2.1** released on **2024-10-17**
## license
This project is licensed under the GNU General Public License version 2 (GPL-2.0). See LICENSE file.
Bootstrap is used in this project and is licensed under the MIT License. See license-bootstrap file.
JQuery is used in this project and is licensed under the MIT License. See license-jquery file.
Chart.js is used in this project and is licensed under the MIT License. See license-chartjs file.
## requirements ## requirements
- web server (deb: apache | nginx) - web server (deb: apache | nginx)
- php support in the web server (deb: php-fpm | libapache2-mod-php) - php support in the web server (deb: php-fpm | libapache2-mod-php)
- pdo and pdo_sqlite support in php (deb: php-db, php-sqlite3) uncomment in php.ini: ;extension=pdo_sqlite - pdo and pdo_sqlite support in php (deb: php-db, php-sqlite3) uncomment in php.ini: ;extension=pdo_sqlite
- php-curl module
## installation
You can install it in the following ways:
- download the latest release from the **"Releases"** section here
```bash
tar -xzf jilo-web_*.tgz
cd jilo-web/doc/
./install.sh
```
- clone the **git repo**:
```bash
git clone https://github.com/lindeas/jilo-web.git
cd jilo-web
./install.sh
```
- DEB and RPM packages are planned, but not yet available. There are experimental build scripts in "packaging" folder.
## config ## config
- edit jilo-web.conf.php and set all the variables correctly - edit jilo-web.conf.php and set all the variables correctly
- "sqlite_file" is the sqlite db file for jilo-web itself, it goes to `app/jilo-web.db`, create it with
- "database" is the sqlite db file for jilo-web itself, create it with `cat jilo-web.schema | sqlite3 jilo-web.db` ```bash
cd app
cat ../doc/jilo-web.schema | sqlite3 jilo-web.db
```
- "jilo_database" is the sqlite db file for jilo, with data from the Jitsi logs - "jilo_database" is the sqlite db file for jilo, with data from the Jitsi logs
## database
The database is an SQLite file. You also need the sqlite db from jilo (mysql/mariadb support is planned, but not yet available).

27
TODO.md
View File

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

View File

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

View File

@ -0,0 +1,69 @@
<?php
class Component {
private $db;
public function __construct($database) {
$this->db = $database->getConnection();
}
// list of component events
public function jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time, $offset=0, $items_per_page='') {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
$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);
}
}
?>

View File

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

View File

@ -0,0 +1,91 @@
<?php
class Config {
// loading the config.js
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;
}
// loading the interface_config.js
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;
}
}
?>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,40 @@
<?php
return [
//*******************
// edit to customize
//*******************
// domain for the web app
'domain' => 'localhost',
// subfolder for the web app, if any
'folder' => '/jilo-web/',
// set to false to disable new registrations
'registration_enabled' => true,
// will be displayed on login screen
'login_message' => '',
//*******************************************
// edit only if needed for tests or debugging
//*******************************************
// database
'db' => [
// DB type for the web app, currently only "sqlite" is used
'db_type' => 'sqlite',
// default is ../app/jilo-web.db
'sqlite_file' => '../app/jilo-web.db',
],
// avatars path
'avatars_path' => 'uploads/avatars/',
// default avatar
'default_avatar' => 'static/default_avatar.png',
// system info
'version' => '0.2.1',
// development has verbose error messages, production has not
'environment' => 'development',
];
?>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,39 @@
<?php
// render config variables array
function renderConfig($configPart, $indent, $platform=false, $parent='') {
global $app_root;
global $config;
?>
<div style="padding-left: <?= $indent ?>px; padding-bottom: 20px;">
<?php foreach ($configPart as $config_item => $config_value) { ?>
<div class="row mb-1" style="padding-left: <?= $indent ?>px;">
<div class="col-md-4 text-end">
<?= htmlspecialchars($config_item) ?>:
</div>
<?php
if (is_array($config_value)) {
// here we render recursively nested arrays
$indent = $indent + 50;
if ($parent === 'platforms') {
$indent = 100;
}
if ($config_item === 'platforms') {
renderConfig($config_value, $indent, $platform, 'platforms');
} else {
renderConfig($config_value, $indent, $platform);
}
$indent = 0;
} else {
// if it's not array, just display it
?>
<div class="border col-md-8 text-start">
<?= htmlspecialchars($config_value ?? '')?>
</div>
<?php } ?>
</div>
<?php } ?>
</div>
<?php
}
?>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,85 @@
<?php
require '../app/classes/component.php';
// connect to database
$db = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
// specify time range
include '../app/helpers/time_range.php';
// jitsi component events list
// we use $_REQUEST, so that both links and forms work
// if it's there, but empty, we make it same as the field name; otherwise assign the value
$jitsi_component = !empty($_REQUEST['name']) ? "'" . $_REQUEST['name'] . "'" : 'jitsi_component';
$component_id = !empty($_REQUEST['id']) ? "'" . $_REQUEST['id'] . "'" : 'component_id';
$event_type = !empty($_REQUEST['event']) ? "'" . $_REQUEST['event'] . "'" : 'event_type';
//
// Component events listings
//
// list of all component events (default)
$componentObject = new Component($db);
// pagination variables
$items_per_page = 15;
$browse_page = $_REQUEST['p'] ?? 1;
$browse_page = (int)$browse_page;
$offset = ($browse_page -1) * $items_per_page;
// prepare the result
$search = $componentObject->jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time, $offset, $items_per_page);
$search_all = $componentObject->jitsiComponents($jitsi_component, $component_id, $event_type, $from_time, $until_time);
if (!empty($search)) {
// we get total items and number of pages
$item_count = count($search_all);
$page_count = ceil($item_count / $items_per_page);
$components = array();
$components['records'] = array();
foreach ($search as $item) {
extract($item);
$component_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'loglevel' => $loglevel,
'time' => $time,
'component ID' => $component_id,
'event' => $event_type,
'param' => $event_param,
);
// populate the result array
array_push($components['records'], $component_record);
}
}
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'AllComponents';
$widget['filter'] = true;
$widget['pagination'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
$widget['title'] = 'Jitsi events for component&nbsp;<strong>' . $_REQUEST['name'] . '</strong>';
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$widget['title'] = 'Jitsi events for component ID&nbsp;<strong>' . $_REQUEST['id'] . '</strong>';
} else {
$widget['title'] = 'Jitsi events for&nbsp;<strong>all components</strong>';
}
// widget records
if (!empty($components['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($components['records'][0]);
$widget['table_records'] = $components['records'];
}
// display the widget
include '../app/templates/event-list-components.php';
?>

View File

@ -0,0 +1,149 @@
<?php
require '../app/classes/conference.php';
// connect to database
$db = connectDB($config, 'jilo', $platformDetails[0]['jilo_database'], $platform_id);
// specify time range
include '../app/helpers/time_range.php';
// conference id/name are specified when searching specific conference(s)
// we use $_REQUEST, so that both links and forms work
// if it's there, but empty, we make it same as the field name; otherwise assign the value
//$conferenceName = !empty($_REQUEST['name']) ? "'" . $_REQUEST['name'] . "'" : 'conference_name';
//$conferenceId = !empty($_REQUEST['id']) ? "'" . $_REQUEST['id'] . "'" : 'conference_id';
if (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$conferenceId = $_REQUEST['id'];
unset($_REQUEST['name']);
unset($conferenceName);
} elseif (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
unset($conferenceId);
$conferenceName = $_REQUEST['name'];
} else {
unset($conferenceId);
unset($conferenceName);
}
//
// Conference listings
//
$conferenceObject = new Conference($db);
// pagination variables
$items_per_page = 15;
$browse_page = $_REQUEST['p'] ?? 1;
$browse_page = (int)$browse_page;
$offset = ($browse_page -1) * $items_per_page;
// search and list specific conference ID
if (isset($conferenceId)) {
$search = $conferenceObject->conferenceById($conferenceId, $from_time, $until_time, $offset, $items_per_page);
$search_all = $conferenceObject->conferenceById($conferenceId, $from_time, $until_time);
// search and list specific conference name
} elseif (isset($conferenceName)) {
$search = $conferenceObject->conferenceByName($conferenceName, $from_time, $until_time, $offset, $items_per_page);
$search_all = $conferenceObject->conferenceByName($conferenceName, $from_time, $until_time);
// list of all conferences (default)
} else {
$search = $conferenceObject->conferencesAllFormatted($from_time, $until_time, $offset, $items_per_page);
$search_all = $conferenceObject->conferencesAllFormatted($from_time, $until_time);
}
if (!empty($search)) {
// we get total items and number of pages
$item_count = count($search_all);
$page_count = ceil($item_count / $items_per_page);
$conferences = array();
$conferences['records'] = array();
foreach ($search as $item) {
extract($item);
// we don't have duration field, so we calculate it
if (!empty($start) && !empty($end)) {
$duration = gmdate("H:i:s", abs(strtotime($end) - strtotime($start)));
} else {
$duration = '';
}
// search and list specific conference ID
if (isset($conferenceId)) {
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// search and list specific conference name
} elseif (isset($conferenceName)) {
$conference_record = array(
// assign title to the field in the array record
'time' => $time,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'conference host' => $conference_host,
'loglevel' => $loglevel,
'participant ID' => $participant_id,
'event' => $event_type,
'parameter' => $event_param
);
// list of all conferences (default)
} else {
$conference_record = array(
// assign title to the field in the array record
'component' => $jitsi_component,
'start' => $start,
'end' => $end,
'duration' => $duration,
'conference ID' => $conference_id,
'conference name' => $conference_name,
'participants' => $participants,
'name count' => $name_count,
'conference host' => $conference_host
);
}
// populate the result array
array_push($conferences['records'], $conference_record);
}
}
// prepare the widget
$widget['full'] = false;
$widget['name'] = 'Conferences';
$widget['collapsible'] = false;
$widget['collapsed'] = false;
$widget['filter'] = true;
$widget['pagination'] = true;
// widget title
if (isset($_REQUEST['name']) && $_REQUEST['name'] != '') {
$widget['title'] = 'Conferences with name matching "<strong>' . $_REQUEST['name'] . '"</strong>';
} elseif (isset($_REQUEST['id']) && $_REQUEST['id'] != '') {
$widget['title'] = 'Conference with ID "<strong>' . $_REQUEST['id'] . '"</strong>';
} else {
$widget['title'] = 'All conferences';
}
// widget records
if (!empty($conferences['records'])) {
$widget['full'] = true;
$widget['table_headers'] = array_keys($conferences['records'][0]);
$widget['table_records'] = $conferences['records'];
}
// display the widget
include '../app/templates/event-list-conferences.php';
?>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +1,19 @@
<?php <?php
require_once 'classes/database.php';
require 'classes/user.php';
// clear the global error var before login // clear the global error var before login
unset($error); unset($error);
try { try {
$db = new Database($config['database']);
$user = new User($db); // connect to database
$dbWeb = connectDB($config);
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) { if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
$username = $_POST['username']; $username = $_POST['username'];
$password = $_POST['password']; $password = $_POST['password'];
// login successful // login successful
if ( $user->login($username, $password) ) { if ( $userObject->login($username, $password) ) {
// if remember_me is checked, max out the session // if remember_me is checked, max out the session
if (isset($_POST['remember_me'])) { if (isset($_POST['remember_me'])) {
// 30*24*60*60 = 30 days // 30*24*60*60 = 30 days
@ -31,18 +29,6 @@ try {
} }
// set session lifetime and cookies // set session lifetime and cookies
// FIXME: need to set this before session start (otherwise we need the separate cookie)
// ini_set('session.gc_maxlifetime', $gc_maxlifetime);
// session_set_cookie_params([
// 'lifetime' => $setcookie_lifetime,
// 'samesite' => 'Strict',
// 'httponly' => true,
// 'secure' => isset($_SERVER['HTTPS']),
// 'domain' => $config['domain'],
// 'path' => $config['folder']
// ]);
// session_start();
// FIXME we use separate cookie, because the above won't work
setcookie('username', $username, [ setcookie('username', $username, [
'expires' => $setcookie_lifetime, 'expires' => $setcookie_lifetime,
'path' => $config['folder'], 'path' => $config['folder'],
@ -54,20 +40,29 @@ try {
// redirect to index // redirect to index
$_SESSION['notice'] = "Login successful"; $_SESSION['notice'] = "Login successful";
header('Location: index.php'); $user_id = $userObject->getUserId($username)[0]['id'];
$logObject->insertLog($user_id, "Login: User \"$username\" logged in. IP: $user_IP", 'user');
header('Location: ' . htmlspecialchars($app_root));
exit(); exit();
// login failed // login failed
} else { } else {
$_SESSION['error'] = "Login failed."; $_SESSION['error'] = "Login failed.";
header('Location: index.php'); $user_id = $userObject->getUserId($username)[0]['id'];
$logObject->insertLog($user_id, "Login: Failed login attempt for user \"$username\". IP: $user_IP", 'user');
header('Location: ' . htmlspecialchars($app_root));
exit(); exit();
} }
} }
} catch (Exception $e) { } catch (Exception $e) {
$error = $e->getMessage(); $error = getError('There was an unexpected error. Please try again.', $e->getMessage());
} }
include 'templates/form-login.php'; if (!empty($config['login_message'])) {
$notice = $config['login_message'];
include '../app/templates/block-message.php';
}
include '../app/templates/form-login.php';
?> ?>

71
app/pages/logs.php 100644
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,29 @@
<!-- Results filter -->
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
<form method="POST" id="filter_form" 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="ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
<input type="text" name="name" placeholder="name"<?php if (isset($_REQUEST['name'])) echo " value=\"" . htmlspecialchars($_REQUEST['name']) . "\"" ?> />
<?php if ($page == 'participants') { ?>
<input type="text" name="ip" placeholder="ip address"<?php if (isset($_REQUEST['ip'])) echo " value=\"" . htmlspecialchars($_REQUEST['ip']) . "\"" ?> maxlength="15" size="15" />
<?php } ?>
<input type="button" onclick="clearFilter()" value="clear" />
<input type="submit" value="search" />
</form>
<script>
function clearFilter() {
document.getElementById("filter_form").reset();
const filterFields = document.querySelectorAll("#filter_form input");
filterFields.forEach(input => {
if (input.type === 'text' ||input.type === 'date') {
input.value = '';
}
});
}
</script>
</div>
<!-- /Results filter -->

View File

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

View File

@ -0,0 +1,50 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Add new Jitsi platform</p>
<div class="card-body">
<!--p class="card-text">add new platform:</p-->
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<div class="row mb-3">
<div class="col-md-4 text-end">
<label for="name" class="form-label">name</label>
<span class="text-danger" style="margin-right: -12px;">*</span>
</div>
<div class="col-md-8">
<input class="form-control" type="text" name="name" value="" required 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-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>
</div>
<!-- /widget "config" -->

View File

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

View File

@ -0,0 +1,32 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Jilo web configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
<div class="card-body">
<p class="card-text">delete a platform:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<?php
foreach ($platformDetails[0] as $key => $value) {
if ($key === 'id') continue;
?>
<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-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>" />Cancel</a>
<input type="submit" class="btn btn-danger" value="Delete" />
</form>
</div>
</div>
<!-- /widget "config" -->

View File

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

View File

@ -0,0 +1,36 @@
<!-- widget "config" -->
<div class="card text-center w-50 mx-auto">
<p class="h4 card-header">Jilo web configuration for Jitsi platform <strong>"<?= htmlspecialchars($platformDetails[0]['name']) ?>"</strong></p>
<div class="card-body">
<p class="card-text">edit the platform details:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config">
<?php
foreach ($platformDetails[0] as $key => $value) {
if ($key === 'id') continue;
?>
<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-secondary" href="<?= htmlspecialchars($app_root) ?>?page=config#platform<?= htmlspecialchars($platform_id) ?>" />Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,22 @@
<!-- widget "config" -->
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails[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=config&item=configjs">view only active lines</a></span>
<?php } else { ?>
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs&mode=raw">view raw file contents</a></span>
<?php } ?>
</p>
<pre class="results">
<?php
echo htmlspecialchars($platformConfigjs);
?>
</pre>
</div>
</div>
<!-- /widget "config" -->

View File

@ -0,0 +1,22 @@
<!-- widget "config" -->
<div class="card text-center w-75 mx-lef">
<p class="h4 card-header">Configuration of the Jitsi platform <strong><?= htmlspecialchars($platformDetails[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=config&item=interfaceconfigjs">view only active lines</a></span>
<?php } else { ?>
<span class="m-3"><a class="btn btn-light" href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs&mode=raw">view raw file contents</a></span>
<?php } ?>
</p>
<pre class="results">
<?php
echo htmlspecialchars($platformInterfaceConfigjs);
?>
</pre>
</div>
</div>
<!-- /widget "config" -->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
<!-- login form -->
<div class="card text-center w-50 mx-auto">
<h2 class="card-header">Login</h2>
<div class="card-body">
<p class="card-text"><strong>Welcome to JILO!</strong><br />Please enter login credentials:</p>
<form method="POST" action="<?= 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 />&nbsp;<br />
<input type="submit" class="btn btn-primary" value="Login" />
</form>
</div>
</div>
<!-- /login form -->

View File

@ -0,0 +1,15 @@
<!-- registration form -->
<div class="card text-center w-50 mx-auto">
<h2 class="card-header">Register</h2>
<div class="card-body">
<p class="card-text">Enter credentials for registration:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
<input type="text" name="username" placeholder="Username" required autofocus />
<br />
<input type="password" name="password" placeholder="Password" required />
<br />&nbsp;<br />
<input type="submit" class="btn btn-primary" value="Register" />
</form>
</div>
</div>
<!-- /registration form -->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<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" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css" integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
<script>
// restore sidebar state before the page is rendered
(function () {
var savedState = localStorage.getItem('sidebarState');
if (savedState === 'collapsed') {
document.documentElement.classList.add('sidebar-collapsed');
}
})();
</script>
<?php if ($page === 'agents') { ?>
<script src="<?= htmlspecialchars($app_root) ?>static/agents.js"></script>
<?php } ?>
<?php if ($page === 'graphs') { ?>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.0"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@1.2.1/dist/chartjs-plugin-zoom.min.js"></script>
<?php } ?>
<title>Jilo Web</title>
<link rel="icon" type="image/x-icon" href="<?= htmlspecialchars($app_root) ?>static/favicon.ico">
</head>
<body>

View File

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

View File

@ -0,0 +1,105 @@
<div class="row">
<!-- Sidebar -->
<div class="col-md-3 sidebar-wrapper bg-light" id="sidebar">
<div class="text-center" style="border: 1px solid #0dcaf0; height: 22px;" id="time_now">
<?php
$timeNow = new DateTime('now', new DateTimeZone($userTimezone));
?>
<!--span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('d M Y H:i')) ?> <?= htmlspecialchars($userTimezone) ?></span-->
<span style="vertical-align: top; font-size: 12px;"><?= htmlspecialchars($timeNow->format('H:i')) ?>&nbsp;&nbsp;<?= htmlspecialchars($userTimezone) ?></span>
</div>
<div class="col-4"><button class="btn btn-sm btn-info toggle-sidebar-button" type="button" id="toggleSidebarButton" value=">>"></button></div>
<div class="sidebar-content card ml-3 mt-3">
<ul class="list-group">
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=dashboard">
<li class="list-group-item<?php if ($page === 'dashboard') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-chart-line" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="general jitsi stats"></i>general stats
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>logs statistics</small></p></li>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences">
<li class="list-group-item<?php if ($page === 'conferences') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-video" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="conferences"></i>conferences
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants">
<li class="list-group-item<?php if ($page === 'participants') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-users" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="participants"></i>participants
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=components">
<li class="list-group-item<?php if ($page === 'components') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-puzzle-piece" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="components"></i>components
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>graphs</small></p></li>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=graphs">
<li class="list-group-item<?php if ($page === 'graphs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-chart-bar" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="combined graphs"></i>combined graphs
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=latest">
<li class="list-group-item<?php if ($page === 'latest') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-list" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="latest data"></i>latest data
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>live data</small></p></li>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=configjs">
<li class="list-group-item<?php if ($page === 'config' && $item === 'configjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-tv" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="config.js"></i>config.js
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=config&item=interfaceconfigjs">
<li class="list-group-item<?php if ($page === 'config' && $item === 'interfaceconfigjs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-th" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="interface_config.js"></i>interface_config.js
</li>
</a>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=agents">
<li class="list-group-item<?php if ($page === 'agents') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-mask" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="jilo agents"></i>jilo agents
</li>
</a>
<li class="list-group-item bg-light" style="border: none;"><p class="text-end mb-0"><small>system</small></p></li>
<?php if ($userObject->hasRight($user_id, 'view config file')) {?>
<a href="<?= htmlspecialchars($app_root) ?>?page=config">
<li class="list-group-item<?php if ($page === 'config' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-wrench" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>config
</li>
</a>
<?php } ?>
<a href="<?= htmlspecialchars($app_root) ?>?page=status">
<li class="list-group-item<?php if ($page === 'status' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-heartbeat" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="status"></i>status
</li>
</a>
<?php if ($userObject->hasRight($user_id, 'view app logs')) {?>
<a href="<?= htmlspecialchars($app_root) ?>?page=logs">
<li class="list-group-item<?php if ($page === 'logs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-shoe-prints" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="logs"></i>logs
</li>
</a>
<?php } ?>
<a href="<?= htmlspecialchars($app_root) ?>?page=help">
<li class="list-group-item<?php if ($page === 'help') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-question-circle" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="help"></i>help
</li>
</a>
</ul>
</div>
</div>
<!-- /Sidebar -->
<!-- Main content -->
<div class="col-md-9 main-content" id="mainContent">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,60 @@
<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;"><?= $widget['title'] ?></div>
<?php } else { ?>
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<?php } ?>
<?php if ($widget['filter'] === true) {
include '../app/templates/block-results-filter.php'; } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a>
<?php } ?>
</div>
<!-- widget "<?= 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>
<th scope="col"></th>
<?php foreach ($widget['records'] as $record) { ?>
<th scope="col"><?= htmlspecialchars($record['table_headers']) ?></th>
<?php } ?>
</tr>
</thead>
<tbody>
<tr>
<td>conferences</td>
<?php foreach ($widget['records'] as $record) { ?>
<td><?php if (!empty($record['conferences'])) { ?>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=conferences&from_time=<?= htmlspecialchars($record['from_time']) ?>&until_time=<?= htmlspecialchars($record['until_time']) ?>"><?= htmlspecialchars($record['conferences']) ?></a> <?php } else { ?>
0<?php } ?>
</td>
<?php } ?>
</tr>
<tr>
<td>participants</td>
<?php foreach ($widget['records'] as $record) { ?>
<td><?php if (!empty($record['participants'])) { ?>
<a href="<?= htmlspecialchars($app_root) ?>?platform=<?= htmlspecialchars($platform_id) ?>&page=participants&from_time=<?= htmlspecialchars($record['from_time']) ?>&until_time=<?= htmlspecialchars($record['until_time']) ?>"><?= htmlspecialchars($record['participants']) ?></a> <?php } else { ?>
0<?php } ?>
</td>
<?php } ?>
</tr>
</tbody>
</table>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->

View File

@ -0,0 +1,86 @@
<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;"><?= $widget['title'] ?></div>
<?php } else { ?>
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= $widget['title'] ?></div>
<?php } ?>
<?php if ($widget['filter'] === true) {
include '../app/templates/block-results-filter.php'; } ?>
<?php if ($widget['collapsible'] === true) { ?>
</a>
<?php } ?>
</div>
<!-- widget "<?= 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' && 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
// in general listings we don't show seconds and miliseconds
} elseif ($key === 'start' || $key === 'end') { ?>
<td><?= htmlspecialchars(substr($column ?? '', 0, -7)) ?></td>
<?php } else { ?>
<td><?= htmlspecialchars($column ?? '') ?></td>
<?php }
} ?>
</tr>
<?php } ?>
</tbody>
</table>
<?php
if ($widget['pagination'] && $item_count > $items_per_page) {
$url = "$app_root?platform=$platform_id&page=$page";
include '../app/helpers/pagination.php';
}
?>
<?php } else { ?>
<p class="m-3">No matching records found.</p>
<?php } ?>
</div>
</div>
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->

View File

14
doc/.htaccess 100644
View File

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

12
doc/config.apache 100644
View File

@ -0,0 +1,12 @@
<VirtualHost *:80>
ServerName $DOMAIN
DocumentRoot $INSTALL_DIR
CustomLog \${APACHE_LOG_DIR}/jilo-web_access.log combined
ErrorLog \${APACHE_LOG_DIR}/jilo-web_error.log
<Directory $INSTALL_DIR>
AllowOverride All
</Directory>
</VirtualHost>

22
doc/config.nginx 100644
View File

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

144
doc/install.sh 100755
View File

@ -0,0 +1,144 @@
#!/usr/bin/env bash
# first we check the privileges, we need root or sudo
if [ "$EUID" -ne 0 ] && [ -z "$SUDO_USER" ]; then
echo "This script needs administrative privileges. Either run as root or with sudo."
exit 1
fi
VERSION=`grep version ../app/config/jilo-web.conf.php | cut -d "'" -f 4`
# main install function
function install() {
# enter domain
read -p "Domain name for the web service [localhost]: " DOMAIN
DOMAIN=${DOMAIN:-localhost}
# enter folder
read -p "Web subfolder [jilo-web]: " WEB_DIR
WEB_DIR=${WEB_DIR:-jilo-web}
INSTALL_DIR="/opt/jilo-web/public_html"
APP_DIR="/opt/jilo-web/app"
DOC_DIR="/opt/jilo-web/doc"
ETC_DIR="/opt/jilo-web/etc"
mkdir -p $INSTALL_DIR
cp -r ../public_html/* $INSTALL_DIR
mkdir -p $DOC_DIR
cp ../CHANGELOG.md $DOC_DIR
cp ../LICENSE $DOC_DIR
cp ../README.md $DOC_DIR
cp ../TODO.md $DOC_DIR
cp ../license-bootstrap $DOC_DIR
cp ../license-jquery $DOC_DIR
cp -r ../doc/ $DOC_DIR
mkdir -p $ETC_DIR
cp ../app/config/jilo-web.conf.php $ETC_DIR
#FIXME
#mkdir -p "jilo-web-$VERSION/usr/share/man/man8"
#cp ../man-jilo-web.8 "jilo-web-$VERSION/usr/share/man/man8/jilo-web.8"
# we need a webserver, check for Apache and Nginx
WEB_SERVER=""
# install and enable apache vhost
if dpkg-query -W -f='${status}' apache2 2>/dev/null | grep -q "ok installed"; then
WEB_SERVER="apache2"
cp "${DOC_DIR}/config.apache" /etc/apache2/sites-available/jilo-web.conf
sed -i -e "s/\$DOMAIN/$DOMAIN/g" /etc/apache2/sites-available/jilo-web.conf
# there is '/' in INSTALL_DIR, we use '%'
sed -i -e "s%\$INSTALL_DIR%$INSTALL_DIR%g" /etc/apache2/sites-available/jilo-web.conf
a2ensite jilo-web.conf
/etc/init.d/apache2 reload
# install and enable nginx vhost
elif dpkg-query -W -f='${status}' nginx 2>/dev/null | grep -q "ok installed"; then
WEB_SERVER="nginx"
cp "${DOC_DIR}/config.nginx" /etc/nginx/sites-available/jilo-web
sed -i -e "s/\$DOMAIN/$DOMAIN/g" /etc/nginx/sites-available/jilo-web
# there is '/' in INSTALL_DIR, we use '%'
sed -i -e "s%\$INSTALL_DIR%$INSTALL_DIR%g" /etc/nginx/sites-available/jilo-web
ln -s /etc/nginx/sites-available/jilo-web /etc/nginx/sites-enabled/
/etc/init.d/nginx reload
else
echo "Nginx or Apache is needed, please install one of them first."
exit 1
fi
# permissions for web folder
chown -R www-data:www-data "$INSTALL_DIR"
chmod -R ug+rw "$INSTALL_DIR"
# etc configs (we use % because the variables contain paths with "/")
sed -i -e "s%\('domain'[[:space:]]*=>[[:space:]]*'\)[^']*\('.*\)%\1$DOMAIN\2%g" "$ETC_DIR/jilo-web.conf.php"
sed -i -e "s%\('folder'[[:space:]]*=>[[:space:]]*'\)[^']*\('.*\)%\1$WEB_DIR\2%g" "$ETC_DIR/jilo-web.conf.php"
sed -i -e "s%\('database'[[:space:]]*=>[[:space:]]*'\)[^']*\('.*\)%\1$ETC_DIR\/jilo-web.db\2%g" "$ETC_DIR/jilo-web.conf.php"
sed -i -e "s%\('jilo_database'[[:space:]]*=>[[:space:]]*'\)[^']*\('.*\)%\1$ETC_DIR\/jilo.db\2%g" "$ETC_DIR/jilo-web.conf.php"
echo 'Install finished.'
}
help="Jilo-Web deployment script
Usage:
$0 [OPTION]
Options:
--install|-i - install Jilo-Web
--help|-h - show this help message
--version|-v - show version"
version="version $VERSION"
# called with an option
if [[ $1 ]]; then
case $1 in
-i | --install)
install
exit 0
;;
-h | --help)
echo -e "$help"
exit 0
;;
-v | --version)
echo -e "$version"
exit 0
;;
*)
echo "Invalid option: $1" >&2
echo -e "$help"
exit 1
;;
esac
# called without any options, ask how to proceed
else
read -p "Choose an option, blank for \"install\" [install | help | version]: " OPTION
OPTION=${OPTION:-install}
case $OPTION in
install)
install
exit 0
;;
help)
echo -e "$help"
exit 0
;;
version)
echo -e "$version"
exit 0
;;
*)
echo "Invalid option: $1" >&2
echo -e "$help"
exit 1
;;
esac
fi

View File

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

View File

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

View File

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

View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
VERSION=$1
mkdir -p deb-package
cd deb-package
mkdir -p "jilo-web-$VERSION/DEBIAN"
cp ../deb-control "jilo-web-$VERSION/DEBIAN/control"
cp ../deb-postinst "jilo-web-$VERSION/DEBIAN/postinst"
cp ../deb-postrm "jilo-web-$VERSION/DEBIAN/postrm"
cp ../deb-templates "jilo-web-$VERSION/DEBIAN/templates"
mkdir -p "jilo-web-$VERSION/etc/jilo-web"
cp ../../jilo-web.conf.php "jilo-web-$VERSION/etc/jilo-web/jilo-web.conf.php"
cp ../../jilo-web.schema "jilo-web-$VERSION/etc/jilo-web/jilo-web.schema"
mkdir -p "jilo-web-$VERSION/usr/share/jilo-web"
cp -r ../../public_html/* "jilo-web-$VERSION/usr/share/jilo-web"
mkdir -p "jilo-web-$VERSION/usr/share/doc/jilo-web"
cp ../deb-README.debian "jilo-web-$VERSION/usr/share/doc/jilo-web/"
cp ../../CHANGELOG.md "jilo-web-$VERSION/usr/share/doc/jilo-web/"
cp ../../LICENSE "jilo-web-$VERSION/usr/share/doc/jilo-web/"
cp ../../README.md "jilo-web-$VERSION/usr/share/doc/jilo-web/"
cp ../../TODO.md "jilo-web-$VERSION/usr/share/doc/jilo-web/"
cp ../../config.apache "jilo-web-$VERSION/usr/share/doc/jilo-web/"
cp ../../config.nginx "jilo-web-$VERSION/usr/share/doc/jilo-web/"
mkdir -p "jilo-web-$VERSION/usr/share/man/man8"
cp ../man-jilo-web.8 "jilo-web-$VERSION/usr/share/man/man8/jilo-web.8"
dpkg-deb --build "jilo-web-$VERSION"
rm -rf "jilo-web-$VERSION/"

View File

@ -0,0 +1,31 @@
#!/usr/bin/env bash
VERSION=$1
RELEASE=1
mkdir -p rpm-package/SOURCES rpm-package/SPECS
cd rpm-package
PACKAGE_DIR="jilo-web-$VERSION"
mkdir -p "$PACKAGE_DIR"
mkdir -p "$PACKAGE_DIR/public_html"
cp ../rpm-jilo-web.spec SPECS/jilo-web.spec
cp ../../jilo-web.conf.php "$PACKAGE_DIR/"
cp ../../jilo-web.schema "$PACKAGE_DIR/"
cp -r ../../public_html/* "$PACKAGE_DIR/public_html/"
cp ../../CHANGELOG.md "$PACKAGE_DIR/"
cp ../../LICENSE "$PACKAGE_DIR/"
cp ../../README.md "$PACKAGE_DIR/"
cp ../../TODO.md "$PACKAGE_DIR/"
cp ../../config.apache "$PACKAGE_DIR/"
cp ../../config.nginx "$PACKAGE_DIR/"
cp ../man-jilo-web.8 "$PACKAGE_DIR/"
tar -czf "jilo-web-$VERSION.tar.gz" "$PACKAGE_DIR"
mv "jilo-web-$VERSION.tar.gz" SOURCES/
rpmbuild --define "_topdir $(pwd)" --define "VERSION $VERSION" --define "RELEASE $RELEASE" -ba SPECS/jilo-web.spec
rm -rf "$PACKAGE_DIR"

View File

@ -0,0 +1,7 @@
Jilo-Web for Debian
-------------------
This is a PHP frontend to Jilo (JItsi Logs Observer). It needs access to
the database, generated by the 'jilo' program.
-- Yasen Pramatarov <Yasen@lindeas.com> Tue, 9 Jul 2024 19:02:00 +0300

View File

@ -0,0 +1,11 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
db_input medium jilo-web/domain || true
db_input medium jilo-web/folder || true
db_go
exit 0

View File

@ -0,0 +1,12 @@
Package: jilo-web
Version: 0.2.1
Section: web
Priority: optional
Architecture: all
Maintainer: Yasen Pramatarov <yasen@lindeas.com>
Description: Jitsi logs web observer
PHP web interface to Jilo (JItsi Logs Observer)
To use this you need a webserver with php and sqlite support, and you need a database previously
generated by 'jilo'
Homepage: https://lindeas.com/jilo
Depends: nginx|apache2, php, php-db, php-sqlite3, debconf (>=0.5) | debconf-2.0

View File

@ -0,0 +1,90 @@
#!/bin/bash
# postinst script for jilo-web
#
# see: dh_installdeb(1)
set -e
case "$1" in
configure)
# loading debconf
. /usr/share/debconf/confmodule
# get the domain and web folder
db_get jilo-web/domain
if [ -z "$RET" ] ; then
db_get jilo-web/domain "localhost"
db_input critical jilo-web/domain || true
db_go
db_get jilo-web/domain
fi
DOMAIN=$(echo "$RET" | xargs echo -n)
# FIXME use this for subfolder install, if given
db_get jilo-web/folder
if [ -z "$RET" ] ; then
db_get jilo-web/folder "jilo-web"
db_input critical jilo-web/folder || true
db_go
db_get jilo-web/folder
fi
WEB_DIR=$(echo "$RET" | xargs echo -n)
INSTALL_DIR="/usr/share/jilo-web"
DOC_DIR="/usr/share/doc/jilo-web"
# store the info for later reconfiguration
db_set jilo-web/domain $DOMAIN
db_set jilo-web/folder $WEB_DIR
# we need a webserver, check for Apache and Nginx
WEB_SERVER=""
# install and enable apache vhost
if dpkg-query -W -f='${status}' apache2 2>/dev/null | grep -q "ok installed"; then
WEB_SERVER="apache2"
cp "${DOC_DIR}/config.apache" /etc/apache2/sites-available/jilo-web.conf
sed -i -e "s/\$DOMAIN/$DOMAIN/g" /etc/apache2/sites-available/jilo-web.conf
# there is '/' in INSTALL_DIR, we use '%'
sed -i -e "s%\$INSTALL_DIR%$INSTALL_DIR%g" /etc/apache2/sites-available/jilo-web.conf
a2ensite jilo-web.conf
/etc/init.d/apache2 reload
# install and enable nginx vhost
elif dpkg-query -W -f='${status}' nginx 2>/dev/null | grep -q "ok installed"; then
WEB_SERVER="nginx"
cp "${DOC_DIR}/config.nginx" /etc/nginx/sites-available/jilo-web
sed -i -e "s/\$DOMAIN/$DOMAIN/g" /etc/nginx/sites-available/jilo-web
# there is '/' in INSTALL_DIR, we use '%'
sed -i -e "s%\$INSTALL_DIR%$INSTALL_DIR%g" /etc/nginx/sites-available/jilo-web
ln -s /etc/nginx/sites-available/jilo-web /etc/nginx/sites-enabled/
/etc/init.d/nginx reload
else
echo "Nginx or Apache is needed, please install one of them first."
exit 1
fi
# permissions for web folder
chown -R www-data:www-data "$INSTALL_DIR"
chmod -R ug+rw "$INSTALL_DIR"
# stopping debconf
db_stop
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

View File

@ -0,0 +1,13 @@
#!/bin/bash
# postrm script for jilo-web
#
# see: dh_installdeb(1)
set -e
rm /etc/nginx/sites-enabled/jilo-web
rm /etc/nginx/sites-available/jilo-web
echo PURGE | debconf-communicate jilo-web
exit 0

View File

@ -0,0 +1,9 @@
Template: jilo-web/domain
Type: string
Default: localhost
Description: Please enter the domain name for the Jilo-web app.
Template: jilo-web/folder
Type: string
Default: jilo-web
Description: Please enter the web folder for Jilo-web.

View File

@ -0,0 +1,23 @@
.TH JILO-WEB "8" "October 2024" "jilo-web 0.2.1"
.SH NAME
jilo-web \- PHP frontent to jilo (jitsi logs observer) database.
.SH DESCRIPTION
.B jilo-web
is a PHP app for searching through a jilo database.
You need a jilo database to have this package working. You can create the jilo database with the program 'jilo'.
.SH CONFIGURATION
The configuration file is /etc/jilo-web/jilo-web.conf.php.
.SH AUTHOR
Written and maintained by Yasen Pramatarov <yasen@lindeas.com>
.SH HOMEPAGE
https://lindeas.com/jilo
.SH VERSION
0.2.1
.SH SEE ALSO
jilo(8), jilo-cli(8)

View File

@ -0,0 +1,64 @@
Name: jilo-web
Version: 0.2.1
Release: 1%{?dist}
Summary: Jitsi logs web observer
License: GPLv2
URL: https://lindeas.com/jilo
Source0: %{name}-%{version}.tar.gz
%define sourcedir %{_builddir}/%{name}-%{version}
BuildArch: noarch
Requires: (nginx or apache2)
Requires: php
Requires: php-db
Requires: php-sqlite3
%description
PHP web interface to Jilo (JItsi Logs Observer)
To use this you need a webserver with php and sqlite support, and you need a database previously
generated by 'jilo'
%prep
%setup -q
%install
# directories
mkdir -p %{buildroot}/etc/jilo-web
mkdir -p %{buildroot}/usr/share/%{name}
mkdir -p %{buildroot}/usr/share/doc/%{name}
mkdir -p %{buildroot}/usr/share/man/man8
# then copy the files
cp %{sourcedir}/jilo-web.conf.php %{buildroot}/etc/%{name}/jilo-web.conf.php
cp %{sourcedir}/jilo-web.schema %{buildroot}/etc/%{name}/jilo-web.schema
cp %{sourcedir}/public_html/* %{buildroot}/usr/share/%{name}
cp %{sourcedir}/CHANGELOG.md %{buildroot}/usr/share/doc/%{name}/
cp %{sourcedir}/LICENSE %{buildroot}/usr/share/doc/%{name}/
cp %{sourcedir}/README.md %{buildroot}/usr/share/doc/%{name}/
cp %{sourcedir}/TODO.md %{buildroot}/usr/share/doc/%{name}/
cp %{sourcedir}/config.apache %{buildroot}/usr/share/doc/%{name}/
cp %{sourcedir}/config.nginx %{buildroot}/usr/share/doc/%{name}/
cp %{sourcedir}/man-jilo.8 %{buildroot}/usr/share/man/man8/%{name}.8
%files
/etc/jilo-web/jilo-web.conf.php
/etc/jilo-web/jilo-web.schema
/usr/share/doc/%{name}/CHANGELOG.md
/usr/share/doc/%{name}/LICENSE
/usr/share/doc/%{name}/README.md
/usr/share/doc/%{name}/TODO.md
/usr/share/doc/%{name}/config.apache
/usr/share/doc/%{name}/config.nginx
/usr/share/man/man8/%{name}.8.gz
%changelog
* Thu Oct 17 2024 Yasen Pramatarov <yasen@lindeas.com> 0.2.1
- Build of upstream v0.2.1
* Sat Aug 31 2024 Yasen Pramatarov <yasen@lindeas.com> 0.2
- Build of upstream v0.2
* Thu Jul 25 2024 Yasen Pramatarov <yasen@lindeas.com> 0.1.1
- Build of upstream v0.1.1
* Wed Jul 12 2024 Yasen Pramatarov <yasen@lindeas.com> 0.1
- Initial build

View File

@ -1,3 +0,0 @@
#!/usr/bin/env bash
echo 'todo install script'

View File

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

View File

@ -1,5 +0,0 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
);

21
license-bootstrap 100644
View File

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

21
license-chartjs 100644
View File

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

20
license-jquery 100644
View File

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

View File

@ -1,40 +0,0 @@
<?php
class Component {
private $db;
private $queries;
public function __construct($database) {
$this->db = $database->getConnection();
$this->queries = include('queries.php');
}
// list of component events
public function jitsiComponents($jitsi_component, $component_id, $from_time, $until_time) {
// time period drill-down
// FIXME make it similar to the bash version
if (empty($from_time)) {
$from_time = '0000-01-01';
}
if (empty($until_time)) {
$until_time = '9999-12-31';
}
// this is needed for compatibility with the bash version, so we use '%s' placeholders
$from_time = htmlspecialchars(strip_tags($from_time));
$until_time = htmlspecialchars(strip_tags($until_time));
$sql = $this->queries['jitsi_components'];
$sql = sprintf($sql, $jitsi_component, $component_id, $from_time, $until_time);
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
}
?>

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More