From 60cf5f7440f3a5c21adc6fede3aab59a73428ec3 Mon Sep 17 00:00:00 2001 From: Ahmad Farhat Date: Wed, 29 Jul 2020 11:03:22 -0400 Subject: [PATCH] Merge v2.7-alpha (#1951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix wrong conditional (reported by LGTM) (#1477) Signed-off-by: Stefan Weil Co-authored-by: Ahmad Farhat * Bump rack from 2.2.2 to 2.2.3 (#1839) Bumps [rack](https://github.com/rack/rack) from 2.2.2 to 2.2.3. - [Release notes](https://github.com/rack/rack/releases) - [Changelog](https://github.com/rack/rack/blob/master/CHANGELOG.md) - [Commits](https://github.com/rack/rack/compare/v2.2.2...2.2.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [FIX] Unable to edit long recording names #1776 (#1780) * Allow to set a filter for LDAP authentication * [FIX] Unable to edit long recording names #1776 Co-authored-by: François Ménabé Co-authored-by: farhatahmad * Desgin for Manage Users Tabs (#1777) * Update _subtitle.html.erb * Update _manage_users_tags.html.erb * Update admins.scss * Update _primary_themes.scss * Update _manage_users_tags.html.erb * Minor style changes to manage users (#1845) * Maintenance banner moved to admin site (#1775) * initial * finish * travis fixes * travis again * not required * Co-authored-by: Tobias Fiebig (#1296) Co-authored-by: Ahmad Farhat * Enhance Room OpenGraph Metadata (#1601) * Revert "Enhance Room OpenGraph Metadata (#1601)" (#1852) This reverts commit 3b007c233ae12e0407f216ae269c63d6179f73b8. * GRN2-xx: Tab title now displays the current page name (#1853) * Tab title now displays the current page name * Added page title for the rest of the pages * Split Site Settings into 3 different tabs (#1858) * Split Site Settings into 3 different tabs * Fix copyright * Added redirect to correct tab * Make sure settings are displaying when they should * Update en.yml (#1857) * Build images for alpha branches (#1867) * Upgraded jquery to latest version (#1896) * Added favicon tag (#1898) * Fixed XSS issue with role name (#1899) * Update path for coloring redirect (#1908) * Added a fourth section to the room uid (#1910) * Fixed issue with insecure room sharing removal (#1914) * Fixes typo (#1917) Fixes typo: successfully was written incorrect. * Fixed order of rooms in server rooms (#1915) * Change default room sort to latest activity (#1919) * GRN2-xx: Small changes/improvements to the recording settings (#1851) * Small changes/improvements to the recording settings * Replaced room warning with info flash * Added global setting to enable/disable the recording consent feature * Replace Legal with Terms (#1931) * Added a more friendly OpenGraph description when invited to join a room (#1932) * Fixed issue causing maintenance banner not to hide correctly (#1933) * Hide recording menu and recording list when it is disabled (#1935) * Hide recording menu and recording list when it is disabled * Hide recording list when disabled * GRN2-xx: Added an auto-refresh after 2 mins while waiting for room to start (#1947) * Added an auto-refresh after 2 mins while waiting for room to start * Fixed random issue with test case * GRN2-xx: Added ability to preupload presentations to rooms (#1895) * Added ability to preupload presentations to rooms (#1868) * Added setting to site settings and allowed admins to change the presentation * Added AWS S3 and GCS Storage ENV variables * Added check to ensure file extension is correct * Added icon to remove presentation * Added testcases for preupload * Add nginx redirect to solve issue with relative root * Record title, instead of room name, in the popup (#1924) * Update _public_recording_row.html.erb * Update _recording_row.html.erb Co-authored-by: Stefan Weil Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: beckerr-rzht Co-authored-by: François Ménabé Co-authored-by: MrKeksi Co-authored-by: yanosz Co-authored-by: Moritz Schlarb Co-authored-by: chronikum <34622984+chronikum@users.noreply.github.com> Co-authored-by: Mitsutaka Sato Co-authored-by: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> --- .gitignore | 3 + Gemfile | 8 +- Gemfile.lock | 91 +++++- app/assets/images/favicon.ico | Bin 0 -> 2587 bytes app/assets/javascripts/admins.js | 27 +- app/assets/javascripts/application.js | 2 +- app/assets/javascripts/cookies.js | 9 +- app/assets/javascripts/room.js | 70 ++++- app/assets/javascripts/wait.js | 10 +- app/assets/stylesheets/admins.scss | 12 +- app/assets/stylesheets/application.scss | 4 + app/assets/stylesheets/rooms.scss | 9 + app/controllers/admins_controller.rb | 16 +- app/controllers/application_controller.rb | 18 +- app/controllers/concerns/bbb_server.rb | 16 +- app/controllers/concerns/joiner.rb | 2 + app/controllers/rooms_controller.rb | 64 ++++- app/helpers/admins_helper.rb | 20 ++ app/helpers/application_helper.rb | 7 + app/helpers/rooms_helper.rb | 4 + app/helpers/theming_helper.rb | 4 + app/models/ability.rb | 2 +- app/models/room.rb | 29 +- app/models/setting.rb | 6 + .../admins/components/_admins_role.html.erb | 2 +- .../components/_manage_users_tags.html.erb | 31 +- .../admins/components/_room_settings.html.erb | 29 +- .../components/_server_room_row.html.erb | 5 + .../admins/components/_settings.html.erb | 267 ++---------------- .../site_settings/_administration.html.erb | 96 +++++++ .../site_settings/_appearance.html.erb | 56 ++++ .../site_settings/_settings.html.erb | 201 +++++++++++++ app/views/admins/server_rooms.html.erb | 3 + app/views/layouts/application.html.erb | 9 +- .../rooms/components/_room_block.html.erb | 5 + .../rooms/components/_room_event.html.erb | 9 +- app/views/rooms/join.html.erb | 10 +- app/views/rooms/show.html.erb | 12 +- app/views/shared/_flash_messages.html.erb | 2 +- app/views/shared/_header.html.erb | 4 +- .../components/_public_recording_row.html.erb | 11 +- .../shared/components/_recording_row.html.erb | 11 +- .../shared/components/_subtitle.html.erb | 12 +- .../shared/modals/_create_room_modal.html.erb | 10 +- .../_preupload_presentation_modal.html.erb | 52 ++++ .../modals/_remove_access_modal.html.erb | 1 - config/application.rb | 11 +- config/environments/production.rb | 8 +- config/locales/en.yml | 50 +++- config/routes.rb | 3 + config/storage.yml | 32 ++- ...te_active_storage_tables.active_storage.rb | 29 ++ db/schema.rb | 23 +- docker-compose.yml | 1 + greenlight.nginx | 7 + lib/assets/_primary_themes.scss | 14 + lib/tasks/room.rake | 14 + lib/tasks/user.rake | 2 +- sample.env | 24 +- scripts/image_build.sh | 2 +- spec/controllers/admins_controller_spec.rb | 54 ++-- spec/controllers/rooms_controller_spec.rb | 119 +++++++- spec/fixtures/files/invalid.jpg | Bin 0 -> 102117 bytes spec/fixtures/files/sample.pdf | 198 +++++++++++++ 64 files changed, 1463 insertions(+), 399 deletions(-) create mode 100644 app/assets/images/favicon.ico create mode 100644 app/views/admins/components/site_settings/_administration.html.erb create mode 100644 app/views/admins/components/site_settings/_appearance.html.erb create mode 100644 app/views/admins/components/site_settings/_settings.html.erb create mode 100644 app/views/shared/modals/_preupload_presentation_modal.html.erb create mode 100644 db/migrate/20200615190507_create_active_storage_tables.active_storage.rb create mode 100644 spec/fixtures/files/invalid.jpg create mode 100644 spec/fixtures/files/sample.pdf 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 0000000000000000000000000000000000000000..737b8540813842b060a2531b47135e82813bca0c GIT binary patch literal 2587 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 literal 0 HcmV?d00001 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("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 0000000000000000000000000000000000000000..1db39e04b4ccf009cf0d4503a684ca75e8e2c3d4 GIT binary patch literal 102117 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 YTje32U?tACE0QPnjkMh1(toS}12^vyF8}}l literal 0 HcmV?d00001 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