feat(idaa): implement jitsi report streaming and conference lifecycle improvements
- Refactor Jitsi reports to use SvelteKit streaming with a skeleton loader. - Add conference lifecycle event listeners (left, close) to video conference page. - Implement manual Novi data re-sync and improve initialization robustness. - Fix skeleton visibility by using standard Tailwind colors.
This commit is contained in:
@@ -117,7 +117,7 @@
|
|||||||
api.on('videoConferenceJoined', async (jitsi_data: { id: string; displayName: string; roomName: string }) => {
|
api.on('videoConferenceJoined', async (jitsi_data: { id: string; displayName: string; roomName: string }) => {
|
||||||
// Map Jitsi's camelCase to our internal snake_case
|
// Map Jitsi's camelCase to our internal snake_case
|
||||||
const { id: participant_id, displayName: participant_name, roomName: jitsi_room_name } = jitsi_data;
|
const { id: participant_id, displayName: participant_name, roomName: jitsi_room_name } = jitsi_data;
|
||||||
|
|
||||||
console.log('Jitsi Event: videoConferenceJoined', jitsi_data);
|
console.log('Jitsi Event: videoConferenceJoined', jitsi_data);
|
||||||
meeting_start_time = new Date();
|
meeting_start_time = new Date();
|
||||||
if (jitsi_meeting_id === null) jitsi_meeting_id = `${jitsi_room_name}-${Date.now()}`;
|
if (jitsi_meeting_id === null) jitsi_meeting_id = `${jitsi_room_name}-${Date.now()}`;
|
||||||
@@ -215,17 +215,29 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Discrete Event Logging ---
|
// --- Discrete Event Logging ---
|
||||||
// NOTE: This does not seem to be triggered.
|
|
||||||
api.on('raiseHandUpdated', (participant: { id:string; raisesHand: boolean }) => {
|
api.on('raiseHandUpdated', (participant: { id:string; raisesHand: boolean }) => {
|
||||||
if (participant.raisesHand) {
|
if (participant.raisesHand) {
|
||||||
const p_info = meeting_participants.get(participant.id);
|
const p_info = meeting_participants.get(participant.id);
|
||||||
console.log('Jitsi Event: raiseHandUpdated', p_info);
|
if (p_info) {
|
||||||
create_discrete_activity_log('jitsi_meeting_raise_hand', 'raiseHandUpdated', {
|
console.log('Jitsi Event: raiseHandUpdated', p_info);
|
||||||
attendee_id: p_info.id,
|
create_discrete_activity_log('jitsi_meeting_raise_hand', 'raiseHandUpdated', {
|
||||||
full_name: p_info.displayName,
|
attendee_id: p_info.id,
|
||||||
});
|
full_name: p_info.displayName,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
api.on('videoConferenceLeft', () => {
|
||||||
|
console.log('Jitsi Event: videoConferenceLeft');
|
||||||
|
if (duration_timer_id) clearInterval(duration_timer_id);
|
||||||
|
// meeting_duration = '00:00:00';
|
||||||
|
});
|
||||||
|
|
||||||
|
api.on('readyToClose', () => {
|
||||||
|
console.log('Jitsi Event: readyToClose');
|
||||||
|
show_jitsi_container = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handle_profile_update() {
|
async function handle_profile_update() {
|
||||||
@@ -277,7 +289,7 @@
|
|||||||
settings: {
|
settings: {
|
||||||
startMuted: data.params.start_muted === 'true',
|
startMuted: data.params.start_muted === 'true',
|
||||||
startHidden: data.params.start_hidden === 'true',
|
startHidden: data.params.start_hidden === 'true',
|
||||||
reactionsMuted: data.params.reactions_muted === 'true'
|
reactionsMuted: disable_reaction_sound
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
'prejoinConfig.enabled': false
|
'prejoinConfig.enabled': false
|
||||||
@@ -301,19 +313,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
async function fetch_novi_data() {
|
||||||
if (log_lvl) {
|
|
||||||
console.log('Jitsi: onMount - fetching user data and initializing Jitsi...');
|
|
||||||
}
|
|
||||||
const url_params = data.params;
|
const url_params = data.params;
|
||||||
if (log_lvl > 1) {
|
|
||||||
console.log('Jitsi: url_params:', url_params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Start with fallback data from URL ---
|
// --- Start with fallback data from URL ---
|
||||||
user_id = url_params.uuid; // Novi Customer GUID
|
user_id = url_params.uuid; // Novi Customer GUID
|
||||||
display_name = url_params.full_name ?? 'Guest'; // May be overridden
|
display_name = url_params.full_name ?? 'Guest'; // May be overridden
|
||||||
email = (url_params.email ?? 'guest@example.com').replace(/\s+/g, '+'); // May be overridden
|
email = (url_params.email ?? 'guest@example.com').replace(/\s+/g, '+'); // May be overridden
|
||||||
|
is_moderator = url_params.moderator === 'true'; // URL fallback
|
||||||
room_name = url_params.room ?? 'Default-Room';
|
room_name = url_params.room ?? 'Default-Room';
|
||||||
domain = url_params.domain ?? 'jitsi.dgrzone.com';
|
domain = url_params.domain ?? 'jitsi.dgrzone.com';
|
||||||
|
|
||||||
@@ -370,12 +376,26 @@
|
|||||||
console.log(`Jitsi: User ${user_id} is not a moderator.`);
|
console.log(`Jitsi: User ${user_id} is not a moderator.`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Jitsi: Novi API not configured. Skipping user details/moderator check.');
|
console.warn('Jitsi: Novi API not configured. Using URL fallback for user details/moderator check.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial value for the profile editor inputs
|
// Set initial value for the profile editor inputs
|
||||||
name_input = display_name ?? '';
|
name_input = display_name ?? '';
|
||||||
email_input = email ?? '';
|
email_input = email ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handle_novi_resync() {
|
||||||
|
console.log('Jitsi: Manually re-syncing Novi Data...');
|
||||||
|
await fetch_novi_data();
|
||||||
|
await init_jitsi();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (log_lvl) {
|
||||||
|
console.log('Jitsi: onMount - fetching user data and initializing Jitsi...');
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch_novi_data();
|
||||||
|
|
||||||
// --- All data fetched, now initialize Jitsi ---
|
// --- All data fetched, now initialize Jitsi ---
|
||||||
await init_jitsi();
|
await init_jitsi();
|
||||||
@@ -538,6 +558,17 @@
|
|||||||
jitsi_api = null;
|
jitsi_api = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Check for Jitsi API script ---
|
||||||
|
// @ts-ignore
|
||||||
|
if (typeof JitsiMeetExternalAPI === 'undefined') {
|
||||||
|
console.error('Jitsi: JitsiMeetExternalAPI script not loaded yet.');
|
||||||
|
const container = document.getElementById(jitsi_container_id);
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '<h1>Jitsi API script not loaded. Please refresh the page.</h1>';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Jitsi: Initializing Jitsi meeting interface...');
|
console.log('Jitsi: Initializing Jitsi meeting interface...');
|
||||||
|
|
||||||
// These variables are now expected to be set in the component's state
|
// These variables are now expected to be set in the component's state
|
||||||
@@ -869,10 +900,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="mt-2 px-2 py-1 bg-blue-200 text-white rounded hover:bg-blue-400"
|
class="mt-2 px-2 py-1 bg-blue-200 text-white rounded hover:bg-blue-400"
|
||||||
onclick={() => {
|
onclick={handle_novi_resync}
|
||||||
// Placeholder for function calls to update Novi data
|
|
||||||
console.log('Re-sync Novi Data button clicked. Implement as needed.');
|
|
||||||
}}
|
|
||||||
title="Re-synchronize Novi data"
|
title="Re-synchronize Novi data"
|
||||||
>
|
>
|
||||||
<span class="fas fa-sync" aria-hidden="true"></span>
|
<span class="fas fa-sync" aria-hidden="true"></span>
|
||||||
@@ -907,9 +935,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.jitsi-tools {
|
.jitsi-tools {
|
||||||
z-index: 1000;
|
z-index: 10;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 6em;
|
bottom: 10em;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
/* background-color: rgba(255, 255, 255, 0.8); */
|
/* background-color: rgba(255, 255, 255, 0.8); */
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|||||||
@@ -20,86 +20,101 @@
|
|||||||
<div class="p-4 space-y-4">
|
<div class="p-4 space-y-4">
|
||||||
<h1 class="h1">Jitsi Meeting Reports</h1>
|
<h1 class="h1">Jitsi Meeting Reports</h1>
|
||||||
|
|
||||||
{#if data.meetings && data.meetings.length > 0}
|
{#await data.streamed.meetings}
|
||||||
<div class="space-y-2">
|
<div class="space-y-4 animate-pulse">
|
||||||
{#each data.meetings as meeting (meeting.meeting_id)}
|
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
|
||||||
<div class="card card-hover">
|
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
|
||||||
<header class="card-header p-2 cursor-pointer" onclick={() => toggle_accordion(meeting.meeting_id)}>
|
</div>
|
||||||
<div class="flex justify-between items-center w-full">
|
{:then meetings}
|
||||||
<div class="flex-1">
|
{#if meetings && meetings.length > 0}
|
||||||
<!-- NOTE: Normally I would the "h3" class, but Novi classes make things look odd. -->
|
<div class="space-y-2">
|
||||||
<h3 class="text-base">{meeting.room_name}</h3>
|
{#each meetings as meeting (meeting.meeting_id)}
|
||||||
<p class="text-sm text-gray-500">{new Date(meeting.start_time).toLocaleString()}</p>
|
<div class="card card-hover">
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<header class="card-header p-2 cursor-pointer" onclick={() => toggle_accordion(meeting.meeting_id)}>
|
||||||
|
<div class="flex justify-between items-center w-full">
|
||||||
|
<div class="flex-1">
|
||||||
|
<!-- NOTE: Normally I would the "h3" class, but Novi classes make things look odd. -->
|
||||||
|
<h3 class="text-base">{meeting.room_name}</h3>
|
||||||
|
<p class="text-sm text-gray-500">{new Date(meeting.start_time).toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none flex items-center space-x-4 text-sm mr-4">
|
||||||
|
<span>Duration: {meeting.final_duration}</span>
|
||||||
|
<span>Participants: {meeting.final_participant_count}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<span class="transition-transform duration-200" class:rotate-180={open_accordions[meeting.meeting_id]}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none flex items-center space-x-4 text-sm mr-4">
|
</header>
|
||||||
<span>Duration: {meeting.final_duration}</span>
|
{#if open_accordions[meeting.meeting_id]}
|
||||||
<span>Participants: {meeting.final_participant_count}</span>
|
<div class="p-4 border-t border-gray-200 dark:border-gray-700 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
</div>
|
<div>
|
||||||
<div class="flex-none">
|
<!-- NOTE: Normally I would the "h4" class, but Novi classes make things look odd. -->
|
||||||
<span class="transition-transform duration-200" class:rotate-180={open_accordions[meeting.meeting_id]}>
|
<h4 class="text-base">Event Timeline</h4>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
{#if meeting.events && meeting.events.length > 0}
|
||||||
</span>
|
<ul class="list-disc list-inside space-y-2 mt-2">
|
||||||
</div>
|
{#each meeting.events as event}
|
||||||
</div>
|
<li>
|
||||||
</header>
|
<span class="font-mono text-xs">[{new Date(event.timestamp).toLocaleTimeString()}]</span>
|
||||||
{#if open_accordions[meeting.meeting_id]}
|
<span class="font-semibold">{ae_util.to_title_case(event.action.replace('jitsi_meeting_', ''))}</span>
|
||||||
<div class="p-4 border-t border-gray-200 dark:border-gray-700 grid grid-cols-1 md:grid-cols-2 gap-4">
|
{#if event.details.full_name}
|
||||||
<div>
|
- by {event.details.full_name}
|
||||||
<!-- NOTE: Normally I would the "h4" class, but Novi classes make things look odd. -->
|
{/if}
|
||||||
<h4 class="text-base">Event Timeline</h4>
|
</li>
|
||||||
{#if meeting.events && meeting.events.length > 0}
|
{/each}
|
||||||
<ul class="list-disc list-inside space-y-2 mt-2">
|
</ul>
|
||||||
{#each meeting.events as event}
|
{:else}
|
||||||
<li>
|
<p class="text-gray-500 italic mt-2">No discrete events recorded.</p>
|
||||||
<span class="font-mono text-xs">[{new Date(event.timestamp).toLocaleTimeString()}]</span>
|
{/if}
|
||||||
<span class="font-semibold">{ae_util.to_title_case(event.action.replace('jitsi_meeting_', ''))}</span>
|
</div>
|
||||||
{#if event.details.full_name}
|
<div>
|
||||||
- by {event.details.full_name}
|
<!-- NOTE: Normally I would the "h4" class, but Novi classes make things look odd. -->
|
||||||
{/if}
|
<h4 class="text-base">Final Participants</h4>
|
||||||
</li>
|
{#if meeting.final_participants && meeting.final_participants.length > 0}
|
||||||
{/each}
|
<div class="table-container mt-2">
|
||||||
</ul>
|
<table class="table table-hover">
|
||||||
{:else}
|
<thead>
|
||||||
<p class="text-gray-500 italic mt-2">No discrete events recorded.</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<!-- NOTE: Normally I would the "h4" class, but Novi classes make things look odd. -->
|
|
||||||
<h4 class="text-base">Final Participants</h4>
|
|
||||||
{#if meeting.final_participants && meeting.final_participants.length > 0}
|
|
||||||
<div class="table-container mt-2">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Role</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each meeting.final_participants as participant}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{participant.displayName}</td>
|
<th>Name</th>
|
||||||
<td>{ae_util.to_title_case(participant.role)}</td>
|
<th>Role</th>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{#each meeting.final_participants as participant}
|
||||||
</div>
|
<tr>
|
||||||
{:else}
|
<td>{participant.displayName}</td>
|
||||||
<p class="text-gray-500 italic mt-2">No participant data available.</p>
|
<td>{ae_util.to_title_case(participant.role)}</td>
|
||||||
{/if}
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="text-gray-500 italic mt-2">No participant data available.</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="card p-4 text-center">
|
||||||
|
<h3 class="h3">No Meeting Reports Found</h3>
|
||||||
|
<p>There are no Jitsi activity logs to display.</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:catch error}
|
||||||
|
<div class="card p-4 bg-red-100 text-red-900 border-l-4 border-red-500">
|
||||||
|
<h3 class="h3 font-bold">Error Loading Reports</h3>
|
||||||
|
<p>An error occurred while fetching the meeting reports:</p>
|
||||||
|
<pre class="mt-2 text-xs overflow-auto">{error.message}</pre>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/await}
|
||||||
<div class="card p-4 text-center">
|
|
||||||
<h3 class="h3">No Meeting Reports Found</h3>
|
|
||||||
<p>There are no Jitsi activity logs to display.</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,17 +12,21 @@ export const load: PageLoad = async ({ fetch }) => {
|
|||||||
if (!api_cfg || !account_id) {
|
if (!api_cfg || !account_id) {
|
||||||
console.error('API config or Account ID not available for loading Jitsi reports.');
|
console.error('API config or Account ID not available for loading Jitsi reports.');
|
||||||
return {
|
return {
|
||||||
meetings: []
|
streamed: {
|
||||||
|
meetings: Promise.resolve([])
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const meetings = await load_jitsi_report({
|
const meetings_promise = load_jitsi_report({
|
||||||
api_cfg,
|
api_cfg,
|
||||||
account_id,
|
account_id,
|
||||||
log_lvl: 1
|
log_lvl: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
meetings
|
streamed: {
|
||||||
|
meetings: meetings_promise
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user