forked from External/greenlight
Allow recordings to be 'unlisted'
Unlisted is a state between published and unpublished. They are still published in the server, but will not appear to anyone other than the user that created the recording. It is done using a metadata attribute and required several changes in how the application handles publishing and unpublishing.
This commit is contained in:
parent
40cbc8a575
commit
b518458622
|
@ -23,17 +23,21 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
received: function(data) {
|
received: function(data) {
|
||||||
|
|
||||||
var recordings = Recordings.getInstance();
|
var recordings = Recordings.getInstance();
|
||||||
var table = recordings.table.api()
|
var table = recordings.table.api();
|
||||||
var row = table.row("#"+data.record_id);
|
var row = table.row("#"+data.record_id);
|
||||||
|
|
||||||
if (data.action === 'update') {
|
if (data.action === 'update') {
|
||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
rowData.published = data.published
|
|
||||||
|
rowData.published = data.published;
|
||||||
|
rowData.listed = data.listed;
|
||||||
table.row("#"+data.record_id).data(rowData);
|
table.row("#"+data.record_id).data(rowData);
|
||||||
recordings.draw();
|
recordings.draw();
|
||||||
|
|
||||||
var published = (data.published) ? 'published' : 'unpublished';
|
var status = data.published ? (data.listed ? 'published' : 'unlisted') : 'unpublished';
|
||||||
showAlert(I18n['recording_'+published], 4000);
|
showAlert(I18n['recording_'+status], 4000);
|
||||||
} else if (data.action === 'delete') {
|
} else if (data.action === 'delete') {
|
||||||
row.remove();
|
row.remove();
|
||||||
recordings.draw();
|
recordings.draw();
|
||||||
|
|
|
@ -100,7 +100,8 @@
|
||||||
.tooltip('fixTitle');
|
.tooltip('fixTitle');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.center-panel-wrapper').on ('click', '.meeting-invite', function (event) {
|
// button used to send invitations to the meeting (i.e. "mailto:" link)
|
||||||
|
$('.center-panel-wrapper').on('click', '.meeting-invite', function (event) {
|
||||||
var meetingURL = Meeting.getInstance().getURL();
|
var meetingURL = Meeting.getInstance().getURL();
|
||||||
var subject = $(this).data("invite-subject");
|
var subject = $(this).data("invite-subject");
|
||||||
var body = $(this).data("invite-body").replace("&&URL&&", meetingURL);
|
var body = $(this).data("invite-body").replace("&&URL&&", meetingURL);
|
||||||
|
|
|
@ -37,7 +37,7 @@ class @Recordings
|
||||||
{ data: "previews", orderable: false },
|
{ data: "previews", orderable: false },
|
||||||
{ data: "duration", orderable: false },
|
{ data: "duration", orderable: false },
|
||||||
{ data: "playbacks", orderable: false },
|
{ data: "playbacks", orderable: false },
|
||||||
{ data: "published", visible: false },
|
{ data: "listed", visible: false },
|
||||||
{ data: "id", orderable: false }
|
{ data: "id", orderable: false }
|
||||||
],
|
],
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
|
@ -78,18 +78,18 @@ class @Recordings
|
||||||
render: (data, type, row) ->
|
render: (data, type, row) ->
|
||||||
if type == 'display'
|
if type == 'display'
|
||||||
roomName = Meeting.getInstance().getId()
|
roomName = Meeting.getInstance().getId()
|
||||||
published = row.published
|
|
||||||
publishText = if published then 'unpublish' else 'publish'
|
|
||||||
recordingActions = $('.hidden-elements').find('.recording-actions')
|
recordingActions = $('.hidden-elements').find('.recording-actions')
|
||||||
recordingActions.find('.recording-update > i.default')
|
classes = ['recording-inaccessible', 'recording-unlisted', 'recording-published']
|
||||||
.removeClass(PUBLISHED_CLASSES.join(' '))
|
if row.published
|
||||||
.addClass(getPublishClass(published))
|
if row.listed
|
||||||
recordingActions.find('.recording-update > i.hover')
|
cls = classes[2]
|
||||||
.removeClass(PUBLISHED_CLASSES.join(' '))
|
else
|
||||||
.addClass(getPublishClass(!published))
|
cls = classes[1]
|
||||||
recordingActions.find('.recording-update')
|
else
|
||||||
.attr('data-published', published)
|
cls = classes[0]
|
||||||
.attr('title', I18n[publishText+'_recording'])
|
trigger = recordingActions.find('.recording-update-trigger')
|
||||||
|
trigger.removeClass(classes.join(' '))
|
||||||
|
trigger.addClass(cls)
|
||||||
return recordingActions.html()
|
return recordingActions.html()
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -111,9 +111,10 @@ class @Recordings
|
||||||
html: true,
|
html: true,
|
||||||
trigger: 'focus',
|
trigger: 'focus',
|
||||||
title: ->
|
title: ->
|
||||||
return I18n.are_you_sure;
|
return $(this).data("popover-title");
|
||||||
content: ->
|
content: ->
|
||||||
return $(".delete-popover-body").html()
|
bodySelector = $(this).data("popover-body")
|
||||||
|
return $(bodySelector).html()
|
||||||
}
|
}
|
||||||
$('#recordings').popover(options)
|
$('#recordings').popover(options)
|
||||||
|
|
||||||
|
@ -153,17 +154,25 @@ class @Recordings
|
||||||
# setup click handlers for the action buttons
|
# setup click handlers for the action buttons
|
||||||
setupActionHandlers: ->
|
setupActionHandlers: ->
|
||||||
table_api = this.table.api()
|
table_api = this.table.api()
|
||||||
|
|
||||||
@getTable().on 'click', '.recording-update', (event) ->
|
@getTable().on 'click', '.recording-update', (event) ->
|
||||||
btn = $(this)
|
btn = $(this)
|
||||||
row = table_api.row($(this).closest('tr')).data()
|
row = table_api.row($(this).closest('tr')).data()
|
||||||
url = $('.meeting-url').val()
|
url = $('.meeting-url').val()
|
||||||
id = row.id
|
id = row.id
|
||||||
published = btn.data('published')
|
|
||||||
|
published = btn.data('visibility') == "unlisted" ||
|
||||||
|
btn.data('visibility') == "published"
|
||||||
|
listed = btn.data('visibility') == "published"
|
||||||
|
|
||||||
btn.prop('disabled', true)
|
btn.prop('disabled', true)
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: url+'/recordings/'+id,
|
url: url+'/recordings/'+id,
|
||||||
data: {published: (!published).toString()}
|
data: {
|
||||||
|
published: published.toString(),
|
||||||
|
"meta_greenlight-listed": listed.toString()
|
||||||
|
}
|
||||||
}).done((data) ->
|
}).done((data) ->
|
||||||
|
|
||||||
).fail((data) ->
|
).fail((data) ->
|
||||||
|
|
|
@ -20,12 +20,6 @@ $.ajaxSetup({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var PUBLISHED_CLASSES = ['fa-eye-slash', 'fa-eye']
|
|
||||||
|
|
||||||
var getPublishClass = function(published) {
|
|
||||||
return PUBLISHED_CLASSES[+published];
|
|
||||||
}
|
|
||||||
|
|
||||||
var loopJoin = function() {
|
var loopJoin = function() {
|
||||||
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
||||||
jqxhr.done(function(data) {
|
jqxhr.done(function(data) {
|
||||||
|
@ -57,4 +51,4 @@ var showAlert = function(html, timeout_delay) {
|
||||||
|
|
||||||
var displayRoomURL = function() {
|
var displayRoomURL = function() {
|
||||||
$('.meeting-url').val(Meeting.getInstance().getURL());
|
$('.meeting-url').val(Meeting.getInstance().getURL());
|
||||||
}
|
};
|
||||||
|
|
|
@ -86,3 +86,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recording-update-trigger {
|
||||||
|
&.recording-inaccessible {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.recording-unlisted {
|
||||||
|
color: #e3a91e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.recording-published {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -106,3 +106,7 @@ html, body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
|
@ -76,9 +76,11 @@ class BbbController < ApplicationController
|
||||||
|
|
||||||
# PATCH /rooms/:id/recordings/:record_id
|
# PATCH /rooms/:id/recordings/:record_id
|
||||||
def update_recordings
|
def update_recordings
|
||||||
bbb_res = bbb_update_recordings(params[:record_id], params[:published] == 'true')
|
published = params[:published] == 'true'
|
||||||
|
metadata = params.select{ |k, v| k.match(/^meta_/) }
|
||||||
|
bbb_res = bbb_update_recordings(params[:record_id], published, metadata)
|
||||||
if bbb_res[:returncode]
|
if bbb_res[:returncode]
|
||||||
RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id], bbb_res[:published])
|
RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id])
|
||||||
end
|
end
|
||||||
render_bbb_response bbb_res
|
render_bbb_response bbb_res
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,22 +19,13 @@ class RecordingUpdatesJob < ApplicationJob
|
||||||
|
|
||||||
queue_as :default
|
queue_as :default
|
||||||
|
|
||||||
def perform(room, record_id, published)
|
def perform(room, record_id)
|
||||||
tries = 0
|
bbb_res = bbb_get_recordings(nil, record_id)
|
||||||
sleep_time = 2
|
recording = bbb_res[:recordings].first
|
||||||
|
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
||||||
while tries < 4
|
action: 'update',
|
||||||
bbb_res = bbb_get_recordings(nil, record_id)
|
record_id: record_id,
|
||||||
if bbb_res[:recordings].first[:published].to_s == published
|
published: recording[:published],
|
||||||
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
listed: bbb_is_recording_listed(recording)
|
||||||
action: 'update',
|
|
||||||
record_id: record_id,
|
|
||||||
published: bbb_res[:recordings].first[:published]
|
|
||||||
break
|
|
||||||
end
|
|
||||||
sleep sleep_time
|
|
||||||
sleep_time = sleep_time * 2
|
|
||||||
tries += 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
module BbbApi
|
module BbbApi
|
||||||
|
META_LISTED = "greenlight-listed"
|
||||||
|
|
||||||
def bbb_endpoint
|
def bbb_endpoint
|
||||||
Rails.configuration.bigbluebutton_endpoint || ''
|
Rails.configuration.bigbluebutton_endpoint || ''
|
||||||
end
|
end
|
||||||
|
@ -65,7 +67,14 @@ module BbbApi
|
||||||
logout_url = options[:meeting_logout_url] || "#{request.base_url}"
|
logout_url = options[:meeting_logout_url] || "#{request.base_url}"
|
||||||
moderator_password = random_password(12)
|
moderator_password = random_password(12)
|
||||||
viewer_password = random_password(12)
|
viewer_password = random_password(12)
|
||||||
meeting_options = {record: options[:meeting_recorded].to_s, logoutURL: logout_url, moderatorPW: moderator_password, attendeePW: viewer_password}
|
meeting_options = {
|
||||||
|
record: options[:meeting_recorded].to_s,
|
||||||
|
logoutURL: logout_url,
|
||||||
|
moderatorPW: moderator_password,
|
||||||
|
attendeePW: viewer_password,
|
||||||
|
"meta_#{BbbApi::META_LISTED}": false
|
||||||
|
}
|
||||||
|
|
||||||
# Create the meeting
|
# Create the meeting
|
||||||
bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options)
|
bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options)
|
||||||
|
|
||||||
|
@ -148,6 +157,8 @@ module BbbApi
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
recording[:listed] = bbb_is_recording_listed(recording)
|
||||||
end
|
end
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
@ -168,8 +179,11 @@ module BbbApi
|
||||||
response_data = bbb_exception_res exc
|
response_data = bbb_exception_res exc
|
||||||
end
|
end
|
||||||
|
|
||||||
def bbb_update_recordings(id, published)
|
def bbb_update_recordings(id, published, metadata)
|
||||||
bbb_safe_execute :publish_recordings, id, published
|
bbb_safe_execute :publish_recordings, id, published
|
||||||
|
|
||||||
|
params = { recordID: id }.merge(metadata)
|
||||||
|
bbb_safe_execute :send_api_request, "updateRecordings", params
|
||||||
end
|
end
|
||||||
|
|
||||||
def bbb_delete_recordings(id)
|
def bbb_delete_recordings(id)
|
||||||
|
@ -191,6 +205,11 @@ module BbbApi
|
||||||
response_data
|
response_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bbb_is_recording_listed(recording)
|
||||||
|
!recording[:metadata].blank? &&
|
||||||
|
recording[:metadata][BbbApi::META_LISTED.to_sym] == "true"
|
||||||
|
end
|
||||||
|
|
||||||
def success_join_res(join_url)
|
def success_join_res(join_url)
|
||||||
{
|
{
|
||||||
returncode: true,
|
returncode: true,
|
||||||
|
|
|
@ -24,6 +24,7 @@ json.recordings do
|
||||||
json.end_time recording[:endTime]
|
json.end_time recording[:endTime]
|
||||||
json.published recording[:published]
|
json.published recording[:published]
|
||||||
json.length recording[:length]
|
json.length recording[:length]
|
||||||
|
json.listed recording[:listed]
|
||||||
json.previews do
|
json.previews do
|
||||||
json.array!(recording[:previews]) do |preview|
|
json.array!(recording[:previews]) do |preview|
|
||||||
json.partial! 'preview', preview: preview
|
json.partial! 'preview', preview: preview
|
||||||
|
|
|
@ -50,6 +50,17 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
<%= t('no') %>
|
<%= t('no') %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="recording-visibility-popover">
|
||||||
|
<button type="button" class="btn btn-default btn-danger recording-update" data-visibility="inaccessible">
|
||||||
|
<%= t('innaccessible') %>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-warning recording-update" data-visibility="unlisted">
|
||||||
|
<%= t('unlisted') %>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-success recording-update" data-visibility="published">
|
||||||
|
<%= t('published') %>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="alert-template">
|
<div class="alert-template">
|
||||||
<div class="alert alert-success alert-dismissible fade in" role="alert">
|
<div class="alert alert-success alert-dismissible fade in" role="alert">
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
@ -59,12 +70,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="recording-actions">
|
<div class="recording-actions">
|
||||||
<button type="button" class="btn btn-default recording-update bottom-tooltip" data-published="">
|
<button type="button" class="btn btn-default has-popover recording-update-trigger"
|
||||||
<i class="fa default" aria-hidden="true"></i>
|
data-placement="left" data-popover-body=".recording-visibility-popover"
|
||||||
<i class="fa hover" aria-hidden="true"></i>
|
data-popover-title="<%= t('change_recording_visibility') %>">
|
||||||
|
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<a tabindex="0" role="button" class="btn btn-default has-popover delete-tooltip"
|
<a tabindex="0" role="button" class="btn btn-default has-popover delete-tooltip"
|
||||||
data-placement="top">
|
data-placement="top" data-popover-body=".delete-popover-body"
|
||||||
|
data-popover-title="<%= t('are_you_sure') %>">
|
||||||
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,6 +38,8 @@
|
||||||
en-US:
|
en-US:
|
||||||
actions: Actions
|
actions: Actions
|
||||||
are_you: Are you %{name}?
|
are_you: Are you %{name}?
|
||||||
|
are_you_sure: Are you sure?
|
||||||
|
change_recording_visibility: "Change visibility to:"
|
||||||
client:
|
client:
|
||||||
are_you_sure: Are you sure?
|
are_you_sure: Are you sure?
|
||||||
delete_recording: Delete recording
|
delete_recording: Delete recording
|
||||||
|
@ -48,6 +50,7 @@ en-US:
|
||||||
publish_recording: Publish recording
|
publish_recording: Publish recording
|
||||||
recording_deleted: Recording was deleted
|
recording_deleted: Recording was deleted
|
||||||
recording_published: Recording was published
|
recording_published: Recording was published
|
||||||
|
recording_unlisted: Recording was unlisted
|
||||||
recording_unpublished: Recording was unpublished
|
recording_unpublished: Recording was unpublished
|
||||||
unpublish_recording: Hide recording
|
unpublish_recording: Hide recording
|
||||||
copied: Copied
|
copied: Copied
|
||||||
|
@ -61,6 +64,7 @@ en-US:
|
||||||
greet_user: Welcome, %{name}
|
greet_user: Welcome, %{name}
|
||||||
greet_guest: Welcome to %{name} Meeting Space
|
greet_guest: Welcome to %{name} Meeting Space
|
||||||
hi_all: Hi Everyone
|
hi_all: Hi Everyone
|
||||||
|
innaccessible: Innaccessible
|
||||||
join: Join
|
join: Join
|
||||||
join_session: Join the current session
|
join_session: Join the current session
|
||||||
join_session_id: Join %{id}
|
join_session_id: Join %{id}
|
||||||
|
@ -82,11 +86,13 @@ en-US:
|
||||||
footer_html: "%{greenlight_link} build %{version}, Powered by %{bbb_link}"
|
footer_html: "%{greenlight_link} build %{version}, Powered by %{bbb_link}"
|
||||||
presentation: Presentation
|
presentation: Presentation
|
||||||
previously_joined_meetings: Previously Joined Sessions
|
previously_joined_meetings: Previously Joined Sessions
|
||||||
|
published: Published
|
||||||
return_to_room: Return to your personal room
|
return_to_room: Return to your personal room
|
||||||
session_url_explanation: The session will be taking place using the following URL
|
session_url_explanation: The session will be taking place using the following URL
|
||||||
start: Start
|
start: Start
|
||||||
your_personal_room: Your Personal Room
|
your_personal_room: Your Personal Room
|
||||||
thumbnails: Thumbnails
|
thumbnails: Thumbnails
|
||||||
|
unlisted: Unlisted
|
||||||
url_copy_explanation: Copy this URL to invite others to the meeting
|
url_copy_explanation: Copy this URL to invite others to the meeting
|
||||||
url_refresh_hint: Generate a new meeting URL
|
url_refresh_hint: Generate a new meeting URL
|
||||||
video: Video
|
video: Video
|
||||||
|
|
Loading…
Reference in New Issue