Merge branch 'v2.2.1-alpha' into master

This commit is contained in:
Jesus Federico 2019-07-23 15:57:08 -04:00 committed by GitHub
commit f7c88cfc6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 1740 additions and 921 deletions

View File

@ -1,7 +1,14 @@
FROM ruby:2.5 FROM ruby:2.5
# Install app dependencies. # Install app dependencies.
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN apt-get update -qq && apt-get install -y build-essential libpq-dev curl
ADD https://dl.yarnpkg.com/debian/pubkey.gpg /tmp/yarn-pubkey.gpg
RUN apt-key add /tmp/yarn-pubkey.gpg && rm /tmp/yarn-pubkey.gpg && \
echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list && \
curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
apt-get update && apt-get install -y nodejs yarn
# Set an environment variable for the install location. # Set an environment variable for the install location.
ENV RAILS_ROOT /usr/src/app ENV RAILS_ROOT /usr/src/app
@ -23,9 +30,9 @@ RUN bundle install --without development test --deployment --clean
# Adding project files. # Adding project files.
COPY . . COPY . .
# Precompile assets. # Precompile assets
RUN bundle exec rake assets:clean RUN SECRET_KEY_BASE="$(bundle exec rake secret)" bundle exec rake assets:clean
RUN bundle exec rake assets:precompile RUN SECRET_KEY_BASE="$(bundle exec rake secret)" bundle exec rake assets:precompile
# Expose port 80. # Expose port 80.
EXPOSE 80 EXPOSE 80

11
Gemfile
View File

@ -8,13 +8,13 @@ git_source(:github) do |repo_name|
end end
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.7' gem 'rails', '~> 5.2.3'
# Use Puma as the app server # Use Puma as the app server
gem 'puma', '~> 3.0' gem 'puma', '~> 3.0'
# Use SCSS for stylesheets # Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0' gem 'sassc-rails'
# Use Uglifier as compressor for JavaScript assets # Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0' gem 'uglifier', '>= 1.3.0'
@ -35,11 +35,14 @@ gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5' gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production # Use Redis adapter to run Action Cable in production
gem 'redis', '~> 3.0' # gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7' gem 'bcrypt', '~> 3.1.7'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false
# Authentication. # Authentication.
gem 'omniauth' gem 'omniauth'
gem 'omniauth-twitter' gem 'omniauth-twitter'
@ -54,7 +57,7 @@ gem 'bigbluebutton-api-ruby'
# Front-end. # Front-end.
gem 'bootstrap', '~> 4.3.1' gem 'bootstrap', '~> 4.3.1'
gem 'tabler-rubygem' gem 'tabler-rubygem', git: 'https://github.com/vbalazs/tabler-rubygem.git', branch: 'fix-sass'
gem 'pagy' gem 'pagy'
# For detecting the users preferred language. # For detecting the users preferred language.

View File

@ -23,58 +23,73 @@ GIT
omniauth (>= 1.3.2) omniauth (>= 1.3.2)
omniauth-oauth2 (>= 1.5.0) omniauth-oauth2 (>= 1.5.0)
GIT
remote: https://github.com/vbalazs/tabler-rubygem.git
revision: 1360795a174f6e6317eb6c81461597ddc965c2ce
branch: fix-sass
specs:
tabler-rubygem (0.1.4)
autoprefixer-rails (>= 6.0.3)
bootstrap (~> 4.3.1)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
action-cable-testing (0.5.0) action-cable-testing (0.5.0)
actioncable (>= 5.0) actioncable (>= 5.0)
actioncable (5.0.7.2) actioncable (5.2.3)
actionpack (= 5.0.7.2) actionpack (= 5.2.3)
nio4r (>= 1.2, < 3.0) nio4r (~> 2.0)
websocket-driver (~> 0.6.1) websocket-driver (>= 0.6.1)
actionmailer (5.0.7.2) actionmailer (5.2.3)
actionpack (= 5.0.7.2) actionpack (= 5.2.3)
actionview (= 5.0.7.2) actionview (= 5.2.3)
activejob (= 5.0.7.2) activejob (= 5.2.3)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.0.7.2) actionpack (5.2.3)
actionview (= 5.0.7.2) actionview (= 5.2.3)
activesupport (= 5.0.7.2) activesupport (= 5.2.3)
rack (~> 2.0) rack (~> 2.0)
rack-test (~> 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.0.7.2) actionview (5.2.3)
activesupport (= 5.0.7.2) activesupport (= 5.2.3)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.0.7.2) activejob (5.2.3)
activesupport (= 5.0.7.2) activesupport (= 5.2.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.0.7.2) activemodel (5.2.3)
activesupport (= 5.0.7.2) activesupport (= 5.2.3)
activerecord (5.0.7.2) activerecord (5.2.3)
activemodel (= 5.0.7.2) activemodel (= 5.2.3)
activesupport (= 5.0.7.2) activesupport (= 5.2.3)
arel (~> 7.0) arel (>= 9.0)
activesupport (5.0.7.2) activestorage (5.2.3)
actionpack (= 5.2.3)
activerecord (= 5.2.3)
marcel (~> 0.3.1)
activesupport (5.2.3)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.6.0) addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0) public_suffix (>= 2.0.2, < 4.0)
arel (7.1.4) arel (9.0.0)
ast (2.4.0) ast (2.4.0)
autoprefixer-rails (9.5.1.1) autoprefixer-rails (9.6.1)
execjs execjs
bcrypt (3.1.12) bcrypt (3.1.13)
bigbluebutton-api-ruby (1.7.0) bigbluebutton-api-ruby (1.7.0)
xml-simple (~> 1.1) xml-simple (~> 1.1)
bindex (0.7.0) bindex (0.8.1)
bootsnap (1.4.4)
msgpack (~> 1.0)
bootstrap (4.3.1) bootstrap (4.3.1)
autoprefixer-rails (>= 9.1.0) autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.14.3, < 2) popper_js (>= 1.14.3, < 2)
@ -100,26 +115,26 @@ GEM
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.4) crass (1.0.4)
diff-lcs (1.3) diff-lcs (1.3)
docile (1.3.1) docile (1.3.2)
dotenv (2.7.2) dotenv (2.7.4)
dotenv-rails (2.7.2) dotenv-rails (2.7.4)
dotenv (= 2.7.2) dotenv (= 2.7.4)
railties (>= 3.2, < 6.1) railties (>= 3.2, < 6.1)
erubis (2.7.0) erubi (1.8.0)
execjs (2.7.0) execjs (2.7.0)
factory_bot (5.0.2) factory_bot (5.0.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
factory_bot_rails (5.0.2) factory_bot_rails (5.0.2)
factory_bot (~> 5.0.2) factory_bot (~> 5.0.2)
railties (>= 4.2.0) railties (>= 4.2.0)
faker (1.9.3) faker (1.9.6)
i18n (>= 0.7) i18n (>= 0.7)
faraday (0.15.4) faraday (0.15.4)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ffi (1.11.1) ffi (1.11.1)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
hashdiff (0.3.9) hashdiff (0.4.0)
hashie (3.6.0) hashie (3.6.0)
health_check (3.0.0) health_check (3.0.0)
railties (>= 5.0) railties (>= 5.0)
@ -127,15 +142,15 @@ GEM
i18n (1.6.0) i18n (1.6.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n-language-mapping (0.1.0) i18n-language-mapping (0.1.0)
jaro_winkler (1.5.2) jaro_winkler (1.5.3)
jbuilder (2.9.1) jbuilder (2.9.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
jquery-rails (4.3.3) jquery-rails (4.3.5)
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json (2.2.0) json (2.2.0)
jwt (2.1.0) jwt (2.2.1)
libv8 (7.3.492.27.1) libv8 (7.3.492.27.1)
listen (3.0.8) listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.9, >= 0.9.4)
@ -145,17 +160,21 @@ GEM
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
method_source (0.9.2) method_source (0.9.2)
mini_mime (1.0.1) mimemagic (0.3.3)
mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
mini_racer (0.2.6) mini_racer (0.2.6)
libv8 (>= 6.9.411) libv8 (>= 6.9.411)
minitest (5.11.3) minitest (5.11.3)
msgpack (1.3.0)
multi_json (1.13.1) multi_json (1.13.1)
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.1.1) multipart-post (2.1.1)
net-ldap (0.16.1) net-ldap (0.16.1)
nio4r (2.3.1) nio4r (2.4.0)
nokogiri (1.10.3) nokogiri (1.10.3)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
oauth (0.5.4) oauth (0.5.4)
@ -168,7 +187,7 @@ GEM
omniauth (1.9.0) omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0) hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
omniauth-google-oauth2 (0.6.1) omniauth-google-oauth2 (0.7.0)
jwt (>= 2.0) jwt (>= 2.0)
omniauth (>= 1.1.1) omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5) omniauth-oauth2 (>= 1.5)
@ -181,28 +200,29 @@ GEM
omniauth-twitter (1.4.0) omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
rack rack
pagy (3.2.0) pagy (3.3.2)
parallel (1.17.0) parallel (1.17.0)
parser (2.6.3.0) parser (2.6.3.0)
ast (~> 2.4.0) ast (~> 2.4.0)
pg (0.21.0) pg (0.21.0)
popper_js (1.14.5) popper_js (1.14.5)
public_suffix (3.0.3) public_suffix (3.1.1)
puma (3.12.1) puma (3.12.1)
rack (2.0.7) rack (2.0.7)
rack-test (0.6.3) rack-test (1.1.0)
rack (>= 1.0) rack (>= 1.0, < 3)
rails (5.0.7.2) rails (5.2.3)
actioncable (= 5.0.7.2) actioncable (= 5.2.3)
actionmailer (= 5.0.7.2) actionmailer (= 5.2.3)
actionpack (= 5.0.7.2) actionpack (= 5.2.3)
actionview (= 5.0.7.2) actionview (= 5.2.3)
activejob (= 5.0.7.2) activejob (= 5.2.3)
activemodel (= 5.0.7.2) activemodel (= 5.2.3)
activerecord (= 5.0.7.2) activerecord (= 5.2.3)
activesupport (= 5.0.7.2) activestorage (= 5.2.3)
activesupport (= 5.2.3)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 5.0.7.2) railties (= 5.2.3)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4) rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x) actionpack (>= 5.0.1.x)
@ -213,31 +233,30 @@ GEM
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4) rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2) loofah (~> 2.2, >= 2.2.2)
railties (5.0.7.2) railties (5.2.3)
actionpack (= 5.0.7.2) actionpack (= 5.2.3)
activesupport (= 5.0.7.2) activesupport (= 5.2.3)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.19.0, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
rake (12.3.2) rake (12.3.2)
random_password (0.1.1) random_password (0.1.1)
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.10.0) rb-inotify (0.10.0)
ffi (~> 1.0) ffi (~> 1.0)
recaptcha (4.14.0) recaptcha (5.0.0)
json json
redcarpet (3.4.0) redcarpet (3.4.0)
redis (3.3.5)
remote_syslog_logger (1.0.4) remote_syslog_logger (1.0.4)
syslog_protocol syslog_protocol
rolify (5.2.0) rolify (5.2.0)
rspec-core (3.8.0) rspec-core (3.8.2)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-expectations (3.8.3) rspec-expectations (3.8.4)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-mocks (3.8.0) rspec-mocks (3.8.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-rails (3.8.2) rspec-rails (3.8.2)
@ -248,31 +267,20 @@ GEM
rspec-expectations (~> 3.8.0) rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0) rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-support (3.8.0) rspec-support (3.8.2)
rubocop (0.70.0) rubocop (0.72.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.6) parser (>= 2.6)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7) unicode-display_width (>= 1.4.0, < 1.7)
ruby-progressbar (1.10.0) ruby-progressbar (1.10.1)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sass-rails (5.0.7)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sassc (2.0.1) sassc (2.0.1)
ffi (~> 1.9) ffi (~> 1.9)
rake rake
sassc-rails (2.1.1) sassc-rails (2.1.2)
railties (>= 4.0.0) railties (>= 4.0.0)
sassc (>= 2.0) sassc (>= 2.0)
sprockets (> 3.0) sprockets (> 3.0)
@ -285,8 +293,7 @@ GEM
json (>= 1.8, < 3) json (>= 1.8, < 3)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.2) simplecov-html (0.10.2)
spring (2.0.2) spring (2.1.0)
activesupport (>= 4.2)
spring-watcher-listen (2.0.1) spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0) listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0) spring (>= 1.2, < 3.0)
@ -299,14 +306,12 @@ GEM
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.3.13) sqlite3 (1.3.13)
syslog_protocol (0.9.2) syslog_protocol (0.9.2)
tabler-rubygem (0.1.4)
autoprefixer-rails (>= 6.0.3)
term-ansicolor (1.7.1) term-ansicolor (1.7.1)
tins (~> 1.0) tins (~> 1.0)
thor (0.20.3) thor (0.20.3)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.9) tilt (2.0.9)
tins (1.20.2) tins (1.21.0)
turbolinks (5.2.0) turbolinks (5.2.0)
turbolinks-source (~> 5.2) turbolinks-source (~> 5.2)
turbolinks-source (5.2.0) turbolinks-source (5.2.0)
@ -320,13 +325,13 @@ GEM
activemodel (>= 5.0) activemodel (>= 5.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 5.0) railties (>= 5.0)
webmock (3.5.1) webmock (3.6.0)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff hashdiff (>= 0.4.0, < 2.0.0)
websocket-driver (0.6.5) websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3) websocket-extensions (0.1.4)
xml-simple (1.1.5) xml-simple (1.1.5)
PLATFORMS PLATFORMS
@ -337,6 +342,7 @@ DEPENDENCIES
bcrypt (~> 3.1.7) bcrypt (~> 3.1.7)
bigbluebutton-api-ruby bigbluebutton-api-ruby
bn-ldap-authentication! bn-ldap-authentication!
bootsnap (>= 1.1.0)
bootstrap (~> 4.3.1) bootstrap (~> 4.3.1)
byebug byebug
cancancan (~> 2.0) cancancan (~> 2.0)
@ -361,24 +367,24 @@ DEPENDENCIES
pagy pagy
pg (~> 0.18) pg (~> 0.18)
puma (~> 3.0) puma (~> 3.0)
rails (~> 5.0.7) rails (~> 5.2.3)
rails-controller-testing rails-controller-testing
random_password random_password
recaptcha recaptcha
redcarpet redcarpet
redis (~> 3.0)
remote_syslog_logger remote_syslog_logger
rolify rolify
rspec-rails (~> 3.7) rspec-rails (~> 3.7)
rubocop rubocop
sass-rails (~> 5.0) sassc-rails
shoulda-matchers (~> 3.1) shoulda-matchers (~> 3.1)
spring spring
spring-watcher-listen (~> 2.0.0) spring-watcher-listen (~> 2.0.0)
sqlite3 (~> 1.3.6) sqlite3 (~> 1.3.6)
tabler-rubygem tabler-rubygem!
turbolinks (~> 5) turbolinks (~> 5)
tzinfo-data tzinfo-data
uglifier (>= 1.3.0) uglifier (>= 1.3.0)
web-console (>= 3.3.0) web-console (>= 3.3.0)
webmock webmock

View File

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

View File

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

View File

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

View File

@ -39,29 +39,18 @@ $(document).on('turbolinks:load', function(){
}, 2000) }, 2000)
} }
}); });
// Handle recording emails.
$('.email-link').each(function(){
$(this).click(function(){
var subject = $(".username").text() + " " + t('room.mailer.subject');
var body = t('room.mailer.body') + "\n\n" + $(this).attr("data-pres-link");
var autogenerated = "\n\n" + t('room.mailer.autogenerated') + "\n";
var footer = t('room.mailer.footer');
var url = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body) + encodeURIComponent(autogenerated) + encodeURIComponent(footer);
var win = window.open(url, '_blank');
win.focus();
});
});
} }
// Display and update all fields related to creating a room in the createRoomModal // Display and update all fields related to creating a room in the createRoomModal
$("#create-room-block").click(function(){ $("#create-room-block").click(function(){
$("#create-room-name").val("") $("#create-room-name").val("")
$("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code_placeholder") %>")
$("#room_access_code").val(null)
$("#createRoomModal form").attr("action", $("body").data('relative-root')) $("#createRoomModal form").attr("action", $("body").data('relative-root'))
updateDropdown($(".dropdown-item[value='default']")) updateDropdown($(".dropdown-item[value='default']"))
$("#room_mute_on_join").prop("checked", false) $("#room_mute_on_join").prop("checked", false)
$("#room_anyone_can_start").prop("checked", false)
//show all elements & their children with a create-only class //show all elements & their children with a create-only class
$(".create-only").each(function() { $(".create-only").each(function() {
@ -95,6 +84,16 @@ $(document).on('turbolinks:load', function(){
}) })
updateCurrentSettings($(this).closest("#room-block").data("room-settings")) updateCurrentSettings($(this).closest("#room-block").data("room-settings"))
accessCode = $(this).closest("#room-block").data("room-access-code")
if(accessCode){
$("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code") %>: " + accessCode)
$("#room_access_code").val(accessCode)
} else{
$("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code_placeholder") %>")
$("#room_access_code").val(null)
}
}) })
//Update the createRoomModal to show the correct current settings //Update the createRoomModal to show the correct current settings
@ -106,6 +105,12 @@ $(document).on('turbolinks:load', function(){
$("#room_mute_on_join").prop("checked", false) $("#room_mute_on_join").prop("checked", false)
} }
if(settings.anyoneCanStart){
$("#room_anyone_can_start").prop("checked", true)
} else { //default option
$("#room_anyone_can_start").prop("checked", false)
}
//set dropdown value //set dropdown value
if (settings.joinViaHtml5) { if (settings.joinViaHtml5) {
updateDropdown($(".dropdown-item[value='html5']")) updateDropdown($(".dropdown-item[value='html5']"))
@ -122,3 +127,21 @@ function updateDropdown(element) {
$("#dropdown-trigger").text(element.text()) $("#dropdown-trigger").text(element.text())
$("#room_client").val(element.val()) $("#room_client").val(element.val())
} }
function generateAccessCode(){
const accessCodeLength = 6
var validCharacters = "0123456789"
var accessCode = ""
for( var i = 0; i < accessCodeLength; i++){
accessCode += validCharacters.charAt(Math.floor(Math.random() * validCharacters.length));
}
$("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code") %>: " + accessCode)
$("#room_access_code").val(accessCode)
}
function ResetAccessCode(){
$("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code_placeholder") %>")
$("#room_access_code").val(null)
}

View File

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

View File

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

View File

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

View File

@ -65,3 +65,7 @@
background-color: rgba(0, 0, 0, 0.04); background-color: rgba(0, 0, 0, 0.04);
} }
} }
.allow-icon-click{
pointer-events: auto;
}

View File

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

View File

@ -20,10 +20,11 @@ class AdminsController < ApplicationController
include Pagy::Backend include Pagy::Backend
include Themer include Themer
include Emailer include Emailer
include Recorder
manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve] manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve]
site_settings = [:branding, :coloring, :coloring_lighten, :coloring_darken, site_settings = [:branding, :coloring, :coloring_lighten, :coloring_darken,
:registration_method, :room_authentication, :room_limit] :registration_method, :room_authentication, :room_limit, :default_recording_visibility]
authorize_resource class: false authorize_resource class: false
before_action :find_user, only: manage_users before_action :find_user, only: manage_users
@ -40,11 +41,27 @@ class AdminsController < ApplicationController
@pagy, @users = pagy(user_list) @pagy, @users = pagy(user_list)
end end
# GET /admins/site_settings
def site_settings
end
# GET /admins/server_recordings
def server_recordings
server_rooms = if Rails.configuration.loadbalanced_configuration
Room.includes(:owner).where(users: { provider: user_settings_provider }).pluck(:bbb_id)
else
Room.pluck(:bbb_id)
end
@search, @order_column, @order_direction, recs =
all_recordings(server_rooms, @user_domain, params.permit(:search, :column, :direction), true, true)
@pagy, @recordings = pagy_array(recs)
end
# MANAGE USERS # MANAGE USERS
# GET /admins/edit/:user_uid # GET /admins/edit/:user_uid
def edit_user def edit_user
render "admins/index", locals: { setting_id: "account" }
end end
# POST /admins/promote/:user_uid # POST /admins/promote/:user_uid
@ -111,7 +128,7 @@ class AdminsController < ApplicationController
# POST /admins/branding # POST /admins/branding
def branding def branding
@settings.update_value("Branding Image", params[:url]) @settings.update_value("Branding Image", params[:url])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end end
# POST /admins/color # POST /admins/color
@ -119,23 +136,23 @@ class AdminsController < ApplicationController
@settings.update_value("Primary Color", params[:color]) @settings.update_value("Primary Color", params[:color])
@settings.update_value("Primary Color Lighten", color_lighten(params[:color])) @settings.update_value("Primary Color Lighten", color_lighten(params[:color]))
@settings.update_value("Primary Color Darken", color_darken(params[:color])) @settings.update_value("Primary Color Darken", color_darken(params[:color]))
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end end
def coloring_lighten def coloring_lighten
@settings.update_value("Primary Color Lighten", params[:color]) @settings.update_value("Primary Color Lighten", params[:color])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end end
def coloring_darken def coloring_darken
@settings.update_value("Primary Color Darken", params[:color]) @settings.update_value("Primary Color Darken", params[:color])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end end
# POST /admins/room_authentication # POST /admins/room_authentication
def room_authentication def room_authentication
@settings.update_value("Room Authentication", params[:value]) @settings.update_value("Room Authentication", params[:value])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end end
# POST /admins/registration_method/:method # POST /admins/registration_method/:method
@ -144,11 +161,11 @@ class AdminsController < ApplicationController
# Only allow change to Join by Invitation if user has emails enabled # 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] if !Rails.configuration.enable_email_verification && new_method == Rails.configuration.registration_methods[:invite]
redirect_to admins_path, redirect_to admin_site_settings_path,
flash: { alert: I18n.t("administrator.flash.invite_email_verification") } flash: { alert: I18n.t("administrator.flash.invite_email_verification") }
else else
@settings.update_value("Registration Method", new_method) @settings.update_value("Registration Method", new_method)
redirect_to admins_path, redirect_to admin_site_settings_path,
flash: { success: I18n.t("administrator.flash.registration_method_updated") } flash: { success: I18n.t("administrator.flash.registration_method_updated") }
end end
end end
@ -156,13 +173,20 @@ class AdminsController < ApplicationController
# POST /admins/room_limit # POST /admins/room_limit
def room_limit def room_limit
@settings.update_value("Room Limit", params[:limit]) @settings.update_value("Room Limit", params[:limit])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") }
end
# POST /admins/default_recording_visibility
def default_recording_visibility
@settings.update_value("Default Recording Visibility", params[:visibility])
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") + ". " +
I18n.t("administrator.site_settings.recording_visibility.warning") }
end end
private private
def find_user def find_user
@user = User.find_by!(uid: params[:user_uid]) @user = User.where(uid: params[:user_uid]).includes(:roles).first
end end
def find_setting def find_setting
@ -176,21 +200,19 @@ class AdminsController < ApplicationController
# Gets the list of users based on your configuration # Gets the list of users based on your configuration
def user_list def user_list
initial_list = if current_user.has_role? :super_admin initial_list = if current_user.has_cached_role? :super_admin
User.where.not(id: current_user.id) User.where.not(id: current_user.id).includes(:roles)
else else
User.without_role(:super_admin).where.not(id: current_user.id) User.without_role(:super_admin).where.not(id: current_user.id).includes(:roles)
end end
list = @role.present? ? initial_list.with_role(@role.to_sym) : initial_list
if Rails.configuration.loadbalanced_configuration if Rails.configuration.loadbalanced_configuration
list.where(provider: user_settings_provider) initial_list.where(provider: user_settings_provider)
.admins_search(@search) .admins_search(@search, @role)
.admins_order(@order_column, @order_direction) .admins_order(@order_column, @order_direction)
else else
list.admins_search(@search) initial_list.admins_search(@search, @role)
.admins_order(@order_column, @order_direction) .admins_order(@order_column, @order_direction)
end end
end end

View File

@ -121,6 +121,9 @@ class ApplicationController < ActionController::Base
meeting_logout_url: request.base_url + logout_room_path(@room), meeting_logout_url: request.base_url + logout_room_path(@room),
meeting_recorded: true, meeting_recorded: true,
moderator_message: "#{invite_msg}\n\n#{request.base_url + room_path(@room)}", moderator_message: "#{invite_msg}\n\n#{request.base_url + room_path(@room)}",
host: request.host,
recording_default_visibility: Setting.find_or_create_by!(provider: user_settings_provider)
.get_value("Default Recording Visibility") == "public"
} }
end end
@ -131,7 +134,7 @@ class ApplicationController < ActionController::Base
# Checks to make sure that the admin has changed his password from the default # Checks to make sure that the admin has changed his password from the default
def check_admin_password def check_admin_password
if current_user&.has_role?(:admin) && current_user&.greenlight_account? && if current_user&.has_cached_role?(:admin) && current_user&.greenlight_account? &&
current_user&.authenticate(Rails.configuration.admin_password_default) current_user&.authenticate(Rails.configuration.admin_password_default)
flash.now[:alert] = I18n.t("default_admin", flash.now[:alert] = I18n.t("default_admin",
@ -179,10 +182,10 @@ class ApplicationController < ActionController::Base
# Checks if the user is banned and logs him out if he is # Checks if the user is banned and logs him out if he is
def check_user_role def check_user_role
if current_user&.has_role? :denied if current_user&.has_cached_role? :denied
session.delete(:user_id) session.delete(:user_id)
redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") } redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") }
elsif current_user&.has_role? :pending elsif current_user&.has_cached_role? :pending
session.delete(:user_id) session.delete(:user_id)
redirect_to root_path, flash: { alert: I18n.t("registration.approval.fail") } redirect_to root_path, flash: { alert: I18n.t("registration.approval.fail") }
end end

View File

@ -64,13 +64,19 @@ module Emailer
def send_approval_user_signup_email(user) def send_approval_user_signup_email(user)
return unless Rails.configuration.enable_email_verification return unless Rails.configuration.enable_email_verification
UserMailer.approval_user_signup(user, admins_url, logo_image, user_color, admin_emails).deliver_now admin_emails = admin_emails()
unless admin_emails.empty?
UserMailer.approval_user_signup(user, admins_url, logo_image, user_color, admin_emails).deliver_now
end
end end
def send_invite_user_signup_email(user) def send_invite_user_signup_email(user)
return unless Rails.configuration.enable_email_verification return unless Rails.configuration.enable_email_verification
UserMailer.invite_user_signup(user, admins_url, logo_image, user_color, admin_emails).deliver_now admin_emails = admin_emails()
unless admin_emails.empty?
UserMailer.invite_user_signup(user, admins_url, logo_image, user_color, admin_emails).deliver_now
end
end end
private private

View File

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

View File

@ -24,7 +24,7 @@ module Themer
# Uses the built in Sass Engine to lighten the color # Uses the built in Sass Engine to lighten the color
dummy_scss = "h1 { color: $lighten; }" dummy_scss = "h1 { color: $lighten; }"
compiled = Sass::Engine.new("$lighten:lighten(#{color}, 40%);" + dummy_scss, syntax: :scss).render compiled = SassC::Engine.new("$lighten:lighten(#{color}, 40%);" + dummy_scss, syntax: :scss).render
string_locater = 'color: ' string_locater = 'color: '
color_start = compiled.index(string_locater) + string_locater.length color_start = compiled.index(string_locater) + string_locater.length
@ -37,7 +37,7 @@ module Themer
# Uses the built in Sass Engine to darken the color # Uses the built in Sass Engine to darken the color
dummy_scss = "h1 { color: $darken; }" dummy_scss = "h1 { color: $darken; }"
compiled = Sass::Engine.new("$darken:darken(#{color}, 10%);" + dummy_scss, syntax: :scss).render compiled = SassC::Engine.new("$darken:darken(#{color}, 10%);" + dummy_scss, syntax: :scss).render
string_locater = 'color: ' string_locater = 'color: '
color_start = compiled.index(string_locater) + string_locater.length color_start = compiled.index(string_locater) + string_locater.length

View File

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

View File

@ -19,12 +19,13 @@
class RoomsController < ApplicationController class RoomsController < ApplicationController
include RecordingsHelper include RecordingsHelper
include Pagy::Backend include Pagy::Backend
include Recorder
before_action :validate_accepted_terms, unless: -> { !Rails.configuration.terms } before_action :validate_accepted_terms, unless: -> { !Rails.configuration.terms }
before_action :validate_verified_email, except: [:show, :join], before_action :validate_verified_email, except: [:show, :join],
unless: -> { !Rails.configuration.enable_email_verification } unless: -> { !Rails.configuration.enable_email_verification }
before_action :find_room, except: :create before_action :find_room, except: :create
before_action :verify_room_ownership, except: [:create, :show, :join, :logout] before_action :verify_room_ownership, except: [:create, :show, :join, :logout, :login]
before_action :verify_room_owner_verified, only: [:show, :join], before_action :verify_room_owner_verified, only: [:show, :join],
unless: -> { !Rails.configuration.enable_email_verification } unless: -> { !Rails.configuration.enable_email_verification }
before_action :verify_user_not_admin, only: [:show] before_action :verify_user_not_admin, only: [:show]
@ -35,9 +36,10 @@ class RoomsController < ApplicationController
return redirect_to current_user.main_room, flash: { alert: I18n.t("room.room_limit") } if room_limit_exceeded return redirect_to current_user.main_room, flash: { alert: I18n.t("room.room_limit") } if room_limit_exceeded
@room = Room.new(name: room_params[:name]) @room = Room.new(name: room_params[:name], access_code: room_params[:access_code])
@room.owner = current_user @room.owner = current_user
@room.room_settings = create_room_settings_string(room_params[:mute_on_join], room_params[:client]) @room.room_settings = create_room_settings_string(room_params[:mute_on_join], room_params[:client],
room_params[:anyone_can_start])
if @room.save if @room.save
if room_params[:auto_join] == "1" if room_params[:auto_join] == "1"
@ -54,13 +56,15 @@ class RoomsController < ApplicationController
# GET /:room_uid # GET /:room_uid
def show def show
@is_running = @room.running?
@anyone_can_start = JSON.parse(@room[:room_settings])["anyoneCanStart"]
if current_user && @room.owned_by?(current_user) if current_user && @room.owned_by?(current_user)
@search, @order_column, @order_direction, recs = @search, @order_column, @order_direction, recs =
@room.recordings(params.permit(:search, :column, :direction), true) recordings(@room.bbb_id, @user_domain, params.permit(:search, :column, :direction), true)
@pagy, @recordings = pagy_array(recs) @pagy, @recordings = pagy_array(recs)
@is_running = @room.running?
else else
# Get users name # Get users name
@name = if current_user @name = if current_user
@ -72,7 +76,7 @@ class RoomsController < ApplicationController
end end
@search, @order_column, @order_direction, pub_recs = @search, @order_column, @order_direction, pub_recs =
@room.public_recordings(params.permit(:search, :column, :direction), true) public_recordings(@room.bbb_id, @user_domain, params.permit(:search, :column, :direction), true)
@pagy, @public_recordings = pagy_array(pub_recs) @pagy, @public_recordings = pagy_array(pub_recs)
@ -105,6 +109,12 @@ class RoomsController < ApplicationController
opts = default_meeting_options opts = default_meeting_options
unless @room.owned_by?(current_user) unless @room.owned_by?(current_user)
# Don't allow users to join unless they have a valid access code or the room doesn't
# have an access code
if @room.access_code && !@room.access_code.empty? && @room.access_code != session[:access_code]
return redirect_to room_path(room_uid: params[:room_uid]), flash: { alert: I18n.t("room.access_code_required") }
end
# Assign join name if passed. # Assign join name if passed.
if params[@room.invite_path] if params[@room.invite_path]
@join_name = params[@room.invite_path][:join_name] @join_name = params[@room.invite_path][:join_name]
@ -117,31 +127,7 @@ class RoomsController < ApplicationController
# create or update cookie with join name # create or update cookie with join name
cookies.encrypted[:greenlight_name] = @join_name unless cookies.encrypted[:greenlight_name] == @join_name cookies.encrypted[:greenlight_name] = @join_name unless cookies.encrypted[:greenlight_name] == @join_name
if @room.running? || @room.owned_by?(current_user) join_room(opts)
# Determine if the user needs to join as a moderator.
opts[:user_is_moderator] = @room.owned_by?(current_user)
# Check if the user has specified which client to use
room_settings = JSON.parse(@room[:room_settings])
opts[:join_via_html5] = room_settings["joinViaHtml5"] if room_settings["joinViaHtml5"]
if current_user
redirect_to @room.join_path(current_user.name, opts, current_user.uid)
else
join_name = params[:join_name] || params[@room.invite_path][:join_name]
redirect_to @room.join_path(join_name, opts)
end
else
search_params = params[@room.invite_path] || params
@search, @order_column, @order_direction, pub_recs =
@room.public_recordings(search_params.permit(:search, :column, :direction), true)
@pagy, @public_recordings = pagy_array(pub_recs)
# They need to wait until the meeting begins.
render :wait
end
end end
# DELETE /:room_uid # DELETE /:room_uid
@ -184,6 +170,8 @@ class RoomsController < ApplicationController
update_room_attributes("settings") update_room_attributes("settings")
# Update the rooms name if it has been changed # Update the rooms name if it has been changed
update_room_attributes("name") if @room.name != room_params[:name] update_room_attributes("name") if @room.name != room_params[:name]
# Update the room's access code if it has changed
update_room_attributes("access_code") if @room.access_code != room_params[:access_code]
rescue StandardError rescue StandardError
flash[:alert] = I18n.t("room.update_settings_error") flash[:alert] = I18n.t("room.update_settings_error")
else else
@ -198,6 +186,15 @@ class RoomsController < ApplicationController
redirect_to @room redirect_to @room
end end
# POST /:room_uid/login
def login
session[:access_code] = room_params[:access_code]
flash[:alert] = I18n.t("room.access_code_required") if session[:access_code] != @room.access_code
redirect_to room_path(@room.uid)
end
private private
def update_room_attributes(update_type) def update_room_attributes(update_type)
@ -205,13 +202,16 @@ class RoomsController < ApplicationController
if update_type.eql? "name" if update_type.eql? "name"
@room.update_attributes(name: params[:room_name] || room_params[:name]) @room.update_attributes(name: params[:room_name] || room_params[:name])
elsif update_type.eql? "settings" elsif update_type.eql? "settings"
room_settings_string = create_room_settings_string(room_params[:mute_on_join], room_params[:client]) room_settings_string = create_room_settings_string(room_params[:mute_on_join], room_params[:client],
room_params[:anyone_can_start])
@room.update_attributes(room_settings: room_settings_string) @room.update_attributes(room_settings: room_settings_string)
elsif update_type.eql? "access_code"
@room.update_attributes(access_code: room_params[:access_code])
end end
end end
end end
def create_room_settings_string(mute_res, client_res) def create_room_settings_string(mute_res, client_res, start_res)
room_settings = {} room_settings = {}
room_settings["muteOnStart"] = mute_res == "1" room_settings["muteOnStart"] = mute_res == "1"
@ -221,11 +221,13 @@ class RoomsController < ApplicationController
room_settings["joinViaHtml5"] = false room_settings["joinViaHtml5"] = false
end end
room_settings["anyoneCanStart"] = start_res == "1"
room_settings.to_json room_settings.to_json
end end
def room_params def room_params
params.require(:room).permit(:name, :auto_join, :mute_on_join, :client) params.require(:room).permit(:name, :auto_join, :mute_on_join, :client, :access_code, :anyone_can_start)
end end
# Find the room from the uid. # Find the room from the uid.
@ -274,7 +276,7 @@ class RoomsController < ApplicationController
end end
def verify_user_not_admin def verify_user_not_admin
redirect_to admins_path if current_user && current_user&.has_role?(:super_admin) redirect_to admins_path if current_user && current_user&.has_cached_role?(:super_admin)
end end
def auth_required def auth_required
@ -287,8 +289,38 @@ class RoomsController < ApplicationController
# Does not apply to admin # Does not apply to admin
# 15+ option is used as unlimited # 15+ option is used as unlimited
return false if current_user&.has_role?(:admin) || limit == 15 return false if current_user&.has_cached_role?(:admin) || limit == 15
current_user.rooms.count >= limit current_user.rooms.count >= limit
end end
def join_room(opts)
room_settings = JSON.parse(@room[:room_settings])
if @room.running? || @room.owned_by?(current_user) || room_settings["anyoneCanStart"]
# Determine if the user needs to join as a moderator.
opts[:user_is_moderator] = @room.owned_by?(current_user) ||
(room_settings["anyoneCanStart"] && !@room.running?)
# Check if the user has specified which client to use
opts[:join_via_html5] = room_settings["joinViaHtml5"] if room_settings["joinViaHtml5"]
if current_user
redirect_to @room.join_path(current_user.name, opts, current_user.uid)
else
join_name = params[:join_name] || params[@room.invite_path][:join_name]
redirect_to @room.join_path(join_name, opts)
end
else
search_params = params[@room.invite_path] || params
@search, @order_column, @order_direction, pub_recs =
public_recordings(@room.bbb_id, @user_domain, search_params.permit(:search, :column, :direction), true)
@pagy, @public_recordings = pagy_array(pub_recs)
# They need to wait until the meeting begins.
render :wait
end
end
end end

View File

@ -30,7 +30,7 @@ class ThemesController < ApplicationController
@file_contents = File.read(file_name) @file_contents = File.read(file_name)
# Include the variables and covert scss file to css # Include the variables and covert scss file to css
@compiled = Sass::Engine.new("$primary-color:#{color};" \ @compiled = SassC::Engine.new("$primary-color:#{color};" \
"$primary-color-lighten:#{lighten_color};" \ "$primary-color-lighten:#{lighten_color};" \
"$primary-color-darken:#{darken_color};" + "$primary-color-darken:#{darken_color};" +
@file_contents, syntax: :scss).render @file_contents, syntax: :scss).render

View File

@ -21,6 +21,7 @@ class UsersController < ApplicationController
include Pagy::Backend include Pagy::Backend
include Emailer include Emailer
include Registrar include Registrar
include Recorder
before_action :find_user, only: [:edit, :update, :destroy] before_action :find_user, only: [:edit, :update, :destroy]
before_action :ensure_unauthenticated, only: [:new, :create] before_action :ensure_unauthenticated, only: [:new, :create]
@ -66,6 +67,12 @@ class UsersController < ApplicationController
flash[:alert] = I18n.t("registration.deprecated.new_signin") flash[:alert] = I18n.t("registration.deprecated.new_signin")
session[:old_twitter_user_id] = params[:old_twitter_user_id] unless params[:old_twitter_user_id].nil? session[:old_twitter_user_id] = params[:old_twitter_user_id] unless params[:old_twitter_user_id].nil?
end end
providers = configured_providers
if (!allow_user_signup? || !allow_greenlight_accounts?) && providers.count == 1 &&
!Rails.configuration.loadbalanced_configuration
return redirect_to "#{Rails.configuration.relative_url_root}/auth/#{providers.first}"
end
end end
# GET /ldap_signin # GET /ldap_signin
@ -103,6 +110,8 @@ class UsersController < ApplicationController
# PATCH /u/:user_uid/edit # PATCH /u/:user_uid/edit
def update def update
redirect_path = current_user.admin_of?(@user) ? admins_path : edit_user_path(@user)
if params[:setting] == "password" if params[:setting] == "password"
# Update the users password. # Update the users password.
errors = {} errors = {}
@ -123,7 +132,7 @@ class UsersController < ApplicationController
if errors.empty? && @user.save if errors.empty? && @user.save
# Notify the user that their account has been updated. # Notify the user that their account has been updated.
flash[:success] = I18n.t("info_update_success") flash[:success] = I18n.t("info_update_success")
redirect_to edit_user_path(@user) redirect_to redirect_path
else else
# Append custom errors. # Append custom errors.
errors.each { |k, v| @user.errors.add(k, v) } errors.each { |k, v| @user.errors.add(k, v) }
@ -132,11 +141,11 @@ class UsersController < ApplicationController
elsif user_params[:email] != @user.email && @user.update_attributes(user_params) elsif user_params[:email] != @user.email && @user.update_attributes(user_params)
@user.update_attributes(email_verified: false) @user.update_attributes(email_verified: false)
flash[:success] = I18n.t("info_update_success") flash[:success] = I18n.t("info_update_success")
redirect_to edit_user_path(@user) redirect_to redirect_path
elsif @user.update_attributes(user_params) elsif @user.update_attributes(user_params)
update_locale(@user) update_locale(@user)
flash[:success] = I18n.t("info_update_success") flash[:success] = I18n.t("info_update_success")
redirect_to edit_user_path(@user) redirect_to redirect_path
else else
render :edit, params: { settings: params[:settings] } render :edit, params: { settings: params[:settings] }
end end
@ -165,7 +174,8 @@ class UsersController < ApplicationController
def recordings def recordings
if current_user && current_user.uid == params[:user_uid] if current_user && current_user.uid == params[:user_uid]
@search, @order_column, @order_direction, recs = @search, @order_column, @order_direction, recs =
current_user.all_recordings(params.permit(:search, :column, :direction), true) all_recordings(current_user.rooms.pluck(:bbb_id), current_user.provider,
params.permit(:search, :column, :direction), true)
@pagy, @recordings = pagy_array(recs) @pagy, @recordings = pagy_array(recs)
else else
redirect_to root_path redirect_to root_path
@ -185,7 +195,7 @@ class UsersController < ApplicationController
private private
def find_user def find_user
@user = User.find_by!(uid: params[:user_uid]) @user = User.where(uid: params[:user_uid]).includes(:roles).first
end end
def ensure_unauthenticated def ensure_unauthenticated

View File

@ -19,6 +19,18 @@
module AdminsHelper module AdminsHelper
include Pagy::Frontend include Pagy::Frontend
# Returns the action method of the current page
def active_page
route = Rails.application.routes.recognize_path(request.env['PATH_INFO'])
route[:action]
end
# Gets the email of the room owner to which the recording belongs to
def recording_owner_email(room_id)
Room.find_by(bbb_id: room_id).owner.email
end
def display_invite def display_invite
current_page?(admins_path) && invite_registration current_page?(admins_path) && invite_registration
end end
@ -43,6 +55,15 @@ module AdminsHelper
end end
end end
def recording_default_visibility_string
if Setting.find_or_create_by!(provider: user_settings_provider)
.get_value("Default Recording Visibility") == "public"
I18n.t("administrator.site_settings.recording_visibility.public")
else
I18n.t("administrator.site_settings.recording_visibility.private")
end
end
def registration_method_string def registration_method_string
case registration_method case registration_method
when Rails.configuration.registration_methods[:open] when Rails.configuration.registration_methods[:open]

View File

@ -107,7 +107,7 @@ module ApplicationHelper
# Returns the page that the logo redirects to when clicked on # Returns the page that the logo redirects to when clicked on
def home_page def home_page
return root_path unless current_user return root_path unless current_user
return admins_path if current_user.has_role? :super_admin return admins_path if current_user.has_cached_role? :super_admin
current_user.main_room current_user.main_room
end end
end end

View File

@ -37,7 +37,7 @@ module RoomsHelper
# Does not apply to admin or users that aren't signed in # Does not apply to admin or users that aren't signed in
# 15+ option is used as unlimited # 15+ option is used as unlimited
return false if current_user&.has_role?(:admin) || limit == 15 return false if current_user&.has_cached_role?(:admin) || limit == 15
current_user.rooms.length >= limit current_user.rooms.length >= limit
end end
@ -46,7 +46,7 @@ module RoomsHelper
# Get how many rooms need to be deleted to reach allowed room number # Get how many rooms need to be deleted to reach allowed room number
limit = Setting.find_or_create_by!(provider: user_settings_provider).get_value("Room Limit").to_i limit = Setting.find_or_create_by!(provider: user_settings_provider).get_value("Room Limit").to_i
return false if current_user&.has_role?(:admin) || limit == 15 return false if current_user&.has_cached_role?(:admin) || limit == 15
@diff = current_user.rooms.count - limit @diff = current_user.rooms.count - limit
@diff.positive? && current_user.rooms.pluck(:id).index(room.id) + 1 > limit @diff.positive? && current_user.rooms.pluck(:id).index(room.id) + 1 > limit

View File

@ -61,7 +61,17 @@ module SessionsHelper
# Retrieves the current user. # Retrieves the current user.
def current_user def current_user
@current_user ||= User.find_by(id: session[:user_id]) @current_user ||= User.where(id: session[:user_id]).includes(:roles).first
if Rails.configuration.loadbalanced_configuration
if @current_user && !@current_user.has_role?(:super_admin) &&
@current_user.provider != @user_domain
@current_user = nil
session.clear
end
end
@current_user
end end
def generate_checksum(user_domain, redirect_url, secret) def generate_checksum(user_domain, redirect_url, secret)

View File

@ -31,7 +31,7 @@ module ThemingHelper
# Returns the user's provider in the settings context # Returns the user's provider in the settings context
def user_settings_provider def user_settings_provider
if Rails.configuration.loadbalanced_configuration && current_user && !current_user&.has_role?(:super_admin) if Rails.configuration.loadbalanced_configuration && current_user && !current_user&.has_cached_role?(:super_admin)
current_user.provider current_user.provider
elsif Rails.configuration.loadbalanced_configuration elsif Rails.configuration.loadbalanced_configuration
@user_domain @user_domain

View File

@ -19,7 +19,6 @@
require 'bbb_api' require 'bbb_api'
class Room < ApplicationRecord class Room < ApplicationRecord
include ::APIConcern
include ::BbbApi include ::BbbApi
before_create :setup before_create :setup
@ -40,7 +39,7 @@ class Room < ApplicationRecord
# Checks if a room is running on the BigBlueButton server. # Checks if a room is running on the BigBlueButton server.
def running? def running?
bbb.is_meeting_running?(bbb_id) bbb(owner.provider).is_meeting_running?(bbb_id)
end end
# Determines the invite path for the room. # Determines the invite path for the room.
@ -57,12 +56,15 @@ class Room < ApplicationRecord
attendeePW: attendee_pw, attendeePW: attendee_pw,
moderatorOnlyMessage: options[:moderator_message], moderatorOnlyMessage: options[:moderator_message],
muteOnStart: options[:mute_on_start] || false, muteOnStart: options[:mute_on_start] || false,
"meta_#{META_LISTED}": false, "meta_#{META_LISTED}": options[:recording_default_visibility] || false,
"meta_bbb-origin-version": Greenlight::Application::VERSION,
"meta_bbb-origin": "Greenlight",
"meta_bbb-origin-server-name": options[:host]
} }
# Send the create request. # Send the create request.
begin begin
meeting = bbb.create_meeting(name, bbb_id, create_options) meeting = bbb(owner.provider).create_meeting(name, bbb_id, create_options)
# Update session info. # Update session info.
unless meeting[:messageKey] == 'duplicateWarning' unless meeting[:messageKey] == 'duplicateWarning'
update_attributes(sessions: sessions + 1, update_attributes(sessions: sessions + 1,
@ -85,10 +87,10 @@ class Room < ApplicationRecord
options[:user_is_moderator] ||= false options[:user_is_moderator] ||= false
options[:meeting_recorded] ||= false options[:meeting_recorded] ||= false
return call_invalid_res unless bbb return call_invalid_res unless bbb(owner.provider)
# Get the meeting info. # Get the meeting info.
meeting_info = bbb.get_meeting_info(bbb_id, nil) meeting_info = bbb(owner.provider).get_meeting_info(bbb_id, nil)
# Determine the password to use when joining. # Determine the password to use when joining.
password = if options[:user_is_moderator] password = if options[:user_is_moderator]
@ -102,7 +104,7 @@ class Room < ApplicationRecord
join_opts[:userID] = uid if uid join_opts[:userID] = uid if uid
join_opts[:joinViaHtml5] = options[:join_via_html5] if options[:join_via_html5] join_opts[:joinViaHtml5] = options[:join_via_html5] if options[:join_via_html5]
bbb.join_meeting_url(bbb_id, name, password, join_opts) bbb(owner.provider).join_meeting_url(bbb_id, name, password, join_opts)
end end
# Notify waiting users that a meeting has started. # Notify waiting users that a meeting has started.
@ -112,7 +114,7 @@ class Room < ApplicationRecord
# Retrieves all the users in a room. # Retrieves all the users in a room.
def participants def participants
res = bbb.get_meeting_info(bbb_id, nil) res = bbb(owner.provider).get_meeting_info(bbb_id, nil)
res[:attendees].map do |att| res[:attendees].map do |att|
User.find_by(uid: att[:userID], name: att[:fullName]) User.find_by(uid: att[:userID], name: att[:fullName])
end end
@ -121,27 +123,18 @@ class Room < ApplicationRecord
[] []
end end
# Fetches all recordings for a room. def recording_count
def recordings(search_params = {}, ret_search_params = false) bbb(owner.provider).get_recordings(meetingID: bbb_id)[:recordings].length
res = bbb.get_recordings(meetingID: bbb_id)
format_recordings(res, search_params, ret_search_params)
end
# Fetches a rooms public recordings.
def public_recordings(search_params = {}, ret_search_params = false)
search, order_col, order_dir, recs = recordings(search_params, ret_search_params)
[search, order_col, order_dir, recs.select { |r| r[:metadata][:"gl-listed"] == "true" }]
end end
def update_recording(record_id, meta) def update_recording(record_id, meta)
meta[:recordID] = record_id meta[:recordID] = record_id
bbb.send_api_request("updateRecordings", meta) bbb(owner.provider).send_api_request("updateRecordings", meta)
end end
# Deletes a recording from a room. # Deletes a recording from a room.
def delete_recording(record_id) def delete_recording(record_id)
bbb.delete_recordings(record_id) bbb(owner.provider).delete_recordings(record_id)
end end
private private
@ -156,7 +149,7 @@ class Room < ApplicationRecord
# Deletes all recordings associated with the room. # Deletes all recordings associated with the room.
def delete_all_recordings def delete_all_recordings
record_ids = recordings.map { |r| r[:recordID] } record_ids = bbb(owner.provider).get_recordings(meetingID: bbb_id)[:recordings].pluck(:recordID)
delete_recording(record_ids) unless record_ids.empty? delete_recording(record_ids) unless record_ids.empty?
end end

View File

@ -20,7 +20,6 @@ require 'bbb_api'
class User < ApplicationRecord class User < ApplicationRecord
rolify rolify
include ::APIConcern
include ::BbbApi include ::BbbApi
attr_accessor :reset_token attr_accessor :reset_token
@ -119,7 +118,7 @@ class User < ApplicationRecord
end end
end end
def self.admins_search(string) def self.admins_search(string, role)
active_database = Rails.configuration.database_configuration[Rails.env]["adapter"] active_database = Rails.configuration.database_configuration[Rails.env]["adapter"]
# Postgres requires created_at to be cast to a string # Postgres requires created_at to be cast to a string
created_at_query = if active_database == "postgresql" created_at_query = if active_database == "postgresql"
@ -128,38 +127,29 @@ class User < ApplicationRecord
"created_at" "created_at"
end end
search_query = "users.name LIKE :search OR email LIKE :search OR username LIKE :search" \ search_query = ""
" OR users.#{created_at_query} LIKE :search OR provider LIKE :search" role_search_param = ""
if role.present?
search_query = "(users.name LIKE :search OR email LIKE :search OR username LIKE :search" \
" OR users.#{created_at_query} LIKE :search OR provider LIKE :search)" \
" AND roles.name = :roles_search"
role_search_param = role
else
search_query = "users.name LIKE :search OR email LIKE :search OR username LIKE :search" \
" OR users.#{created_at_query} LIKE :search OR provider LIKE :search" \
" OR roles.name LIKE :roles_search"
role_search_param = "%#{string}%".downcase
end
search_param = "%#{string}%" search_param = "%#{string}%"
where(search_query, search: search_param) joins("LEFT OUTER JOIN users_roles ON users_roles.user_id = users.id LEFT OUTER JOIN roles " \
"ON roles.id = users_roles.role_id").distinct
.where(search_query, search: search_param, roles_search: role_search_param)
end end
def self.admins_order(column, direction) def self.admins_order(column, direction)
order("#{column} #{direction}") # Arel.sql to avoid sql injection
end order(Arel.sql("#{column} #{direction}"))
def all_recordings(search_params = {}, ret_search_params = false)
pag_num = Rails.configuration.pagination_number
pag_loops = rooms.length / pag_num - 1
res = { recordings: [] }
(0..pag_loops).each do |i|
pag_rooms = rooms[pag_num * i, pag_num]
# bbb.get_recordings returns an object
# take only the array portion of the object that is returned
full_res = bbb.get_recordings(meetingID: pag_rooms.pluck(:bbb_id))
res[:recordings].push(*full_res[:recordings])
end
last_pag_room = rooms[pag_num * (pag_loops + 1), rooms.length % pag_num]
full_res = bbb.get_recordings(meetingID: last_pag_room.pluck(:bbb_id))
res[:recordings].push(*full_res[:recordings])
format_recordings(res, search_params, ret_search_params)
end end
# Activates an account and initialize a users main room # Activates an account and initialize a users main room
@ -231,13 +221,17 @@ class User < ApplicationRecord
def admin_of?(user) def admin_of?(user)
if Rails.configuration.loadbalanced_configuration if Rails.configuration.loadbalanced_configuration
if has_role? :super_admin # Pulls in the user roles if they weren't request in the original request
# So the has_cached_role? doesn't always return false
user.roles
if has_cached_role? :super_admin
id != user.id id != user.id
else else
(has_role? :admin) && (id != user.id) && (provider == user.provider) && (!user.has_role? :super_admin) (has_cached_role? :admin) && (id != user.id) && (provider == user.provider) &&
(!user.has_cached_role? :super_admin)
end end
else else
((has_role? :admin) || (has_role? :super_admin)) && (id != user.id) ((has_cached_role? :admin) || (has_cached_role? :super_admin)) && (id != user.id)
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -98,6 +98,27 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="mb-6 row">
<div class="col-12">
<div class="form-group">
<label class="form-label"><%= t("administrator.site_settings.recording_visibility.title") %></label>
<label class="form-label text-muted"><%= t("administrator.site_settings.recording_visibility.info") %></label>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="room-auth" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<%= recording_default_visibility_string %>
</button>
<div class="dropdown-menu" aria-labelledby="room-auth">
<%= button_to admin_recording_visibility_path(visibility: "public"), class: "dropdown-item" do %>
<%= t("administrator.site_settings.recording_visibility.public") %>
<% end %>
<%= button_to admin_recording_visibility_path(visibility: "private"), class: "dropdown-item" do %>
<%= t("administrator.site_settings.recording_visibility.private") %>
<% end %>
</div>
</div>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,9 +13,21 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%> %>
<%= render 'shared/room_event' do %> <% valid_access_code = @room.access_code.nil? || @room.access_code.empty? || @room.access_code == session[:access_code] %>
<%= render 'shared/room_event', render_recordings: valid_access_code do %>
<% if room_authentication_required %> <% if room_authentication_required %>
<h2><%= t("administrator.site_settings.authentication.user-info") %></h2> <h2><%= t("administrator.site_settings.authentication.user-info") %></h2>
<% elsif !valid_access_code %>
<%= form_for :room, url: login_room_path(@room.uid) do |f| %>
<div class="input-group join-input">
<%= f.text_field :access_code,
required: true,
class: "form-control join-form",
placeholder: t("room.enter_the_access_code"),
value: "" %>
<%= f.submit t("room.login"), class: "btn btn-primary btn-sm col-sm-3 form-control join-form" %>
</div>
<% end %>
<% else %> <% else %>
<%= form_for room_path(@room), method: :post do |f| %> <%= form_for room_path(@room), method: :post do |f| %>
<div class="input-group join-input"> <div class="input-group join-input">
@ -28,7 +40,7 @@
placeholder: t("enter_your_name"), placeholder: t("enter_your_name"),
value: "#{@name}", value: "#{@name}",
readonly: !current_user.nil? %> readonly: !current_user.nil? %>
<%= f.submit t("room.join"), class: "btn btn-primary btn-sm col-sm-3 form-control join-form" %> <%= f.submit (!@is_running && @anyone_can_start)? t("room.start") : t("room.join"), class: "btn btn-primary btn-sm col-sm-3 form-control join-form" %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>

View File

@ -36,7 +36,7 @@
<% unless exceeds_limit %> <% unless exceeds_limit %>
<label class="form-label"><%= t("room.invite_participants") %></label> <label class="form-label"><%= t("room.invite_participants") %></label>
<div class="row"> <div class="row">
<div class="col-lg-5 col-md-12 mt-2 pr-0"> <div class="col-lg-7 col-md-12 mt-2 pr-0">
<div class="input-icon invite-link-input"> <div class="input-icon invite-link-input">
<span class="input-icon-addon"> <span class="input-icon-addon">
<i class="fas fa-link"></i> <i class="fas fa-link"></i>
@ -44,7 +44,7 @@
<input id="invite-url" type="text" class="form-control w-100" value="<%= request.base_url + @room.invite_path %>" readonly=""> <input id="invite-url" type="text" class="form-control w-100" value="<%= request.base_url + @room.invite_path %>" readonly="">
</div> </div>
</div> </div>
<div class="col-lg-7 col-md-12 pr-0"> <div class="col-lg-5 col-md-12 pr-0">
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
<a href="#" id="copy" class="btn btn-primary btn-block mt-2"> <a href="#" id="copy" class="btn btn-primary btn-block mt-2">
@ -89,7 +89,7 @@
<%= render "shared/components/room_block", room: room %> <%= render "shared/components/room_block", room: room %>
<% end %> <% end %>
</div> </div>
<%= render "shared/modals/delete_room_modal", room: room %> <%= render "shared/modals/delete_room_modal", recording_count: room.recording_count, room: room %>
<% end %> <% end %>
<% end %> <% end %>
<% unless room_limit_exceeded %> <% unless room_limit_exceeded %>

View File

@ -13,7 +13,7 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%> %>
<%= render 'shared/room_event' do %> <%= render 'shared/room_event', render_recordings: true do %>
<div class="row"> <div class="row">
<div class="col-9"> <div class="col-9">
<h3><%= t("room.wait.message") %></h3> <h3><%= t("room.wait.message") %></h3>

View File

@ -23,7 +23,7 @@
<div class="d-flex ml-auto"> <div class="d-flex ml-auto">
<% if current_user %> <% if current_user %>
<% if current_user.has_role? :super_admin %> <% if current_user.has_cached_role? :super_admin %>
<% admins_page = params[:controller] == "admins" && params[:action] == "index" ? "active" : "" %> <% admins_page = params[:controller] == "admins" && params[:action] == "index" ? "active" : "" %>
<%= link_to admins_path, class: "px-3 mx-1 mt-1 header-nav #{admins_page}" do %> <%= link_to admins_path, class: "px-3 mx-1 mt-1 header-nav #{admins_page}" do %>
<i class="fas fa-home pr-1 "></i> <%= t("header.dropdown.home") %> <i class="fas fa-home pr-1 "></i> <%= t("header.dropdown.home") %>
@ -56,7 +56,7 @@
<%= link_to edit_user_path(current_user), class: "dropdown-item" do %> <%= link_to edit_user_path(current_user), class: "dropdown-item" do %>
<i class="dropdown-icon fas fa-id-card mr-3"></i><%= t("header.dropdown.settings") %> <i class="dropdown-icon fas fa-id-card mr-3"></i><%= t("header.dropdown.settings") %>
<% end %> <% end %>
<% if current_user.has_role? :admin %> <% if current_user.has_cached_role? :admin %>
<%= link_to admins_path, class: "dropdown-item" do %> <%= link_to admins_path, class: "dropdown-item" do %>
<i class="dropdown-icon fas fa-user-tie mr-3"></i><%= t("header.dropdown.account_settings") %> <i class="dropdown-icon fas fa-user-tie mr-3"></i><%= t("header.dropdown.account_settings") %>
<% end %> <% end %>

View File

@ -40,4 +40,6 @@
</div> </div>
</div> </div>
<%= render "shared/sessions", recordings: @public_recordings, pagy: @pagy, only_public: true, user_recordings: false, title: t("room.recordings") %> <% if render_recordings %>
<%= render "shared/sessions", recordings: @public_recordings, pagy: @pagy, only_public: true, user_recordings: false, title: t("room.recordings") %>
<% end %>

View File

@ -13,7 +13,7 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%> %>
<div id="<%= if room == current_user.main_room then 'home_room_block' else 'room-block' end %>" data-room-uid="<%= room.uid %>" data-room-settings=<%= room.room_settings %> class="card"> <div id="<%= if room == current_user.main_room then 'home_room_block' else 'room-block' end %>" data-room-uid="<%= room.uid %>" data-room-settings=<%= room.room_settings %> data-room-access-code="<%= room.access_code %>" class="card">
<div class="card-body p-1"> <div class="card-body p-1">
<table class="table table-hover table-vcenter text-wrap table-no-border"> <table class="table table-hover table-vcenter text-wrap table-no-border">
<tbody class="no-border-top"> <tbody class="no-border-top">

View File

@ -28,10 +28,21 @@
<span class="input-icon-addon"> <span class="input-icon-addon">
<i class="fas fa-chalkboard-teacher"></i> <i class="fas fa-chalkboard-teacher"></i>
</span> </span>
<%= f.text_field :name, id: "create-room-name", class: "form-control", value: "", placeholder: t("modal.create_room.name_placeholder"), autocomplete: :off %> <%= f.text_field :name, id: "create-room-name", class: "form-control text-center", value: "", placeholder: t("modal.create_room.name_placeholder"), autocomplete: :off %>
<div class="invalid-feedback text-left"><%= t("modal.create_room.not_blank") %></div> <div class="invalid-feedback text-left"><%= t("modal.create_room.not_blank") %></div>
</div> </div>
<div class="input-icon mb-2">
<span onclick="generateAccessCode()" class="input-icon-addon allow-icon-click">
<i class="fas fa-dice"></i>
</span>
<%= f.label :access_code, t("modal.create_room.access_code_placeholder"), id: "create-room-access-code", class: "form-control" %>
<%= f.hidden_field :access_code %>
<span onclick="ResetAccessCode()" class="input-icon-addon allow-icon-click">
<i class="far fa-trash-alt"></i>
</span>
</div>
<% if Rails.configuration.room_features.include? "default-client" %> <% if Rails.configuration.room_features.include? "default-client" %>
<label class="mt-3 mb-3 w-100 text-left d-inline-block"> <label class="mt-3 mb-3 w-100 text-left d-inline-block">
<input type="hidden" name="room[client]" id="room_client"> <input type="hidden" name="room[client]" id="room_client">
@ -57,6 +68,14 @@
</label> </label>
<% end %> <% end %>
<% if Rails.configuration.room_features.include? "anyone-can-start" %>
<label class="custom-switch pl-0 mt-3 mb-3 w-100 text-left d-inline-block">
<span class="custom-switch-description"><%= t("modal.room_settings.start")%></span>
<%= f.check_box :anyone_can_start, class: "custom-switch-input", checked: false %>
<span class="custom-switch-indicator float-right"></span>
</label>
<% end %>
<label id="auto-join-label" class="create-only custom-switch pl-0 mt-3 mb-3 w-100 text-left d-inline-block"> <label id="auto-join-label" class="create-only custom-switch pl-0 mt-3 mb-3 w-100 text-left d-inline-block">
<span class="custom-switch-description"><%= t("modal.create_room.auto_join") %></span> <span class="custom-switch-description"><%= t("modal.create_room.auto_join") %></span>
<%= f.check_box :auto_join, class: "custom-switch-input", checked: false %> <%= f.check_box :auto_join, class: "custom-switch-input", checked: false %>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true # frozen_string_literal: true
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
load Gem.bin_path('bundler', 'bundle') load Gem.bin_path('bundler', 'bundle')

View File

@ -1,12 +1,11 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true # frozen_string_literal: true
require 'pathname'
require 'fileutils' require 'fileutils'
include FileUtils include FileUtils
# path to your application root. # path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) APP_ROOT = File.expand_path('..', __dir__)
def system!(*args) def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==") system(*args) || abort("\n== Command #{args} failed ==")
@ -20,6 +19,9 @@ chdir APP_ROOT do
system! 'gem install bundler --conservative' system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install') system('bundle check') || system!('bundle install')
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
# puts "\n== Copying sample files ==" # puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml') # unless File.exist?('config/database.yml')
# cp 'config/database.yml.sample', 'config/database.yml' # cp 'config/database.yml.sample', 'config/database.yml'

View File

@ -1,12 +1,11 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true # frozen_string_literal: true
require 'pathname'
require 'fileutils' require 'fileutils'
include FileUtils include FileUtils
# path to your application root. # path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) APP_ROOT = File.expand_path('..', __dir__)
def system!(*args) def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==") system(*args) || abort("\n== Command #{args} failed ==")
@ -20,6 +19,9 @@ chdir APP_ROOT do
system! 'gem install bundler --conservative' system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install') system('bundle check') || system!('bundle install')
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
puts "\n== Updating database ==" puts "\n== Updating database =="
system! 'bin/rails db:migrate' system! 'bin/rails db:migrate'

13
bin/yarn Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do
begin
exec "yarnpkg", *ARGV
rescue Errno::ENOENT
warn "Yarn executable was not detected in the system."
warn "Download Yarn at https://yarnpkg.com/en/docs/install"
exit 1
end
end

View File

@ -26,6 +26,9 @@ Bundler.require(*Rails.groups)
module Greenlight module Greenlight
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded. # -- all .rb files in that directory are automatically loaded.

View File

@ -3,3 +3,4 @@
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile. require 'bundler/setup' # Set up gems listed in the Gemfile.
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.

View File

@ -15,12 +15,13 @@ Rails.application.configure do
config.consider_all_requests_local = false config.consider_all_requests_local = false
# Enable/disable caching. By default caching is disabled. # Enable/disable caching. By default caching is disabled.
if Rails.root.join('tmp/caching-dev.txt').exist? # Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
config.cache_store = :memory_store config.cache_store = :memory_store
config.public_file_server.headers = { config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=172800', 'Cache-Control' => "public, max-age=#{2.days.to_i}"
} }
else else
config.action_controller.perform_caching = false config.action_controller.perform_caching = false
@ -28,6 +29,9 @@ Rails.application.configure do
config.cache_store = :null_store config.cache_store = :null_store
end end
# Store uploaded files on the local file system (see config/storage.yml for options)
config.active_storage.service = :local
# Don't wrap form components in field_with_error divs # Don't wrap form components in field_with_error divs
ActionView::Base.field_error_proc = proc do |html_tag| ActionView::Base.field_error_proc = proc do |html_tag|
html_tag.html_safe html_tag.html_safe
@ -57,6 +61,9 @@ Rails.application.configure do
# Raise an error on page load if there are pending migrations. # Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Debug mode disables concatenation and preprocessing of assets. # Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large # This option may cause significant delays in view rendering with a large
# number of complex assets. # number of complex assets.

View File

@ -16,6 +16,10 @@ Rails.application.configure do
config.consider_all_requests_local = false config.consider_all_requests_local = false
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from the `/public` folder by default since # Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this. # Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].blank? config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].blank?
@ -36,6 +40,9 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # 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
# Mount Action Cable outside main process or domain # Mount Action Cable outside main process or domain
# config.action_cable.mount_path = nil # config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable' # config.action_cable.url = 'wss://example.com/cable'

View File

@ -17,7 +17,7 @@ Rails.application.configure do
# Configure public file server for tests with Cache-Control for performance. # Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true config.public_file_server.enabled = true
config.public_file_server.headers = { config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=3600', 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
} }
# Show full error reports and disable caching. # Show full error reports and disable caching.
@ -29,6 +29,10 @@ Rails.application.configure do
# Disable request forgery protection in test environment. # Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory
config.active_storage.service = :test
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world. # Tell Action Mailer not to deliver emails to the real world.

View File

@ -5,9 +5,12 @@
# Version of your assets, change this if you want to expire all your assets. # Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0' Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path # Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path # Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')
# Precompile additional assets. # Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # application.js, application.css, and all non-JS/CSS in the app/assets
# Rails.application.config.assets.precompile += %w() # folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
# Rails.application.config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
# If you are using UJS then enable automatic nonce generation
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
# Report CSP violations to a specified URI
# For further information see the following documentation:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# Rails.application.config.content_security_policy_report_only = true

View File

@ -23,17 +23,11 @@ HealthCheck.setup do |config|
config.http_status_for_error_object = 500 config.http_status_for_error_object = 500
# bucket names to test connectivity - required only if s3 check used, access permissions can be mixed
config.buckets = { 'bucket_name' => [:R, :W, :D] }
# You can customize which checks happen on a standard health check, eg to set an explicit list use: # You can customize which checks happen on a standard health check, eg to set an explicit list use:
config.standard_checks = %w(database migrations custom) config.standard_checks = %w(database migrations emailconf)
# Or to exclude one check:
config.standard_checks -= %w(emailconf)
# You can set what tests are run with the 'full' or 'all' parameter # You can set what tests are run with the 'full' or 'all' parameter
config.full_checks = %w(database migrations custom email cache redis resque-redis sidekiq-redis s3) config.full_checks = %w(database migrations email cache)
# max-age of response in seconds # max-age of response in seconds
# cache-control is public when max_age > 1 and basic_auth_username is not set # cache-control is public when max_age > 1 and basic_auth_username is not set
@ -43,7 +37,4 @@ HealthCheck.setup do |config|
# http status code used when the ip is not allowed for the request # http status code used when the ip is not allowed for the request
config.http_status_for_ip_whitelist_error = 403 config.http_status_for_ip_whitelist_error = 403
# When redis url is non-standard
config.redis_url = 'redis_url'
end end

View File

@ -1,28 +0,0 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
#
# This file contains migration options to ease your Rails 5.0 upgrade.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
Rails.application.config.action_controller.raise_on_unfiltered_parameters = true
# Enable per-form CSRF tokens. Previous versions had false.
Rails.application.config.action_controller.per_form_csrf_tokens = true
# Enable origin-checking CSRF mitigation. Previous versions had false.
Rails.application.config.action_controller.forgery_protection_origin_check = true
# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
# Previous versions had false.
ActiveSupport.to_time_preserves_timezone = true
# Require `belongs_to` associations by default. Previous versions had false.
Rails.application.config.active_record.belongs_to_required_by_default = true
# Do not halt callback chains when a callback returns false. Previous versions had true.
ActiveSupport.halt_callback_chains_on_return_false = false
# Configure SSL options to enable HSTS with subdomains. Previous versions had false.
Rails.application.config.ssl_options = { hsts: { subdomains: true } }

View File

@ -2,4 +2,9 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: '_greenlight-2_0_session' if Rails.configuration.loadbalanced_configuration
Rails.application.config.session_store :cookie_store, key: '_greenlight-2_0_session',
domain: ENV['GREENLIGHT_PARENT_DOMAIN'] || 'blindside-dev.com'
else
Rails.application.config.session_store :cookie_store, key: '_greenlight-2_0_session'
end

View File

@ -48,6 +48,12 @@ en:
regular: Regular regular: Regular
lighten: Lighten lighten: Lighten
darken: Darken darken: Darken
recording_visibility:
public: Public
private: Private
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
registration: registration:
info: Change the way that users register to the website info: Change the way that users register to the website
title: Registration Method title: Registration Method
@ -73,6 +79,9 @@ en:
registration_method_updated: Registration method successfully updated registration_method_updated: Registration method successfully updated
settings: Site Settings successfully changed settings: Site Settings successfully changed
unauthorized: You are not authorized to perform actions on this user unauthorized: You are not authorized to perform actions on this user
recordings:
title: Server Recordings
no_recordings: This server has no recordings.
title: Organization Settings title: Organization Settings
users: users:
invite: Invite User invite: Invite User
@ -256,6 +265,8 @@ en:
max_concurrent: The maximum number of concurrent sessions allowed has been reached! max_concurrent: The maximum number of concurrent sessions allowed has been reached!
modal: modal:
create_room: create_room:
access_code: Access Code
access_code_placeholder: Generate an optional room access code
auto_join: Automatically join me into the room auto_join: Automatically join me into the room
create: Create Room create: Create Room
free_delete: You will be free to delete this room at any time. free_delete: You will be free to delete this room at any time.
@ -288,6 +299,7 @@ en:
update: Update Room update: Update Room
client: Select client type client: Select client type
mute: Mute users when they join mute: Mute users when they join
start: Allow any user to start this meeting
default: Default default: Default
html: HTML5 html: HTML5
flash: Flash flash: Flash
@ -359,19 +371,22 @@ en:
confirm: New Password Confirmation confirm: New Password Confirmation
update: Update Password update: Update Password
roles: roles:
administrator: Administrator administrator: Admin
banned: Banned banned: Banned
pending: Pending pending: Pending
super_admin: Super Admin super_admin: Super Admin
user: User user: User
room: room:
access_code_required: Please enter a valid access code to join the room
create_room: Create a Room create_room: Create a Room
create_room_error: There was an error creating the room create_room_error: There was an error creating the room
create_room_success: Room created successfully create_room_success: Room created successfully
enter_the_access_code: Enter the room's access code
invited: You have been invited to join invited: You have been invited to join
invite_participants: Invite Participants invite_participants: Invite Participants
join: Join join: Join
last_session: Last session on %{session} last_session: Last session on %{session}
login: Login
owner: Owner owner: Owner
no_sessions: This room has no sessions, yet! no_sessions: This room has no sessions, yet!
recordings: Room Recordings recordings: Room Recordings

View File

@ -1,15 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
# Puma can serve each request in a thread from an internal thread pool. # Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers a minimum and maximum. # The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match # Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum # the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum, this matches the default thread size of Active Record. # and maximum; this matches the default thread size of Active Record.
# #
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count threads threads_count, threads_count
# Specifies the `port` that Puma will listen on to receive requests, default is 3000. # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
# #
port ENV.fetch("PORT") { 80 } port ENV.fetch("PORT") { 80 }
@ -28,22 +28,9 @@ environment ENV.fetch("RAILS_ENV") { "development" }
# Use the `preload_app!` method when specifying a `workers` number. # Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code # This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write # before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory. If you use this option # process behavior so workers use less memory.
# you need to make sure to reconnect any threads in the `on_worker_boot`
# block.
# #
# preload_app! # preload_app!
# The code in the `on_worker_boot` will be called if you are using
# clustered mode by specifying a number of `workers`. After each worker
# process is booted this block will be run, if you are using `preload_app!`
# option you will want to use this block to reconnect to any threads
# or connections that may have been created at application boot, Ruby
# cannot share connections between processes.
#
# on_worker_boot do
# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
# end
# Allow puma to be restarted by `rails restart` command. # Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart plugin :tmp_restart

View File

@ -37,6 +37,8 @@ Rails.application.routes.draw do
resources :admins, only: [:index] resources :admins, only: [:index]
scope '/admins' do scope '/admins' do
get '/site_settings', to: 'admins#site_settings', as: :admin_site_settings
get '/recordings', to: 'admins#server_recordings', as: :admin_recordings
post '/branding', to: 'admins#branding', as: :admin_branding post '/branding', to: 'admins#branding', as: :admin_branding
post '/coloring', to: 'admins#coloring', as: :admin_coloring post '/coloring', to: 'admins#coloring', as: :admin_coloring
post '/room_authentication', to: 'admins#room_authentication', as: :admin_room_authentication post '/room_authentication', to: 'admins#room_authentication', as: :admin_room_authentication
@ -52,6 +54,7 @@ Rails.application.routes.draw do
post '/registration_method/:method', to: 'admins#registration_method', as: :admin_change_registration post '/registration_method/:method', to: 'admins#registration_method', as: :admin_change_registration
post '/approve/:user_uid', to: 'admins#approve', as: :admin_approve post '/approve/:user_uid', to: 'admins#approve', as: :admin_approve
post '/room_limit', to: 'admins#room_limit', as: :admin_room_limit post '/room_limit', to: 'admins#room_limit', as: :admin_room_limit
post '/default_recording_visibility', to: 'admins#default_recording_visibility', as: :admin_recording_visibility
end end
scope '/themes' do scope '/themes' do
@ -100,6 +103,7 @@ Rails.application.routes.draw do
post '/update_settings', to: 'rooms#update_settings' post '/update_settings', to: 'rooms#update_settings'
post '/start', to: 'rooms#start', as: :start_room post '/start', to: 'rooms#start', as: :start_room
get '/logout', to: 'rooms#logout', as: :logout_room get '/logout', to: 'rooms#logout', as: :logout_room
post '/login', to: 'rooms#login', as: :login_room
end end
# Recording operations routes # Recording operations routes

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
%w( %w[
.ruby-version .ruby-version
.rbenv-vars .rbenv-vars
tmp/restart.txt tmp/restart.txt
tmp/caching-dev.txt tmp/caching-dev.txt
).each { |path| Spring.watch(path) } ].each { |path| Spring.watch(path) }

34
config/storage.yml Normal file
View File

@ -0,0 +1,34 @@
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
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
# 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
# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
# service: AzureStorage
# storage_account_name: your_account_name
# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
# container: your_container_name
# mirror:
# service: Mirror
# primary: local
# mirrors: [ amazon, google, microsoft ]

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddPasswordDigestToRooms < ActiveRecord::Migration[5.0]
def change
add_column :rooms, :access_code, :string, null: true, default: nil
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20190522195242) do ActiveRecord::Schema.define(version: 20190711192033) do
create_table "features", force: :cascade do |t| create_table "features", force: :cascade do |t|
t.integer "setting_id" t.integer "setting_id"
@ -56,6 +56,7 @@ ActiveRecord::Schema.define(version: 20190522195242) do
t.string "room_settings", default: "{ }" t.string "room_settings", default: "{ }"
t.string "moderator_pw" t.string "moderator_pw"
t.string "attendee_pw" t.string "attendee_pw"
t.string "access_code"
t.index ["bbb_id"], name: "index_rooms_on_bbb_id" t.index ["bbb_id"], name: "index_rooms_on_bbb_id"
t.index ["last_session"], name: "index_rooms_on_last_session" t.index ["last_session"], name: "index_rooms_on_last_session"
t.index ["name"], name: "index_rooms_on_name" t.index ["name"], name: "index_rooms_on_name"

View File

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

View File

@ -1,7 +1,7 @@
# Create a Secret Key for Rails # Create a Secret Key for Rails
# #
# You can generate a secure one through the Greenlight docker image # You can generate a secure one through the Greenlight docker image
# with with the command. # with the command.
# #
# docker run --rm bigbluebutton/greenlight:v2 bundle exec rake secret # docker run --rm bigbluebutton/greenlight:v2 bundle exec rake secret
# #
@ -20,7 +20,7 @@ BIGBLUEBUTTON_SECRET=
# #
# For in-depth steps on setting up a Google Login Provider, see: # For in-depth steps on setting up a Google Login Provider, see:
# #
# http://docs.bigbluebutton.org/install/greenlight-v2.html#google-oauth2 # https://docs.bigbluebutton.org/greenlight/gl-customize.html#google-oauth2
# #
# The GOOGLE_OAUTH2_HD variable is used to limit sign-ins to a particular set of Google Apps hosted # The GOOGLE_OAUTH2_HD variable is used to limit sign-ins to a particular set of Google Apps hosted
# domains. This can be a string with separating commas such as, 'domain.com, example.com' or # domains. This can be a string with separating commas such as, 'domain.com, example.com' or
@ -32,18 +32,13 @@ GOOGLE_OAUTH2_HD=
# Twitter Login Provider (optional) # Twitter Login Provider (optional)
# #
# For in-depth steps on setting up a Twitter Login Provider, see: # Twitter Authentication is deprecated and will be phased out in a future release.
#
# http://docs.bigbluebutton.org/install/greenlight-v2.html#twitter-oauth2
#
TWITTER_ID=
TWITTER_SECRET=
# Microsoft Office365 Login Provider (optional) # Microsoft Office365 Login Provider (optional)
# #
# For in-depth steps on setting up a Office 365 Login Provider, see: # For in-depth steps on setting up a Office 365 Login Provider, see:
# #
# http://docs.bigbluebutton.org/install/greenlight-v2.html#office365-oauth2 # https://docs.bigbluebutton.org/greenlight/gl-customize.html#office365-oauth2
# #
OFFICE365_KEY= OFFICE365_KEY=
OFFICE365_SECRET= OFFICE365_SECRET=
@ -55,7 +50,7 @@ OFFICE365_HD=
# Configuring LDAP authentication will take precedence over all other providers. # Configuring LDAP authentication will take precedence over all other providers.
# For information about setting up LDAP, see: # For information about setting up LDAP, see:
# #
# http://docs.bigbluebutton.org/install/greenlight-v2.html#ldap-auth # https://docs.bigbluebutton.org/greenlight/gl-customize.html#ldap-auth
# #
# LDAP_SERVER=ldap.example.com # LDAP_SERVER=ldap.example.com
# LDAP_PORT=389 # LDAP_PORT=389
@ -75,7 +70,7 @@ LDAP_PASSWORD=
# Set this to true if you want GreenLight to support user signup and login without # Set this to true if you want GreenLight to support user signup and login without
# Omniauth. For more information, see: # Omniauth. For more information, see:
# #
# http://docs.bigbluebutton.org/install/greenlight-v2.html#in-application-greenlight # https://docs.bigbluebutton.org/greenlight/gl-overview.html#accounts-and-profile
# #
ALLOW_GREENLIGHT_ACCOUNTS=true ALLOW_GREENLIGHT_ACCOUNTS=true
@ -130,7 +125,8 @@ RELATIVE_URL_ROOT=/b
# Current settings available: # Current settings available:
# default-client: Room owners can decide between the Flash Client and the HTML5 Client for a room # default-client: Room owners can decide between the Flash Client and the HTML5 Client for a room
# mute-on-join: Automatically mute users by default when they join a room # mute-on-join: Automatically mute users by default when they join a room
ROOM_FEATURES=default-client,mute-on-join # anyone-can-start: Allows anyone with the join url to start the room in BigBlueButton
ROOM_FEATURES=default-client,mute-on-join,anyone-can-start
# Specify the maximum number of records to be sent to the BigBlueButton API in one call # Specify the maximum number of records to be sent to the BigBlueButton API in one call
# Default is set to 25 records # Default is set to 25 records

View File

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

View File

@ -20,6 +20,8 @@ require "rails_helper"
describe AdminsController, type: :controller do describe AdminsController, type: :controller do
before do before do
allow_any_instance_of(ApplicationController).to receive(:set_user_domain).and_return("provider1")
controller.instance_variable_set(:@user_domain, "provider1")
@user = create(:user, provider: "provider1") @user = create(:user, provider: "provider1")
@admin = create(:user, provider: "provider1") @admin = create(:user, provider: "provider1")
@admin.add_role :admin @admin.add_role :admin
@ -52,7 +54,7 @@ describe AdminsController, type: :controller do
get :edit_user, params: { user_uid: @user.uid } get :edit_user, params: { user_uid: @user.uid }
expect(response).to render_template(:index) expect(response).to render_template(:edit_user)
end end
end end
@ -144,7 +146,7 @@ describe AdminsController, type: :controller do
email = Faker::Internet.email email = Faker::Internet.email
post :invite, params: { invite_user: { email: email } } post :invite, params: { invite_user: { email: email } }
invite = Invitation.find_by(email: email, provider: "greenlight") invite = Invitation.find_by(email: email, provider: "provider1")
expect(invite.present?).to eq(true) expect(invite.present?).to eq(true)
expect(flash[:success]).to be_present expect(flash[:success]).to be_present
@ -197,7 +199,7 @@ describe AdminsController, type: :controller do
feature = Setting.find_by(provider: "provider1").features.find_by(name: "Branding Image") feature = Setting.find_by(provider: "provider1").features.find_by(name: "Branding Image")
expect(feature[:value]).to eq(fake_image_url) expect(feature[:value]).to eq(fake_image_url)
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admin_site_settings_path)
end end
end end
@ -214,7 +216,7 @@ describe AdminsController, type: :controller do
feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color") feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color")
expect(feature[:value]).to eq(primary_color) expect(feature[:value]).to eq(primary_color)
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admin_site_settings_path)
end end
it "changes the primary-lighten on the page" do it "changes the primary-lighten on the page" do
@ -229,7 +231,7 @@ describe AdminsController, type: :controller do
feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Lighten") feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Lighten")
expect(feature[:value]).to eq(primary_color) expect(feature[:value]).to eq(primary_color)
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admin_site_settings_path)
end end
it "changes the primary-darken on the page" do it "changes the primary-darken on the page" do
@ -244,7 +246,7 @@ describe AdminsController, type: :controller do
feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Darken") feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Darken")
expect(feature[:value]).to eq(primary_color) expect(feature[:value]).to eq(primary_color)
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admin_site_settings_path)
end end
end end
end end
@ -264,7 +266,7 @@ describe AdminsController, type: :controller do
expect(feature[:value]).to eq(Rails.configuration.registration_methods[:invite]) expect(feature[:value]).to eq(Rails.configuration.registration_methods[:invite])
expect(flash[:success]).to be_present expect(flash[:success]).to be_present
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admin_site_settings_path)
end end
it "does not allow the user to change to invite if emails are off" do it "does not allow the user to change to invite if emails are off" do
@ -277,7 +279,7 @@ describe AdminsController, type: :controller do
post :registration_method, params: { method: "invite" } post :registration_method, params: { method: "invite" }
expect(flash[:alert]).to be_present expect(flash[:alert]).to be_present
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admin_site_settings_path)
end end
end end
@ -293,7 +295,7 @@ describe AdminsController, type: :controller do
feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Authentication") feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Authentication")
expect(feature[:value]).to eq("true") expect(feature[:value]).to eq("true")
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admin_site_settings_path)
end end
end end
@ -309,6 +311,22 @@ describe AdminsController, type: :controller do
feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Limit") feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Limit")
expect(feature[:value]).to eq("5") expect(feature[:value]).to eq("5")
expect(response).to redirect_to(admin_site_settings_path)
end
end
context "POST #default_recording_visibility" do
it "changes the default recording visibility setting" 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
post :default_recording_visibility, params: { visibility: "public" }
feature = Setting.find_by(provider: "provider1").features.find_by(name: "Default Recording Visibility")
expect(feature[:value]).to eq("public")
expect(response).to redirect_to(admins_path) expect(response).to redirect_to(admins_path)
end end
end end

View File

@ -28,6 +28,8 @@ def random_valid_room_params
end end
describe RoomsController, type: :controller do describe RoomsController, type: :controller do
it_behaves_like "recorder"
include Recorder
describe "GET #show" do describe "GET #show" do
before do before do
@user = create(:user) @user = create(:user)
@ -39,7 +41,7 @@ describe RoomsController, type: :controller do
get :show, params: { room_uid: @owner.main_room } get :show, params: { room_uid: @owner.main_room }
expect(assigns(:recordings)).to eql(@owner.main_room.recordings) expect(assigns(:recordings)).to eql(recordings(@owner.main_room.bbb_id, @owner.provider))
expect(assigns(:is_running)).to eql(@owner.main_room.running?) expect(assigns(:is_running)).to eql(@owner.main_room.running?)
end end
@ -117,8 +119,8 @@ describe RoomsController, type: :controller do
@request.session[:user_id] = @owner.id @request.session[:user_id] = @owner.id
name = Faker::Games::Pokemon.name name = Faker::Games::Pokemon.name
room_params = { name: name, "client": "html5", "mute_on_join": "1" } room_params = { name: name, "client": "html5", "mute_on_join": "1", "anyone_can_start": "1" }
json_room_settings = "{\"muteOnStart\":true,\"joinViaHtml5\":true}" json_room_settings = "{\"muteOnStart\":true,\"joinViaHtml5\":true,\"anyoneCanStart\":true}"
post :create, params: { room: room_params } post :create, params: { room: room_params }
@ -201,6 +203,46 @@ describe RoomsController, type: :controller do
expect(response).to render_template(:wait) expect(response).to render_template(:wait)
end end
it "should join the room if the room has the anyone_can_start setting" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(false)
room = Room.new(name: "test")
room.room_settings = "{\"muteOnStart\":false,\"joinViaHtml5\":false,\"anyoneCanStart\":true}"
room.owner = @owner
room.save
@request.session[:user_id] = @user.id
post :join, params: { room_uid: room, join_name: @user.name }
expect(response).to redirect_to(room.join_path(@user.name, { user_is_moderator: true }, @user.uid))
end
it "should render wait if the correct access code is supplied" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(false)
protected_room = Room.new(name: 'test', access_code: "123456")
protected_room.owner = @owner
protected_room.save
@request.session[:user_id] = @user.id
post :join, params: { room_uid: protected_room, join_name: @user.name }, session: { access_code: "123456" }
expect(response).to render_template(:wait)
end
it "should redirect to login if the correct access code isn't supplied" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(false)
protected_room = Room.new(name: 'test', access_code: "123456")
protected_room.owner = @owner
protected_room.save
@request.session[:user_id] = @user.id
post :join, params: { room_uid: protected_room, join_name: @user.name }, session: { access_code: "123455" }
expect(response).to redirect_to room_path(protected_room.uid)
end
it "should join owner as moderator if meeting running" do it "should join owner as moderator if meeting running" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true) allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true)
@ -315,7 +357,8 @@ describe RoomsController, type: :controller do
@request.session[:user_id] = @user.id @request.session[:user_id] = @user.id
room_params = { "client": "html5", "mute_on_join": "1", "name": @secondary_room.name } room_params = { "client": "html5", "mute_on_join": "1", "name": @secondary_room.name }
formatted_room_params = "{\"muteOnStart\":true,\"joinViaHtml5\":true}" # JSON string format formatted_room_params = "{\"muteOnStart\":true,\"joinViaHtml5\":true,\"anyoneCanStart\":false}"
# JSON string format
expect { post :update_settings, params: { room_uid: @secondary_room.uid, room: room_params } } expect { post :update_settings, params: { room_uid: @secondary_room.uid, room: room_params } }
.to change { @secondary_room.reload.room_settings } .to change { @secondary_room.reload.room_settings }
@ -372,4 +415,27 @@ describe RoomsController, type: :controller do
expect(response).to redirect_to(@room) expect(response).to redirect_to(@room)
end end
end end
describe "POST #login" do
before do
@user = create(:user)
@room = @user.main_room
@room.access_code = "123456"
@room.save
end
it "should redirect to show with valid access code" do
post :login, params: { room_uid: @room.uid, room: { access_code: "123456" } }
expect(response).to redirect_to room_path(@room.uid)
expect(flash[:alert]).to be_nil
end
it "should redirect to show with and notify user of invalid access code" do
post :login, params: { room_uid: @room.uid, room: { access_code: "123455" } }
expect(response).to redirect_to room_path(@room.uid)
expect(flash[:alert]).to eq(I18n.t("room.access_code_required"))
end
end
end end

View File

@ -42,9 +42,13 @@ describe ThemesController, type: :controller do
it "returns the correct color based on provider" do it "returns the correct color based on provider" do
allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true)
allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true) allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true)
allow_any_instance_of(ApplicationController).to receive(:set_user_domain).and_return("provider1")
color1 = Faker::Color.hex_color color1 = Faker::Color.hex_color
provider1 = Faker::Company.name provider1 = Faker::Company.name
controller.instance_variable_set(:@user_domain, provider1)
Setting.create(provider: provider1).features.create(name: "Primary Color", value: color1, enabled: true) Setting.create(provider: provider1).features.create(name: "Primary Color", value: color1, enabled: true)
user1 = create(:user, provider: provider1) user1 = create(:user, provider: provider1)

View File

@ -87,6 +87,8 @@ describe UsersController, type: :controller do
it "allows admins to edit other users" do it "allows admins to edit other users" do
allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true)
allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true) allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true)
allow_any_instance_of(ApplicationController).to receive(:set_user_domain).and_return("provider1")
controller.instance_variable_set(:@user_domain, "provider1")
user = create(:user, provider: "provider1") user = create(:user, provider: "provider1")
user.add_role :admin user.add_role :admin
@ -302,6 +304,7 @@ describe UsersController, type: :controller do
describe "PATCH #update" do describe "PATCH #update" do
it "properly updates user attributes" do it "properly updates user attributes" do
user = create(:user) user = create(:user)
@request.session[:user_id] = user.id
params = random_valid_user_params params = random_valid_user_params
patch :update, params: params.merge!(user_uid: user) patch :update, params: params.merge!(user_uid: user)
@ -315,6 +318,7 @@ describe UsersController, type: :controller do
it "renders #edit on unsuccessful save" do it "renders #edit on unsuccessful save" do
@user = create(:user) @user = create(:user)
@request.session[:user_id] = @user.id
patch :update, params: invalid_params.merge!(user_uid: @user) patch :update, params: invalid_params.merge!(user_uid: @user)
expect(response).to render_template(:edit) expect(response).to render_template(:edit)
@ -337,6 +341,8 @@ describe UsersController, type: :controller do
allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true)
allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true) allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true)
allow_any_instance_of(Room).to receive(:delete_all_recordings).and_return('') allow_any_instance_of(Room).to receive(:delete_all_recordings).and_return('')
allow_any_instance_of(ApplicationController).to receive(:set_user_domain).and_return("provider1")
controller.instance_variable_set(:@user_domain, "provider1")
user = create(:user, provider: "provider1") user = create(:user, provider: "provider1")
admin = create(:user, provider: "provider1") admin = create(:user, provider: "provider1")
@ -352,6 +358,8 @@ describe UsersController, type: :controller do
it "doesn't allow admins of other providers to delete users" do it "doesn't allow admins of other providers to delete users" do
allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true)
allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true) allow_any_instance_of(User).to receive(:greenlight_account?).and_return(true)
allow_any_instance_of(ApplicationController).to receive(:set_user_domain).and_return("provider2")
controller.instance_variable_set(:@user_domain, "provider2")
user = create(:user, provider: "provider1") user = create(:user, provider: "provider1")
admin = create(:user, provider: "provider2") admin = create(:user, provider: "provider2")

View File

@ -133,420 +133,6 @@ describe Room, type: :model do
end end
context "#recordings" do context "#recordings" do
it "should properly find meeting recordings" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return(
recordings: [
{
name: "Example",
playback: {
format:
{
type: "presentation"
}
}
}
]
)
expect(@room.recordings).to contain_exactly(
name: "Example",
playbacks:
[
{
type: "presentation"
}
]
)
end
context '#filtering' do
before do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return(
recordings: [
{
name: "Example",
participants: "3",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "5",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
}
},
{
name: "test",
participants: "1",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
name: "Exam",
participants: "1",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
name: "z",
}
}
]
)
end
it "should filter recordings on name" do
expect(@room.recordings(search: "Exam")).to contain_exactly(
{
name: "aExamaaa",
participants: "5",
playbacks:
[
{
type: "other"
}
],
metadata: {
"gl-listed": "false",
}
},
name: "Example",
participants: "3",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
)
end
it "should filter recordings on participants" do
expect(@room.recordings(search: "5")).to contain_exactly(
name: "aExamaaa",
participants: "5",
playbacks:
[
{
type: "other"
}
],
metadata: {
"gl-listed": "false",
}
)
end
it "should filter recordings on format" do
expect(@room.recordings(search: "presentation")).to contain_exactly(
{
name: "test",
participants: "1",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
},
name: "Example",
participants: "3",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
)
end
it "should filter recordings on visibility" do
expect(@room.recordings(search: "public")).to contain_exactly(
{
name: "test",
participants: "1",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
},
},
name: "Example",
participants: "3",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
)
end
it "should filter recordings on metadata name by default" do
expect(@room.recordings(search: "z")).to contain_exactly(
name: "Exam",
participants: "1",
playbacks:
[
{
type: "other"
}
],
metadata: {
"gl-listed": "false",
name: "z",
}
)
end
end
context '#sorting' do
before do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return(
recordings: [
{
name: "Example",
participants: "3",
playback: {
format: {
type: "presentation",
length: "4"
}
},
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "1",
playback: {
format: {
type: "other",
length: "3"
}
},
metadata: {
name: "Z",
"gl-listed": "false"
}
}
]
)
end
it "should sort recordings on name" do
expect(@room.recordings(column: "name", direction: "asc")).to eq(
[
{
name: "Example",
participants: "3",
playbacks: [
{
type: "presentation",
length: "4"
}
],
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "1",
playbacks: [
{
type: "other",
length: "3"
}
],
metadata: {
name: "Z",
"gl-listed": "false"
}
}
]
)
end
it "should sort recordings on participants" do
expect(@room.recordings(column: "users", direction: "desc")).to eq(
[
{
name: "Example",
participants: "3",
playbacks: [
{
type: "presentation",
length: "4"
}
],
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "1",
playbacks: [
{
type: "other",
length: "3"
}
],
metadata: {
name: "Z",
"gl-listed": "false"
}
}
]
)
end
it "should sort recordings on visibility" do
expect(@room.recordings(column: "visibility", direction: "desc")).to eq(
[
{
name: "Example",
participants: "3",
playbacks: [
{
type: "presentation",
length: "4"
}
],
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "1",
playbacks: [
{
type: "other",
length: "3"
}
],
metadata: {
name: "Z",
"gl-listed": "false"
}
}
]
)
end
it "should sort recordings on length" do
expect(@room.recordings(column: "length", direction: "asc")).to eq(
[
{
name: "aExamaaa",
participants: "1",
playbacks: [
{
type: "other",
length: "3"
}
],
metadata: {
name: "Z",
"gl-listed": "false"
}
},
{
name: "Example",
participants: "3",
playbacks: [
{
type: "presentation",
length: "4"
}
],
metadata: {
"gl-listed": "true",
}
}
]
)
end
it "should sort recordings on format" do
expect(@room.recordings(column: "formats", direction: "desc")).to eq(
[
{
name: "Example",
participants: "3",
playbacks: [
{
type: "presentation",
length: "4"
}
],
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "1",
playbacks: [
{
type: "other",
length: "3"
}
],
metadata: {
name: "Z",
"gl-listed": "false"
}
}
]
)
end
end
it "deletes the recording" do it "deletes the recording" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:delete_recordings).and_return( allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:delete_recordings).and_return(
returncode: true, deleted: true returncode: true, deleted: true

View File

@ -173,97 +173,4 @@ describe User, type: :model do
.to raise_exception(ActiveRecord::RecordInvalid, "Validation failed: Email can't be blank") .to raise_exception(ActiveRecord::RecordInvalid, "Validation failed: Email can't be blank")
end end
end end
context '#recordings' do
it "gets all filtered and sorted recordings for the user" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return(
recordings: [
{
name: "Example",
participants: "3",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "5",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
}
},
{
name: "test",
participants: "1",
playback: {
format:
{
type: "presentation"
}
},
metadata: {
"gl-listed": "true",
}
},
{
name: "Exam",
participants: "1",
playback: {
format:
{
type: "other"
}
},
metadata: {
"gl-listed": "false",
name: "z",
}
}
]
)
expect(@user.all_recordings(search: "Exam", column: "name", direction: "desc")).to eq(
[
{
name: "Example",
participants: "3",
playbacks:
[
{
type: "presentation"
}
],
metadata: {
"gl-listed": "true",
}
},
{
name: "aExamaaa",
participants: "5",
playbacks:
[
{
type: "other"
}
],
metadata: {
"gl-listed": "false",
}
}
]
)
end
end
end end