diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala index ecd2c9e3ec..c21a58d0e3 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala @@ -32,4 +32,5 @@ object MeetingEndReason { val BREAKOUT_ENDED_EXCEEDING_DURATION = "BREAKOUT_ENDED_EXCEEDING_DURATION" val BREAKOUT_ENDED_BY_MOD = "BREAKOUT_ENDED_BY_MOD" val ENDED_DUE_TO_NO_AUTHED_USER = "ENDED_DUE_TO_NO_AUTHED_USER" + val ENDED_DUE_TO_NO_MODERATOR = "ENDED_DUE_TO_NO_MODERATOR" } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingTrackers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingTrackers.scala index 5b1c906870..01b82a8721 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingTrackers.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingTrackers.scala @@ -3,14 +3,18 @@ package org.bigbluebutton.core.domain case class MeetingExpiryTracker( startedOnInMs: Long, userHasJoined: Boolean, + moderatorHasJoined: Boolean, isBreakout: Boolean, lastUserLeftOnInMs: Option[Long], + lastModeratorLeftOnInMs: Long, durationInMs: Long, meetingExpireIfNoUserJoinedInMs: Long, meetingExpireWhenLastUserLeftInMs: Long, userInactivityInspectTimerInMs: Long, userInactivityThresholdInMs: Long, - userActivitySignResponseDelayInMs: Long + userActivitySignResponseDelayInMs: Long, + endWhenNoModerator: Boolean, + endWhenNoModeratorDelayInMs: Long ) { def setUserHasJoined(): MeetingExpiryTracker = { if (!userHasJoined) { @@ -24,6 +28,18 @@ case class MeetingExpiryTracker( copy(lastUserLeftOnInMs = Some(timestampInMs)) } + def setModeratorHasJoined(): MeetingExpiryTracker = { + if (!moderatorHasJoined) { + copy(moderatorHasJoined = true, lastModeratorLeftOnInMs = 0) + } else { + copy(lastModeratorLeftOnInMs = 0) + } + } + + def setLastModeratorLeftOn(timestampInMs: Long): MeetingExpiryTracker = { + copy(lastModeratorLeftOnInMs = timestampInMs) + } + def hasMeetingExpiredAfterLastUserLeft(timestampInMs: Long): Boolean = { val expire = for { lastUserLeftOn <- lastUserLeftOnInMs diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala index 79cae79e00..2c131fb1c7 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala @@ -55,6 +55,10 @@ object Users2x { users.toVector.length } + def numActiveModerators(users: Users2x): Int = { + users.toVector.filter(u => u.role == Roles.MODERATOR_ROLE && !u.userLeftFlag.left).length + } + def findNotPresenters(users: Users2x): Vector[UserState] = { users.toVector.filter(u => !u.presenter) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index a8e052e5e3..3a125c5e1f 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -135,14 +135,18 @@ class MeetingActor( val expiryTracker = new MeetingExpiryTracker( startedOnInMs = TimeUtil.timeNowInMs(), userHasJoined = false, + moderatorHasJoined = false, isBreakout = props.meetingProp.isBreakout, lastUserLeftOnInMs = None, + lastModeratorLeftOnInMs = 0, durationInMs = TimeUtil.minutesToMillis(props.durationProps.duration), meetingExpireIfNoUserJoinedInMs = TimeUtil.minutesToMillis(props.durationProps.meetingExpireIfNoUserJoinedInMinutes), meetingExpireWhenLastUserLeftInMs = TimeUtil.minutesToMillis(props.durationProps.meetingExpireWhenLastUserLeftInMinutes), userInactivityInspectTimerInMs = TimeUtil.minutesToMillis(props.durationProps.userInactivityInspectTimerInMinutes), userInactivityThresholdInMs = TimeUtil.minutesToMillis(props.durationProps.userInactivityThresholdInMinutes), - userActivitySignResponseDelayInMs = TimeUtil.minutesToMillis(props.durationProps.userActivitySignResponseDelayInMinutes) + userActivitySignResponseDelayInMs = TimeUtil.minutesToMillis(props.durationProps.userActivitySignResponseDelayInMinutes), + endWhenNoModerator = props.durationProps.endWhenNoModerator, + endWhenNoModeratorDelayInMs = TimeUtil.minutesToMillis(props.durationProps.endWhenNoModeratorDelayInMinutes) ) val recordingTracker = new MeetingRecordingTracker(startedOnInMs = 0L, previousDurationInMs = 0L, currentDurationInMs = 0L) @@ -298,6 +302,23 @@ class MeetingActor( } } + private def updateModeratorsPresence() { + if (Users2x.numActiveModerators(liveMeeting.users2x) > 0) { + if (state.expiryTracker.moderatorHasJoined == false || + state.expiryTracker.lastModeratorLeftOnInMs != 0) { + log.info("A moderator has joined. Setting setModeratorHasJoined(). meetingId=" + props.meetingProp.intId) + val tracker = state.expiryTracker.setModeratorHasJoined() + state = state.update(tracker) + } + } else { + if (state.expiryTracker.moderatorHasJoined == true) { + log.info("All moderators have left. Setting setLastModeratorLeftOn(). meetingId=" + props.meetingProp.intId) + val tracker = state.expiryTracker.setLastModeratorLeftOn(TimeUtil.timeNowInMs()) + state = state.update(tracker) + } + } + } + private def updateUserLastInactivityInspect(userId: String) { for { user <- Users2x.findWithIntId(liveMeeting.users2x, userId) @@ -317,20 +338,26 @@ class MeetingActor( private def handleMessageThatAffectsInactivity(msg: BbbCommonEnvCoreMsg): Unit = { msg.core match { - case m: EndMeetingSysCmdMsg => handleEndMeeting(m, state) + case m: EndMeetingSysCmdMsg => handleEndMeeting(m, state) // Users - case m: ValidateAuthTokenReqMsg => state = usersApp.handleValidateAuthTokenReqMsg(m, state) - case m: UserJoinMeetingReqMsg => state = handleUserJoinMeetingReqMsg(m, state) - case m: UserJoinMeetingAfterReconnectReqMsg => state = handleUserJoinMeetingAfterReconnectReqMsg(m, state) - case m: UserLeaveReqMsg => state = handleUserLeaveReqMsg(m, state) - case m: UserBroadcastCamStartMsg => handleUserBroadcastCamStartMsg(m) - case m: UserBroadcastCamStopMsg => handleUserBroadcastCamStopMsg(m) - case m: GetCamBroadcastPermissionReqMsg => handleGetCamBroadcastPermissionReqMsg(m) - case m: GetCamSubscribePermissionReqMsg => handleGetCamSubscribePermissionReqMsg(m) + case m: ValidateAuthTokenReqMsg => state = usersApp.handleValidateAuthTokenReqMsg(m, state) + case m: UserJoinMeetingReqMsg => + state = handleUserJoinMeetingReqMsg(m, state) + updateModeratorsPresence() + case m: UserJoinMeetingAfterReconnectReqMsg => + state = handleUserJoinMeetingAfterReconnectReqMsg(m, state) + updateModeratorsPresence() + case m: UserLeaveReqMsg => + state = handleUserLeaveReqMsg(m, state) + updateModeratorsPresence() + case m: UserBroadcastCamStartMsg => handleUserBroadcastCamStartMsg(m) + case m: UserBroadcastCamStopMsg => handleUserBroadcastCamStopMsg(m) + case m: GetCamBroadcastPermissionReqMsg => handleGetCamBroadcastPermissionReqMsg(m) + case m: GetCamSubscribePermissionReqMsg => handleGetCamSubscribePermissionReqMsg(m) - case m: UserJoinedVoiceConfEvtMsg => handleUserJoinedVoiceConfEvtMsg(m) - case m: LogoutAndEndMeetingCmdMsg => usersApp.handleLogoutAndEndMeetingCmdMsg(m, state) + case m: UserJoinedVoiceConfEvtMsg => handleUserJoinedVoiceConfEvtMsg(m) + case m: LogoutAndEndMeetingCmdMsg => usersApp.handleLogoutAndEndMeetingCmdMsg(m, state) case m: SetRecordingStatusCmdMsg => state = usersApp.handleSetRecordingStatusCmdMsg(m, state) updateUserLastActivity(m.body.setBy) @@ -354,6 +381,7 @@ class MeetingActor( case m: ChangeUserRoleCmdMsg => usersApp.handleChangeUserRoleCmdMsg(m) updateUserLastActivity(m.body.changedBy) + updateModeratorsPresence() // Whiteboard case m: SendCursorPositionPubMsg => wbApp.handle(m, liveMeeting, msgBus) @@ -596,7 +624,8 @@ class MeetingActor( processUserInactivityAudit() flagRegisteredUsersWhoHasNotJoined() - checkIfNeetToEndMeetingWhenNoAuthedUsers(liveMeeting) + checkIfNeedToEndMeetingWhenNoAuthedUsers(liveMeeting) + checkIfNeedToEndMeetingWhenNoNoModerators(liveMeeting) } def checkVoiceConfUsersStatus(): Unit = { @@ -663,7 +692,7 @@ class MeetingActor( } - private def checkIfNeetToEndMeetingWhenNoAuthedUsers(liveMeeting: LiveMeeting): Unit = { + private def checkIfNeedToEndMeetingWhenNoAuthedUsers(liveMeeting: LiveMeeting): Unit = { val authUserJoined = MeetingStatus2x.hasAuthedUserJoined(liveMeeting.status) if (endMeetingWhenNoMoreAuthedUsers && @@ -684,6 +713,28 @@ class MeetingActor( } } + private def checkIfNeedToEndMeetingWhenNoNoModerators(liveMeeting: LiveMeeting): Unit = { + if (state.expiryTracker.endWhenNoModerator && + !liveMeeting.props.meetingProp.isBreakout && + state.expiryTracker.moderatorHasJoined && + state.expiryTracker.lastModeratorLeftOnInMs != 0 && + //Check if has moderator with leftFlag + Users2x.findModerator(liveMeeting.users2x).toVector.length == 0) { + val hasModeratorLeftRecently = (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs) < state.expiryTracker.lastModeratorLeftOnInMs + if (!hasModeratorLeftRecently) { + log.info("Meeting will end due option endWhenNoModerator is enabled and all moderators have left the meeting. meetingId=" + props.meetingProp.intId) + sendEndMeetingDueToExpiry( + MeetingEndReason.ENDED_DUE_TO_NO_MODERATOR, + eventBus, outGW, liveMeeting, + "system" + ) + } else { + val msToEndMeeting = state.expiryTracker.lastModeratorLeftOnInMs - (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs) + log.info("All moderators have left. Meeting will end in " + TimeUtil.millisToSeconds(msToEndMeeting) + " seconds. meetingId=" + props.meetingProp.intId) + } + } + } + def handleExtendMeetingDuration(msg: ExtendMeetingDuration) { } diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala index 67f842e615..cbc449393e 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala @@ -4,7 +4,9 @@ case class ConfigProps(defaultConfigToken: String, config: String) case class DurationProps(duration: Int, createdTime: Long, createdDate: String, meetingExpireIfNoUserJoinedInMinutes: Int, meetingExpireWhenLastUserLeftInMinutes: Int, - userInactivityInspectTimerInMinutes: Int, userInactivityThresholdInMinutes: Int, userActivitySignResponseDelayInMinutes: Int) + userInactivityInspectTimerInMinutes: Int, userInactivityThresholdInMinutes: Int, + userActivitySignResponseDelayInMinutes: Int, + endWhenNoModerator: Boolean, endWhenNoModeratorDelayInMinutes: Int) case class MeetingProp(name: String, extId: String, intId: String, isBreakout: Boolean) diff --git a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala index 127f0e6cb6..0ddf252dcf 100755 --- a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala +++ b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala @@ -17,6 +17,8 @@ trait TestFixtures { val userInactivityInspectTimerInMinutes = 60 val userInactivityThresholdInMinutes = 10 val userActivitySignResponseDelayInMinutes = 5 + val endWhenNoModerator = false + val endWhenNoModeratorDelayInMinutes = 1 val autoStartRecording = false val allowStartStopRecording = false diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java index 3adb52b1ad..75ac2b6a0d 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java @@ -432,7 +432,8 @@ public class MeetingService implements MessageListener { m.getDialNumber(), m.getMaxUsers(), m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes(), m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(), - m.getUserActivitySignResponseDelayInMinutes(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getMeetingKeepEvents(), + m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(), + m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getMeetingKeepEvents(), m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId()); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java index 741d4099e0..a114cd9ee8 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java @@ -115,6 +115,7 @@ public class ParamsProcessorUtil { private Integer userActivitySignResponseDelayInMinutes = 5; private Boolean defaultAllowDuplicateExtUserid = true; private Boolean defaultEndWhenNoModerator = false; + private Integer defaultEndWhenNoModeratorDelayInMinutes = 1; private Integer defaultHtml5InstanceId = 1; private String formatConfNum(String s) { @@ -523,6 +524,8 @@ public class ParamsProcessorUtil { meeting.setUserActivitySignResponseDelayInMinutes(userActivitySignResponseDelayInMinutes); meeting.setUserInactivityThresholdInMinutes(userInactivityThresholdInMinutes); // meeting.setHtml5InstanceId(html5InstanceId); + meeting.setEndWhenNoModerator(defaultEndWhenNoModerator); + meeting.setEndWhenNoModeratorDelayInMinutes(defaultEndWhenNoModeratorDelayInMinutes); // Add extra parameters for breakout room if (isBreakout) { @@ -1156,5 +1159,8 @@ public class ParamsProcessorUtil { this.defaultEndWhenNoModerator = val; } + public void setEndWhenNoModeratorDelayInMinutes(Integer value) { + this.defaultEndWhenNoModeratorDelayInMinutes = value; + } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java index b17b07c7fb..b2eaefea9a 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java @@ -91,6 +91,8 @@ public class Meeting { private Integer userInactivityInspectTimerInMinutes = 120; private Integer userInactivityThresholdInMinutes = 30; private Integer userActivitySignResponseDelayInMinutes = 5; + private Boolean endWhenNoModerator = false; + private Integer endWhenNoModeratorDelayInMinutes = 1; public final BreakoutRoomsParams breakoutRoomsParams; public final LockSettingsParams lockSettingsParams; @@ -99,8 +101,6 @@ public class Meeting { private String meetingEndedCallbackURL = ""; - public final Boolean endWhenNoModerator; - private Integer html5InstanceId; public Meeting(Meeting.Builder builder) { @@ -134,6 +134,7 @@ public class Meeting { lockSettingsParams = builder.lockSettingsParams; allowDuplicateExtUserid = builder.allowDuplicateExtUserid; endWhenNoModerator = builder.endWhenNoModerator; + endWhenNoModeratorDelayInMinutes = builder.endWhenNoModeratorDelayInMinutes; html5InstanceId = builder.html5InstanceId; /* @@ -660,6 +661,22 @@ public class Meeting { this.userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes; } + public Boolean getEndWhenNoModerator() { + return endWhenNoModerator; + } + + public void setEndWhenNoModerator(Boolean endWhenNoModerator) { + this.endWhenNoModerator = endWhenNoModerator; + } + + public Integer getEndWhenNoModeratorDelayInMinutes() { + return endWhenNoModeratorDelayInMinutes; + } + + public void setEndWhenNoModeratorDelayInMinutes(Integer endWhenNoModeratorDelayInMinutes) { + this.endWhenNoModeratorDelayInMinutes = endWhenNoModeratorDelayInMinutes; + } + public String getMeetingEndedCallbackURL() { return meetingEndedCallbackURL; } @@ -742,6 +759,7 @@ public class Meeting { private LockSettingsParams lockSettingsParams; private Boolean allowDuplicateExtUserid; private Boolean endWhenNoModerator; + private Integer endWhenNoModeratorDelayInMinutes; private int html5InstanceId; public Builder(String externalId, String internalId, long createTime) { @@ -885,6 +903,11 @@ public class Meeting { return this; } + public Builder withEndWhenNoModeratorDelayInMinutes(Integer endWhenNoModeratorDelayInMinutes) { + this.endWhenNoModeratorDelayInMinutes = endWhenNoModeratorDelayInMinutes; + return this; + } + public Builder withHTML5InstanceId(int instanceId) { html5InstanceId = instanceId; return this; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java index 66859a934f..9f3dd1cd2a 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java @@ -26,6 +26,8 @@ public interface IBbbWebApiGWApp { Integer userInactivityInspectTimerInMinutes, Integer userInactivityThresholdInMinutes, Integer userActivitySignResponseDelayInMinutes, + Boolean endWhenNoModerator, + Integer endWhenNoModeratorDelayInMinutes, Boolean muteOnStart, Boolean allowModsToUnmuteUsers, Boolean keepEvents, diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala index 8d720d60dd..6ba6d28e33 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala @@ -135,6 +135,8 @@ class BbbWebApiGWApp( userInactivityInspectTimerInMinutes: java.lang.Integer, userInactivityThresholdInMinutes: java.lang.Integer, userActivitySignResponseDelayInMinutes: java.lang.Integer, + endWhenNoModerator: java.lang.Boolean, + endWhenNoModeratorDelayInMinutes: java.lang.Integer, muteOnStart: java.lang.Boolean, allowModsToUnmuteUsers: java.lang.Boolean, keepEvents: java.lang.Boolean, @@ -151,7 +153,9 @@ class BbbWebApiGWApp( meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes.intValue(), userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes.intValue(), userInactivityThresholdInMinutes = userInactivityThresholdInMinutes.intValue(), - userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes.intValue() + userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes.intValue(), + endWhenNoModerator = endWhenNoModerator.booleanValue(), + endWhenNoModeratorDelayInMinutes.intValue() ) val password = PasswordProp(moderatorPass = moderatorPass, viewerPass = viewerPass) diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js index 9aaff67b77..a059fc3c00 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js @@ -47,6 +47,8 @@ export default function addMeeting(meeting) { userInactivityInspectTimerInMinutes: Number, userInactivityThresholdInMinutes: Number, userActivitySignResponseDelayInMinutes: Number, + endWhenNoModerator: Boolean, + endWhenNoModeratorDelayInMinutes: Number, timeRemaining: Number, }, welcomeProp: { diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index a07eeb7ecd..422db063a1 100755 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -387,3 +387,6 @@ defaultTextTrackUrl=${bigbluebutton.web.serverURL}/bigbluebutton # Needed for classes where teacher gets disconnected and can't get back in. Prevents # students from running amok. endWhenNoModerator=false + +# Number of minutes to wait for moderator rejoin before end meeting (if `endWhenNoModerator` enabled) +endWhenNoModeratorDelayInMinutes=1 diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index e8986bdc91..dcda4360f7 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -164,6 +164,7 @@ with BigBlueButton; if not, see . +