diff --git a/.gitignore b/.gitignore index 7a58b323..d8d3ef6c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ vendor/bundle /public/assets/** /public/b/** +# Ignore uploaded files +/storage + # Ignore production paths. /db/production /db/production-postgres diff --git a/Gemfile b/Gemfile index 17f17ca3..57d811e7 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,7 @@ gem 'coffee-rails', '~> 4.2' # gem 'mini_racer', platforms: :ruby # Use jquery as the JavaScript library -gem 'jquery-rails', '~> 4.3.3' +gem 'jquery-rails', '~> 4.4' gem 'jquery-ui-rails' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks @@ -56,7 +56,7 @@ gem 'bn-ldap-authentication', '~> 0.1.4' gem 'omniauth-bn-office365', '~> 0.1.1' # BigBlueButton API wrapper. -gem 'bigbluebutton-api-ruby' +gem 'bigbluebutton-api-ruby', git: 'https://github.com/mconf/bigbluebutton-api-ruby.git', branch: 'master' # Front-end. gem 'bootstrap', '~> 4.3.1' @@ -76,6 +76,10 @@ gem 'redcarpet' # For limiting access based on user roles gem 'cancancan', '~> 2.0' +# Active Storage gems +gem 'aws-sdk-s3', '~> 1.75' +gem 'google-cloud-storage', '~> 1.26' + group :production do # Use a postgres database in production. gem 'pg', '~> 0.18' diff --git a/Gemfile.lock b/Gemfile.lock index 06be1aca..2395d097 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,6 +6,20 @@ GIT tabler-rubygem (0.1.4.1) autoprefixer-rails (>= 6.0.3) +GIT + remote: https://github.com/mconf/bigbluebutton-api-ruby.git + revision: 91dc495324a6b7e162773227ec3650f8a5b39c50 + branch: master + specs: + bigbluebutton-api-ruby (1.7.0) + childprocess (>= 1.0.1) + ffi (>= 1.9.24) + json (>= 1.8.6) + nokogiri (>= 1.10.4) + rack (>= 1.6.11) + rubyzip (>= 1.3.0) + xml-simple (~> 1.1) + GEM remote: https://rubygems.org/ specs: @@ -58,9 +72,23 @@ GEM ast (2.4.0) autoprefixer-rails (9.7.6) execjs + aws-eventstream (1.1.0) + aws-partitions (1.343.0) + aws-sdk-core (3.104.1) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.36.0) + aws-sdk-core (~> 3, >= 3.99.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.75.0) + aws-sdk-core (~> 3, >= 3.104.1) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.1) + aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.13) - bigbluebutton-api-ruby (1.7.0) - xml-simple (~> 1.1) bindex (0.8.1) bn-ldap-authentication (0.1.4) net-ldap (~> 0) @@ -73,6 +101,7 @@ GEM builder (3.2.4) byebug (11.1.3) cancancan (2.3.0) + childprocess (4.0.0) coffee-rails (4.2.2) coffee-script (>= 2.2.0) railties (>= 4.0.0) @@ -90,7 +119,11 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.6) + declarative (0.0.20) + declarative-option (0.1.0) diff-lcs (1.3) + digest-crc (0.6.1) + rake (~> 13.0) docile (1.3.2) dotenv (2.7.5) dotenv-rails (2.7.5) @@ -112,16 +145,46 @@ GEM sassc (>= 1.11) globalid (0.4.2) activesupport (>= 4.2.0) + google-api-client (0.42.1) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.3.3) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.0.1) + google-cloud-storage (1.26.2) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.13.0) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.14) hashdiff (1.0.1) hashie (4.1.0) hiredis (0.6.3) http_accept_language (2.1.1) + httpclient (2.8.3) i18n (1.8.2) concurrent-ruby (~> 1.0) i18n-language-mapping (0.1.2) jbuilder (2.10.0) activesupport (>= 5.0.0) - jquery-rails (4.3.5) + jmespath (1.4.0) + jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) @@ -144,6 +207,7 @@ GEM mini_mime (>= 0.1.1) marcel (0.3.3) mimemagic (~> 0.3.2) + memoist (0.16.2) method_source (1.0.0) mimemagic (0.3.5) mini_mime (1.0.2) @@ -186,6 +250,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack + os (1.1.0) pagy (3.8.1) parallel (1.19.1) parser (2.7.1.3) @@ -194,7 +259,7 @@ GEM popper_js (1.16.0) public_suffix (4.0.5) puma (3.12.6) - rack (2.2.2) + rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) rails (5.2.4.3) @@ -237,8 +302,13 @@ GEM redis (4.1.4) remote_syslog_logger (1.0.4) syslog_protocol + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) request_store (1.5.0) rack (>= 1.4) + retriable (3.1.2) rexml (3.2.4) rspec-core (3.9.2) rspec-support (~> 3.9.3) @@ -268,6 +338,7 @@ GEM rubocop-ast (0.0.3) parser (>= 2.7.0.1) ruby-progressbar (1.10.1) + rubyzip (2.3.0) safe_yaml (1.0.5) sassc (2.3.0) ffi (~> 1.9) @@ -280,6 +351,11 @@ GEM sequel (5.32.0) shoulda-matchers (3.1.3) activesupport (>= 4.0.0) + signet (0.14.0) + addressable (~> 2.3) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) simplecov (0.16.1) docile (~> 1.1) json (>= 1.8, < 3) @@ -313,6 +389,7 @@ GEM thread_safe (~> 0.1) tzinfo-data (1.2020.1) tzinfo (>= 1.0.0) + uber (0.1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) unicode-display_width (1.7.0) @@ -335,8 +412,9 @@ PLATFORMS DEPENDENCIES action-cable-testing + aws-sdk-s3 (~> 1.75) bcrypt (~> 3.1.7) - bigbluebutton-api-ruby + bigbluebutton-api-ruby! bn-ldap-authentication (~> 0.1.4) bootsnap (>= 1.1.0) bootstrap (~> 4.3.1) @@ -348,11 +426,12 @@ DEPENDENCIES factory_bot_rails faker font-awesome-sass (~> 5.9.0) + google-cloud-storage (~> 1.26) hiredis http_accept_language i18n-language-mapping (~> 0.1.1) jbuilder (~> 2.5) - jquery-rails (~> 4.3.3) + jquery-rails (~> 4.4) jquery-ui-rails listen (~> 3.0.5) lograge diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 00000000..737b8540 Binary files /dev/null and b/app/assets/images/favicon.ico differ diff --git a/app/assets/javascripts/admins.js b/app/assets/javascripts/admins.js index f2db3fe8..3a1702d5 100644 --- a/app/assets/javascripts/admins.js +++ b/app/assets/javascripts/admins.js @@ -86,7 +86,11 @@ $(document).on('turbolinks:load', function(){ }) } else if(action == "site_settings"){ - loadColourSelectors() + var urlParams = new URLSearchParams(window.location.search); + // Only load the colour selectors if on the appearance tab + if (urlParams.get("tab") == null || urlParams.get("tab") == "appearance") { + loadColourSelectors() + } } else if (action == "roles"){ // Refreshes the new role modal @@ -119,19 +123,30 @@ $(document).on('turbolinks:load', function(){ // Change the branding image to the image provided function changeBrandingImage(path) { var url = $("#branding-url").val() - $.post(path, {value: url}) + $.post(path, {value: url, tab: "appearance"}) } // Change the Legal URL to the one provided function changeLegalURL(path) { var url = $("#legal-url").val() - $.post(path, {value: url}) + $.post(path, {value: url, tab: "administration"}) } // Change the Privacy Policy URL to the one provided function changePrivacyPolicyURL(path) { var url = $("#privpolicy-url").val() - $.post(path, {value: url}) + $.post(path, {value: url, tab: "administration"}) +} + +// Display the maintenance Banner +function displayMaintenanceBanner(path) { + var message = $("#maintenance-banner").val() + $.post(path, {value: message, tab: "administration"}) +} + +// Clear the maintenance Banner +function clearMaintenanceBanner(path) { + $.post(path, {value: "", tab: "administration"}) } function mergeUsers() { @@ -234,13 +249,13 @@ function loadColourSelectors() { }) pickrLighten.on("save", (color, instance) => { - $.post($("#coloring-path-lighten").val(), {value: color.toHEXA().toString()}).done(function() { + $.post($("#coloring-path-lighten").val(), {value: color.toHEXA().toString(), tab: "appearance"}).done(function() { location.reload() }); }) pickrDarken.on("save", (color, instance) => { - $.post($("#coloring-path-darken").val(), {value: color.toHEXA().toString()}).done(function() { + $.post($("#coloring-path-darken").val(), {value: color.toHEXA().toString(), tab: "appearance"}).done(function() { location.reload() }); }) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 6c82146b..72c83bb7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -27,7 +27,7 @@ // about supported directives. // //= require turbolinks -//= require jquery +//= require jquery3 //= require tabler //= require tabler.plugins //= require jquery_ujs diff --git a/app/assets/javascripts/cookies.js b/app/assets/javascripts/cookies.js index be2c0560..912ba412 100644 --- a/app/assets/javascripts/cookies.js +++ b/app/assets/javascripts/cookies.js @@ -26,9 +26,12 @@ $(document).on('turbolinks:load', function(){ }) $("#maintenance-close").click(function(event) { - //create a cookie that lasts 1 year - var cookieDate = new Date(); - cookieDate.setFullYear(cookieDate.getFullYear() + 1); //1 year from now + //create a cookie that lasts 1 day + + var cookieDate = new Date() + cookieDate.setDate(cookieDate.getDate() + 1) //1 day from now + console.log("maintenance_window=" + $(event.target).data("date") + "; path=/; expires=" + cookieDate.toUTCString() + ";") + document.cookie = "maintenance_window=" + $(event.target).data("date") + "; path=/; expires=" + cookieDate.toUTCString() + ";" }) }) diff --git a/app/assets/javascripts/room.js b/app/assets/javascripts/room.js index 8137d0c6..fd346c18 100644 --- a/app/assets/javascripts/room.js +++ b/app/assets/javascripts/room.js @@ -48,6 +48,8 @@ $(document).on('turbolinks:load', function(){ $("#create-room-block").click(function(){ showCreateRoom(this) }) + + checkIfAutoJoin() } // Autofocus on the Room Name label when creating a room only @@ -129,6 +131,27 @@ $(document).on('turbolinks:load', function(){ $("#user-list").append(listItem) } }) + + $("#presentation-upload").change(function(data) { + var file = data.target.files[0] + + // Check file type and size to make sure they aren't over the limit + if (validFileUpload(file)) { + $("#presentation-upload-label").text(file.name) + } else { + $("#invalid-file-type").show() + $("#presentation-upload").val("") + $("#presentation-upload-label").text($("#presentation-upload-label").data("placeholder")) + } + }) + + $(".preupload-room").click(function() { + updatePreuploadPresentationModal(this) + }) + + $("#remove-presentation").click(function(data) { + removePreuploadPresentation($(this).data("remove")) + }) } }); @@ -138,11 +161,11 @@ function showCreateRoom(target) { $("#room_access_code").val(null) $("#createRoomModal form").attr("action", $("body").data('relative-root')) - $("#room_mute_on_join").prop("checked", $("#room_mute_on_join").data("default")) $("#room_require_moderator_approval").prop("checked", $("#room_require_moderator_approval").data("default")) $("#room_anyone_can_start").prop("checked", $("#room_anyone_can_start").data("default")) $("#room_all_join_moderator").prop("checked", $("#room_all_join_moderator").data("default")) + $("#room_recording").prop("checked", $("#room_recording").data("default")) //show all elements & their children with a create-only class $(".create-only").each(function() { @@ -197,12 +220,12 @@ function showDeleteRoom(target) { //Update the createRoomModal to show the correct current settings function updateCurrentSettings(settings_path){ // Get current room settings and set checkbox - $.get(settings_path, function(room_settings) { - var settings = JSON.parse(room_settings) + $.get(settings_path, function(settings) { $("#room_mute_on_join").prop("checked", $("#room_mute_on_join").data("default") || settings.muteOnStart) $("#room_require_moderator_approval").prop("checked", $("#room_require_moderator_approval").data("default") || settings.requireModeratorApproval) $("#room_anyone_can_start").prop("checked", $("#room_anyone_can_start").data("default") || settings.anyoneCanStart) $("#room_all_join_moderator").prop("checked", $("#room_all_join_moderator").data("default") || settings.joinModerator) + $("#room_recording").prop("checked", $("#room_recording").data("default") || Boolean(settings.recording)) }) } @@ -264,3 +287,44 @@ function removeSharedUser(target) { parentLI.classList.add("remove-shared") } } + +function updatePreuploadPresentationModal(target) { + $.get($(target).data("settings-path"), function(presentation) { + if(presentation.attached) { + $("#current-presentation").show() + $("#presentation-name").text(presentation.name) + $("#change-pres").show() + $("#use-pres").hide() + } else { + $("#current-presentation").hide() + $("#change-pres").hide() + $("#use-pres").show() + } + }); + + $("#preuploadPresentationModal form").attr("action", $(target).data("path")) + $("#remove-presentation").data("remove", $(target).data("remove")) + + // Reset values to original to prevent confusion + $("#presentation-upload").val("") + $("#presentation-upload-label").text($("#presentation-upload-label").data("placeholder")) + $("#invalid-file-type").hide() +} + +function removePreuploadPresentation(path) { + $.post(path, {}) +} + +function validFileUpload(file) { + return file.size/1024/1024 <= 30 +} + +// Automatically click the join button if this is an action cable reload +function checkIfAutoJoin() { + var url = new URL(window.location.href) + + if (url.searchParams.get("reload") == "true") { + $("#joiner-consent").click() + $("#room-join").click() + } +} \ No newline at end of file diff --git a/app/assets/javascripts/wait.js b/app/assets/javascripts/wait.js index f538954c..f619f26a 100644 --- a/app/assets/javascripts/wait.js +++ b/app/assets/javascripts/wait.js @@ -27,6 +27,7 @@ $(document).on("turbolinks:load", function(){ }, { connected: function() { console.log("connected"); + setTimeout(startRefreshTimeout, 120000); }, disconnected: function(data) { @@ -40,7 +41,7 @@ $(document).on("turbolinks:load", function(){ received: function(data){ console.log(data); - if(data.action = "started"){ + if(data.action == "started"){ request_to_join_meeting(); } } @@ -68,3 +69,10 @@ var request_to_join_meeting = function(){ } }); } + +// Refresh the page after 2 mins and attempt to reconnect to ActionCable +function startRefreshTimeout() { + var url = new URL(window.location.href) + url.searchParams.set("reload","true") + window.location.href = url.href +} diff --git a/app/assets/stylesheets/admins.scss b/app/assets/stylesheets/admins.scss index 3e872d74..65036144 100644 --- a/app/assets/stylesheets/admins.scss +++ b/app/assets/stylesheets/admins.scss @@ -84,10 +84,8 @@ color: white !important; } -.manage-users-tab { - &:hover { - cursor: pointer; - } +#manage-users-nav.nav-tabs .nav-item { + margin-bottom: -1px; } #merge-account-arrow { @@ -96,4 +94,8 @@ right: 47%; z-index: 999; background: white; -} \ No newline at end of file +} + +.admin-tabs { + justify-content: space-around; +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 48d44c02..32f192a3 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -145,6 +145,10 @@ input:focus { border-color: $primary !important; } +.input-group button:focus { + box-shadow: none !important; +} + .list-group-item-action.active { color: $primary; } diff --git a/app/assets/stylesheets/rooms.scss b/app/assets/stylesheets/rooms.scss index ec6b664f..16627859 100644 --- a/app/assets/stylesheets/rooms.scss +++ b/app/assets/stylesheets/rooms.scss @@ -113,3 +113,12 @@ background: lightgray; pointer-events: none; } + +#recording-table .edit_hover_class { + word-break: break-all; + white-space: normal; +} + +#room-owner-name { + line-height: 12px; +} \ No newline at end of file diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index baabb19e..24f6d91d 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -47,6 +47,7 @@ class AdminsController < ApplicationController # GET /admins/site_settings def site_settings + @tab = params[:tab] || "appearance" end # GET /admins/server_recordings @@ -191,6 +192,7 @@ class AdminsController < ApplicationController # POST /admins/update_settings def update_settings + tab = params[:tab] || "settings" @settings.update_value(params[:setting], params[:value]) flash_message = I18n.t("administrator.flash.settings") @@ -199,7 +201,7 @@ class AdminsController < ApplicationController flash_message += ". " + I18n.t("administrator.site_settings.recording_visibility.warning") end - redirect_to admin_site_settings_path, flash: { success: flash_message } + redirect_to admin_site_settings_path(tab: tab), flash: { success: flash_message } end # POST /admins/color @@ -207,7 +209,7 @@ class AdminsController < ApplicationController @settings.update_value("Primary Color", params[:value]) @settings.update_value("Primary Color Lighten", color_lighten(params[:value])) @settings.update_value("Primary Color Darken", color_darken(params[:value])) - redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path(tab: "appearance"), flash: { success: I18n.t("administrator.flash.settings") } end # POST /admins/registration_method/:method @@ -216,11 +218,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 admin_site_settings_path, + redirect_to admin_site_settings_path(tab: "settings"), flash: { alert: I18n.t("administrator.flash.invite_email_verification") } else @settings.update_value("Registration Method", new_method) - redirect_to admin_site_settings_path, + redirect_to admin_site_settings_path(tab: "settings"), flash: { success: I18n.t("administrator.flash.registration_method_updated") } end end @@ -229,7 +231,7 @@ class AdminsController < ApplicationController def clear_auth User.include_deleted.where(provider: @user_domain).update_all(social_uid: nil) - redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path(tab: "settings"), flash: { success: I18n.t("administrator.flash.settings") } end # POST /admins/clear_cache @@ -237,14 +239,14 @@ class AdminsController < ApplicationController Rails.cache.delete("#{@user_domain}/getUser") Rails.cache.delete("#{@user_domain}/getUserGreenlightCredentials") - redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path(tab: "settings"), flash: { success: I18n.t("administrator.flash.settings") } end # POST /admins/log_level def log_level Rails.logger.level = params[:value].to_i - redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path(tab: "administration"), flash: { success: I18n.t("administrator.flash.settings") } end # ROOM CONFIGURATION diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f3b4f30a..6c767c20 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -84,9 +84,9 @@ class ApplicationController < ActionController::Base help: I18n.t("errors.maintenance.help"), } end - if Rails.configuration.maintenance_window.present? - unless cookies[:maintenance_window] == Rails.configuration.maintenance_window - flash.now[:maintenance] = Rails.configuration.maintenance_window + if @settings.get_value("Maintenance Banner").present? + unless cookies[:maintenance_window] == @settings.get_value("Maintenance Banner") + flash.now[:maintenance] = @settings.get_value("Maintenance Banner") end end end @@ -182,6 +182,18 @@ class ApplicationController < ActionController::Base end helper_method :shared_access_allowed + # Indicates whether users are allowed to share rooms + def recording_consent_required? + @settings.get_value("Require Recording Consent") == "true" + end + helper_method :recording_consent_required? + + # Returns a list of allowed file types + def allowed_file_types + Rails.configuration.allowed_file_types + end + helper_method :allowed_file_types + # Returns the page that the logo redirects to when clicked on def home_page return admins_path if current_user.has_role? :super_admin diff --git a/app/controllers/concerns/bbb_server.rb b/app/controllers/concerns/bbb_server.rb index f8a99a56..0a343bb9 100644 --- a/app/controllers/concerns/bbb_server.rb +++ b/app/controllers/concerns/bbb_server.rb @@ -61,7 +61,7 @@ module BbbServer # Creates a meeting on the BigBlueButton server. def start_session(room, options = {}) create_options = { - record: options[:meeting_recorded].to_s, + record: options[:record].to_s, logoutURL: options[:meeting_logout_url] || '', moderatorPW: room.moderator_pw, attendeePW: room.attendee_pw, @@ -77,11 +77,17 @@ module BbbServer # Send the create request. begin - meeting = bbb_server.create_meeting(room.name, room.bbb_id, create_options) - # Update session info. + meeting = if room.presentation.attached? + modules = BigBlueButton::BigBlueButtonModules.new + logger.info("Support: Room #{room.uid} starting using presentation: #{rails_blob_url(room.presentation)}") + modules.add_presentation(:url, rails_blob_url(room.presentation)) + bbb_server.create_meeting(room.name, room.bbb_id, create_options, modules) + else + bbb_server.create_meeting(room.name, room.bbb_id, create_options) + end + unless meeting[:messageKey] == 'duplicateWarning' - room.update_attributes(sessions: room.sessions + 1, - last_session: DateTime.now) + room.update_attributes(sessions: room.sessions + 1, last_session: DateTime.now) end rescue BigBlueButton::BigBlueButtonException => e puts "BigBlueButton failed on create: #{e.key}: #{e.message}" diff --git a/app/controllers/concerns/joiner.rb b/app/controllers/concerns/joiner.rb index 3295de21..2583a286 100644 --- a/app/controllers/concerns/joiner.rb +++ b/app/controllers/concerns/joiner.rb @@ -105,6 +105,8 @@ module Joiner "Room Configuration All Join Moderator" when "anyoneCanStart" "Room Configuration Allow Any Start" + when "recording" + "Room Configuration Recording" end case @settings.get_value(config) diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index c9ef6417..59bc4698 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -27,7 +27,7 @@ class RoomsController < ApplicationController unless: -> { !Rails.configuration.enable_email_verification } before_action :find_room, except: [:create, :join_specific_room, :cant_create_rooms] before_action :verify_room_ownership_or_admin_or_shared, only: [:start, :shared_access] - before_action :verify_room_ownership_or_admin, only: [:update_settings, :destroy] + before_action :verify_room_ownership_or_admin, only: [:update_settings, :destroy, :preupload_presentation, :remove_presentation] before_action :verify_room_ownership_or_shared, only: [:remove_shared_access] before_action :verify_room_owner_verified, only: [:show, :join], unless: -> { !Rails.configuration.enable_email_verification } @@ -171,6 +171,7 @@ class RoomsController < ApplicationController @room_settings = JSON.parse(@room[:room_settings]) opts[:mute_on_start] = room_setting_with_config("muteOnStart") opts[:require_moderator_approval] = room_setting_with_config("requireModeratorApproval") + opts[:record] = record_meeting begin redirect_to join_path(@room, current_user.name, opts, current_user.uid) @@ -209,6 +210,45 @@ class RoomsController < ApplicationController redirect_back fallback_location: room_path(@room) end + # GET /:room_uid/current_presentation + def current_presentation + attached = @room.presentation.attached? + + # Respond with JSON object of presentation name + respond_to do |format| + format.json { render body: { attached: attached, name: attached ? @room.presentation.filename.to_s : "" }.to_json } + end + end + + # POST /:room_uid/preupload_presenstation + def preupload_presentation + begin + raise "Invalid file type" unless valid_file_type + @room.presentation.attach(room_params[:presentation]) + + flash[:success] = I18n.t("room.preupload_success") + rescue => e + logger.error "Support: Error in updating room presentation: #{e}" + flash[:alert] = I18n.t("room.preupload_error") + end + + redirect_back fallback_location: room_path(@room) + end + + # POST /:room_uid/remove_presenstation + def remove_presentation + begin + @room.presentation.purge + + flash[:success] = I18n.t("room.preupload_remove_success") + rescue => e + logger.error "Support: Error in removing room presentation: #{e}" + flash[:alert] = I18n.t("room.preupload_remove_error") + end + + redirect_back fallback_location: room_path(@room) + end + # POST /:room_uid/update_shared_access def shared_access begin @@ -240,7 +280,7 @@ class RoomsController < ApplicationController # POST /:room_uid/remove_shared_access def remove_shared_access begin - SharedAccess.find_by!(room_id: @room.id, user_id: params[:user_id]).destroy + SharedAccess.find_by!(room_id: @room.id, user_id: current_user).destroy flash[:success] = I18n.t("room.remove_shared_access_success") rescue => e logger.error "Support: Error in removing room shared access: #{e}" @@ -262,7 +302,7 @@ class RoomsController < ApplicationController def room_settings # Respond with JSON object of the room_settings respond_to do |format| - format.json { render body: @room.room_settings.to_json } + format.json { render body: @room.room_settings } end end @@ -291,6 +331,7 @@ class RoomsController < ApplicationController "requireModeratorApproval": options[:require_moderator_approval] == "1", "anyoneCanStart": options[:anyone_can_start] == "1", "joinModerator": options[:all_join_moderator] == "1", + "recording": options[:recording] == "1", } room_settings.to_json @@ -298,7 +339,8 @@ class RoomsController < ApplicationController def room_params params.require(:room).permit(:name, :auto_join, :mute_on_join, :access_code, - :require_moderator_approval, :anyone_can_start, :all_join_moderator) + :require_moderator_approval, :anyone_can_start, :all_join_moderator, + :recording, :presentation) end # Find the room from the uid. @@ -364,4 +406,18 @@ class RoomsController < ApplicationController current_user.rooms.length >= limit end helper_method :room_limit_exceeded + + def record_meeting + # If the require consent setting is checked, then check the room setting, else, set to true + if recording_consent_required? + room_setting_with_config("recording") + else + true + end + end + + # Checks if the file extension is allowed + def valid_file_type + Rails.configuration.allowed_file_types.split(",").include?(File.extname(room_params[:presentation].original_filename)) + end end diff --git a/app/helpers/admins_helper.rb b/app/helpers/admins_helper.rb index 47613320..f704ae97 100644 --- a/app/helpers/admins_helper.rb +++ b/app/helpers/admins_helper.rb @@ -61,6 +61,14 @@ module AdminsHelper end end + def preupload_string + if @settings.get_value("Preupload Presentation") == "true" + I18n.t("administrator.site_settings.authentication.enabled") + else + I18n.t("administrator.site_settings.authentication.disabled") + end + end + def recording_default_visibility_string if @settings.get_value("Default Recording Visibility") == "public" I18n.t("recording.visibility.public") @@ -80,6 +88,14 @@ module AdminsHelper end end + def require_consent_string + if @settings.get_value("Require Recording Consent") == "true" + I18n.t("administrator.site_settings.authentication.enabled") + else + I18n.t("administrator.site_settings.authentication.disabled") + end + end + def log_level_string case Rails.logger.level when 0 @@ -97,6 +113,10 @@ module AdminsHelper end end + def show_log_dropdown + current_user.has_role?(:super_admin) || !Rails.configuration.loadbalanced_configuration + end + def room_limit_number @settings.get_value("Room Limit").to_i end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3bf29d47..f6ffe003 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -117,4 +117,11 @@ module ApplicationHelper rescue false end + + # Specifies which title should be the tab title and returns original string + def title(page_title) + # Only set the content_for if not already set on the page so that only the first title appears as the tab title + content_for(:page_title) { page_title } if content_for(:page_title).blank? + page_title + end end diff --git a/app/helpers/rooms_helper.rb b/app/helpers/rooms_helper.rb index bae0053d..de80dd24 100644 --- a/app/helpers/rooms_helper.rb +++ b/app/helpers/rooms_helper.rb @@ -41,4 +41,8 @@ module RoomsHelper def room_configuration(name) @settings.get_value(name) end + + def preupload_allowed? + @settings.get_value("Preupload Presentation") == "true" + end end diff --git a/app/helpers/theming_helper.rb b/app/helpers/theming_helper.rb index 527dd260..b1a28205 100644 --- a/app/helpers/theming_helper.rb +++ b/app/helpers/theming_helper.rb @@ -36,4 +36,8 @@ module ThemingHelper def user_color @settings.get_value("Primary Color") || Rails.configuration.primary_color_default end + + def maintenance_banner + @settings.get_value("Maintenance Banner") + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index be439105..80a10900 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -28,7 +28,7 @@ class Ability highest_role = user.role if highest_role.get_permission("can_edit_site_settings") can [:site_settings, :room_configuration, :update_settings, - :update_room_configuration, :coloring, :registration_method], :admin + :update_room_configuration, :coloring, :registration_method, :log_level], :admin end if highest_role.get_permission("can_edit_roles") diff --git a/app/models/room.rb b/app/models/room.rb index be585763..fa35c69f 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -23,11 +23,15 @@ class Room < ApplicationRecord before_create :setup + before_destroy :destroy_presentation + validates :name, presence: true belongs_to :owner, class_name: 'User', foreign_key: :user_id has_many :shared_access + has_one_attached :presentation + def self.admins_search(string) active_database = Rails.configuration.database_configuration[Rails.env]["adapter"] # Postgres requires created_at to be cast to a string @@ -51,6 +55,8 @@ class Room < ApplicationRecord # Rely on manual ordering if trying to sort by status return order_by_status(table, running_ids) if column == "status" + return table.order("COALESCE(rooms.last_session,rooms.created_at) DESC") if column == "created_at" + return table.order(Arel.sql("rooms.#{column} #{direction}")) if table.column_names.include?(column) return table.order(Arel.sql("#{column} #{direction}")) if column == "users.name" @@ -86,15 +92,17 @@ class Room < ApplicationRecord def self.order_by_status(table, ids) return table if ids.blank? - order_string = "CASE bbb_id " + # Get active rooms first + active_rooms = table.where(bbb_id: ids) - ids.each_with_index do |id, index| - order_string += "WHEN '#{id}' THEN #{index} " - end + # Get other rooms sorted by last session date || created at date (whichever is higher) + inactive_rooms = table.where.not(bbb_id: ids).order("COALESCE(rooms.last_session,rooms.created_at) DESC") - order_string += "ELSE #{ids.length} END" + active_rooms + inactive_rooms + end - table.order(Arel.sql(order_string)) + def recording_enabled? + JSON.parse(room_settings)["recording"] end private @@ -110,9 +118,9 @@ class Room < ApplicationRecord # Generates a fully random room uid. def random_room_uid # 6 character long random string of chars from a..z and 0..9 - full_chunk = SecureRandom.alphanumeric(6).downcase + full_chunk = SecureRandom.alphanumeric(9).downcase - [owner.name_chunk, full_chunk[0..2], full_chunk[3..5]].join("-") + [owner.name_chunk, full_chunk[0..2], full_chunk[3..5], full_chunk[6..8]].join("-") end # Generates a unique bbb_id based on uuid. @@ -122,4 +130,9 @@ class Room < ApplicationRecord break bbb_id unless Room.exists?(bbb_id: bbb_id) end end + + # Before destroying the room, make sure you also destroy the presentation attached + def destroy_presentation + presentation.purge if presentation.attached? + end end diff --git a/app/models/setting.rb b/app/models/setting.rb index 38b0a9b4..1ee18192 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -58,10 +58,14 @@ class Setting < ApplicationRecord Rails.configuration.registration_method_default when "Room Authentication" false + when "Require Recording Consent" + Rails.configuration.require_consent_default when "Room Limit" Rails.configuration.number_of_rooms_default when "Shared Access" Rails.configuration.shared_access_default + when "Preupload Presentation" + Rails.configuration.preupload_presentation_default when "Room Configuration Mute On Join" room_config_setting("mute-on-join") when "Room Configuration Require Moderator" @@ -70,6 +74,8 @@ class Setting < ApplicationRecord room_config_setting("anyone-can-start") when "Room Configuration All Join Moderator" room_config_setting("all-join-moderator") + when "Room Configuration Recording" + room_config_setting("recording") end end diff --git a/app/views/admins/components/_admins_role.html.erb b/app/views/admins/components/_admins_role.html.erb index fdc830de..ecdff28a 100644 --- a/app/views/admins/components/_admins_role.html.erb +++ b/app/views/admins/components/_admins_role.html.erb @@ -13,6 +13,6 @@ # with BigBlueButton; if not, see . %> - \ No newline at end of file diff --git a/app/views/admins/components/_manage_users_tags.html.erb b/app/views/admins/components/_manage_users_tags.html.erb index e622c867..d3928db6 100644 --- a/app/views/admins/components/_manage_users_tags.html.erb +++ b/app/views/admins/components/_manage_users_tags.html.erb @@ -1,5 +1,5 @@ <% -# BigBlueButton open source conferencing system - http://www.bigbluespan.org/. +# 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 @@ -14,20 +14,25 @@ %>
- \ No newline at end of file + <% if admin_invite_registration %> + <%= link_to "#inviteModal", class: "btn btn-primary pt-3", id: "invite-user", "data-toggle": "modal" do %> + <%= t("administrator.users.invite") %> + <% end %> + <% end %> + +
diff --git a/app/views/admins/components/_room_settings.html.erb b/app/views/admins/components/_room_settings.html.erb index 19a00959..53d37046 100644 --- a/app/views/admins/components/_room_settings.html.erb +++ b/app/views/admins/components/_room_settings.html.erb @@ -98,4 +98,31 @@ - \ No newline at end of file + <% if recording_consent_required? %> +
+
+
+ + + +
+
+
+ <% end %> + + diff --git a/app/views/admins/components/_server_room_row.html.erb b/app/views/admins/components/_server_room_row.html.erb index 6a37ba55..34d9a06f 100644 --- a/app/views/admins/components/_server_room_row.html.erb +++ b/app/views/admins/components/_server_room_row.html.erb @@ -65,6 +65,11 @@ <%= t("room.settings") %> + <% if preupload_allowed? %> + + <%= t("room.add_presentation") %> + + <% end %> <% if shared_access_allowed %> <%= t("room.share") %> diff --git a/app/views/admins/components/_settings.html.erb b/app/views/admins/components/_settings.html.erb index 17c6e8a6..c38c8767 100644 --- a/app/views/admins/components/_settings.html.erb +++ b/app/views/admins/components/_settings.html.erb @@ -13,247 +13,30 @@ # with BigBlueButton; if not, see . %> -
-
-
-
- - -
- - - - -
-
-
-
-
-
-
- - -
- - - - -
-
-
-
-
-
-
- - -
- - "> - "> + +
-
- <%= t("administrator.site_settings.color.regular") %> -
- -
- <%= t("administrator.site_settings.color.lighten") %> -
- -
- <%= t("administrator.site_settings.color.darken") %> -
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
- - - -
-
-
-
-
-
- - - -
-
-
-
-
-
- - - -
-
-
-
-
-
- - -
-
- -
-
- -
-
- -
-
- -
-
-
-
-
- <% if current_user.has_role? :super_admin%> -
-
-
-
- - - <%= button_to t("administrator.site_settings.cache.button"), admin_clear_cache_path, class: "btn btn-primary", "data-disable": "" %> -
-
-
-
-
-
- - - <%= button_to t("administrator.site_settings.clear_auth.button"), admin_clear_auth_path, class: "btn btn-primary" %> -
-
-
-
-
-
- - - -
-
-
- <% end %> +<% if @tab == "appearance"%> + <%= render "admins/components/site_settings/appearance" %> +<% elsif @tab == "administration"%> + <%= render "admins/components/site_settings/administration" %> +<% else %> + <%= render "admins/components/site_settings/settings" %> +<% end %> + diff --git a/app/views/admins/components/site_settings/_administration.html.erb b/app/views/admins/components/site_settings/_administration.html.erb new file mode 100644 index 00000000..c5ce08a4 --- /dev/null +++ b/app/views/admins/components/site_settings/_administration.html.erb @@ -0,0 +1,96 @@ +<% +# 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 . +%> + +
+
+
+
+ + +
+ "> + + + + +
+
+
+
+
+
+
+ + +
+ + + + +
+
+
+
+
+
+
+ + +
+ + + + +
+
+
+
+ + <% if show_log_dropdown %> +
+
+
+ + + +
+
+
+ <% end %> +
\ No newline at end of file diff --git a/app/views/admins/components/site_settings/_appearance.html.erb b/app/views/admins/components/site_settings/_appearance.html.erb new file mode 100644 index 00000000..4c16c23a --- /dev/null +++ b/app/views/admins/components/site_settings/_appearance.html.erb @@ -0,0 +1,56 @@ +<% +# 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 . +%> + +
+
+
+
+ + +
+ + + + +
+
+
+
+
+
+
+ + +
+ + "> + "> + +
+ <%= t("administrator.site_settings.color.regular") %> +
+ +
+ <%= t("administrator.site_settings.color.lighten") %> +
+ +
+ <%= t("administrator.site_settings.color.darken") %> +
+
+
+
+
+
\ No newline at end of file diff --git a/app/views/admins/components/site_settings/_settings.html.erb b/app/views/admins/components/site_settings/_settings.html.erb new file mode 100644 index 00000000..a6f666a5 --- /dev/null +++ b/app/views/admins/components/site_settings/_settings.html.erb @@ -0,0 +1,201 @@ +<% +# 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 . +%> + +
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ <% if current_user.has_role? :super_admin %> +
+
+
+
+ + + <%= button_to t("administrator.site_settings.cache.button"), admin_clear_cache_path, class: "btn btn-primary", "data-disable": "" %> +
+
+
+
+
+
+ + + <%= button_to t("administrator.site_settings.clear_auth.button"), admin_clear_auth_path, class: "btn btn-primary" %> +
+
+
+ <% end %> +
\ No newline at end of file diff --git a/app/views/admins/server_rooms.html.erb b/app/views/admins/server_rooms.html.erb index 1322e780..ceb10bd7 100644 --- a/app/views/admins/server_rooms.html.erb +++ b/app/views/admins/server_rooms.html.erb @@ -28,6 +28,9 @@ <%= render "shared/modals/delete_room_modal" %> <%= render "shared/modals/create_room_modal" %> +<% if preupload_allowed? %> + <%= render "shared/modals/preupload_presentation_modal" %> +<% end %> <% if shared_access_allowed %> <%= render "shared/modals/share_room_modal" %> <% end %> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 56266497..520ec36c 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -28,13 +28,12 @@ <% end %> - <%= t("bigbluebutton") %> - " /> + <%= yield(:page_title).present? ? yield(:page_title) : t("bigbluebutton") %> + " /> - " /> + " /> - <%= csrf_meta_tags %> @@ -48,6 +47,8 @@ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + <%= favicon_link_tag asset_path('favicon.ico') %> + <%= stylesheet_link_tag themes_primary_path %> diff --git a/app/views/rooms/components/_room_block.html.erb b/app/views/rooms/components/_room_block.html.erb index a352a082..7e140585 100644 --- a/app/views/rooms/components/_room_block.html.erb +++ b/app/views/rooms/components/_room_block.html.erb @@ -50,6 +50,11 @@ <%= t("room.settings") %> + <% if preupload_allowed? %> + + <%= t("room.add_presentation") %> + + <% end %> <% if shared_access_allowed %>
@@ -104,12 +103,19 @@
-<%= render "shared/sessions", recordings: @recordings, pagy: @pagy, only_public: false, shared_room: @shared_room, user_recordings: false, title: t("room.recordings")%> +<% recording = room_configuration("Room Configuration Recording") %> +<% if recording != "disabled" %> + <%= render "shared/sessions", recordings: @recordings, pagy: @pagy, only_public: false, shared_room: @shared_room, user_recordings: false, title: t("room.recordings")%> +<% end %> <%= render "shared/modals/delete_room_modal" %> <%= render "shared/modals/create_room_modal" %> +<% if preupload_allowed? %> + <%= render "shared/modals/preupload_presentation_modal" %> +<% end %> + <% if shared_access_allowed %> <%= render "shared/modals/share_room_modal" %> <%= render "shared/modals/remove_access_modal" %> diff --git a/app/views/shared/_flash_messages.html.erb b/app/views/shared/_flash_messages.html.erb index baef42ea..5eb3d706 100644 --- a/app/views/shared/_flash_messages.html.erb +++ b/app/views/shared/_flash_messages.html.erb @@ -27,7 +27,7 @@ <% elsif key.eql? "maintenance" %>
<%= value %> - +
<% elsif key.eql? "info" %>
diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 5fc1ed08..33961a66 100755 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -31,7 +31,9 @@ <%= t("header.dropdown.home") %> <% end %> - <% if current_user.role.get_permission("can_create_rooms") && !current_user.has_role?(:super_admin) %> + <% recording = room_configuration("Room Configuration Recording") %> + <% if current_user.role.get_permission("can_create_rooms") && !current_user.has_role?(:super_admin) && + recording != "disabled" %> <% all_rec_page = params[:controller] == "users" && params[:action] == "recordings" ? "active" : "" %> <%= link_to get_user_recordings_path(current_user), class: "px-3 mx-1 mt-1 header-nav #{all_rec_page}" do %> <%= t("header.all_recordings") %> diff --git a/app/views/shared/components/_public_recording_row.html.erb b/app/views/shared/components/_public_recording_row.html.erb index 9848cc0d..c450eb1f 100644 --- a/app/views/shared/components/_public_recording_row.html.erb +++ b/app/views/shared/components/_public_recording_row.html.erb @@ -16,13 +16,14 @@
- - <% if recording[:metadata][:name] %> + <% if recording[:metadata][:name] %> + <%= recording[:metadata][:name] %> - <% else %> + <% else %> + <%= recording[:name] %> - <% end %> - + <% end %> +
<%= t("recording.recorded_on", date: recording_date(recording[:startTime])) %> diff --git a/app/views/shared/components/_recording_row.html.erb b/app/views/shared/components/_recording_row.html.erb index 17d1dd5f..695e46b1 100644 --- a/app/views/shared/components/_recording_row.html.erb +++ b/app/views/shared/components/_recording_row.html.erb @@ -16,13 +16,14 @@
- - <% if recording[:metadata][:name] %> + <% if recording[:metadata][:name] %> + <%= recording[:metadata][:name] %> - <% else %> + <% else %> + <%= recording[:name] %> - <% end %> - + <% end %> +
diff --git a/app/views/shared/components/_subtitle.html.erb b/app/views/shared/components/_subtitle.html.erb index 01699cc3..43acd90a 100644 --- a/app/views/shared/components/_subtitle.html.erb +++ b/app/views/shared/components/_subtitle.html.erb @@ -16,18 +16,10 @@
<% if search %>
-

<%= subtitle %>

+

<%= title(subtitle) %>

- <% if admin_invite_registration %> -
- <%= link_to "#inviteModal", :class => "btn btn-primary", "data-toggle": "modal" do %> - <%= t("administrator.users.invite") %> - <% end %> -
- <% end %> - diff --git a/app/views/shared/modals/_create_room_modal.html.erb b/app/views/shared/modals/_create_room_modal.html.erb index 2534d5b8..255ff574 100644 --- a/app/views/shared/modals/_create_room_modal.html.erb +++ b/app/views/shared/modals/_create_room_modal.html.erb @@ -69,7 +69,6 @@ <% end %> - <% moderator = room_configuration("Room Configuration All Join Moderator") %> <% if moderator != "disabled" %> <% end %> - + <% recording = room_configuration("Room Configuration Recording") %> + <% if recording_consent_required? && recording != "disabled" %> + + <% end %>
<%= button_to "/", method: :delete, id: "remove-shared-confirm", class: "btn btn-danger my-1 btn-del-room" do %> - <%= hidden_field_tag :user_id, current_user.id %> <%= t("modal.remove_shared.delete") %> <% end %> diff --git a/config/application.rb b/config/application.rb index c8ab2e0d..8fe4db4d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -52,7 +52,7 @@ module Greenlight # Use standalone BigBlueButton server. config.bigbluebutton_endpoint = if ENV["BIGBLUEBUTTON_ENDPOINT"].present? - ENV["BIGBLUEBUTTON_ENDPOINT"] + ENV["BIGBLUEBUTTON_ENDPOINT"] else config.bigbluebutton_endpoint_default end @@ -128,6 +128,9 @@ module Greenlight config.report_issue_url = ENV["REPORT_ISSUE_URL"] config.help_url = ENV["HELP_URL"].nil? ? "https://docs.bigbluebutton.org/greenlight/gl-overview.html" : ENV["HELP_URL"] + # File types allowed in preupload presentation + config.allowed_file_types = ".doc,.docx,.pptx,.pdf" + # DEFAULTS # Default branding image if the user does not specify one @@ -157,6 +160,12 @@ module Greenlight # Allow users to share rooms by default config.shared_access_default = "true" + # Don't require recording consent by default + config.require_consent_default = "false" + + # Don't allow users to preupload presentations by default + config.preupload_presentation_default = "false" + # Default admin password config.admin_password_default = ENV['ADMIN_PASSWORD'] || 'administrator' end diff --git a/config/environments/production.rb b/config/environments/production.rb index 771a6c69..99771b60 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -61,7 +61,13 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options) - config.active_storage.service = :local + config.active_storage.service = if ENV["AWS_ACCESS_KEY_ID"].present? + :amazon + elsif ENV["GCS_PRIVATE_KEY_ID"].present? + :google + else + :local + end # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil diff --git a/config/locales/en.yml b/config/locales/en.yml index a7531e33..daf29b19 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -36,7 +36,7 @@ en: enabled: Enabled info: Only allow authenticated users to join a room title: Require Authentication for Rooms - user-info: You must sign in to Greenlight to join this room + user-info: You must sign in above to join this room. branding: change: Change Image info: Change the branding image that appears in the top left corner @@ -45,9 +45,9 @@ en: invalid: Invalid URL legal: change: Change URL - info: Change the Legal Link that appears in the bottom of the page - placeholder: Legal URL... - title: Legal + info: Change the Terms Link that appears in the bottom of the page + placeholder: Terms URL... + title: Terms invalid: Invalid URL privpolicy: change: Change URL @@ -82,6 +82,18 @@ en: info: Set the default recording visbility for new recordings title: Recording Default Visibility warning: This setting will only be applied to rooms that aren't running + require_consent: + info: This setting enables a room setting that allows room owners to specify which rooms can be recorded. Users joining a recorded room must consent before joining. + title: Require Room Owner and Joiner Consent to Recording + maintenance_banner: + info: Displays a Banner to inform the user of a scheduled maintenance + title: Maintenance Banner + display: Set + clear: Clear + time: "Example: Update scheduled on December 13 @ 23:00 ET. Users may experience problems signing in." + preupload: + info: Users can preupload a presentation to be used as the default presentation for that specific room + title: Allow Users to Preupload Presentations registration: info: Change the way that users register to the website title: Registration Method @@ -96,6 +108,10 @@ en: info: Setting to disabled will remove the button from the Room options dropdown, preventing users from sharing rooms title: Allow Users to Share Rooms subtitle: Customize Greenlight + tabs: + appearance: Appearance + administration: Administration + settings: Settings title: Site Settings flash: approved: User has been successfully approved. @@ -106,7 +122,7 @@ en: demoted: User has been successfully demoted invite: Invite successfully sent to %{email} invite_email_verification: Emails must be enabled in order to use this method. Please contact your system administrator. - merge_fail: There was an issue merging the user accounts. Please check the users selected and try again + merge_fail: There was an issue merging the user accounts. Please check the users selected and try again merge_success: User accounts merged successfully perm_deleted: User has been permanently deleted promoted: User has been successfully promoted @@ -136,7 +152,7 @@ en: edit_site_settings: Allow users with this role to edit site settings edit_roles: Allow users with this role to edit other roles manage_users: Allow users with this role to manage users - invalid_assignment: There was a problem assigning the roles to the user. Please check the values and try again + invalid_assignment: There was a problem assigning the roles to the user. Please check the values and try again colour: title: Role Colour info: Set the colour that will be associated with the role @@ -150,6 +166,8 @@ en: info: Allows any user to start the meeting at any time. By default, only the room owner can start the meeting. all_moderator: info: Gives all users moderator privileges in BigBlueButton when they join the meeting. + recordings: + info: Allows room owners to specify whether they want the option to record a room or not. If enabled, the moderator must still click the "Record" button once the meeting has started. options: disabled: Disabled enabled: Always Enabled @@ -261,7 +279,7 @@ en: designs: Custom Designs authentication: User Authentication footer: - legal: Legal + legal: Terms privpolicy: Privacy Policy powered_by: Powered by %{href}. forgot_password: @@ -393,6 +411,14 @@ en: or: or with: Sign in with %{provider} forgot_password: Forgot Password? + preupload: + change: Replace Presentation + choose: Choose a file (%{type}) + current: "Current Presentation:" + footer: Depending on the size of the presentation, it may require additional time to upload before it can be used. + invalid: Invalid size/file type. Please see the restrictions below. + title: Add Presentation + use: Use Presentation rename_recording: remove_shared: title: Are you sure you want to remove this room from your room list? @@ -407,6 +433,7 @@ en: require_approval: Require moderator approval before joining start: Allow any user to start this meeting footer_text: Adjustment to your room can be done at anytime. + recording: Allow room to be recorded rename_room: name_placeholder: Enter a new room name... share_access: @@ -474,7 +501,7 @@ en: fail: Your account has not been approved yet. If multiples days have passed since you signed up, please contact your administrator. signup: Your account was successfully created. It has been sent to an administrator for approval. banned: - fail: You do not have access to this application. If you believe this is a mistake, please contact your administrator. + fail: You do not have access to this application. If you believe this is a mistake, please contact your administrator. deprecated: new_signin: Select a new login method for you account. All your rooms from your old account will be migrated to the new account twitter_signin: Signing in via Twitter has been deprecated and will be removed in the next release. Click here to move your account to a new authentication method @@ -501,6 +528,7 @@ en: user: User room: access_code_required: Please enter a valid access code to join the room + add_presentation: Add Presentation create_room: Create a Room create_room_error: There was an error creating the room create_room_success: Room created successfully @@ -510,7 +538,9 @@ en: fail: Failed to delete room (%{error}) enter_the_access_code: Enter the room's access code invalid_provider: You have entered an invalid url. Please check the url and try again. + invitation_description: You have been invited to join %{name} using BigBlueButton. To join, click the link above and enter your name. invited: You have been invited to join + recording_present: The session is going to be recorded. This may include voice and video from your side. invite_participants: Invite Participants join: Join last_session: Last session on %{session} @@ -527,6 +557,10 @@ en: recent_rooms: Go To a Recently Joined Room title: Join a Room no_sessions: This room has no sessions, yet! + preupload_success: Successfully added presentation + preupload_error: There was an error updating the room presentation + preupload_remove_success: Successfully removed presentation + preupload_remove_error: There was an error removing the room presentation recordings: Room Recordings room_limit: You have reached the maximum number of rooms allowed room_limit_exceeded: You have exceeded the number of rooms allowed. Please delete %{difference} room(s) to access this room. diff --git a/config/routes.rb b/config/routes.rb index 5290b9f6..8082da57 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -122,6 +122,9 @@ Rails.application.routes.draw do patch '/', to: 'rooms#update', as: :update_room get '/room_settings', to: 'rooms#room_settings' post '/update_settings', to: 'rooms#update_settings' + get '/current_presentation', to: 'rooms#current_presentation' + post '/preupload_presentation', to: 'rooms#preupload_presentation' + post '/remove_presentation', to: 'rooms#remove_presentation' post '/update_shared_access', to: 'rooms#shared_access', as: :room_shared_access delete '/remove_shared_access', to: 'rooms#remove_shared_access', as: :room_remove_shared_access get '/shared_users', to: 'rooms#shared_users', as: :room_shared_users diff --git a/config/storage.yml b/config/storage.yml index d32f76e8..1c872b81 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -7,19 +7,29 @@ local: root: <%= Rails.root.join("storage") %> # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) -# amazon: -# service: S3 -# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> -# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> -# region: us-east-1 -# bucket: your_own_bucket +amazon: + service: S3 + access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> + secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> + region: <%= ENV['AWS_REGION'] %> + bucket: <%= ENV['AWS_BUCKET'] %> # Remember not to checkin your GCS keyfile to a repository -# google: -# service: GCS -# project: your_project -# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> -# bucket: your_own_bucket +google: + service: GCS + project: "<%= ENV['GCS_PROJECT'] %>" + bucket: "<%= ENV['GCS_BUCKET'] %>" + credentials: + type: 'service_account' + project_id: "<%= ENV['GCS_PROJECT_ID'] %>" + private_key_id: "<%= ENV['GCS_PRIVATE_KEY_ID'] %>" + private_key: "<%= ENV['GCS_PRIVATE_KEY']&.lines&.join("\\n") %>" + client_email: "<%= ENV['GCS_CLIENT_EMAIL'] %>" + client_id: "<%= ENV['GCS_CLIENT_ID'] %>" + auth_uri: 'https://accounts.google.com/o/oauth2/auth' + token_uri: 'https://accounts.google.com/o/oauth2/token' + auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs' + client_x509_cert_url: "<%= ENV['GCS_CLIENT_CERT'] %>" # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) # microsoft: diff --git a/db/migrate/20200615190507_create_active_storage_tables.active_storage.rb b/db/migrate/20200615190507_create_active_storage_tables.active_storage.rb new file mode 100644 index 00000000..8a54e20f --- /dev/null +++ b/db/migrate/20200615190507_create_active_storage_tables.active_storage.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [:key], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [:record_type, :record_id, :name, :blob_id], name: "index_active_storage_attachments_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 89cc2aae..13d6c35d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,28 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_04_13_150518) do +ActiveRecord::Schema.define(version: 2020_06_15_190507) do + + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.integer "record_id", null: false + t.integer "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end create_table "features", force: :cascade do |t| t.integer "setting_id" diff --git a/docker-compose.yml b/docker-compose.yml index 63d97a0b..d334c9ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,7 @@ services: # tag: $LOG_TAG volumes: - ./log:/usr/src/app/log + - ./storage:/usr/src/app/storage # When using sqlite3 as the database # - ./db/production:/usr/src/app/db/production # When using postgresql as the database diff --git a/greenlight.nginx b/greenlight.nginx index b3d5f904..fb0b90f0 100644 --- a/greenlight.nginx +++ b/greenlight.nginx @@ -24,3 +24,10 @@ location /b/cable { client_body_timeout 6h; send_timeout 6h; } + +# Only needed if using presentations and deployed at a relative root (ex "/b") +# If deploying at "/", delete the section below + +location /rails/active_storage { + return 301 /b$request_uri; +} \ No newline at end of file diff --git a/lib/assets/_primary_themes.scss b/lib/assets/_primary_themes.scss index e493602b..76e7fe9f 100644 --- a/lib/assets/_primary_themes.scss +++ b/lib/assets/_primary_themes.scss @@ -80,6 +80,20 @@ a { } } +.nav-tabs .nav-link{ + &.active { + border-color: $primary-color !important; + } + + &:not(.active){ + color: #9aa0ac !important; + &:hover:not(.disabled) { + border-color: $primary-color; + color: $primary-color !important; + } + } +} + .dropdown-item { color: #6e7687 !important; &:hover { diff --git a/lib/tasks/room.rake b/lib/tasks/room.rake index 72c2007a..7deec6a9 100644 --- a/lib/tasks/room.rake +++ b/lib/tasks/room.rake @@ -33,4 +33,18 @@ namespace :room do end end end + + task four: :environment do + Room.all.each do |room| + next if room.uid.split("-").length > 3 + + begin + new_uid = room.uid + "-" + SecureRandom.alphanumeric(3).downcase + puts "Updating #{room.uid} to #{new_uid}" + room.update_attributes(uid: new_uid) + rescue => e + puts "Failed to update #{room.uid} to #{new_uid} - #{e}" + end + end + end end diff --git a/lib/tasks/user.rake b/lib/tasks/user.rake index 57abe064..21b8ecea 100644 --- a/lib/tasks/user.rake +++ b/lib/tasks/user.rake @@ -47,7 +47,7 @@ namespace :user do user.set_role(u[:role]) - puts "Account succesfully created." + puts "Account successfully created." puts "Email: #{u[:email]}" puts "Password: #{u[:password]}" puts "Role: #{u[:role]}" diff --git a/sample.env b/sample.env index 6b3d67a8..0a0c50e5 100644 --- a/sample.env +++ b/sample.env @@ -156,7 +156,8 @@ RELATIVE_URL_ROOT=/b # require-moderator-approval: Require moderators to approve new users before they can join the room # anyone-can-start: Allows anyone with the join url to start the room in BigBlueButton # all-join-moderator: All users join as moderators in BigBlueButton -ROOM_FEATURES=mute-on-join,require-moderator-approval,anyone-can-start,all-join-moderator +# recording: Sessions are recorded +ROOM_FEATURES=mute-on-join,require-moderator-approval,anyone-can-start,all-join-moderator,recording # Specify the maximum number of records to be sent to the BigBlueButton API in one call # Default is set to 25 records @@ -258,3 +259,24 @@ DB_PASSWORD=password # invite - For invite only registration # approval - For approve/decline registration DEFAULT_REGISTRATION=open + +# Preupload Presentation Storage +# +# By default, if Preupload Presentation is enabled for rooms, presentations are uploaded locally to ~/greenlight/storage +# If you prefer to use AWS S3 or GCS Storage, you can set the variables below +# +# For AWS S3: +# AWS_ACCESS_KEY_ID= +# AWS_SECRET_ACCESS_KEY= +# AWS_REGION= +# AWS_BUCKET= +# +# For GCS Storage: +# GCS_PROJECT_ID= +# GCS_PRIVATE_KEY_ID= +# GCS_PRIVATE_KEY= +# GCS_CLIENT_EMAIL= +# GCS_CLIENT_ID= +# GCS_CLIENT_CERT= +# GCS_PROJECT= +# GCS_BUCKET= \ No newline at end of file diff --git a/scripts/image_build.sh b/scripts/image_build.sh index 4794c2d2..47566a43 100755 --- a/scripts/image_build.sh +++ b/scripts/image_build.sh @@ -50,7 +50,7 @@ if [ -z $CD_REF_NAME ]; then export CD_REF_NAME=$(git branch | grep \* | cut -d ' ' -f2) fi -if [ "$CD_REF_NAME" != "master" ] && [[ "$CD_REF_NAME" != *"release"* ]] && ( [ -z "$CD_BUILD_ALL" ] || [ "$CD_BUILD_ALL" != "true" ] ); then +if [ "$CD_REF_NAME" != "master" ] && [[ "$CD_REF_NAME" != *"release"* ]] && [[ "$CD_REF_NAME" != *"alpha"* ]] && ( [ -z "$CD_BUILD_ALL" ] || [ "$CD_BUILD_ALL" != "true" ] ); then echo "#### Docker image for $CD_REF_SLUG:$CD_REF_NAME won't be built" exit 0 fi diff --git a/spec/controllers/admins_controller_spec.rb b/spec/controllers/admins_controller_spec.rb index bace5761..a172ea89 100644 --- a/spec/controllers/admins_controller_spec.rb +++ b/spec/controllers/admins_controller_spec.rb @@ -290,12 +290,12 @@ describe AdminsController, type: :controller do @request.session[:user_id] = @admin.id fake_image_url = "example.com" - post :update_settings, params: { setting: "Branding Image", value: fake_image_url } + post :update_settings, params: { setting: "Branding Image", value: fake_image_url, tab: "appearance" } feature = Setting.find_by(provider: "provider1").features.find_by(name: "Branding Image") expect(feature[:value]).to eq(fake_image_url) - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "appearance")) end end @@ -307,12 +307,12 @@ describe AdminsController, type: :controller do @request.session[:user_id] = @admin.id fake_url = "example.com" - post :update_settings, params: { setting: "Legal URL", value: fake_url } + post :update_settings, params: { setting: "Legal URL", value: fake_url, tab: "administration" } feature = Setting.find_by(provider: "provider1").features.find_by(name: "Legal URL") expect(feature[:value]).to eq(fake_url) - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "administration")) end end @@ -324,12 +324,12 @@ describe AdminsController, type: :controller do @request.session[:user_id] = @admin.id fake_url = "example.com" - post :update_settings, params: { setting: "Privacy Policy URL", value: fake_url } + post :update_settings, params: { setting: "Privacy Policy URL", value: fake_url, tab: "administration" } feature = Setting.find_by(provider: "provider1").features.find_by(name: "Privacy Policy URL") expect(feature[:value]).to eq(fake_url) - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "administration")) end end @@ -346,7 +346,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color") expect(feature[:value]).to eq(primary_color) - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "appearance")) end it "changes the primary-lighten on the page" do @@ -356,12 +356,12 @@ describe AdminsController, type: :controller do @request.session[:user_id] = @admin.id primary_color = Faker::Color.hex_color - post :update_settings, params: { setting: "Primary Color Lighten", value: primary_color } + post :update_settings, params: { setting: "Primary Color Lighten", value: primary_color, tab: "appearance" } feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Lighten") expect(feature[:value]).to eq(primary_color) - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "appearance")) end it "changes the primary-darken on the page" do @@ -371,12 +371,12 @@ describe AdminsController, type: :controller do @request.session[:user_id] = @admin.id primary_color = Faker::Color.hex_color - post :update_settings, params: { setting: "Primary Color Darken", value: primary_color } + post :update_settings, params: { setting: "Primary Color Darken", value: primary_color, tab: "appearance" } feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Darken") expect(feature[:value]).to eq(primary_color) - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "appearance")) end end end @@ -396,7 +396,7 @@ describe AdminsController, type: :controller do expect(feature[:value]).to eq(Rails.configuration.registration_methods[:invite]) expect(flash[:success]).to be_present - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "settings")) end it "does not allow the user to change to invite if emails are off" do @@ -409,7 +409,7 @@ describe AdminsController, type: :controller do post :registration_method, params: { value: "invite" } expect(flash[:alert]).to be_present - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "settings")) end end @@ -425,7 +425,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Authentication") expect(feature[:value]).to eq("true") - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "settings")) end end @@ -441,7 +441,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Limit") expect(feature[:value]).to eq("5") - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "settings")) end end @@ -457,7 +457,25 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Default Recording Visibility") expect(feature[:value]).to eq("public") - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "settings")) + end + end + + context "POST #maintenance_banner" do + it "displays a banner with the maintenance string" do + allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) + allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true) + + @request.session[:user_id] = @admin.id + fake_banner_string = "Maintenance work at 2 pm" + + post :update_settings, params: { setting: "Maintenance Banner", value: fake_banner_string, tab: "administration" } + + feature = Setting.find_by(provider: "provider1").features.find_by(name: "Maintenance Banner") + + expect(flash[:success]).to be_present + expect(feature[:value]).to eq(fake_banner_string) + expect(response).to redirect_to(admin_site_settings_path(tab: "administration")) end end @@ -473,7 +491,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Shared Access") expect(feature[:value]).to eq("false") - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "settings")) end end @@ -537,7 +555,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Shared Access") expect(feature[:value]).to eq("false") - expect(response).to redirect_to(admin_site_settings_path) + expect(response).to redirect_to(admin_site_settings_path(tab: "settings")) end it "doesn't allow a user with the incorrect permission to edit site settings" do diff --git a/spec/controllers/rooms_controller_spec.rb b/spec/controllers/rooms_controller_spec.rb index 38134af5..59b661ee 100644 --- a/spec/controllers/rooms_controller_spec.rb +++ b/spec/controllers/rooms_controller_spec.rb @@ -185,7 +185,7 @@ describe RoomsController, type: :controller do room_params = { name: name, "mute_on_join": "1", "require_moderator_approval": "1", "anyone_can_start": "1", "all_join_moderator": "1" } json_room_settings = "{\"muteOnStart\":true,\"requireModeratorApproval\":true," \ - "\"anyoneCanStart\":true,\"joinModerator\":true}" + "\"anyoneCanStart\":true,\"joinModerator\":true,\"recording\":false}" post :create, params: { room: room_params } @@ -202,8 +202,10 @@ describe RoomsController, type: :controller do @owner.main_room.update_attribute(:room_settings, { "muteOnStart": true, "requireModeratorApproval": true, "anyoneCanStart": true, "joinModerator": true }.to_json) - json_room_settings = "{\"muteOnStart\":true,\"requireModeratorApproval\":true," \ - "\"anyoneCanStart\":true,\"joinModerator\":true}" + json_room_settings = { "anyoneCanStart" => true, + "joinModerator" => true, + "muteOnStart" => true, + "requireModeratorApproval" => true } get :room_settings, params: { room_uid: @owner.main_room }, format: :json @@ -571,7 +573,7 @@ describe RoomsController, type: :controller do it "properly updates room name through the room settings modal and redirects to current page" do @request.session[:user_id] = @user.id - name = Faker::Games::Pokemon.name + name = Faker::Name.first_name room_params = { room_uid: @secondary_room.uid, room: { "name": name } } @@ -583,9 +585,9 @@ describe RoomsController, type: :controller do it "properly updates room settings through the room settings modal and redirects to current page" do @request.session[:user_id] = @user.id - room_params = { "mute_on_join": "1", "name": @secondary_room.name } + room_params = { "mute_on_join": "1", "name": @secondary_room.name, "recording": "1" } formatted_room_params = "{\"muteOnStart\":true,\"requireModeratorApproval\":false," \ - "\"anyoneCanStart\":false,\"joinModerator\":false}" # JSON string format + "\"anyoneCanStart\":false,\"joinModerator\":false,\"recording\":true}" # JSON string format expect { post :update_settings, params: { room_uid: @secondary_room.uid, room: room_params } } .to change { @secondary_room.reload.room_settings } @@ -608,7 +610,7 @@ describe RoomsController, type: :controller do room_params = { "mute_on_join": "1", "name": @secondary_room.name } formatted_room_params = "{\"muteOnStart\":true,\"requireModeratorApproval\":false," \ - "\"anyoneCanStart\":false,\"joinModerator\":false}" # JSON string format + "\"anyoneCanStart\":false,\"joinModerator\":false,\"recording\":false}" # JSON string format expect { post :update_settings, params: { room_uid: @secondary_room.uid, room: room_params } } .to change { @secondary_room.reload.room_settings } @@ -814,4 +816,107 @@ describe RoomsController, type: :controller do expect(response).to redirect_to root_path end end + + describe "POST #preupload_presentation" do + before do + @user = create(:user) + @file = fixture_file_upload('files/sample.pdf', 'application/pdf') + @invalid_file = fixture_file_upload('files/invalid.jpg', 'image/jpg') + allow(Rails.configuration).to receive(:preupload_presentation_default).and_return("true") + end + + it "adds a presentation to the room" do + @request.session[:user_id] = @user.id + + post :preupload_presentation, params: { room_uid: @user.main_room, room: { presentation: @file } } + + expect(@user.main_room.presentation.attached?).to be true + expect(flash[:success]).to be_present + expect(response).to redirect_to @user.main_room + end + + it "rejects file types that are not allowed" do + @request.session[:user_id] = @user.id + + post :preupload_presentation, params: { room_uid: @user.main_room, room: { presentation: @invalid_file } } + + expect(@user.main_room.presentation.attached?).to be false + expect(flash[:alert]).to be_present + expect(response).to redirect_to @user.main_room + end + + it "allows admins to add a presentation to the room" do + allow_any_instance_of(User).to receive(:admin_of?).and_return(true) + @admin = create(:user) + @admin.set_role :admin + @request.session[:user_id] = @admin.id + + post :preupload_presentation, params: { room_uid: @user.main_room, room: { presentation: @file } } + + expect(@user.main_room.presentation.attached?).to be true + expect(flash[:success]).to be_present + expect(response).to redirect_to @user.main_room + end + + it "redirects to root path if not admin of current user" do + allow_any_instance_of(User).to receive(:admin_of?).and_return(false) + @admin = create(:user) + @admin.set_role :admin + @request.session[:user_id] = @admin.id + + post :preupload_presentation, params: { room_uid: @user.main_room, room: { presentation: @file } } + + expect(response).to redirect_to(root_path) + end + end + + describe "POST #remove_presentation" do + before do + @user = create(:user) + @user.main_room.presentation.attach(fixture_file_upload('files/sample.pdf', 'application/pdf')) + allow(Rails.configuration).to receive(:shared_access_default).and_return("true") + end + + it "removes a presentation from a room" do + @request.session[:user_id] = @user.id + + expect(@user.main_room.presentation.attached?).to be true + + post :remove_presentation, params: { room_uid: @user.main_room } + + @user.main_room.reload + + expect(@user.main_room.presentation.attached?).to be false + expect(flash[:success]).to be_present + expect(response).to redirect_to @user.main_room + end + + it "allows admins to remove a presentation from a room" do + allow_any_instance_of(User).to receive(:admin_of?).and_return(true) + @admin = create(:user) + @admin.set_role :admin + @request.session[:user_id] = @admin.id + + expect(@user.main_room.presentation.attached?).to be true + + post :remove_presentation, params: { room_uid: @user.main_room } + + @user.main_room.reload + + expect(@user.main_room.presentation.attached?).to be false + expect(flash[:success]).to be_present + expect(response).to redirect_to @user.main_room + end + + it "redirects to root path if not admin of current user" do + allow_any_instance_of(User).to receive(:admin_of?).and_return(false) + @admin = create(:user) + @admin.set_role :admin + @request.session[:user_id] = @admin.id + + post :preupload_presentation, params: { room_uid: @user.main_room, room: { presentation: @file } } + + expect(response).to redirect_to(root_path) + end + end end diff --git a/spec/fixtures/files/invalid.jpg b/spec/fixtures/files/invalid.jpg new file mode 100644 index 00000000..1db39e04 Binary files /dev/null and b/spec/fixtures/files/invalid.jpg differ diff --git a/spec/fixtures/files/sample.pdf b/spec/fixtures/files/sample.pdf new file mode 100644 index 00000000..dbf091df --- /dev/null +++ b/spec/fixtures/files/sample.pdf @@ -0,0 +1,198 @@ +%PDF-1.3 +%âãÏÓ + +1 0 obj +<< +/Type /Catalog +/Outlines 2 0 R +/Pages 3 0 R +>> +endobj + +2 0 obj +<< +/Type /Outlines +/Count 0 +>> +endobj + +3 0 obj +<< +/Type /Pages +/Count 2 +/Kids [ 4 0 R 6 0 R ] +>> +endobj + +4 0 obj +<< +/Type /Page +/Parent 3 0 R +/Resources << +/Font << +/F1 9 0 R +>> +/ProcSet 8 0 R +>> +/MediaBox [0 0 612.0000 792.0000] +/Contents 5 0 R +>> +endobj + +5 0 obj +<< /Length 1074 >> +stream +2 J +BT +0 0 0 rg +/F1 0027 Tf +57.3750 722.2800 Td +( A Simple PDF File ) Tj +ET +BT +/F1 0010 Tf +69.2500 688.6080 Td +( This is a small demonstration .pdf file - ) Tj +ET +BT +/F1 0010 Tf +69.2500 664.7040 Td +( just for use in the Virtual Mechanics tutorials. More text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 652.7520 Td +( text. And more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 628.8480 Td +( And more text. And more text. And more text. And more text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 616.8960 Td +( text. And more text. Boring, zzzzz. And more text. And more text. And ) Tj +ET +BT +/F1 0010 Tf +69.2500 604.9440 Td +( more text. And more text. And more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 592.9920 Td +( And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 569.0880 Td +( And more text. And more text. And more text. And more text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 557.1360 Td +( text. And more text. And more text. Even more. Continued on page 2 ...) Tj +ET +endstream +endobj + +6 0 obj +<< +/Type /Page +/Parent 3 0 R +/Resources << +/Font << +/F1 9 0 R +>> +/ProcSet 8 0 R +>> +/MediaBox [0 0 612.0000 792.0000] +/Contents 7 0 R +>> +endobj + +7 0 obj +<< /Length 676 >> +stream +2 J +BT +0 0 0 rg +/F1 0027 Tf +57.3750 722.2800 Td +( Simple PDF File 2 ) Tj +ET +BT +/F1 0010 Tf +69.2500 688.6080 Td +( ...continued from page 1. Yet more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 676.6560 Td +( And more text. And more text. And more text. And more text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 664.7040 Td +( text. Oh, how boring typing this stuff. But not as boring as watching ) Tj +ET +BT +/F1 0010 Tf +69.2500 652.7520 Td +( paint dry. And more text. And more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 640.8000 Td +( Boring. More, a little more text. The end, and just as well. ) Tj +ET +endstream +endobj + +8 0 obj +[/PDF /Text] +endobj + +9 0 obj +<< +/Type /Font +/Subtype /Type1 +/Name /F1 +/BaseFont /Helvetica +/Encoding /WinAnsiEncoding +>> +endobj + +10 0 obj +<< +/Creator (Rave \(http://www.nevrona.com/rave\)) +/Producer (Nevrona Designs) +/CreationDate (D:20060301072826) +>> +endobj + +xref +0 11 +0000000000 65535 f +0000000019 00000 n +0000000093 00000 n +0000000147 00000 n +0000000222 00000 n +0000000390 00000 n +0000001522 00000 n +0000001690 00000 n +0000002423 00000 n +0000002456 00000 n +0000002574 00000 n + +trailer +<< +/Size 11 +/Root 1 0 R +/Info 10 0 R +>> + +startxref +2714 +%%EOF