From a9d1e30fc4bb3ccb2530a729fd373b02e361f77f Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 16 Dec 2025 15:07:27 -0500 Subject: [PATCH] This seems like a good pause point. The new Jitsi reports page is functional. The meetings log various activities and events. --- src/lib/ae_api/api_get__crud_obj_li_v2.ts | 1 + src/lib/ae_reports/reports_functions.ts | 115 +++++++++++++++++++++ src/routes/idaa/jitsi_reports/+page.svelte | 105 +++++++++++++++++++ src/routes/idaa/jitsi_reports/+page.ts | 28 +++++ 4 files changed, 249 insertions(+) create mode 100644 src/lib/ae_reports/reports_functions.ts create mode 100644 src/routes/idaa/jitsi_reports/+page.svelte create mode 100644 src/routes/idaa/jitsi_reports/+page.ts diff --git a/src/lib/ae_api/api_get__crud_obj_li_v2.ts b/src/lib/ae_api/api_get__crud_obj_li_v2.ts index 8657523a..153b59ab 100644 --- a/src/lib/ae_api/api_get__crud_obj_li_v2.ts +++ b/src/lib/ae_api/api_get__crud_obj_li_v2.ts @@ -7,6 +7,7 @@ const objTypeToEndpointMap: Record = { address: '/crud/address/list', archive: '/crud/archive/list', archive_content: '/crud/archive/content/list', + activity_log: '/crud/activity_log/list', contact: '/crud/contact/list', data_store: '/crud/data_store/list', event: '/crud/event/list', diff --git a/src/lib/ae_reports/reports_functions.ts b/src/lib/ae_reports/reports_functions.ts new file mode 100644 index 00000000..265e47ae --- /dev/null +++ b/src/lib/ae_reports/reports_functions.ts @@ -0,0 +1,115 @@ +import type { key_val } from '$lib/stores/ae_stores'; +import { api } from '$lib/api/api'; + +/** + * @description Queries all Jitsi-related activity logs and processes them into a structured report, + * grouped by meeting ID. + * @param api_cfg The API configuration object. + * @param account_id The account ID to query against. + * @param log_lvl The logging level. + * @returns A structured array of meeting report objects. + */ +export async function load_jitsi_report({ + api_cfg, + account_id, + log_lvl = 0 +}: { + api_cfg: any; + account_id: string; + log_lvl?: number; +}) { + if (log_lvl) console.log('*** load_jitsi_report() ***'); + + // Step 1: Query all relevant activity logs from the API. + const params_json = { + or_qry: [ + { + field: 'name', + operator: '=', + value: 'jitsi_meeting_stats_update' + }, + { + field: 'name', + operator: '=', + value: 'jitsi_meeting_event' + } + ] + }; + + const flat_log_list = await api.get_ae_obj_li_for_obj_id_crud_v2({ + api_cfg: api_cfg, + obj_type: 'activity_log', + for_obj_type: 'account', + for_obj_id: account_id, + // use_alt_tbl: true, + // use_alt_mdl: true, + enabled: 'all', + hidden: 'all', + // order_by_li: { created_on: 'DESC' }, + limit: 500, // Fetch a reasonable number of recent logs + // order_by_li: { created_on: 'DESC' }, + // params_json: params_json, + log_lvl: 2 + }); + + if (!flat_log_list || flat_log_list.length === 0) { + if (log_lvl) console.log('No Jitsi activity logs found.'); + return []; + } + + // Step 2: Process the flat list into a structured report. + const meetings = new Map(); + + for (const log of flat_log_list) { + const meeting_id = log.external_client_id; + if (!meeting_id) continue; + + // Make sure the name field is prefixed with "jitsi_" + if (!log.name.startsWith('jitsi_')) continue; + + // Ensure a base entry for the meeting exists + if (!meetings.has(meeting_id)) { + meetings.set(meeting_id, { + meeting_id: meeting_id, + room_name: 'Unknown', + start_time: log.created_on, // Fallback start time + final_duration: '00:00:00', + final_participants: [], + final_participant_count: 0, + events: [] + }); + } + + const meeting_report = meetings.get(meeting_id); + + if (log.action === 'jitsi_meeting_init') { + // This is the main log entry, containing the final state. + meeting_report.room_name = log.description; + meeting_report.start_time = log.created_on; // The init log has the true start time + if (log.meta_json) { + meeting_report.final_duration = log.meta_json.duration; + meeting_report.final_participants = log.meta_json.participants; + meeting_report.final_participant_count = log.meta_json.participant_count; + } + } else { + // This is a discrete event log. + meeting_report.events.push({ + timestamp: log.created_on, + action: log.action, + details: log.meta_json + }); + } + } + + // Sort events within each meeting chronologically + for (const report of meetings.values()) { + report.events.sort((a: any, b: any) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + } + + const final_report = Array.from(meetings.values()); + final_report.sort((a,b) => new Date(b.start_time).getTime() - new Date(a.start_time).getTime()); + + if (log_lvl) console.log('Final Jitsi report:', final_report); + + return final_report; +} diff --git a/src/routes/idaa/jitsi_reports/+page.svelte b/src/routes/idaa/jitsi_reports/+page.svelte new file mode 100644 index 00000000..e03a4aa4 --- /dev/null +++ b/src/routes/idaa/jitsi_reports/+page.svelte @@ -0,0 +1,105 @@ + + + + Æ: Jitsi Meeting Reports + + +
+

Jitsi Meeting Reports

+ + {#if data.meetings && data.meetings.length > 0} +
+ {#each data.meetings as meeting (meeting.meeting_id)} +
+ + +
toggle_accordion(meeting.meeting_id)}> +
+
+ +

{meeting.room_name}

+

{new Date(meeting.start_time).toLocaleString()}

+
+
+ Duration: {meeting.final_duration} + Participants: {meeting.final_participant_count} +
+
+ + + +
+
+
+ {#if open_accordions[meeting.meeting_id]} +
+
+ +

Event Timeline

+ {#if meeting.events && meeting.events.length > 0} +
    + {#each meeting.events as event} +
  • + [{new Date(event.timestamp).toLocaleTimeString()}] + {ae_util.to_title_case(event.action.replace('jitsi_meeting_', ''))} + {#if event.details.full_name} + - by {event.details.full_name} + {/if} +
  • + {/each} +
+ {:else} +

No discrete events recorded.

+ {/if} +
+
+ +

Final Participants

+ {#if meeting.final_participants && meeting.final_participants.length > 0} +
+ + + + + + + + + {#each meeting.final_participants as participant} + + + + + {/each} + +
NameRole
{participant.displayName}{ae_util.to_title_case(participant.role)}
+
+ {:else} +

No participant data available.

+ {/if} +
+
+ {/if} +
+ {/each} +
+ {:else} +
+

No Meeting Reports Found

+

There are no Jitsi activity logs to display.

+
+ {/if} +
diff --git a/src/routes/idaa/jitsi_reports/+page.ts b/src/routes/idaa/jitsi_reports/+page.ts new file mode 100644 index 00000000..24fc4454 --- /dev/null +++ b/src/routes/idaa/jitsi_reports/+page.ts @@ -0,0 +1,28 @@ +import type { PageLoad } from './$types'; +import { ae_api, ae_loc } from '$lib/stores/ae_stores'; +import { load_jitsi_report } from '$lib/ae_reports/reports_functions'; +import { get } from 'svelte/store'; + +export const load: PageLoad = async ({ fetch }) => { + console.log('*** /idaa/jitsi_reports/+page.ts ***'); + + const api_cfg = get(ae_api); + const account_id = get(ae_loc)?.account_id; + + if (!api_cfg || !account_id) { + console.error('API config or Account ID not available for loading Jitsi reports.'); + return { + meetings: [] + }; + } + + const meetings = await load_jitsi_report({ + api_cfg, + account_id, + log_lvl: 1 + }); + + return { + meetings + }; +};