GRN2-129: Added server recordings and refactored adminsitrator panel (#662)

* Added server recordings and refactored adminsitrator panel

* Fixed some issues

* Fixed issue with owner email search

* Fixed issue with edit user
This commit is contained in:
farhatahmad 2019-07-22 12:46:48 -04:00 committed by Jesus Federico
parent 8c63f793a5
commit a055b88eb7
38 changed files with 1088 additions and 640 deletions

View File

@ -43,9 +43,9 @@ $(document).on('turbolinks:load', function(){
window.location.replace(url);
})
/* COLOR SELECTORS */
}
if (controller == "admins" && action == "site_settings") {
loadColourSelectors()
}

View File

@ -0,0 +1,41 @@
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
//
// Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
//
// This program is free software; you can redistribute it and/or modify it under the
// terms of the GNU Lesser General Public License as published by the Free Software
// Foundation; either version 3.0 of the License, or (at your option) any later
// version.
//
// BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along
// with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
// Handle changing of settings tabs.
$(document).on('turbolinks:load', function(){
var controller = $("body").data('controller');
var action = $("body").data('action');
if (controller == "rooms" && action == "show"
|| controller == "rooms" && action == "update"
|| controller == "users" && action == "recordings"
|| controller == "admins" && action == "server_recordings"){
// Handle recording emails.
$('.email-link').each(function(){
$(this).click(function(){
var subject = $(".username").text() + " " + t('room.mailer.subject');
var body = t('room.mailer.body') + "\n\n" + $(this).attr("data-pres-link");
var autogenerated = "\n\n" + t('room.mailer.autogenerated') + "\n";
var footer = t('room.mailer.footer');
var url = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body) + encodeURIComponent(autogenerated) + encodeURIComponent(footer);
var win = window.open(url, '_blank');
win.focus();
});
});
}
});

View File

@ -18,7 +18,10 @@ $(document).on('turbolinks:load', function(){
var controller = $("body").data('controller');
var action = $("body").data('action');
if(controller == "rooms" && action == "show" || controller == "rooms" && action == "update" || controller == "users" && action == "recordings"){
if(controller == "rooms" && action == "show"
|| controller == "rooms" && action == "update"
|| controller == "users" && action == "recordings"
|| controller == "admins" && action == "server_recordings"){
// Set a room header rename event
var configure_room_header = function(room_title){

View File

@ -39,21 +39,6 @@ $(document).on('turbolinks:load', function(){
}, 2000)
}
});
// Handle recording emails.
$('.email-link').each(function(){
$(this).click(function(){
var subject = $(".username").text() + " " + t('room.mailer.subject');
var body = t('room.mailer.body') + "\n\n" + $(this).attr("data-pres-link");
var autogenerated = "\n\n" + t('room.mailer.autogenerated') + "\n";
var footer = t('room.mailer.footer');
var url = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body) + encodeURIComponent(autogenerated) + encodeURIComponent(footer);
var win = window.open(url, '_blank');
win.focus();
});
});
}
// Display and update all fields related to creating a room in the createRoomModal

View File

@ -22,7 +22,8 @@ $(document).on('turbolinks:load', function(){
(controller == "rooms" && action == "show") ||
(controller == "rooms" && action == "update") ||
(controller == "rooms" && action == "join") ||
(controller == "users" && action == "recordings")) {
(controller == "users" && action == "recordings") ||
(controller == "admins" && action == "server_recordings")) {
// Submit search if the user hits enter
$("#search-input").keypress(function(key) {
var keyPressed = key.which

View File

@ -20,7 +20,7 @@ $(document).on('turbolinks:load', function(){
var action = $("body").data('action');
// Only run on the settings page.
if ((controller == "users" && action == "edit") || (controller == "users" && action == "update") || (controller == "admins" && action == "index")){
if ((controller == "users" && action == "edit") || (controller == "users" && action == "update")){
var settingsButtons = $('.setting-btn');
var settingsViews = $('.setting-view');

View File

@ -18,7 +18,10 @@ $(document).on('turbolinks:load', function(){
var controller = $("body").data('controller');
var action = $("body").data('action');
if(controller == "rooms" && action == "show" || controller == "rooms" && action == "update" || controller == "users" && action == "recordings"){
if(controller == "rooms" && action == "show"
|| controller == "rooms" && action == "update"
|| controller == "users" && action == "recordings"
|| controller == "admins" && action == "server_recordings"){
// Choose active header
// (Name, Length or Users)

View File

@ -84,6 +84,7 @@ a {
color: #6e7687 !important;
&:hover {
color: $primary-color !important;
background-color: $primary-color-lighten !important;
}
&:active {
background-color: $primary-color-lighten !important;
@ -130,6 +131,7 @@ input:focus, select:focus {
}
& a {
color: $primary-color !important;
border-color: $primary-color !important;
}

View File

@ -20,6 +20,7 @@ class AdminsController < ApplicationController
include Pagy::Backend
include Themer
include Emailer
include Recorder
manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve]
site_settings = [:branding, :coloring, :coloring_lighten, :coloring_darken,
@ -40,11 +41,27 @@ class AdminsController < ApplicationController
@pagy, @users = pagy(user_list)
end
# GET /admins/site_settings
def site_settings
end
# GET /admins/server_recordings
def server_recordings
server_rooms = if Rails.configuration.loadbalanced_configuration
Room.includes(:owner).where(users: { provider: user_settings_provider }).pluck(:bbb_id)
else
Room.pluck(:bbb_id)
end
@search, @order_column, @order_direction, recs =
all_recordings(server_rooms, @user_domain, params.permit(:search, :column, :direction), true, true)
@pagy, @recordings = pagy_array(recs)
end
# MANAGE USERS
# GET /admins/edit/:user_uid
def edit_user
render "admins/index", locals: { setting_id: "account" }
end
# POST /admins/promote/:user_uid
@ -111,7 +128,7 @@ class AdminsController < ApplicationController
# POST /admins/branding
def branding
@settings.update_value("Branding Image", params[:url])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") }
redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end
# POST /admins/color
@ -119,23 +136,23 @@ class AdminsController < ApplicationController
@settings.update_value("Primary Color", params[:color])
@settings.update_value("Primary Color Lighten", color_lighten(params[:color]))
@settings.update_value("Primary Color Darken", color_darken(params[:color]))
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") }
redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end
def coloring_lighten
@settings.update_value("Primary Color Lighten", params[:color])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") }
redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end
def coloring_darken
@settings.update_value("Primary Color Darken", params[:color])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") }
redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end
# POST /admins/room_authentication
def room_authentication
@settings.update_value("Room Authentication", params[:value])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") }
redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end
# POST /admins/registration_method/:method
@ -144,11 +161,11 @@ class AdminsController < ApplicationController
# Only allow change to Join by Invitation if user has emails enabled
if !Rails.configuration.enable_email_verification && new_method == Rails.configuration.registration_methods[:invite]
redirect_to admins_path,
redirect_to admin_site_settings_path,
flash: { alert: I18n.t("administrator.flash.invite_email_verification") }
else
@settings.update_value("Registration Method", new_method)
redirect_to admins_path,
redirect_to admin_site_settings_path,
flash: { success: I18n.t("administrator.flash.registration_method_updated") }
end
end
@ -156,7 +173,7 @@ class AdminsController < ApplicationController
# POST /admins/room_limit
def room_limit
@settings.update_value("Room Limit", params[:limit])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") }
redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end
private

View File

@ -16,11 +16,50 @@
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
module APIConcern
module Recorder
extend ActiveSupport::Concern
include ::BbbApi
# Fetches all recordings for a room.
def recordings(room_bbb_id, provider, search_params = {}, ret_search_params = false)
res = bbb(provider).get_recordings(meetingID: room_bbb_id)
format_recordings(res, search_params, ret_search_params)
end
# Fetches a rooms public recordings.
def public_recordings(room_bbb_id, provider, search_params = {}, ret_search_params = false)
search, order_col, order_dir, recs = recordings(room_bbb_id, provider, search_params, ret_search_params)
[search, order_col, order_dir, recs.select { |r| r[:metadata][:"gl-listed"] == "true" }]
end
# Makes paginated API calls to get recordings
def all_recordings(room_bbb_ids, provider, search_params = {}, ret_search_params = false, search_name = false)
pag_num = Rails.configuration.pagination_number
pag_loops = room_bbb_ids.length / pag_num - 1
res = { recordings: [] }
(0..pag_loops).each do |i|
pag_rooms = room_bbb_ids[pag_num * i, pag_num]
# bbb.get_recordings returns an object
# take only the array portion of the object that is returned
full_res = bbb(provider).get_recordings(meetingID: pag_rooms)
res[:recordings].push(*full_res[:recordings])
end
last_pag_room = room_bbb_ids[pag_num * (pag_loops + 1), room_bbb_ids.length % pag_num]
full_res = bbb(provider).get_recordings(meetingID: last_pag_room)
res[:recordings].push(*full_res[:recordings])
format_recordings(res, search_params, ret_search_params, search_name)
end
# Format, filter, and sort recordings to match their current use in the app
def format_recordings(api_res, search_params, ret_search_params)
def format_recordings(api_res, search_params, ret_search_params, search_name = false)
search = search_params[:search] || ""
order_col = search_params[:column] && search_params[:direction] != "none" ? search_params[:column] : "end_time"
order_dir = search_params[:column] && search_params[:direction] != "none" ? search_params[:direction] : "desc"
@ -40,7 +79,7 @@ module APIConcern
r.delete(:playback)
end
recs = filter_recordings(api_res, search)
recs = filter_recordings(api_res, search, search_name)
recs = sort_recordings(recs, order_col, order_dir)
if ret_search_params
@ -50,7 +89,7 @@ module APIConcern
end
end
def filter_recordings(api_res, search)
def filter_recordings(api_res, search, search_name = false)
api_res[:recordings].select do |r|
(!r[:metadata].nil? && ((!r[:metadata][:name].nil? &&
r[:metadata][:name].downcase.include?(search)) ||
@ -59,7 +98,8 @@ module APIConcern
((r[:metadata].nil? || r[:metadata][:name].nil?) &&
r[:name].downcase.include?(search)) ||
r[:participants].include?(search) ||
!r[:playbacks].select { |p| p[:type].downcase.include?(search) }.empty?
!r[:playbacks].select { |p| p[:type].downcase.include?(search) }.empty? ||
(search_name && Room.find_by(bbb_id: r[:meetingID]).owner.email.downcase.include?(search))
end
end

View File

@ -50,6 +50,11 @@ class RecordingsController < ApplicationController
# Ensure the user is logged into the room they are accessing.
def verify_room_ownership
redirect_to root_path unless @room.owned_by?(current_user)
if !current_user ||
!@room.owned_by?(current_user) ||
!current_user.has_cached_role?(:admin) ||
!current_user.has_cached_role?(:super_admin)
redirect_to root_path
end
end
end

View File

@ -19,6 +19,7 @@
class RoomsController < ApplicationController
include RecordingsHelper
include Pagy::Backend
include Recorder
before_action :validate_accepted_terms, unless: -> { !Rails.configuration.terms }
before_action :validate_verified_email, except: [:show, :join],
@ -56,7 +57,7 @@ class RoomsController < ApplicationController
def show
if current_user && @room.owned_by?(current_user)
@search, @order_column, @order_direction, recs =
@room.recordings(params.permit(:search, :column, :direction), true)
recordings(@room.bbb_id, @user_domain, params.permit(:search, :column, :direction), true)
@pagy, @recordings = pagy_array(recs)
@ -72,7 +73,7 @@ class RoomsController < ApplicationController
end
@search, @order_column, @order_direction, pub_recs =
@room.public_recordings(params.permit(:search, :column, :direction), true)
public_recordings(@room.bbb_id, @user_domain, params.permit(:search, :column, :direction), true)
@pagy, @public_recordings = pagy_array(pub_recs)
@ -135,7 +136,7 @@ class RoomsController < ApplicationController
search_params = params[@room.invite_path] || params
@search, @order_column, @order_direction, pub_recs =
@room.public_recordings(search_params.permit(:search, :column, :direction), true)
public_recordings(@room.bbb_id, @user_domain, search_params.permit(:search, :column, :direction), true)
@pagy, @public_recordings = pagy_array(pub_recs)

View File

@ -21,6 +21,7 @@ class UsersController < ApplicationController
include Pagy::Backend
include Emailer
include Registrar
include Recorder
before_action :find_user, only: [:edit, :update, :destroy]
before_action :ensure_unauthenticated, only: [:new, :create]
@ -103,6 +104,8 @@ class UsersController < ApplicationController
# PATCH /u/:user_uid/edit
def update
redirect_path = current_user.admin_of?(@user) ? admins_path : edit_user_path(@user)
if params[:setting] == "password"
# Update the users password.
errors = {}
@ -123,7 +126,7 @@ class UsersController < ApplicationController
if errors.empty? && @user.save
# Notify the user that their account has been updated.
flash[:success] = I18n.t("info_update_success")
redirect_to edit_user_path(@user)
redirect_to redirect_path
else
# Append custom errors.
errors.each { |k, v| @user.errors.add(k, v) }
@ -132,11 +135,11 @@ class UsersController < ApplicationController
elsif user_params[:email] != @user.email && @user.update_attributes(user_params)
@user.update_attributes(email_verified: false)
flash[:success] = I18n.t("info_update_success")
redirect_to edit_user_path(@user)
redirect_to redirect_path
elsif @user.update_attributes(user_params)
update_locale(@user)
flash[:success] = I18n.t("info_update_success")
redirect_to edit_user_path(@user)
redirect_to redirect_path
else
render :edit, params: { settings: params[:settings] }
end
@ -165,7 +168,8 @@ class UsersController < ApplicationController
def recordings
if current_user && current_user.uid == params[:user_uid]
@search, @order_column, @order_direction, recs =
current_user.all_recordings(params.permit(:search, :column, :direction), true)
all_recordings(current_user.rooms.pluck(:bbb_id), current_user.provider,
params.permit(:search, :column, :direction), true)
@pagy, @recordings = pagy_array(recs)
else
redirect_to root_path

View File

@ -19,6 +19,18 @@
module AdminsHelper
include Pagy::Frontend
# Returns the action method of the current page
def active_page
route = Rails.application.routes.recognize_path(request.env['PATH_INFO'])
route[:action]
end
# Gets the email of the room owner to which the recording belongs to
def recording_owner_email(room_id)
Room.find_by(bbb_id: room_id).owner.email
end
def display_invite
current_page?(admins_path) && invite_registration
end

View File

@ -19,7 +19,6 @@
require 'bbb_api'
class Room < ApplicationRecord
include ::APIConcern
include ::BbbApi
before_create :setup
@ -40,7 +39,7 @@ class Room < ApplicationRecord
# Checks if a room is running on the BigBlueButton server.
def running?
bbb.is_meeting_running?(bbb_id)
bbb(owner.provider).is_meeting_running?(bbb_id)
end
# Determines the invite path for the room.
@ -62,7 +61,7 @@ class Room < ApplicationRecord
# Send the create request.
begin
meeting = bbb.create_meeting(name, bbb_id, create_options)
meeting = bbb(owner.provider).create_meeting(name, bbb_id, create_options)
# Update session info.
unless meeting[:messageKey] == 'duplicateWarning'
update_attributes(sessions: sessions + 1, last_session: DateTime.now)
@ -84,10 +83,10 @@ class Room < ApplicationRecord
options[:user_is_moderator] ||= false
options[:meeting_recorded] ||= false
return call_invalid_res unless bbb
return call_invalid_res unless bbb(owner.provider)
# Get the meeting info.
meeting_info = bbb.get_meeting_info(bbb_id, nil)
meeting_info = bbb(owner.provider).get_meeting_info(bbb_id, nil)
# Determine the password to use when joining.
password = if options[:user_is_moderator]
@ -101,7 +100,7 @@ class Room < ApplicationRecord
join_opts[:userID] = uid if uid
join_opts[:joinViaHtml5] = options[:join_via_html5] if options[:join_via_html5]
bbb.join_meeting_url(bbb_id, name, password, join_opts)
bbb(owner.provider).join_meeting_url(bbb_id, name, password, join_opts)
end
# Notify waiting users that a meeting has started.
@ -111,7 +110,7 @@ class Room < ApplicationRecord
# Retrieves all the users in a room.
def participants
res = bbb.get_meeting_info(bbb_id, nil)
res = bbb(owner.provider).get_meeting_info(bbb_id, nil)
res[:attendees].map do |att|
User.find_by(uid: att[:userID], name: att[:fullName])
end
@ -120,27 +119,18 @@ class Room < ApplicationRecord
[]
end
# Fetches all recordings for a room.
def recordings(search_params = {}, ret_search_params = false)
res = bbb.get_recordings(meetingID: bbb_id)
format_recordings(res, search_params, ret_search_params)
end
# Fetches a rooms public recordings.
def public_recordings(search_params = {}, ret_search_params = false)
search, order_col, order_dir, recs = recordings(search_params, ret_search_params)
[search, order_col, order_dir, recs.select { |r| r[:metadata][:"gl-listed"] == "true" }]
def recording_count
bbb(owner.provider).get_recordings(meetingID: bbb_id)[:recordings].length
end
def update_recording(record_id, meta)
meta[:recordID] = record_id
bbb.send_api_request("updateRecordings", meta)
bbb(owner.provider).send_api_request("updateRecordings", meta)
end
# Deletes a recording from a room.
def delete_recording(record_id)
bbb.delete_recordings(record_id)
bbb(owner.provider).delete_recordings(record_id)
end
private
@ -155,7 +145,7 @@ class Room < ApplicationRecord
# Deletes all recordings associated with the room.
def delete_all_recordings
record_ids = recordings.map { |r| r[:recordID] }
record_ids = bbb(owner.provider).get_recordings(meetingID: bbb_id)[:recordings].pluck(:recordID)
delete_recording(record_ids) unless record_ids.empty?
end

View File

@ -20,7 +20,6 @@ require 'bbb_api'
class User < ApplicationRecord
rolify
include ::APIConcern
include ::BbbApi
attr_accessor :reset_token
@ -118,31 +117,8 @@ class User < ApplicationRecord
end
def self.admins_order(column, direction)
order("#{column} #{direction}")
end
def all_recordings(search_params = {}, ret_search_params = false)
pag_num = Rails.configuration.pagination_number
pag_loops = rooms.length / pag_num - 1
res = { recordings: [] }
(0..pag_loops).each do |i|
pag_rooms = rooms[pag_num * i, pag_num]
# bbb.get_recordings returns an object
# take only the array portion of the object that is returned
full_res = bbb.get_recordings(meetingID: pag_rooms.pluck(:bbb_id))
res[:recordings].push(*full_res[:recordings])
end
last_pag_room = rooms[pag_num * (pag_loops + 1), rooms.length % pag_num]
full_res = bbb.get_recordings(meetingID: last_pag_room.pluck(:bbb_id))
res[:recordings].push(*full_res[:recordings])
format_recordings(res, search_params, ret_search_params)
# Arel.sql to avoid sql injection
order(Arel.sql("#{column} #{direction}"))
end
# Activates an account and initialize a users main room

View File

@ -0,0 +1,26 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<div class="list-group list-group-transparent mb-0">
<%= link_to admins_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "index"}" do %>
<span class="icon mr-3"><i class="fas fa-users"></i></span><%= t("administrator.users.title") %>
<% end %>
<%= link_to admin_site_settings_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "site_settings"}" do %>
<span class="icon mr-4"><i class="fas fa-cogs"></i></span><%= t("administrator.site_settings.title") %>
<% end %>
<%= link_to admin_recordings_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "server_recordings"}" do %>
<span class="icon mr-4"><i class="fas fa-video"></i></i></span><%= t("administrator.recordings.title") %>
<% end %>
</div>

View File

@ -0,0 +1,91 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<div class="row">
<div class="col-12">
<div class="table-responsive">
<table id="recordings-table" class="table table-hover table-outline table-vcenter text-nowrap card-table">
<thead>
<tr>
<th data-header="name" data-order="<%= @order_column == "name" ? @order_direction : "none" %>">
<%= t("recording.table.name") %>
<% if @order_column == "name" && @order_direction == "desc" %>
<% elsif @order_column == "name" && @order_direction == "asc" %>
<% end %>
</th>
<th class="text-left" data-header="length" data-order="<%= @order_column == "length" ? @order_direction : "none" %>">
<%= t("recording.table.length") %>
<% if @order_column == "length" && @order_direction == "desc" %>
<% elsif @order_column == "length" && @order_direction == "asc" %>
<% end %>
</th>
<th class="text-left" data-header="users" data-order="<%= @order_column == "users" ? @order_direction : "none" %>">
<%= t("recording.table.users") %>
<% if @order_column == "users" && @order_direction == "desc" %>
<% elsif @order_column == "users" && @order_direction == "asc" %>
<% end %>
</th>
<th class="text-left" data-header="visibility" data-order="<%= @order_column == "visibility" ? @order_direction : "none" %>">
<%= t("recording.table.visibility") %>
<% if @order_column == "visibility" && @order_direction == "desc" %>
<% elsif @order_column == "visibility" && @order_direction == "asc" %>
<% end %>
</th>
<th data-header="formats" data-order="<%= @order_column == "formats" ? @order_direction : "none" %>">
<%= t("recording.table.formats") %>
<% if @order_column == "formats" && @order_direction == "desc" %>
<% elsif @order_column == "formats" && @order_direction == "asc" %>
<% end %>
</th>
<th class="text-center"><i class="icon-settings"></i></th>
</tr>
</thead>
<tbody id="recording-table">
<tr id="no_recordings_found" style="display: none;">
<td colspan="7" class="text-center h4 p-6 font-weight-normal" >
<%= t("recording.no_matched_recordings", inject:"") %>
</td>
</tr>
<% if @recordings.empty? %>
<tr>
<td colspan="7" class="text-center h4 p-6 font-weight-normal">
<%= t("administrator.recordings.no_recordings") %>
</td>
</tr>
<% else %>
<% @recordings.each do |recording| %>
<%= render "admins/components/server_recording_row", recording: recording %>
<% end %>
<% end %>
</tbody>
</table>
<% if !@recordings.empty?%>
<div class="float-right mr-4 mt-4">
<%== pagy_bootstrap_nav(@pagy) %>
</div>
<% end %>
</div>
</div>
</div>

View File

@ -0,0 +1,81 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<tr>
<td>
<div id="recording-title" class="form-inline edit_hover_class" data-recordid="<%= recording[:recordID] %>" data-room-uid="<%= room_uid_from_bbb(recording[:meetingID]) %>" data-path="<%= update_room_path(room_uid: room_uid_from_bbb(recording[:meetingID])) %>">
<text id='recording-text'>
<% if recording[:metadata][:name] %>
<%= recording[:metadata][:name] %>
<% else %>
<%= recording[:name] %>
<% end %>
</text>
<a><i id="edit-record" class="fa fa-edit align-top ml-2" data-edit-recordid="<%= recording[:recordID] %>"></i></a>
</div>
<div class="small text-muted">
<%= t("recording.recorded_on", date: recording_date(recording[:startTime])) %>
</div>
<div class="small text-muted">
<%= recording_owner_email(recording[:meetingID]) %>
</div>
</td>
<td id="recording-length" class="text-left" data-full-length="<%= recording[:playbacks].empty? ? 0 : recording[:playbacks].first[:length]%>">
<%= recording_length(recording[:playbacks]) %>
</td>
<td id="recording-users" class="text-left">
<%= recording[:participants] || "-" %>
</td>
<td class="text-left">
<div class="dropdown">
<% if recording[:metadata][:"gl-listed"] == "true" %>
<button class="btn btn-sm btn-secondary dropdown-toggle" data-toggle="dropdown"><i class="dropdown-icon fas fa-globe px-2"></i> <%= t("recording.visibility.public") %></button>
<% else %>
<button class="btn btn-sm btn-secondary dropdown-toggle" data-toggle="dropdown"><i class="dropdown-icon fas fa-link px-2"></i> <%= t("recording.visibility.unlisted") %></button>
<% end %>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<%= button_to update_recording_path(meetingID: recording[:meetingID], record_id: recording[:recordID], state: "public"), class: "dropdown-item" do %>
<i class="dropdown-icon fas fa-globe"></i> <%= t("recording.visibility.public") %>
<% end %>
<%= button_to update_recording_path(meetingID: recording[:meetingID], record_id: recording[:recordID], state: "unlisted"), class: "dropdown-item" do %>
<i class="dropdown-icon fas fa-link"></i> <%= t("recording.visibility.unlisted") %>
<% end %>
</div>
</div>
</td>
<td>
<% sorted_formats = recording[:playbacks].sort_by! { |p| p[:type] } %>
<% sorted_formats.each do |p| %>
<%= link_to t("recording.format.#{p[:type]}"), p[:url], class: "btn btn-sm btn-primary", target: "_blank" %>
<% end %>
</td>
<td class="text-center">
<div class="item-action dropdown">
<a href="javascript:void(0)" data-toggle="dropdown" class="icon">
<i class="fas fa-ellipsis-v px-4"></i>
</a>
<div class="dropdown-menu dropdown-menu-right">
<% p = recording[:playbacks].find do |p| p.key?(:length) end %>
<% if p %>
<a class="dropdown-item email-link" data-pres-link="<%= p[:url] %>"><i class="dropdown-icon far fa-envelope"></i> <%= t("recording.email") %></a>
<div class="dropdown-divider"></div>
<% end %>
<%= button_to delete_recording_path(meetingID: recording[:meetingID], record_id: recording[:recordID]), method: :delete, class: "dropdown-item" do %>
<i class="dropdown-icon far fa-trash-alt"></i> <%= t("delete") %>
<% end %>
</div>
</div>
</td>
</tr>

View File

@ -0,0 +1,26 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<%= content_tag(:div, id: setting_id, class: "setting-view card") do %>
<div class="card-body p-6">
<div class="card-title text-primary">
<div class="form-group">
<%= render "shared/components/subtitle", subtitle: setting_title, search: search %>
</div>
</div>
<%= render "admins/components/#{setting_id}" %>
</div>
<% end %>

View File

@ -13,6 +13,21 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<% if @role.present? %>
<%= render "shared/components/admins_tags" %>
<% end %>
@ -146,3 +161,4 @@
</div>
<%= render "shared/modals/invite_user_modal" %>

View File

@ -0,0 +1,27 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<div class="container pt-6">
<%= render "shared/components/subtitle", subtitle: t("administrator.title"), search: false %>
<div class="row">
<div class="col-lg-3 mb-4">
<%= render "admins/components/menu_buttons" %>
</div>
<div id="edit_user" class="col-lg-9">
<%= render "shared/settings/setting_view", setting_id: "account", setting_title: t("settings.account.subtitle") %>
</div>
</div>
</div>

View File

@ -18,26 +18,10 @@
<div class="row">
<div class="col-lg-3 mb-4">
<div class="list-group list-group-transparent mb-0">
<button id="users" class="list-group-item list-group-item-action setting-btn <%= "active" if !params[:setting] || params[:setting] == "users"%>">
<span class="icon mr-3"><i class="fas fa-users"></i></span><%= t("administrator.users.title") %>
</button>
<button id="site_settings" class="list-group-item list-group-item-action setting-btn <%= "active" if params[:setting] == "site_settings"%>">
<span class="icon mr-4"><i class="fas fa-cogs"></i></span><%= t("administrator.site_settings.title") %>
</button>
</div>
<%= render "admins/components/menu_buttons" %>
</div>
<div class="col-lg-9">
<% if defined?(setting_id) && setting_id == "account" %>
<%= render "shared/settings/setting_view", setting_id: "account", setting_title: t("administrator.users.edit.title") %>
<% else %>
<%= render "shared/settings/setting_view", admin_view: true, setting_id: "users", setting_title: t("administrator.users.title") %>
<%= render "shared/settings/setting_view", admin_view: true, setting_id: "site_settings", setting_title: t("administrator.site_settings.subtitle") %>
<% end %>
<%= render "shared/modals/delete_account_modal", delete_location: relative_root %>
<div id="users" class="col-lg-9">
<%= render "admins/components/setting_view", setting_id: "users", setting_title: t("administrator.users.title"), search: true %>
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<div class="container pt-6">
<%= render "shared/components/subtitle", subtitle: t("administrator.title"), search: false %>
<div class="row">
<div class="col-lg-3 mb-4">
<%= render "admins/components/menu_buttons" %>
</div>
<div id="server_recordings" class="col-lg-9">
<%= render "admins/components/setting_view", setting_id: "recordings", setting_title: t("administrator.recordings.title"), search: true %>
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<div class="container pt-6">
<%= render "shared/components/subtitle", subtitle: t("administrator.title"), search: false %>
<div class="row">
<div class="col-lg-3 mb-4">
<%= render "admins/components/menu_buttons" %>
</div>
<div id="site_settings" class="col-lg-9">
<%= render "admins/components/setting_view", setting_id: "settings", setting_title: t("administrator.site_settings.subtitle"), search: false %>
</div>
</div>
</div>

View File

@ -89,7 +89,7 @@
<%= render "shared/components/room_block", room: room %>
<% end %>
</div>
<%= render "shared/modals/delete_room_modal", room: room %>
<%= render "shared/modals/delete_room_modal", recording_count: room.recording_count, room: room %>
<% end %>
<% end %>
<% unless room_limit_exceeded %>

View File

@ -34,8 +34,8 @@
<div class="card-footer">
<p>
<%= t("modal.delete_room.warning").html_safe %>
<% if room.recordings.length > 0 %>
<%= t("modal.delete_room.recording_warning", recordings_num: room.recordings.length).html_safe %>
<% if recording_count > 0 %>
<%= t("modal.delete_room.recording_warning", recordings_num: recording_count).html_safe %>
<% end %>
</p>
</div>

View File

@ -20,17 +20,17 @@
<div class="col-lg-3 mb-4">
<div class="list-group list-group-transparent mb-0">
<button id="account" class="list-group-item list-group-item-action setting-btn <%= "active" if !params[:setting] || params[:setting] == "account"%>">
<button id="account" class="list-group-item list-group-item-action dropdown-item setting-btn <%= "active" if !params[:setting] || params[:setting] == "account"%>">
<span class="icon mr-3"><i class="fas fa-user"></i></span><%= t("settings.account.title") %>
</button>
<% if @user.social_uid.nil? %>
<button id="password" class="list-group-item list-group-item-action setting-btn <%= "active" if params[:setting] == "password"%>">
<button id="password" class="list-group-item list-group-item-action dropdown-item setting-btn <%= "active" if params[:setting] == "password"%>">
<span class="icon mr-3"><i class="fas fa-lock"></i></span><%= t("settings.password.title") %>
</button>
<% end %>
<button id="delete" class="list-group-item list-group-item-action setting-btn <%= "active" if params[:setting] == "delete"%>">
<button id="delete" class="list-group-item list-group-item-action dropdown-item setting-btn <%= "active" if params[:setting] == "delete"%>">
<span class="icon mr-3"><i class="fas fa-trash-alt"></i></span><%= t("settings.delete.title") %>
</button>
</div>

View File

@ -73,6 +73,9 @@ en:
registration_method_updated: Registration method successfully updated
settings: Site Settings successfully changed
unauthorized: You are not authorized to perform actions on this user
recordings:
title: Server Recordings
no_recordings: This server has no recordings.
title: Organization Settings
users:
invite: Invite User

View File

@ -37,6 +37,8 @@ Rails.application.routes.draw do
resources :admins, only: [:index]
scope '/admins' do
get '/site_settings', to: 'admins#site_settings', as: :admin_site_settings
get '/recordings', to: 'admins#server_recordings', as: :admin_recordings
post '/branding', to: 'admins#branding', as: :admin_branding
post '/coloring', to: 'admins#coloring', as: :admin_coloring
post '/room_authentication', to: 'admins#room_authentication', as: :admin_room_authentication

View File

@ -12,15 +12,9 @@ module BbbApi
end
# Sets a BigBlueButtonApi object for interacting with the API.
def bbb
def bbb(user_provider)
if Rails.configuration.loadbalanced_configuration
if instance_of? Room
# currently in the Room Model
user_domain = retrieve_provider_info(owner.provider)
elsif instance_of? User
# currently in the User Model
user_domain = retrieve_provider_info(provider)
end
user_domain = retrieve_provider_info(user_provider)
BigBlueButton::BigBlueButtonApi.new(remove_slash(user_domain["apiURL"]), user_domain["secret"], "0.8")
else

View File

@ -0,0 +1,567 @@
# frozen_string_literal: true
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
#
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
require "rails_helper"
require 'bigbluebutton_api'
shared_examples_for "recorder" do
let(:controller) { described_class } # the class that includes the concern
before do
@user = create(:user)
@room = @user.main_room
allow_any_instance_of(Room).to receive(:owner).and_return(@user)
end
it "should properly find meeting recordings" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return(
recordings: [
{
name: "Example",
playback: {
format:
{
type: "presentation"
}
}
}
]
)
expect(recordings(@room.bbb_id, @room.owner.provider)).to contain_exactly(
name: "Example",
playbacks:
[
{
type: "presentation"
}
]
)
end
it "gets all filtered and sorted recordings for the user" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return(
recordings: [
{
meetingID: @room.bbb_id,
name: "Example",
participants: "3",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
meetingID: @room.bbb_id,
name: "aExamaaa",
participants: "5",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
}
},
{
meetingID: @room.bbb_id,
name: "test",
participants: "1",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
meetingID: @room.bbb_id,
name: "Exam",
participants: "1",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
name: "z",
}
}
]
)
expect(all_recordings(@user.rooms.pluck(:bbb_id), @user.provider, search: "Exam", column: "name",
direction: "desc")).to eq(
[
{
meetingID: @room.bbb_id,
name: "Example",
participants: "3",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
},
{
meetingID: @room.bbb_id,
name: "aExamaaa",
participants: "5",
playbacks:
[
{
type: "other"
}
],
metadata: {
"gl-listed": "false",
}
}
]
)
end
context '#filtering' do
before do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return(
recordings: [
{
meetingID: @room.bbb_id,
name: "Example",
participants: "3",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
meetingID: @room.bbb_id,
name: "aExamaaa",
participants: "5",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
}
},
{
meetingID: @room.bbb_id,
name: "test",
participants: "1",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
meetingID: @room.bbb_id,
name: "Exam",
participants: "1",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
name: "metadata",
}
}
]
)
end
it "should filter recordings on name" do
expect(recordings(@room.bbb_id, @room.owner.provider, search: "Exam")).to contain_exactly(
{
meetingID: @room.bbb_id,
name: "aExamaaa",
participants: "5",
playbacks:
[
{
type: "other"
}
],
metadata: {
"gl-listed": "false",
}
},
meetingID: @room.bbb_id,
name: "Example",
participants: "3",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
)
end
it "should filter recordings on participants" do
expect(recordings(@room.bbb_id, @room.owner.provider, search: "5")).to contain_exactly(
meetingID: @room.bbb_id,
name: "aExamaaa",
participants: "5",
playbacks:
[
{
type: "other"
}
],
metadata: {
"gl-listed": "false",
}
)
end
it "should filter recordings on format" do
expect(recordings(@room.bbb_id, @room.owner.provider, search: "presentation")).to contain_exactly(
{
meetingID: @room.bbb_id,
name: "test",
participants: "1",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
},
meetingID: @room.bbb_id,
name: "Example",
participants: "3