Compare commits
No commits in common. "v1" and "master" have entirely different histories.
|
@ -2,10 +2,11 @@
|
|||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
SECURITY.md
|
||||
.github
|
||||
|
||||
# Rails
|
||||
.env
|
||||
.env.prod
|
||||
*.rbc
|
||||
capybara-*.html
|
||||
.rspec
|
||||
|
@ -13,14 +14,16 @@ log
|
|||
tmp
|
||||
/db/**/*.sqlite3
|
||||
/db/**/*.sqlite3-journal
|
||||
public/system
|
||||
/db/production
|
||||
/db/production-postgres
|
||||
public/assets
|
||||
public/b
|
||||
coverage/
|
||||
spec/tmp
|
||||
.rvmrc
|
||||
.byebug_history
|
||||
|
||||
# docker
|
||||
docker-compose.yml
|
||||
production-compose.yml
|
||||
|
||||
.atom
|
||||
vendor/bundle
|
||||
.bundle
|
||||
Dockerfile
|
||||
.gitlab-ci.yml
|
||||
.rubocop.yml
|
||||
spec
|
||||
test
|
|
@ -0,0 +1,16 @@
|
|||
<!---
|
||||
IMPORTANT
|
||||
This template is mandatory for all Pull Requests.
|
||||
Please follow the template to ensure your Pull Request is reviewed.
|
||||
-->
|
||||
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Testing Steps
|
||||
<!--- Please describe in detail how to test your changes. -->
|
||||
|
||||
## Screenshots (if appropriate):
|
||||
<!--- Please include screenshots that may help to visualize your changes. -->
|
|
@ -0,0 +1,95 @@
|
|||
env:
|
||||
RUBY_VERSION: 2.7
|
||||
|
||||
name: CI Build Pre-Release
|
||||
on:
|
||||
release:
|
||||
types: [prereleased]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Build Docker Image
|
||||
env:
|
||||
DOCKER_REPOSITORY: ${{ secrets.DOCKER_REPOSITORY }}
|
||||
DOCKER_BUILD_ENABLED: ${{ secrets.DOCKER_BUILD_ENABLED }}
|
||||
DOCKER_BUILD_ALTERNATE_ENABLED: ${{ secrets.DOCKER_BUILD_ALTERNATE_ENABLED }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Cache Docker layers
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract Docker Repository
|
||||
id: ci_docker_repository
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=repository;]$(echo ${DOCKER_REPOSITORY:-$GITHUB_REPOSITORY})"
|
||||
|
||||
- name: Extract Tag Release
|
||||
id: ci_tag_release_version
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=tag;]$(echo ${GITHUB_REF#refs/tags/} | cut -c 9-)"
|
||||
|
||||
- name: Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:v${{ steps.ci_tag_release_version.outputs.tag }}"
|
||||
build-args: "version_code=release-${{ steps.ci_tag_release_version.outputs.tag }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
# Alternate Images with alpine
|
||||
- name: Alternate Alpine Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true') && contains(env.DOCKER_BUILD_ALTERNATE_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: dockerfiles/v2/alpine
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:v${{ steps.ci_tag_release_version.outputs.tag }}-alpine"
|
||||
build-args: "version_code=release-${{ steps.ci_tag_release_version.outputs.tag }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
# Alternate Images with amazonlinux
|
||||
- name: Alternate Amazon Linux Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true') && contains(env.DOCKER_BUILD_ALTERNATE_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: dockerfiles/v2/amazonlinux
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:v${{ steps.ci_tag_release_version.outputs.tag }}-amazonlinux"
|
||||
build-args: "version_code=release-${{ steps.ci_tag_release_version.outputs.tag }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Move cache
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
@ -0,0 +1,101 @@
|
|||
env:
|
||||
RUBY_VERSION: 2.7
|
||||
|
||||
name: CI Build Push
|
||||
on:
|
||||
push:
|
||||
branches: "*"
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Build Docker Image
|
||||
env:
|
||||
DOCKER_REPOSITORY: ${{ secrets.DOCKER_REPOSITORY }}
|
||||
DOCKER_BUILD_ENABLED: ${{ secrets.DOCKER_BUILD_ENABLED }}
|
||||
DOCKER_BUILD_ALTERNATE_ENABLED: ${{ secrets.DOCKER_BUILD_ALTERNATE_ENABLED }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Cache Docker layers
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract Docker Repository
|
||||
id: ci_docker_repository
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=repository;]$(echo ${DOCKER_REPOSITORY:-$GITHUB_REPOSITORY})"
|
||||
|
||||
- name: Extract Branch Name
|
||||
id: ci_branch_name
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
|
||||
|
||||
- name: Extract Commit Short SHA
|
||||
id: ci_commit_short_sha
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=short_sha;]$(echo ${GITHUB_SHA} | cut -c1-7)"
|
||||
|
||||
- name: Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:${{ steps.ci_branch_name.outputs.branch }}"
|
||||
build-args: "version_code=${{ steps.ci_branch_name.outputs.branch }}-${{ steps.ci_commit_short_sha.outputs.short_sha }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
# Alternate Image with alpine
|
||||
- name: Alternate Alpine Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true') && contains(env.DOCKER_BUILD_ALTERNATE_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: dockerfiles/v2/alpine
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:${{ steps.ci_branch_name.outputs.branch }}-alpine"
|
||||
build-args: "version_code=${{ steps.ci_branch_name.outputs.branch }}-${{ steps.ci_commit_short_sha.outputs.short_sha }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
# Alternate Images for bbb-bionic-230 with amazonlinux
|
||||
- name: Alternate Amazon Linux Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true') && contains(env.DOCKER_BUILD_ALTERNATE_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: dockerfiles/v2/amazonlinux
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:${{ steps.ci_branch_name.outputs.branch }}-amazonlinux"
|
||||
build-args: "version_code=${{ steps.ci_branch_name.outputs.branch }}-${{ steps.ci_commit_short_sha.outputs.short_sha }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Move cache
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
@ -0,0 +1,110 @@
|
|||
env:
|
||||
RUBY_VERSION: 2.7
|
||||
|
||||
name: CI Build Release
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Build Docker Image
|
||||
env:
|
||||
DOCKER_REPOSITORY: ${{ secrets.DOCKER_REPOSITORY }}
|
||||
DOCKER_BUILD_ENABLED: ${{ secrets.DOCKER_BUILD_ENABLED }}
|
||||
DOCKER_BUILD_ALTERNATE_ENABLED: ${{ secrets.DOCKER_BUILD_ALTERNATE_ENABLED }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Cache Docker layers
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract Docker Repository
|
||||
id: ci_docker_repository
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=repository;]$(echo ${DOCKER_REPOSITORY:-$GITHUB_REPOSITORY})"
|
||||
|
||||
- name: Extract Tag Release
|
||||
id: ci_tag_release
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=tag;]$(echo ${GITHUB_REF#refs/tags/})"
|
||||
|
||||
- name: Extract Tag Release Major
|
||||
id: ci_tag_release_major
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=tag;]$(echo ${{steps.ci_tag_release.outputs.tag}} | cut -c 9- | cut -f1-1 -d'.')"
|
||||
|
||||
- name: Extract Tag Release Minor
|
||||
id: ci_tag_release_minor
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
shell: bash
|
||||
run: echo "##[set-output name=tag;]$(echo ${{steps.ci_tag_release.outputs.tag}} | cut -c 9- | cut -f1-2 -d'.')"
|
||||
|
||||
- name: Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:${{ steps.ci_tag_release.outputs.tag }}"
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:v${{ steps.ci_tag_release_major.outputs.tag }}"
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:v${{ steps.ci_tag_release_minor.outputs.tag }}"
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:latest"
|
||||
build-args: "version_code=${{ steps.ci_tag_release_revision.outputs.tag }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
# Alternate Image with alpine
|
||||
- name: Alternate Alpine Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true') && contains(env.DOCKER_BUILD_ALTERNATE_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: dockerfiles/v2/alpine
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:${{ steps.ci_tag_release.outputs.tag }}-alpine"
|
||||
build-args: "version_code=${{ steps.ci_tag_release_revision.outputs.tag }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
# Alternate Image with amazonlinux
|
||||
- name: Alternate Amazon Linux Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true') && contains(env.DOCKER_BUILD_ALTERNATE_ENABLED, 'true')
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: dockerfiles/v2/amazonlinux
|
||||
push: true
|
||||
tags: |
|
||||
"${{ steps.ci_docker_repository.outputs.repository }}:${{ steps.ci_tag_release.outputs.tag }}-amazonlinux"
|
||||
build-args: "version_code=${{ steps.ci_tag_release_revision.outputs.tag }}"
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Move cache
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true')
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
@ -0,0 +1,68 @@
|
|||
env:
|
||||
RUBY_VERSION: 2.7
|
||||
DB_ADAPTER: postgresql
|
||||
DB_HOST: localhost
|
||||
DB_NAME: postgres
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: postgres
|
||||
DB_PORT: 5432
|
||||
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches-ignore: "master"
|
||||
pull_request:
|
||||
branches: "*"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Rubocop + RSpec
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:13.2-alpine
|
||||
env:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
# Health checks to wait until postgres is ready
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Ruby ${{ env.RUBY_VERSION }}
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ env.RUBY_VERSION }}
|
||||
|
||||
- name: Bundle cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-
|
||||
|
||||
- name: Bundle install
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Setup database
|
||||
run: |
|
||||
bundler exec rails db:create RAILS_ENV=test
|
||||
bundler exec rails db:migrate RAILS_ENV=test
|
||||
|
||||
- name: Run Rubocop
|
||||
run: bundle exec rubocop --parallel --fail-level F
|
||||
|
||||
- name: Run RSpec
|
||||
run: bundle exec rspec
|
|
@ -6,21 +6,45 @@
|
|||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
vendor/.bundle
|
||||
|
||||
# Ignore the default SQLite database.
|
||||
/db/**/*.sqlite3
|
||||
/db/**/*.sqlite3-journal
|
||||
/db/*.sqlite3
|
||||
/db/*.sqlite3-journal
|
||||
|
||||
# Ignore static assets.
|
||||
/public/system/**
|
||||
/public/assets/**
|
||||
/public/b/**
|
||||
|
||||
# Ignore uploaded files
|
||||
/storage
|
||||
|
||||
# Ignore production paths.
|
||||
/db/production
|
||||
/db/production-postgres
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*
|
||||
/tmp/*
|
||||
/tmp
|
||||
!/log/.keep
|
||||
!/tmp/.keep
|
||||
.env
|
||||
coverage
|
||||
|
||||
# Ignore Byebug command history file.
|
||||
.byebug_history
|
||||
|
||||
# Ignore environment configuration.
|
||||
.env
|
||||
env
|
||||
|
||||
# Ignore yarn configs
|
||||
/node_modules
|
||||
|
||||
# IDEs
|
||||
.idea
|
||||
.idea/**
|
||||
.vscode
|
||||
.vscode/**
|
||||
|
||||
config/terms.md
|
||||
coverage*
|
||||
|
||||
/atom-sftp-sync.json
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- /cache
|
||||
|
||||
before_script:
|
||||
|
||||
test:
|
||||
stage: test
|
||||
image: ruby:2.5
|
||||
script:
|
||||
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
|
||||
- bundle install --path /cache
|
||||
- bundle exec rake db:create RAILS_ENV=test
|
||||
- bundle exec rake test & bundle exec rspec & bundle exec rubocop
|
||||
except:
|
||||
variables:
|
||||
- $CD_TEST_IGNORE
|
||||
|
||||
build:
|
||||
stage: build
|
||||
image: docker:stable
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
# Install bash, curl, git for deployment script
|
||||
- apk update && apk add --no-cache bash curl git
|
||||
# Install CA certs, openssl to https downloads, python for gcloud sdk
|
||||
- apk add --update make ca-certificates openssl python
|
||||
- update-ca-certificates
|
||||
# Build.
|
||||
- ./scripts/image_build.sh $CI_PROJECT_PATH $CI_COMMIT_REF_NAME $CI_COMMIT_SHA
|
||||
only:
|
||||
refs:
|
||||
- branches
|
||||
- tags
|
||||
variables:
|
||||
- $CD_DOCKER_USERNAME
|
||||
- $CD_DOCKER_PASSWORD
|
||||
except:
|
||||
variables:
|
||||
- $CD_BUILD_IGNORE
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
image: docker:stable
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
# Install bash, curl, git for deployment script
|
||||
- apk update && apk add --no-cache bash curl git
|
||||
# Install CA certs, openssl to https downloads, python for gcloud sdk
|
||||
- apk add --update make ca-certificates openssl python
|
||||
- update-ca-certificates
|
||||
# Deploy.
|
||||
- ./scripts/image_deploy.sh $CI_PROJECT_PATH $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $CI_COMMIT_BEFORE_SHA
|
||||
only:
|
||||
refs:
|
||||
- branches
|
||||
- tags
|
||||
variables:
|
||||
- $CD_DOCKER_USERNAME
|
||||
- $CD_DOCKER_PASSWORD
|
||||
- $CD_DEPLOY_SCRIPT
|
||||
except:
|
||||
variables:
|
||||
- $CD_DEPLOY_IGNORE
|
|
@ -0,0 +1,42 @@
|
|||
about
|
||||
app:template
|
||||
app:update
|
||||
assets:clean[keep]
|
||||
assets:clobber
|
||||
assets:environment
|
||||
assets:precompile
|
||||
autoprefixer:info
|
||||
cache_digests:dependencies
|
||||
cache_digests:nested_dependencies
|
||||
conf:check
|
||||
db:create
|
||||
db:drop
|
||||
db:environment:set
|
||||
db:fixtures:load
|
||||
db:migrate
|
||||
db:migrate:status
|
||||
db:rollback
|
||||
db:schema:cache:clear
|
||||
db:schema:cache:dump
|
||||
db:schema:dump
|
||||
db:schema:load
|
||||
db:seed
|
||||
db:setup
|
||||
db:structure:dump
|
||||
db:structure:load
|
||||
db:version
|
||||
dev:cache
|
||||
initializers
|
||||
log:clear
|
||||
middleware
|
||||
notes
|
||||
notes:custom
|
||||
restart
|
||||
routes
|
||||
secret
|
||||
stats
|
||||
test
|
||||
test:db
|
||||
time:zones[country_or_offset]
|
||||
tmp:clear
|
||||
tmp:create
|
|
@ -0,0 +1,191 @@
|
|||
AllCops:
|
||||
Exclude:
|
||||
- 'db/schema.rb'
|
||||
- 'vendor/**/*'
|
||||
DisabledByDefault: false
|
||||
TargetRubyVersion: 2.7
|
||||
|
||||
NewCops: enable
|
||||
|
||||
Style/BlockDelimiters:
|
||||
Enabled: false
|
||||
|
||||
# Checks if uses of quotes match the configured preference.
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
|
||||
# Document classes and non-namespace modules.
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
# Check for conditionals that can be replaced with guard clauses
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
# Checks the formatting of empty method definitions.
|
||||
Style/EmptyMethod:
|
||||
Enabled: false
|
||||
|
||||
# Checks for trailing comma in hash literals.
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Checks for trailing comma in argument lists.
|
||||
Style/TrailingCommaInArguments:
|
||||
Enabled: false
|
||||
|
||||
# Checks that `include`, `extend` and `prepend` exists at the top level.
|
||||
Style/MixinUsage:
|
||||
Enabled: false
|
||||
|
||||
# Use %i or %I for arrays of symbols.
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
||||
# Don't use begin blocks when they are not needed.
|
||||
Style/RedundantBegin:
|
||||
Enabled: false
|
||||
|
||||
# Use `%`-literal delimiters consistently
|
||||
Style/PercentLiteralDelimiters:
|
||||
Enabled: false
|
||||
|
||||
# Only use if/unless modifiers on single line statements.
|
||||
Style/MultilineIfModifier:
|
||||
Enabled: false
|
||||
|
||||
# Checks for trailing comma in array literals.
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Use `expand_path(__dir__)` instead of `expand_path('..', __FILE__)`.
|
||||
Style/ExpandPathArguments:
|
||||
Enabled: false
|
||||
|
||||
# Do not assign mutable objects to constants.
|
||||
Style/MutableConstant:
|
||||
Enabled: false
|
||||
|
||||
# Avoid rescuing without specifying an error class.
|
||||
Style/RescueStandardError:
|
||||
Enabled: false
|
||||
|
||||
# Align the elements of a hash literal if they span more than one line.
|
||||
Layout/HashAlignment:
|
||||
Enabled: false
|
||||
|
||||
# Align the parameters of a method definition if they span more than one line.
|
||||
Layout/ParameterAlignment:
|
||||
Enabled: false
|
||||
|
||||
# Align ends corresponding to defs correctly.
|
||||
Layout/EndAlignment:
|
||||
Enabled: false
|
||||
|
||||
# Align elses and elsifs correctly.
|
||||
Layout/ElseAlignment:
|
||||
Enabled: false
|
||||
|
||||
# Add empty line after guard clause.
|
||||
Layout/EmptyLineAfterGuardClause:
|
||||
Enabled: false
|
||||
|
||||
# Align the arguments of a method call if they span more than one line.
|
||||
Layout/ArgumentAlignment:
|
||||
Enabled: false
|
||||
|
||||
#
|
||||
Layout/IndentationWidth:
|
||||
Enabled: false
|
||||
|
||||
Layout/CaseIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Checks for ambiguous block association with method when param passed without parentheses.
|
||||
Lint/AmbiguousBlockAssociation:
|
||||
Enabled: false
|
||||
|
||||
# Avoid long blocks with many lines.
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
|
||||
# A complexity metric geared towards measuring complexity for a human reader.
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 17
|
||||
|
||||
# Avoid classes longer than 100 lines of code.
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
# Limit lines to 80 characters.
|
||||
Layout/LineLength:
|
||||
Max: 130
|
||||
|
||||
# Avoid methods longer than 10 lines of code.
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
|
||||
# A calculated magnitude based on number of assignments,
|
||||
# branches, and conditions.
|
||||
Metrics/AbcSize:
|
||||
Max: 60
|
||||
|
||||
# A complexity metric that is strongly correlated to the number
|
||||
# of test cases needed to validate a method.
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 20
|
||||
|
||||
# Checks for method parameter names that contain capital letters, end in numbers, or do not meet a minimal length.
|
||||
Naming/MethodParameterName:
|
||||
Enabled: false
|
||||
|
||||
Lint/LiteralInInterpolation:
|
||||
Enabled: false
|
||||
|
||||
Layout/EmptyLinesAroundAttributeAccessor:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceAroundMethodCallOperator:
|
||||
Enabled: true
|
||||
|
||||
Lint/DeprecatedOpenSSLConstant:
|
||||
Enabled: true
|
||||
|
||||
Lint/RaiseException:
|
||||
Enabled: true
|
||||
|
||||
Lint/StructNewOverride:
|
||||
Enabled: true
|
||||
|
||||
Style/ExponentialNotation:
|
||||
Enabled: true
|
||||
|
||||
Style/HashEachMethods:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformKeys:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformValues:
|
||||
Enabled: true
|
||||
|
||||
Style/SlicingWithRange:
|
||||
Enabled: true
|
||||
|
||||
Style/OptionalBooleanParameter:
|
||||
Enabled: false
|
||||
|
||||
Lint/DuplicateBranch:
|
||||
Enabled: false
|
||||
|
||||
Lint/ConstantDefinitionInBlock:
|
||||
Enabled: false
|
||||
|
||||
Lint/EmptyBlock:
|
||||
Enabled: false
|
||||
|
||||
Style/HashLikeCase:
|
||||
Enabled: false
|
|
@ -0,0 +1 @@
|
|||
2.7.2
|
80
Dockerfile
|
@ -1,20 +1,74 @@
|
|||
FROM ruby:2.4.0
|
||||
FROM ruby:2.7.2-alpine AS base
|
||||
|
||||
# app dependencies
|
||||
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
|
||||
# Set a variable for the install location.
|
||||
ARG RAILS_ROOT=/usr/src/app
|
||||
# Set Rails environment.
|
||||
ENV RAILS_ENV production
|
||||
ENV BUNDLE_APP_CONFIG="$RAILS_ROOT/.bundle"
|
||||
|
||||
ENV RAILS_ENV=production \
|
||||
APP_HOME=/usr/src/app
|
||||
# Make the directory and set as working.
|
||||
RUN mkdir -p $RAILS_ROOT
|
||||
WORKDIR $RAILS_ROOT
|
||||
|
||||
ARG BUILD_PACKAGES="build-base curl-dev git"
|
||||
ARG DEV_PACKAGES="postgresql-dev sqlite-libs sqlite-dev yaml-dev zlib-dev nodejs yarn"
|
||||
ARG RUBY_PACKAGES="tzdata"
|
||||
|
||||
# Install app dependencies.
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --update --no-cache $BUILD_PACKAGES $DEV_PACKAGES $RUBY_PACKAGES
|
||||
|
||||
COPY Gemfile* ./
|
||||
COPY Gemfile Gemfile.lock $RAILS_ROOT/
|
||||
|
||||
RUN bundle config --global frozen 1 \
|
||||
&& bundle config set deployment 'true' \
|
||||
&& bundle config set without 'development:test:assets' \
|
||||
&& bundle install -j4 --path=vendor/bundle \
|
||||
&& rm -rf vendor/bundle/ruby/2.7.0/cache/*.gem \
|
||||
&& find vendor/bundle/ruby/2.7.0/gems/ -name "*.c" -delete \
|
||||
&& find vendor/bundle/ruby/2.7.0/gems/ -name "*.o" -delete
|
||||
|
||||
# Adding project files.
|
||||
COPY . .
|
||||
|
||||
# Remove folders not needed in resulting image
|
||||
RUN rm -rf tmp/cache spec
|
||||
|
||||
############### Build step done ###############
|
||||
|
||||
FROM ruby:2.7.2-alpine
|
||||
|
||||
# Set a variable for the install location.
|
||||
ARG RAILS_ROOT=/usr/src/app
|
||||
ARG PACKAGES="tzdata curl postgresql-client sqlite-libs yarn nodejs bash"
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV BUNDLE_APP_CONFIG="$RAILS_ROOT/.bundle"
|
||||
|
||||
WORKDIR $RAILS_ROOT
|
||||
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --update --no-cache $PACKAGES
|
||||
|
||||
|
||||
RUN mkdir $APP_HOME
|
||||
WORKDIR $APP_HOME
|
||||
COPY --from=base $RAILS_ROOT $RAILS_ROOT
|
||||
|
||||
# Add the greenlight app
|
||||
ADD . $APP_HOME
|
||||
# Expose port 80.
|
||||
EXPOSE 80
|
||||
|
||||
# Install app dependencies
|
||||
RUN bundle install --without development test doc --deployment --clean
|
||||
RUN bundle exec rake assets:precompile --trace
|
||||
# Sets the footer of greenlight application with current build version
|
||||
ARG version_code
|
||||
ENV VERSION_CODE=$version_code
|
||||
|
||||
CMD ["scripts/default_start.sh"]
|
||||
# Set executable permission to start file
|
||||
RUN chmod +x bin/start
|
||||
|
||||
# FIXME / to remove / https://github.com/nahi/httpclient/issues/445
|
||||
RUN cat /etc/ssl/certs/ca-certificates.crt \
|
||||
>/usr/src/app/vendor/bundle/ruby/2.7.0/gems/httpclient-2.8.3/lib/httpclient/cacert.pem
|
||||
|
||||
# Start the application.
|
||||
CMD ["bin/start"]
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
FROM ruby:2.4.0
|
||||
|
||||
# app dependencies
|
||||
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
|
||||
|
||||
ENV RAILS_ENV=development \
|
||||
APP_HOME=/usr/src/app
|
||||
|
||||
RUN mkdir $APP_HOME
|
||||
WORKDIR $APP_HOME
|
157
Gemfile
|
@ -1,98 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '2.4.0'
|
||||
|
||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
|
||||
# Use sqlite3 as the database for Active Record
|
||||
gem 'sqlite3'
|
||||
# Use postgres as the database for Active Record
|
||||
gem 'pg'
|
||||
# Use Puma as the app server
|
||||
gem 'puma', '~> 3.0'
|
||||
# Use SCSS for stylesheets
|
||||
gem 'sass-rails', '~> 5.0'
|
||||
# Use Uglifier as compressor for JavaScript assets
|
||||
gem 'uglifier', '>= 1.3.0'
|
||||
# Use CoffeeScript for .coffee assets and views
|
||||
gem 'coffee-rails', '~> 4.2'
|
||||
# See https://github.com/rails/execjs#readme for more supported runtimes
|
||||
gem 'therubyracer', platforms: :ruby
|
||||
|
||||
# Use jquery as the JavaScript library
|
||||
gem 'jquery-rails'
|
||||
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
|
||||
gem 'turbolinks', '~> 5'
|
||||
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
|
||||
gem 'jbuilder', '~> 2.5'
|
||||
# Use Redis adapter to run Action Cable in production
|
||||
# gem 'redis', '~> 3.0'
|
||||
# Use ActiveModel has_secure_password
|
||||
# gem 'bcrypt', '~> 3.1.7'
|
||||
|
||||
# Use Capistrano for deployment
|
||||
# gem 'capistrano-rails', group: :development
|
||||
|
||||
group :development, :test do
|
||||
# For environment configuration
|
||||
gem 'dotenv-rails'
|
||||
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
||||
gem 'byebug', platform: :mri
|
||||
git_source(:github) do |repo_name|
|
||||
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
||||
"https://github.com/#{repo_name}.git"
|
||||
end
|
||||
|
||||
group :development do
|
||||
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
|
||||
gem 'web-console'
|
||||
gem 'listen', '~> 3.0.5'
|
||||
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen', '~> 2.0.0'
|
||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||
gem 'aws-sdk-s3', '~> 1.88.1'
|
||||
gem 'bcrypt', '~> 3.1.7'
|
||||
gem 'bigbluebutton-api-ruby', git: 'https://github.com/mconf/bigbluebutton-api-ruby.git', branch: 'master'
|
||||
gem 'bn-ldap-authentication', '~> 0.1.4'
|
||||
gem 'bootsnap', '~> 1.7.2', require: false
|
||||
gem 'bootstrap', '~> 4.3.1'
|
||||
gem 'cancancan', '~> 2.3.0'
|
||||
gem 'coveralls', '~> 0.8.23', require: false
|
||||
gem 'font-awesome-sass', '~> 5.9.0'
|
||||
gem 'google-cloud-storage', '~> 1.30.0'
|
||||
gem 'http_accept_language', '~> 2.1.1'
|
||||
gem 'i18n-language-mapping', '~> 0.1.3.1'
|
||||
gem 'jbuilder', '~> 2.11.2'
|
||||
gem 'jquery-rails', '~> 4.4.0'
|
||||
gem 'jquery-ui-rails', '~> 6.0.1'
|
||||
gem 'local_time', '~> 2.1.0'
|
||||
gem 'net-ldap', '~> 0.17.0'
|
||||
gem 'omniauth', '~> 1.9.1'
|
||||
gem 'omniauth-bn-launcher', '~> 0.1.3'
|
||||
gem 'omniauth-bn-office365', '~> 0.1.1'
|
||||
gem 'omniauth-google-oauth2', '~> 0.7.0'
|
||||
gem 'omniauth_openid_connect', '~> 0.3.5'
|
||||
gem 'omniauth-twitter', '~> 1.4.0'
|
||||
gem 'pagy', '~> 3.11.0'
|
||||
gem 'pluck_to_hash', '~> 1.0.2'
|
||||
gem 'puma', '~> 4.3.8'
|
||||
gem 'rails', '~> 5.2.6'
|
||||
gem 'random_password', '~> 0.1.1'
|
||||
gem "recaptcha", '~> 5.7.0'
|
||||
gem 'redcarpet', '~> 3.5.1'
|
||||
gem 'remote_syslog_logger', '~> 1.0.4'
|
||||
gem 'rubocop', '~> 1.10.0'
|
||||
gem 'sassc-rails', '~> 2.1.2'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'sqlite3', '~> 1.3.6'
|
||||
gem 'tabler-rubygem', git: 'https://github.com/blindsidenetworks/tabler-rubygem.git', tag: '0.1.4.1'
|
||||
gem 'turbolinks', '~> 5.2.1'
|
||||
gem 'tzinfo-data', '~> 1.2021.1'
|
||||
gem 'uglifier', '~> 4.2.0'
|
||||
|
||||
group :production do
|
||||
gem 'hiredis', '~> 0.6.3'
|
||||
gem "lograge", '~> 0.11.2'
|
||||
gem 'pg', '~> 0.18'
|
||||
gem 'redis', '~> 4.2.5'
|
||||
gem 'sequel', '~> 5.41.0'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
gem 'byebug', '~> 11.1', platform: :mri
|
||||
gem 'dotenv-rails', '~> 2.7'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'mocha'
|
||||
gem 'simplecov', :require => false
|
||||
gem 'action-cable-testing', '~> 0.6'
|
||||
gem "factory_bot_rails", '~> 6.1'
|
||||
gem 'faker', '~> 2.16'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-rails', '~> 3.7'
|
||||
gem 'shoulda-matchers', '~> 3.1'
|
||||
gem 'webmock', '~> 3.11'
|
||||
end
|
||||
|
||||
group :production do
|
||||
# For more condensed logging
|
||||
gem "lograge"
|
||||
group :development do
|
||||
gem 'listen', '~> 3.0'
|
||||
gem 'spring', '~> 2.1'
|
||||
gem 'spring-watcher-listen', '~> 2.0'
|
||||
gem 'web-console', '~> 3.7'
|
||||
end
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||
|
||||
gem 'omniauth', '1.6.1'
|
||||
gem 'omniauth-twitter', '1.2.1'
|
||||
gem 'omniauth-google-oauth2', '0.4.1'
|
||||
gem 'omniauth-ldap'
|
||||
|
||||
gem 'bigbluebutton-api-ruby'
|
||||
|
||||
gem 'bootstrap-sass', '3.3.0.0'
|
||||
gem 'bootstrap-social-rails', '~> 4.12'
|
||||
gem 'font-awesome-sass', '4.7.0'
|
||||
gem 'jquery-ui-rails'
|
||||
gem 'jquery-datatables-rails', '~> 3.4.0'
|
||||
gem 'rails-timeago', '~> 2.0'
|
||||
|
||||
gem 'http_accept_language'
|
||||
|
||||
# For sending slack notifications.
|
||||
gem 'slack-notifier'
|
||||
|
||||
# For landing background image uploading.
|
||||
gem 'paperclip', '~> 5.1'
|
||||
|
||||
# For uploading recordings to Youtube.
|
||||
gem 'yt', '~> 0.28.0'
|
||||
|
||||
# Simple HTTP client.
|
||||
gem 'faraday'
|
||||
|
||||
|
||||
# For SAML authentication
|
||||
gem 'omniauth-saml'
|
||||
|
||||
# For device detection to determine BigBlueButton client.
|
||||
gem 'browser'
|
||||
|
|
704
Gemfile.lock
|
@ -1,339 +1,521 @@
|
|||
GIT
|
||||
remote: https://github.com/blindsidenetworks/tabler-rubygem.git
|
||||
revision: 4c16e6dc930f6b92dec093abaf9652d768b46d97
|
||||
tag: 0.1.4.1
|
||||
specs:
|
||||
tabler-rubygem (0.1.4.1)
|
||||
autoprefixer-rails (>= 6.0.3)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/mconf/bigbluebutton-api-ruby.git
|
||||
revision: 91dc495324a6b7e162773227ec3650f8a5b39c50
|
||||
branch: master
|
||||
specs:
|
||||
bigbluebutton-api-ruby (1.7.0)
|
||||
childprocess (>= 1.0.1)
|
||||
ffi (>= 1.9.24)
|
||||
json (>= 1.8.6)
|
||||
nokogiri (>= 1.10.4)
|
||||
rack (>= 1.6.11)
|
||||
rubyzip (>= 1.3.0)
|
||||
xml-simple (~> 1.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
nio4r (>= 1.2, < 3.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
actionview (= 5.0.7)
|
||||
activejob (= 5.0.7)
|
||||
action-cable-testing (0.6.1)
|
||||
actioncable (>= 5.0)
|
||||
actioncable (5.2.6)
|
||||
actionpack (= 5.2.6)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.6)
|
||||
actionpack (= 5.2.6)
|
||||
actionview (= 5.2.6)
|
||||
activejob (= 5.2.6)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.0.7)
|
||||
actionview (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
actionpack (5.2.6)
|
||||
actionview (= 5.2.6)
|
||||
activesupport (= 5.2.6)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
actionview (5.2.6)
|
||||
activesupport (= 5.2.6)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
activejob (5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
activejob (5.2.6)
|
||||
activesupport (= 5.2.6)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
activerecord (5.0.7)
|
||||
activemodel (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
arel (~> 7.0)
|
||||
activesupport (5.0.7)
|
||||
activemodel (5.2.6)
|
||||
activesupport (= 5.2.6)
|
||||
activerecord (5.2.6)
|
||||
activemodel (= 5.2.6)
|
||||
activesupport (= 5.2.6)
|
||||
arel (>= 9.0)
|
||||
activestorage (5.2.6)
|
||||
actionpack (= 5.2.6)
|
||||
activerecord (= 5.2.6)
|
||||
marcel (~> 1.0.0)
|
||||
activesupport (5.2.6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
arel (7.1.4)
|
||||
bigbluebutton-api-ruby (1.6.0)
|
||||
xml-simple (~> 1.1)
|
||||
bindex (0.5.0)
|
||||
bootstrap-sass (3.3.0.0)
|
||||
sass (~> 3.2)
|
||||
bootstrap-social-rails (4.12.0)
|
||||
railties (>= 3.1)
|
||||
browser (2.5.2)
|
||||
builder (3.2.3)
|
||||
byebug (9.1.0)
|
||||
climate_control (0.2.0)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coffee-rails (4.2.2)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
arel (9.0.0)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.1)
|
||||
autoprefixer-rails (10.2.4.0)
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
concurrent-ruby (1.0.5-java)
|
||||
crass (1.0.4)
|
||||
docile (1.1.5)
|
||||
dotenv (2.2.1)
|
||||
dotenv-rails (2.2.1)
|
||||
dotenv (= 2.2.1)
|
||||
railties (>= 3.2, < 5.2)
|
||||
erubis (2.7.0)
|
||||
aws-eventstream (1.1.1)
|
||||
aws-partitions (1.435.0)
|
||||
aws-sdk-core (3.113.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.43.0)
|
||||
aws-sdk-core (~> 3, >= 3.112.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.88.2)
|
||||
aws-sdk-core (~> 3, >= 3.112.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.2.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
bcrypt (3.1.16)
|
||||
bindata (2.4.10)
|
||||
bindex (0.8.1)
|
||||
bn-ldap-authentication (0.1.4)
|
||||
net-ldap (~> 0)
|
||||
bootsnap (1.7.3)
|
||||
msgpack (~> 1.0)
|
||||
bootstrap (4.3.1)
|
||||
autoprefixer-rails (>= 9.1.0)
|
||||
popper_js (>= 1.14.3, < 2)
|
||||
sassc-rails (>= 2.0.0)
|
||||
builder (3.2.4)
|
||||
byebug (11.1.3)
|
||||
cancancan (2.3.0)
|
||||
childprocess (4.0.0)
|
||||
concurrent-ruby (1.1.8)
|
||||
coveralls (0.8.23)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov (~> 0.16.1)
|
||||
term-ansicolor (~> 1.3)
|
||||
thor (>= 0.19.4, < 2.0)
|
||||
tins (~> 1.6)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
diff-lcs (1.4.4)
|
||||
digest-crc (0.6.3)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
docile (1.3.5)
|
||||
dotenv (2.7.6)
|
||||
dotenv-rails (2.7.6)
|
||||
dotenv (= 2.7.6)
|
||||
railties (>= 3.2)
|
||||
erubi (1.10.0)
|
||||
execjs (2.7.0)
|
||||
faraday (0.12.2)
|
||||
factory_bot (6.1.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.1.0)
|
||||
factory_bot (~> 6.1.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (2.17.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (1.3.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.9.18)
|
||||
ffi (1.9.18-java)
|
||||
ffi (1.9.18-x64-mingw32)
|
||||
ffi (1.9.18-x86-mingw32)
|
||||
font-awesome-sass (4.7.0)
|
||||
sass (>= 3.2)
|
||||
globalid (0.4.1)
|
||||
ruby2_keywords
|
||||
faraday-net_http (1.0.1)
|
||||
ffi (1.15.0)
|
||||
font-awesome-sass (5.9.0)
|
||||
sassc (>= 1.11)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
hashie (3.5.7)
|
||||
google-apis-core (0.3.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.14)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
rexml
|
||||
signet (~> 0.14)
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.2.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-storage_v1 (0.3.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.1.0)
|
||||
google-cloud-storage (1.30.0)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.16.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.14)
|
||||
hashdiff (1.0.1)
|
||||
hashie (4.1.0)
|
||||
hiredis (0.6.3)
|
||||
http_accept_language (2.1.1)
|
||||
i18n (1.0.1)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jbuilder (2.7.0)
|
||||
activesupport (>= 4.2.0)
|
||||
multi_json (>= 1.2)
|
||||
jquery-datatables-rails (3.4.0)
|
||||
actionpack (>= 3.1)
|
||||
jquery-rails
|
||||
railties (>= 3.1)
|
||||
sass-rails
|
||||
jquery-rails (4.3.1)
|
||||
i18n-language-mapping (0.1.3.1)
|
||||
jbuilder (2.11.2)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.4.0)
|
||||
jquery-rails (4.4.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-ui-rails (6.0.1)
|
||||
railties (>= 3.2.16)
|
||||
json (1.8.6)
|
||||
json (1.8.6-java)
|
||||
jwt (1.5.6)
|
||||
libv8 (3.16.14.19)
|
||||
listen (3.0.8)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
lograge (0.9.0)
|
||||
json (2.5.1)
|
||||
json-jwt (1.13.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
jwt (2.2.2)
|
||||
listen (3.5.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
local_time (2.1.0)
|
||||
lograge (0.11.2)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.2.2)
|
||||
loofah (2.9.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.0)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
metaclass (0.0.4)
|
||||
method_source (0.9.0)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.2)
|
||||
mini_mime (1.0.0)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.11.3)
|
||||
mocha (1.3.0)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.13.1)
|
||||
marcel (1.0.1)
|
||||
memoist (0.16.2)
|
||||
method_source (1.0.0)
|
||||
mini_mime (1.1.0)
|
||||
mini_portile2 (2.6.1)
|
||||
minitest (5.14.4)
|
||||
msgpack (1.4.2)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
net-ldap (0.16.1)
|
||||
nio4r (2.3.1)
|
||||
nio4r (2.3.1-java)
|
||||
nokogiri (1.8.2)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
nokogiri (1.8.2-java)
|
||||
nokogiri (1.8.2-x64-mingw32)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
nokogiri (1.8.2-x86-mingw32)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
oauth (0.5.4)
|
||||
oauth2 (1.4.0)
|
||||
faraday (>= 0.8, < 0.13)
|
||||
jwt (~> 1.0)
|
||||
multipart-post (2.1.1)
|
||||
net-ldap (0.17.0)
|
||||
nio4r (2.5.7)
|
||||
nokogiri (1.12.5)
|
||||
mini_portile2 (~> 2.6.1)
|
||||
racc (~> 1.4)
|
||||
oauth (0.5.5)
|
||||
oauth2 (1.4.7)
|
||||
faraday (>= 0.8, < 2.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
omniauth (1.6.1)
|
||||
hashie (>= 3.4.6, < 3.6.0)
|
||||
omniauth (1.9.1)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-google-oauth2 (0.4.1)
|
||||
jwt (~> 1.5.2)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-bn-launcher (0.1.3)
|
||||
omniauth (~> 1.3, >= 1.3.2)
|
||||
omniauth-oauth2 (= 1.5.0)
|
||||
omniauth-bn-office365 (0.1.1)
|
||||
omniauth (>= 1.3.2)
|
||||
omniauth-oauth2 (>= 1.5.0)
|
||||
omniauth-google-oauth2 (0.7.0)
|
||||
jwt (>= 2.0)
|
||||
omniauth (>= 1.1.1)
|
||||
omniauth-oauth2 (>= 1.3.1)
|
||||
omniauth-ldap (1.0.5)
|
||||
net-ldap (~> 0.12)
|
||||
omniauth (~> 1.0)
|
||||
pyu-ruby-sasl (~> 0.0.3.2)
|
||||
rubyntlm (~> 0.3.4)
|
||||
omniauth-oauth (1.1.0)
|
||||
omniauth-oauth2 (>= 1.5)
|
||||
omniauth-oauth (1.2.0)
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
omniauth (>= 1.0, < 3)
|
||||
omniauth-oauth2 (1.5.0)
|
||||
oauth2 (~> 1.1)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-saml (1.8.1)
|
||||
omniauth (~> 1.3)
|
||||
ruby-saml (~> 1.4, >= 1.4.3)
|
||||
omniauth-twitter (1.2.1)
|
||||
json (~> 1.3)
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
paperclip (5.2.1)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
cocaine (~> 0.5.5)
|
||||
mime-types
|
||||
mimemagic (~> 0.3.0)
|
||||
pg (1.0.0)
|
||||
pg (1.0.0-x64-mingw32)
|
||||
pg (1.0.0-x86-mingw32)
|
||||
puma (3.11.2)
|
||||
puma (3.11.2-java)
|
||||
pyu-ruby-sasl (0.0.3.3)
|
||||
rack (2.0.5)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (5.0.7)
|
||||
actioncable (= 5.0.7)
|
||||
actionmailer (= 5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
actionview (= 5.0.7)
|
||||
activejob (= 5.0.7)
|
||||
activemodel (= 5.0.7)
|
||||
activerecord (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
rack
|
||||
omniauth_openid_connect (0.3.5)
|
||||
addressable (~> 2.5)
|
||||
omniauth (~> 1.9)
|
||||
openid_connect (~> 1.1)
|
||||
openid_connect (1.2.0)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
json-jwt (>= 1.5.0)
|
||||
rack-oauth2 (>= 1.6.1)
|
||||
swd (>= 1.0.0)
|
||||
tzinfo
|
||||
validate_email
|
||||
validate_url
|
||||
webfinger (>= 1.0.1)
|
||||
os (1.1.1)
|
||||
pagy (3.11.0)
|
||||
parallel (1.20.1)
|
||||
parser (3.0.0.0)
|
||||
ast (~> 2.4.1)
|
||||
pg (0.21.0)
|
||||
pluck_to_hash (1.0.2)
|
||||
activerecord (>= 4.0.2)
|
||||
activesupport (>= 4.0.2)
|
||||
popper_js (1.16.0)
|
||||
public_suffix (4.0.6)
|
||||
puma (4.3.8)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.5.2)
|
||||
rack (2.2.3)
|
||||
rack-oauth2 (1.16.0)
|
||||
activesupport
|
||||
attr_required
|
||||
httpclient
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.2.6)
|
||||
actioncable (= 5.2.6)
|
||||
actionmailer (= 5.2.6)
|
||||
actionpack (= 5.2.6)
|
||||
actionview (= 5.2.6)
|
||||
activejob (= 5.2.6)
|
||||
activemodel (= 5.2.6)
|
||||
activerecord (= 5.2.6)
|
||||
activestorage (= 5.2.6)
|
||||
activesupport (= 5.2.6)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.0.7)
|
||||
railties (= 5.2.6)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.4)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-timeago (2.16.0)
|
||||
actionpack (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
railties (5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
railties (5.2.6)
|
||||
actionpack (= 5.2.6)
|
||||
activesupport (= 5.2.6)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rake (12.3.1)
|
||||
rb-fsevent (0.10.2)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
ref (2.0.0)
|
||||
request_store (1.4.0)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.3)
|
||||
random_password (0.1.1)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
recaptcha (5.7.0)
|
||||
json
|
||||
redcarpet (3.5.1)
|
||||
redis (4.2.5)
|
||||
regexp_parser (2.1.1)
|
||||
remote_syslog_logger (1.0.4)
|
||||
syslog_protocol
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
request_store (1.5.0)
|
||||
rack (>= 1.4)
|
||||
ruby-saml (1.4.3)
|
||||
nokogiri (>= 1.5.10)
|
||||
rubyntlm (0.3.4)
|
||||
sass (3.5.5)
|
||||
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)
|
||||
simplecov (0.15.1)
|
||||
docile (~> 1.1.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rspec-core (3.9.3)
|
||||
rspec-support (~> 3.9.3)
|
||||
rspec-expectations (3.9.4)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (3.9.1)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-support (3.9.4)
|
||||
rubocop (1.10.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.0.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml
|
||||
rubocop-ast (>= 1.2.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.4.1)
|
||||
parser (>= 2.7.1.5)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby2_keywords (0.0.4)
|
||||
rubyzip (2.3.0)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
sequel (5.41.0)
|
||||
shoulda-matchers (3.1.3)
|
||||
activesupport (>= 4.0.0)
|
||||
signet (0.15.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.16.1)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
slack-notifier (2.3.2)
|
||||
spring (2.0.2)
|
||||
activesupport (>= 4.2)
|
||||
spring (2.1.1)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (3.7.1)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
sprockets-rails (3.2.2)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.3.13)
|
||||
sqlite3 (1.3.13-x64-mingw32)
|
||||
sqlite3 (1.3.13-x86-mingw32)
|
||||
sqlite3 (1.3.13-x86-mswin32-60)
|
||||
therubyracer (0.12.3)
|
||||
libv8 (~> 3.16.14.15)
|
||||
ref
|
||||
thor (0.20.0)
|
||||
swd (1.2.0)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
httpclient (>= 2.4)
|
||||
sync (0.5.0)
|
||||
syslog_protocol (0.9.2)
|
||||
term-ansicolor (1.7.1)
|
||||
tins (~> 1.0)
|
||||
thor (1.1.0)
|
||||
thread_safe (0.3.6)
|
||||
thread_safe (0.3.6-java)
|
||||
tilt (2.0.8)
|
||||
turbolinks (5.1.0)
|
||||
turbolinks-source (~> 5.1)
|
||||
turbolinks-source (5.1.0)
|
||||
tzinfo (1.2.5)
|
||||
tilt (2.0.10)
|
||||
tins (1.28.0)
|
||||
sync
|
||||
turbolinks (5.2.1)
|
||||
turbolinks-source (~> 5.2)
|
||||
turbolinks-source (5.2.0)
|
||||
tzinfo (1.2.9)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2017.3)
|
||||
tzinfo-data (1.2021.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
uglifier (4.1.3)
|
||||
uber (0.1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
web-console (3.5.1)
|
||||
unicode-display_width (2.0.0)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
validate_url (1.0.13)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
web-console (3.7.0)
|
||||
actionview (>= 5.0)
|
||||
activemodel (>= 5.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 5.0)
|
||||
websocket-driver (0.6.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-driver (0.6.5-java)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.3)
|
||||
xml-simple (1.1.5)
|
||||
yt (0.28.5)
|
||||
webfinger (1.1.0)
|
||||
activesupport
|
||||
httpclient (>= 2.4)
|
||||
webmock (3.12.1)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.7.0)
|
||||
websocket-driver (0.7.3)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xml-simple (1.1.8)
|
||||
|
||||
PLATFORMS
|
||||
java
|
||||
ruby
|
||||
x64-mingw32
|
||||
x86-mingw32
|
||||
x86-mswin32
|
||||
|
||||
DEPENDENCIES
|
||||
bigbluebutton-api-ruby
|
||||
bootstrap-sass (= 3.3.0.0)
|
||||
bootstrap-social-rails (~> 4.12)
|
||||
browser
|
||||
byebug
|
||||
coffee-rails (~> 4.2)
|
||||
dotenv-rails
|
||||
faraday
|
||||
font-awesome-sass (= 4.7.0)
|
||||
http_accept_language
|
||||
jbuilder (~> 2.5)
|
||||
jquery-datatables-rails (~> 3.4.0)
|
||||
jquery-rails
|
||||
jquery-ui-rails
|
||||
listen (~> 3.0.5)
|
||||
lograge
|
||||
mocha
|
||||
omniauth (= 1.6.1)
|
||||
omniauth-google-oauth2 (= 0.4.1)
|
||||
omniauth-ldap
|
||||
omniauth-saml
|
||||
omniauth-twitter (= 1.2.1)
|
||||
paperclip (~> 5.1)
|
||||
pg
|
||||
puma (~> 3.0)
|
||||
rails (~> 5.0.0, >= 5.0.0.1)
|
||||
rails-timeago (~> 2.0)
|
||||
sass-rails (~> 5.0)
|
||||
simplecov
|
||||
slack-notifier
|
||||
spring
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
sqlite3
|
||||
therubyracer
|
||||
turbolinks (~> 5)
|
||||
tzinfo-data
|
||||
uglifier (>= 1.3.0)
|
||||
web-console
|
||||
yt (~> 0.28.0)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.4.0p0
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.1
|
||||
action-cable-testing (~> 0.6)
|
||||
aws-sdk-s3 (~> 1.88.1)
|
||||
bcrypt (~> 3.1.7)
|
||||
bigbluebutton-api-ruby!
|
||||
bn-ldap-authentication (~> 0.1.4)
|
||||
bootsnap (~> 1.7.2)
|
||||
bootstrap (~> 4.3.1)
|
||||
byebug (~> 11.1)
|
||||
cancancan (~> 2.3.0)
|
||||
coveralls (~> 0.8.23)
|
||||
dotenv-rails (~> 2.7)
|
||||
factory_bot_rails (~> 6.1)
|
||||
faker (~> 2.16)
|
||||
font-awesome-sass (~> 5.9.0)
|
||||
google-cloud-storage (~> 1.30.0)
|
||||
hiredis (~> 0.6.3)
|
||||
http_accept_language (~> 2.1.1)
|
||||
i18n-language-mapping (~> 0.1.3.1)
|
||||
jbuilder (~> 2.11.2)
|
||||
jquery-rails (~> 4.4.0)
|
||||
jquery-ui-rails (~> 6.0.1)
|
||||
listen (~> 3.0)
|
||||
local_time (~> 2.1.0)
|
||||
lograge (~> 0.11.2)
|
||||
net-ldap (~> 0.17.0)
|
||||
omniauth (~> 1.9.1)
|
||||
omniauth-bn-launcher (~> 0.1.3)
|
||||
omniauth-bn-office365 (~> 0.1.1)
|
||||
omniauth-google-oauth2 (~> 0.7.0)
|
||||
omniauth-twitter (~> 1.4.0)
|
||||
omniauth_openid_connect (~> 0.3.5)
|
||||
pagy (~> 3.11.0)
|
||||
pg (~> 0.18)
|
||||
pluck_to_hash (~> 1.0.2)
|
||||
puma (~> 4.3.8)
|
||||
rails (~> 5.2.6)
|
||||
rails-controller-testing (~> 1.0)
|
||||
random_password (~> 0.1.1)
|
||||
recaptcha (~> 5.7.0)
|
||||
redcarpet (~> 3.5.1)
|
||||
redis (~> 4.2.5)
|
||||
remote_syslog_logger (~> 1.0.4)
|
||||
rspec-rails (~> 3.7)
|
||||
rubocop (~> 1.10.0)
|
||||
sassc-rails (~> 2.1.2)
|
||||
sequel (~> 5.41.0)
|
||||
shoulda-matchers (~> 3.1)
|
||||
spring (~> 2.1)
|
||||
spring-watcher-listen (~> 2.0)
|
||||
sprockets (~> 3.7.2)
|
||||
sqlite3 (~> 1.3.6)
|
||||
tabler-rubygem!
|
||||
turbolinks (~> 5.2.1)
|
||||
tzinfo-data (~> 1.2021.1)
|
||||
uglifier (~> 4.2.0)
|
||||
web-console (~> 3.7)
|
||||
webmock (~> 3.11)
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
38
README.md
|
@ -1,31 +1,37 @@
|
|||
# Greenlight
|
||||
|
||||
GreenLight is a simple (but powerful) front-end interface for your BigBlueButton server. At its core, GreenLight provides a minimalistic web-based application that lets users
|
||||

|
||||

|
||||
|
||||
* Create a meeting
|
||||
* Invite others to the meeting
|
||||
* Join a meeting
|
||||
Greenlight is a simple front-end interface for your BigBlueButton server. At its heart, Greenlight provides a minimalistic web-based application that allows users to:
|
||||
|
||||
Furthermore, if you configure GreenLight to use either Google, Twitter (both via OAuth2) or LDAP for authentication, users can login to record meetings and manage recordings.
|
||||
* Signup/Login with Google, Office365, OpenID Connect, or through the application itself.
|
||||
* Manage your account settings and user preferences.
|
||||
* Create and manage your own personal rooms ([BigBlueButton](https://github.com/bigbluebutton/bigbluebutton) sessions).
|
||||
* Invite others to your room using a simple URL.
|
||||
* View recordings and share them with others.
|
||||
|
||||
## Overview video
|
||||
Interested? Try Greenlight out on our [demo server](https://demo.bigbluebutton.org/gl)!
|
||||
|
||||
For a overview of how GreenLight works, see the following video
|
||||
Greenlight is also completely configurable. This means you can turn on/off features to make Greenlight fit your specific use case. For more information on Greenlight and its features, see our [documentation](http://docs.bigbluebutton.org/greenlight/gl-install.html).
|
||||
|
||||
[](https://youtu.be/yGX3JCv7OVM)
|
||||
For a overview of how Greenlight works, checkout our Introduction to Greenlight Video:
|
||||
|
||||
[](https://youtu.be/Hso8yLzkqj8)
|
||||
|
||||
## Installation on the BigBlueButton server
|
||||
We designed GreenLight to install on a [BigBlueButton 1.1-beta](http://docs.bigbluebutton.org/greenlight-v1.html) (or later) server. This means you don't need a separate server to run GreenLight.
|
||||
## Installation on a BigBlueButton Server
|
||||
|
||||
For more informaiton see [Installing GreenLight](http://docs.bigbluebutton.org/greenlight-v1.html#installing-greenlight).
|
||||
Greenlight is designed to work on a [BigBlueButton 2.0](https://github.com/bigbluebutton/bigbluebutton) (or later) server.
|
||||
|
||||
# Source Code
|
||||
For information on installing Greenlight, checkout our [Installing Greenlight on a BigBlueButton Server](http://docs.bigbluebutton.org/greenlight/gl-install.html#installing-on-a-bigbluebutton-server) documentation.
|
||||
|
||||
GreenLight is a rails 5 application.
|
||||
## Source Code & Contributing
|
||||
|
||||
Many developers already know Rails well, and we wanted to create both a full front-end to BigBlueButton but also a reference implementation of how to fully leverage the [BigBlueButton API](http://docs.bigbluebutton.org/dev/api.html).
|
||||
Greenlight is built using Ruby on Rails. Many developers already know Rails well, and we wanted to create both a full front-end to BigBlueButton but also a reference implementation of how to fully leverage the [BigBlueButton API](http://docs.bigbluebutton.org/dev/api.html).
|
||||
|
||||
We invite you to build upon GreenLight and help make it better. See [Contributing to BigBlueButton](http://docs.bigbluebutton.org/support/faq.html#contributing-to-bigbluebutton).
|
||||
We invite you to build upon Greenlight and help make it better. See [Contributing to BigBlueButton](http://docs.bigbluebutton.org/support/faq.html#contributing-to-bigbluebutton).
|
||||
|
||||
We invite your feedback, questions, and suggests about GreenLight too. Please post them to the [developer mailing list](https://groups.google.com/forum/#!forum/bigbluebutton-dev).
|
||||
We invite your feedback, questions, and suggests about Greenlight too. Please post them to the [Greenlight mailing list](https://groups.google.com/forum/#!forum/bigbluebutton-greenlight).
|
||||
|
||||
To help with organization and consistency, we have implemented a Pull Request template that must be used for all Pull Requests. This template helps ensure that the project maintainers can review all PRs in a timely manner. When creating a Pull Request, please provide as much information as possible.
|
9
Rakefile
|
@ -1,6 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require_relative 'config/application'
|
||||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << "test"
|
||||
t.test_files = FileList['test/test*.rb']
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
Rails.application.load_tasks
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We support only the latest version of this project.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you think you’ve found a security issue with BigBlueButton or Greenlight, we ask that you do a [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure) and e-mail us directly at security@bigbluebutton.org.
|
||||
|
||||
We will respond to you quickly, work with you to examine the scope of the issue, and give priority to fixing it as soon as possible.
|
|
@ -1,6 +1,6 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
// 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
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 771 B |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 17 KiB |
|
@ -1,236 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
// Handles live updating and initial population of the previous meetings and active meetings lists on
|
||||
// the landing page using custom Actioncable events.
|
||||
|
||||
var MEETINGS = {}
|
||||
var WAITING = {}
|
||||
var LOADING_DELAY = 1750 // milliseconds.
|
||||
|
||||
// Updates the previous meetings section.
|
||||
var updatePreviousMeetings = function(){
|
||||
$("ul.previously-joined li").each(function(idx, li) {
|
||||
var previous_meeting = $(li);
|
||||
if(Object.keys(MEETINGS).indexOf(previous_meeting.text()) > -1){
|
||||
previous_meeting.remove()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ignore excess on either side of user_id.
|
||||
var trimUserId = function(user_id){
|
||||
components = user_id.split('_')
|
||||
return components.sort(function (a, b) {return b.length - a.length;})[0]
|
||||
}
|
||||
|
||||
// Finds a user by their user_id.
|
||||
var findByUserId = function(users, user_id){
|
||||
for(i = 0; i < users.length; i++){
|
||||
if(trimUserId(users[i]['user_id']) == trimUserId(user_id)){
|
||||
return i
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Adds a user to a meeting.
|
||||
var addUser = function(data){
|
||||
if(data['role'] == 'MODERATOR'){
|
||||
MEETINGS[data['meeting']]['moderators'].push({'name': data['user'], 'user_id': data['user_id']})
|
||||
} else {
|
||||
MEETINGS[data['meeting']]['participants'].push({'name':data['user'], 'user_id': data['user_id']})
|
||||
}
|
||||
updateMeetingText(MEETINGS[data['meeting']])
|
||||
}
|
||||
|
||||
// Removes a user from a meeting.
|
||||
var removeUser = function(data){
|
||||
user = findByUserId(MEETINGS[data['meeting']]['moderators'], data['user_id'])
|
||||
if(user == undefined){
|
||||
user = findByUserId(MEETINGS[data['meeting']]['participants'], data['user_id']);
|
||||
if(user == undefined){ return; }
|
||||
MEETINGS[data['meeting']]['participants'].splice(user, 1);
|
||||
} else {
|
||||
MEETINGS[data['meeting']]['moderators'].splice(user, 1);
|
||||
}
|
||||
updateMeetingText(MEETINGS[data['meeting']])
|
||||
}
|
||||
|
||||
// Updates the display text for an active meeting.
|
||||
var updateMeetingText = function(m){
|
||||
// If a meeting has a moderators property, it is running.
|
||||
var body;
|
||||
if(m.hasOwnProperty('moderators')){
|
||||
var list;
|
||||
if(m['moderators'].length + m['participants'].length == 0){
|
||||
list = '(empty)'
|
||||
} else {
|
||||
list = m['moderators'].map(function(x){ return x['name']; }).join('(mod), ') +
|
||||
(m['moderators'].length > 0 ? '(mod)' : '') +
|
||||
(m['participants'].length > 0 && m['moderators'].length != 0 ? ', ' : '') +
|
||||
(m['participants'].map(function(x){ return x['name']; }).join(', '))
|
||||
}
|
||||
body = '<a>' + m['name'] + '</a><i>: ' + list + '</i>'
|
||||
// Otherwise it hasn't started (users waiting the join).
|
||||
} else {
|
||||
body = '<a>' + m['name'] + '</a><i> (not yet started): ' + m['users'].join(', ') + '</i>'
|
||||
}
|
||||
|
||||
// If the item doesn't exist, add it and set up join meeting event.
|
||||
if($("li[id='" + m['name'].replace(' ', '_') + "']").length == 0){
|
||||
var meeting_item = $("<li>" + body + "</li>")
|
||||
meeting_item.attr('id', m['name'].replace(' ', '_'))
|
||||
$('.actives').append(meeting_item);
|
||||
|
||||
// Set up join on click.
|
||||
meeting_item.click(function(){
|
||||
joinMeeting(m['name']);
|
||||
});
|
||||
// Otherwise, just change the body.
|
||||
} else {
|
||||
$("li[id='" + m['name'].replace(' ', '_') + "']").html(body)
|
||||
}
|
||||
}
|
||||
|
||||
// Initially populates the active meetings when the page loads using the API.
|
||||
var initialPopulate = function(){
|
||||
// Only populate on room resources.
|
||||
var chopped = window.location.href.split('/')
|
||||
if (!window.location.href.includes('rooms') || chopped[chopped.length - 2] == $('body').data('current-user')) { return; }
|
||||
|
||||
$.post((window.location.href + '/statuses').replace('#', ''), {previously_joined: getPreviouslyJoined()})
|
||||
.done(function(data) {
|
||||
|
||||
// Populate waiting meetings.
|
||||
Object.keys(data['waiting']).forEach(function(key) {
|
||||
WAITING[key] = {'name': key, 'users': data['waiting'][key]}
|
||||
updateMeetingText(WAITING[key])
|
||||
})
|
||||
|
||||
// Add the meetings to the active meetings list.
|
||||
for(var i = 0; i < data['active'].length; i++){
|
||||
var meeting = data['active'][i]
|
||||
|
||||
var name = meeting['name']
|
||||
var participants = meeting['participants']
|
||||
var moderators = meeting['moderators']
|
||||
|
||||
// Create meeting.
|
||||
MEETINGS[name] = {'name': name, 'participants': participants, 'moderators': moderators}
|
||||
updateMeetingText(MEETINGS[name])
|
||||
}
|
||||
|
||||
// Remove from previous meetings if they are active.
|
||||
updatePreviousMeetings();
|
||||
$('.hidden-list').show();
|
||||
$('.active-spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
// Gets a list of known previously joined meetings.
|
||||
var getPreviouslyJoined = function(){
|
||||
var joinedMeetings = localStorage.getItem('joinedRooms-' + $('body').data('current-user'));
|
||||
if (joinedMeetings == '' || joinedMeetings == null){ return []; }
|
||||
return joinedMeetings.split(',')
|
||||
}
|
||||
|
||||
// Removes an active meeting.
|
||||
var removeActiveMeeting = function(meeting){
|
||||
if(meeting){ $("li[id='" + meeting['name'].replace(' ', '_') + "']").remove() }
|
||||
}
|
||||
|
||||
// Directly join a meeting from active meetings.
|
||||
var joinMeeting = function(meeting_name){
|
||||
if (meeting_name == undefined || meeting_name == null) { return; }
|
||||
Meeting.getInstance().setUserName(localStorage.getItem('lastJoinedName'));
|
||||
Meeting.getInstance().setMeetingId(meeting_name);
|
||||
|
||||
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
||||
if (jqxhr) {
|
||||
jqxhr.done(function(data) {
|
||||
if (data.messageKey === 'wait_for_moderator') {
|
||||
waitForModerator(Meeting.getInstance().getURL());
|
||||
} else {
|
||||
$(location).attr("href", data.response.join_url);
|
||||
}
|
||||
});
|
||||
jqxhr.fail(function() {
|
||||
console.info("meeting join failed");
|
||||
});
|
||||
} else {
|
||||
$('.meeting-user-name').parent().addClass('has-error');
|
||||
}
|
||||
}
|
||||
|
||||
// Only need to register for logged in users.
|
||||
$(document).on('turbolinks:load', function(){
|
||||
if($('body').data('current-user')){
|
||||
MEETINGS = {}
|
||||
// Ensure actives is empty.
|
||||
$('.actives').empty();
|
||||
|
||||
if(!App.messages){
|
||||
// Setup actioncable.
|
||||
App.messages = App.cable.subscriptions.create('RefreshMeetingsChannel', {
|
||||
received: function(data) {
|
||||
console.log('Recieved ' + data['method'] + ' action for ' + data['meeting'] + ' with room id ' + data['room'] + '.')
|
||||
if(data['room'] == $('body').data('current-user')){
|
||||
// Handle webhook event.
|
||||
if(data['method'] == 'create'){
|
||||
// Create an empty meeting.
|
||||
MEETINGS[data['meeting']] = {'name': data['meeting'],
|
||||
'participants': [],
|
||||
'moderators': []}
|
||||
updateMeetingText(MEETINGS[data['meeting']])
|
||||
updatePreviousMeetings();
|
||||
if (WAITING.hasOwnProperty(data['meeting'])){ delete WAITING[data['meeting']]; }
|
||||
} else if(data['method'] == 'destroy'){
|
||||
removeActiveMeeting(MEETINGS[data['meeting']])
|
||||
PreviousMeetings.uniqueAdd([data['meeting']])
|
||||
delete MEETINGS[data['meeting']]
|
||||
} else if(data['method'] == 'join'){
|
||||
addUser(data)
|
||||
} else if(data['method'] == 'leave'){
|
||||
removeUser(data)
|
||||
} else if(data['method'] == 'waiting'){
|
||||
// Handle waiting meeting.
|
||||
if(WAITING.hasOwnProperty(data['meeting'])){
|
||||
WAITING[data['meeting']]['users'].push(data['user'])
|
||||
updateMeetingText(WAITING[data['meeting']])
|
||||
} else {
|
||||
WAITING[data['meeting']] = {'name': data['meeting'],
|
||||
'users': [data['user']]}
|
||||
updateMeetingText(WAITING[data['meeting']])
|
||||
}
|
||||
} else if((data['method'] == 'no_longer_waiting') && (WAITING.hasOwnProperty(data['meeting']))){
|
||||
WAITING[data['meeting']]['users'].splice(WAITING[data['meeting']]['users'].indexOf(data['user']), 1)
|
||||
updateMeetingText(WAITING[data['meeting']])
|
||||
if(WAITING[data['meeting']]['users'].length == 0){
|
||||
removeActiveMeeting(WAITING[data['meeting']])
|
||||
delete WAITING[data['meeting']]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Populating active meetings.');
|
||||
// Short delay to hide the previous meetings population.
|
||||
setTimeout(initialPopulate, LOADING_DELAY);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,340 @@
|
|||
// 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/>.
|
||||
|
||||
$(document).on('turbolinks:load', function(){
|
||||
var controller = $("body").data('controller');
|
||||
var action = $("body").data('action');
|
||||
|
||||
// Only run on the admins page.
|
||||
if (controller == "admins") {
|
||||
if(action == "index") {
|
||||
//clear the role filter if user clicks on the x
|
||||
$(".clear-role").click(function() {
|
||||
var search = new URL(location.href).searchParams.get('search')
|
||||
|
||||
var url = window.location.pathname + "?page=1"
|
||||
|
||||
if (search) {
|
||||
url += "&search=" + search
|
||||
}
|
||||
|
||||
window.location.replace(url);
|
||||
})
|
||||
|
||||
// Handle selected user tags
|
||||
$(".manage-users-tab").click(function() {
|
||||
$(".manage-users-tab").removeClass("selected")
|
||||
$(this).addClass("selected")
|
||||
|
||||
updateTabParams(this.id)
|
||||
})
|
||||
|
||||
$('.selectpicker').selectpicker({
|
||||
liveSearchPlaceholder: getLocalizedString('javascript.search.start')
|
||||
});
|
||||
// Fixes turbolinks issue with bootstrap select
|
||||
$(window).trigger('load.bs.select.data-api');
|
||||
|
||||
// Display merge accounts modal with correct info
|
||||
$(".merge-user").click(function() {
|
||||
// Update the path of save button
|
||||
$("#merge-save-access").attr("data-path", $(this).data("path"))
|
||||
let userInfo = $(this).data("info")
|
||||
$("#merge-to").html("") // Clear current inputs
|
||||
|
||||
let spanName = document.createElement("span"),
|
||||
spanEmail = document.createElement("span"),
|
||||
spanUid = document.createElement("span");
|
||||
spanName.innerText = userInfo.name
|
||||
spanEmail.setAttribute('class', 'text-muted d-block')
|
||||
spanEmail.innerText = userInfo.email
|
||||
spanUid.setAttribute('class', 'text-muted d-block')
|
||||
spanUid.innerText = userInfo.uid
|
||||
|
||||
$("#merge-to").append(spanName, spanEmail, spanUid)
|
||||
})
|
||||
|
||||
$("#mergeUserModal").on("show.bs.modal", function() {
|
||||
$(".selectpicker").selectpicker('val','')
|
||||
})
|
||||
|
||||
$(".bootstrap-select").on("click", function() {
|
||||
$(".bs-searchbox").siblings().hide()
|
||||
})
|
||||
|
||||
$("#merge-user-select ~ button").on("click", function() {
|
||||
$(".bs-searchbox").siblings().hide()
|
||||
})
|
||||
|
||||
$(".bs-searchbox input").on("input", function() {
|
||||
if ($(".bs-searchbox input").val() == '' || $(".bs-searchbox input").val().length < 3) {
|
||||
$(".select-options").remove()
|
||||
$(".bs-searchbox").siblings().hide()
|
||||
} else {
|
||||
// Manually populate the dropdown
|
||||
$.get($("#merge-user-select").data("path"), { search: $(".bs-searchbox input").val() }, function(users) {
|
||||
$(".select-options").remove()
|
||||
if (users.length > 0) {
|
||||
users.forEach(function(user) {
|
||||
let opt = document.createElement("option")
|
||||
$(opt).val(JSON.stringify({uid: user.uid, email: user.email, name: user.name}))
|
||||
$(opt).text(user.name)
|
||||
$(opt).addClass("select-options")
|
||||
$(opt).attr("data-subtext", user.email)
|
||||
$("#merge-user-select").append(opt)
|
||||
})
|
||||
// Only refresh the select dropdown if there are results to show
|
||||
$('#merge-user-select').selectpicker('refresh');
|
||||
}
|
||||
$(".bs-searchbox").siblings().show()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// User selects an option from the Room Access dropdown
|
||||
$(".bootstrap-select").on("changed.bs.select", function(){
|
||||
// Get the uid of the selected user
|
||||
let user = $(".selectpicker").selectpicker('val')
|
||||
if (user != "") {
|
||||
let userInfo = JSON.parse(user)
|
||||
$("#merge-from").html("") // Clear current input
|
||||
|
||||
let spanName = document.createElement("span"),
|
||||
spanEmail = document.createElement("span"),
|
||||
spanUid = document.createElement("span");
|
||||
spanName.innerText = userInfo.name
|
||||
spanEmail.setAttribute('class', 'text-muted d-block')
|
||||
spanEmail.innerText = userInfo.email
|
||||
spanUid.setAttribute('class', 'text-muted d-block')
|
||||
spanUid.id = 'from-uid'
|
||||
spanUid.innerText = userInfo.uid
|
||||
|
||||
$("#merge-from").append(spanName, spanEmail, spanUid)
|
||||
}
|
||||
})
|
||||
}
|
||||
else if(action == "site_settings"){
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
// Only load the colour selectors if on the appearance tab
|
||||
if (urlParams.get("tab") == null || urlParams.get("tab") == "appearance") {
|
||||
loadColourSelectors()
|
||||
}
|
||||
}
|
||||
else if (action == "roles"){
|
||||
// Refreshes the new role modal
|
||||
$("#newRoleButton").click(function(){
|
||||
$("#createRoleName").val("")
|
||||
})
|
||||
|
||||
// Updates the colour picker to the correct colour
|
||||
let role_colour = $("#role-colorinput-regular").data("colour")
|
||||
$("#role-colorinput-regular").css("background-color", role_colour);
|
||||
$("#role-colorinput-regular").css("border-color", role_colour);
|
||||
|
||||
loadRoleColourSelector(role_colour, $("#role-colorinput-regular").data("disabled"));
|
||||
|
||||
// Loads the jquery sortable so users can manually sort roles
|
||||
$("#rolesSelect").sortable({
|
||||
items: "a:not(.sort-disabled)",
|
||||
update: function() {
|
||||
$.ajax({
|
||||
url: $(this).data("url"),
|
||||
type: 'PATCH',
|
||||
data: $(this).sortable('serialize')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Change the branding image to the image provided
|
||||
function changeBrandingImage(path) {
|
||||
var url = $("#branding-url").val()
|
||||
$.post(path, {value: url, tab: "appearance"})
|
||||
}
|
||||
|
||||
// Change the Legal URL to the one provided
|
||||
function changeLegalURL(path) {
|
||||
var url = $("#legal-url").val()
|
||||
$.post(path, {value: url, tab: "administration"})
|
||||
}
|
||||
|
||||
// Change the Privacy Policy URL to the one provided
|
||||
function changePrivacyPolicyURL(path) {
|
||||
var url = $("#privpolicy-url").val()
|
||||
$.post(path, {value: url, tab: "administration"})
|
||||
}
|
||||
|
||||
// Display the maintenance Banner
|
||||
function displayMaintenanceBanner(path) {
|
||||
var message = $("#maintenance-banner").val()
|
||||
$.post(path, {value: message, tab: "administration"})
|
||||
}
|
||||
|
||||
// Clear the maintenance Banner
|
||||
function clearMaintenanceBanner(path) {
|
||||
$.post(path, {value: "", tab: "administration"})
|
||||
}
|
||||
|
||||
// Change the email mapping to the string provided
|
||||
function changeEmailMapping(path) {
|
||||
var url = $("#email-mapping").val()
|
||||
$.post(path, {value: url, tab: "registration"})
|
||||
}
|
||||
|
||||
function mergeUsers() {
|
||||
let userToMerge = $("#from-uid").text()
|
||||
$.post($("#merge-save-access").data("path"), {merge: userToMerge})
|
||||
}
|
||||
|
||||
// Filters by role
|
||||
function filterRole(role) {
|
||||
var search = new URL(location.href).searchParams.get('search')
|
||||
|
||||
var url = window.location.pathname + "?page=1" + "&role=" + role
|
||||
|
||||
if (search) {
|
||||
url += "&search=" + search
|
||||
}
|
||||
|
||||
window.location.replace(url);
|
||||
}
|
||||
|
||||
function updateTabParams(tab) {
|
||||
var search_params = new URLSearchParams(window.location.search)
|
||||
|
||||
if (window.location.href.includes("tab=")) {
|
||||
search_params.set("tab", tab)
|
||||
} else {
|
||||
search_params.append("tab", tab)
|
||||
}
|
||||
|
||||
search_params.delete("page")
|
||||
|
||||
window.location.search = search_params.toString()
|
||||
}
|
||||
|
||||
function loadColourSelectors() {
|
||||
const pickrRegular = new Pickr({
|
||||
el: '#colorinput-regular',
|
||||
theme: 'monolith',
|
||||
useAsButton: true,
|
||||
lockOpacity: true,
|
||||
defaultRepresentation: 'HEX',
|
||||
closeWithKey: 'Enter',
|
||||
default: $("#colorinput-regular").css("background-color"),
|
||||
|
||||
components: {
|
||||
palette: true,
|
||||
preview: true,
|
||||
hue: true,
|
||||
interaction: {
|
||||
input: true,
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const pickrLighten = new Pickr({
|
||||
el: '#colorinput-lighten',
|
||||
theme: 'monolith',
|
||||
useAsButton: true,
|
||||
lockOpacity: true,
|
||||
defaultRepresentation: 'HEX',
|
||||
closeWithKey: 'Enter',
|
||||
default: $("#colorinput-lighten").css("background-color"),
|
||||
|
||||
components: {
|
||||
palette: true,
|
||||
preview: true,
|
||||
hue: true,
|
||||
interaction: {
|
||||
input: true,
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const pickrDarken = new Pickr({
|
||||
el: '#colorinput-darken',
|
||||
theme: 'monolith',
|
||||
useAsButton: true,
|
||||
lockOpacity: true,
|
||||
defaultRepresentation: 'HEX',
|
||||
closeWithKey: 'Enter',
|
||||
default: $("#colorinput-darken").css("background-color"),
|
||||
|
||||
components: {
|
||||
palette: true,
|
||||
preview: true,
|
||||
hue: true,
|
||||
interaction: {
|
||||
input: true,
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
pickrRegular.on("save", (color, instance) => {
|
||||
$.post($("#coloring-path-regular").val(), {value: color.toHEXA().toString()}).done(function() {
|
||||
location.reload()
|
||||
});
|
||||
})
|
||||
|
||||
pickrLighten.on("save", (color, instance) => {
|
||||
$.post($("#coloring-path-lighten").val(), {value: color.toHEXA().toString(), tab: "appearance"}).done(function() {
|
||||
location.reload()
|
||||
});
|
||||
})
|
||||
|
||||
pickrDarken.on("save", (color, instance) => {
|
||||
$.post($("#coloring-path-darken").val(), {value: color.toHEXA().toString(), tab: "appearance"}).done(function() {
|
||||
location.reload()
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function loadRoleColourSelector(role_colour, disabled) {
|
||||
if (!disabled) {
|
||||
const pickrRoleRegular = new Pickr({
|
||||
el: '#role-colorinput-regular',
|
||||
theme: 'monolith',
|
||||
useAsButton: true,
|
||||
lockOpacity: true,
|
||||
defaultRepresentation: 'HEX',
|
||||
closeWithKey: 'Enter',
|
||||
default: role_colour,
|
||||
|
||||
components: {
|
||||
palette: true,
|
||||
preview: true,
|
||||
hue: true,
|
||||
interaction: {
|
||||
input: true,
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// On save update the colour input's background colour and update the role colour input
|
||||
pickrRoleRegular.on("save", (color, instance) => {
|
||||
$("#role-colorinput-regular").css("background-color", color.toHEXA().toString());
|
||||
$("#role-colorinput-regular").css("border-color", color.toHEXA().toString());
|
||||
$("#role-colour").val(color.toHEXA().toString());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
// Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the GNU Lesser General Public License as published by the Free Software
|
||||
|
@ -14,13 +14,26 @@
|
|||
// 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 jquery2
|
||||
//= require jquery-ui
|
||||
//= require dataTables/jquery.dataTables
|
||||
//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
|
||||
//= require rails-timeago-all
|
||||
//= require bootstrap-sprockets
|
||||
//= require qrcode/qrcode.js
|
||||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||
// listed below.
|
||||
//
|
||||
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
||||
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
||||
//
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
||||
//
|
||||
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
||||
// about supported directives.
|
||||
//
|
||||
//= require turbolinks
|
||||
//= require_self
|
||||
//= require jquery3
|
||||
//= require tabler
|
||||
//= require tabler.plugins
|
||||
//= require jquery_ujs
|
||||
//= require jquery-ui/widget
|
||||
//= require jquery-ui/widgets/sortable
|
||||
//= require pickr.min.js
|
||||
//= require bootstrap-select.min.js
|
||||
//= require local-time
|
||||
//= require_tree .
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 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/>.
|
||||
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
|
@ -1,6 +1,6 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
// 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
|
||||
|
@ -22,13 +22,11 @@
|
|||
//= require_tree ./channels
|
||||
|
||||
(function() {
|
||||
var protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||
var protocol = (window.location.protocol === "https:" ? "wss://" : "ws://");
|
||||
var host = window.GreenLight.WEBSOCKET_HOST || window.location.host + window.GreenLight.RELATIVE_ROOT;
|
||||
var path = window.GreenLight.WEBSOCKET_PATH || '/cable';
|
||||
var url = protocol + host + path;
|
||||
var url = protocol + host + '/cable';
|
||||
|
||||
this.App || (this.App = {});
|
||||
|
||||
App.cable = ActionCable.createConsumer(url);
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
(function() {
|
||||
|
||||
var sessionStatusRefresh = function(url) {
|
||||
$.get(url + "/session_status_refresh", function(html) {
|
||||
$(".center-panel-wrapper").html(html);
|
||||
displayRoomURL();
|
||||
});
|
||||
};
|
||||
|
||||
var enableMeetingUpdates = function() {
|
||||
var meetingId = ''
|
||||
if (!$(".page-wrapper.rooms").data('main-room')) {
|
||||
meetingId = $(".page-wrapper.rooms").data('id');
|
||||
}
|
||||
App.meeting_update = App.cable.subscriptions.create({
|
||||
channel: 'MeetingUpdatesChannel',
|
||||
admin_id: $(".page-wrapper.rooms").data('admin-id'),
|
||||
meeting_id: meetingId
|
||||
},
|
||||
{
|
||||
received: function(data) {
|
||||
if (data.action === 'moderator_joined') {
|
||||
if (!Meeting.getInstance().getModJoined()) {
|
||||
Meeting.getInstance().setModJoined(true);
|
||||
if (Meeting.getInstance().getWaitingForMod()) {
|
||||
loopJoin();
|
||||
} else {
|
||||
sessionStatusRefresh($('.meeting-url').val());
|
||||
showAlert(I18n.meeting_started, 4000);
|
||||
}
|
||||
}
|
||||
} else if (data.action === 'meeting_ended') {
|
||||
sessionStatusRefresh($('.meeting-url').val());
|
||||
showAlert(I18n.meeting_ended, 4000);
|
||||
} else if (data.action === 'user_waiting') {
|
||||
// show a browser notification only to the owner
|
||||
if (isRoomOwner()) {
|
||||
showNotification(I18n.user_waiting_title, {
|
||||
body: I18n.user_waiting_body.replace(/%{user}/, data.user).replace(/%{meeting}/, '"'+data.meeting_name+'"')
|
||||
});
|
||||
}
|
||||
} else if(data.action === 'unable_to_join') {
|
||||
showAlert(I18n.unable_to_join_mobile, 6000)
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var disableMeetingUpdates = function() {
|
||||
App.meeting_update.unsubscribe();
|
||||
delete App.meeting_update
|
||||
};
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
// disable meeting updates if enabled from a previous page
|
||||
if (App.meeting_update) {
|
||||
disableMeetingUpdates();
|
||||
}
|
||||
if ($("body[data-controller=landing]").get(0)) {
|
||||
if ($("body[data-action=rooms]").get(0)) {
|
||||
enableMeetingUpdates();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).call(this);
|
|
@ -1,75 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
(function() {
|
||||
|
||||
var enableRecordingUpdates = function() {
|
||||
App.recording_update = App.cable.subscriptions.create({
|
||||
channel: 'RecordingUpdatesChannel',
|
||||
admin_id: $(".page-wrapper.rooms").data('admin-id'),
|
||||
meeting_id: $(".page-wrapper.rooms").data('id')
|
||||
},
|
||||
{
|
||||
received: function(data) {
|
||||
|
||||
var recordings = Recordings.getInstance();
|
||||
var table = recordings.table.api();
|
||||
var row = table.row("#"+data.id);
|
||||
|
||||
if (data.action === 'update') {
|
||||
var rowData = row.data();
|
||||
|
||||
rowData.published = data.published;
|
||||
rowData.listed = data.listed;
|
||||
table.row("#"+data.id).data(rowData);
|
||||
recordings.draw();
|
||||
|
||||
var status = data.published ? (data.listed ? 'published' : 'unlisted') : 'unpublished';
|
||||
showAlert(I18n['recording_'+status], 4000);
|
||||
|
||||
} else if (data.action === 'delete') {
|
||||
row.remove();
|
||||
recordings.draw();
|
||||
showAlert(I18n.recording_deleted, 4000);
|
||||
|
||||
} else if (data.action === 'create') {
|
||||
if (isRoomOwner() && row.length == 0) {
|
||||
recordings.refresh();
|
||||
showAlert(I18n.recording_created, 4000);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var disableRecordingUpdates = function() {
|
||||
App.recording_update.unsubscribe();
|
||||
delete App.recording_update
|
||||
};
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
// disable recording updates if enabled from a previous page
|
||||
if (App.recording_update) {
|
||||
disableRecordingUpdates();
|
||||
}
|
||||
if ($("body[data-controller=landing]").get(0)) {
|
||||
if ($("body[data-action=rooms]").get(0)) {
|
||||
enableRecordingUpdates();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).call(this);
|
|
@ -0,0 +1,37 @@
|
|||
// 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/>.
|
||||
|
||||
$(document).on('turbolinks:load', function(){
|
||||
$("#cookies-agree-button").click(function() {
|
||||
//create a cookie that lasts 1 year
|
||||
var cookieDate = new Date();
|
||||
cookieDate.setFullYear(cookieDate.getFullYear() + 1); //1 year from now
|
||||
document.cookie = "cookie_consented=true; path=/; expires=" + cookieDate.toUTCString() + ";"
|
||||
|
||||
//hide the banner at the bottom
|
||||
$(".cookies-banner").attr("style","display:none !important")
|
||||
})
|
||||
|
||||
$("#maintenance-close").click(function(event) {
|
||||
//create a cookie that lasts 1 day
|
||||
|
||||
var cookieDate = new Date()
|
||||
cookieDate.setDate(cookieDate.getDate() + 1) //1 day from now
|
||||
console.log("maintenance_window=" + $(event.target).data("date") + "; path=/; expires=" + cookieDate.toUTCString() + ";")
|
||||
|
||||
document.cookie = "maintenance_window=" + $(event.target).data("date") + "; path=/; expires=" + cookieDate.toUTCString() + ";"
|
||||
})
|
||||
})
|
|
@ -0,0 +1,57 @@
|
|||
// 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/>.
|
||||
|
||||
$(document).on('turbolinks:load', function(){
|
||||
var controller = $("body").data('controller');
|
||||
var action = $("body").data('action');
|
||||
|
||||
// Only run on the admins page.
|
||||
if (controller == "admins" && action == "index") {
|
||||
// show the modal with the correct form action url
|
||||
$(".delete-user").click(function(){
|
||||
$("#delete-confirm").parent().attr("action", $(this).data("path"))
|
||||
|
||||
if ($(this).data("delete") == "temp-delete") {
|
||||
$("#perm-delete").hide()
|
||||
$("#delete-warning").show()
|
||||
} else {
|
||||
$("#perm-delete").show()
|
||||
$("#delete-warning").hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$(".delete-user").click(function(data){
|
||||
document.getElementById("delete-checkbox").checked = false
|
||||
$("#delete-confirm").prop("disabled", "disabled")
|
||||
|
||||
if ($(data.target).data("delete") == "temp-delete") {
|
||||
$("#perm-delete").hide()
|
||||
$("#delete-warning").show()
|
||||
} else {
|
||||
$("#perm-delete").show()
|
||||
$("#delete-warning").hide()
|
||||
}
|
||||
})
|
||||
|
||||
$("#delete-checkbox").click(function(data){
|
||||
if (document.getElementById("delete-checkbox").checked) {
|
||||
$("#delete-confirm").removeAttr("disabled")
|
||||
} else {
|
||||
$("#delete-confirm").prop("disabled", "disabled")
|
||||
}
|
||||
})
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
// Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the GNU Lesser General Public License as published by the Free Software
|
||||
|
@ -14,10 +14,23 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License along
|
||||
// with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
.login {
|
||||
.center-panel {
|
||||
.center-panel-size {
|
||||
max-width: 400px
|
||||
}
|
||||
}
|
||||
}
|
||||
$(document).on('turbolinks:load', function(){
|
||||
// Stores the current url when the user clicks the sign in button
|
||||
$(".sign-in-button").click(function(){
|
||||
var url = location.href
|
||||
// Add the slash at the end if it's missing
|
||||
url += url.endsWith("/") ? "" : "/"
|
||||
document.cookie ="return_to=" + url
|
||||
})
|
||||
|
||||
// Checks to see if the user provided an image url and displays it if they did
|
||||
$("#user-image")
|
||||
.on("load", function() {
|
||||
$("#user-image").show()
|
||||
$("#user-avatar").hide()
|
||||
})
|
||||
.on("error", function() {
|
||||
$("#user-image").hide()
|
||||
$("#user-avatar").show()
|
||||
})
|
||||
})
|
|
@ -1,297 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
/** global: Meeting */
|
||||
/** global: PreviousMeetings */
|
||||
/** global: QRCode */
|
||||
/** global: Recordings */
|
||||
/** global: Turbolinks */
|
||||
|
||||
(function() {
|
||||
|
||||
var qrcode;
|
||||
|
||||
var waitForModerator = function(url) {
|
||||
window.localStorage.setItem("waitingName", $('.meeting-user-name').val());
|
||||
$.post(url + "/wait", {name: $('.meeting-user-name').val()}, function(html) {
|
||||
$(".center-panel-wrapper").html(html);
|
||||
});
|
||||
if (!Meeting.getInstance().getWaitingForMod()) {
|
||||
Meeting.getInstance().setWaitingForMod(true);
|
||||
if (Meeting.getInstance().getModJoined()) {
|
||||
loopJoin();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var init = function() {
|
||||
Meeting.clear();
|
||||
var nameInput = $('.meeting-user-name');
|
||||
if (!nameInput.val()) {
|
||||
var lastName = window.localStorage.getItem('lastJoinedName');
|
||||
if (lastName !== 'undefined') {
|
||||
nameInput.val(lastName);
|
||||
}
|
||||
}
|
||||
|
||||
// setup event handlers
|
||||
$('.center-panel-wrapper').on ('click', '.meeting-join', function () {
|
||||
var name = $('.meeting-user-name').val();
|
||||
Meeting.getInstance().setUserName(name);
|
||||
Meeting.getInstance().setMeetingId($(".page-wrapper").data('id'));
|
||||
|
||||
// a user name is set, join the user into the session
|
||||
if (name !== undefined && name !== null) {
|
||||
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
||||
if (jqxhr) {
|
||||
jqxhr.done(function(data) {
|
||||
if (data.messageKey === 'wait_for_moderator') {
|
||||
waitForModerator(Meeting.getInstance().getURL());
|
||||
} else if (data.messageKey === 'ok') {
|
||||
$(location).attr("href", data.response.join_url);
|
||||
}
|
||||
});
|
||||
jqxhr.fail(function() {
|
||||
console.info("meeting join failed");
|
||||
});
|
||||
} else {
|
||||
$('.meeting-user-name').parent().addClass('has-error');
|
||||
}
|
||||
|
||||
// if not user name was set it means we must ask for a name
|
||||
} else {
|
||||
$(location).attr("href", Meeting.getInstance().getURL());
|
||||
}
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on ('click', '.meeting-start', function () {
|
||||
Turbolinks.visit($('.meeting-url').val());
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on ('input', '.meeting-user-name', function () {
|
||||
if ($(this).val() === '') {
|
||||
$(this).parent().addClass('has-error')
|
||||
} else {
|
||||
$(this).parent().removeClass('has-error')
|
||||
}
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on ('keypress', '.meeting-user-name', function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
$('.meeting-join').click();
|
||||
}
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on ('keypress', '.meeting-name', function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
$('.meeting-start').click();
|
||||
}
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on ('click', '.meeting-end', function () {
|
||||
var jqxhr = Meeting.getInstance().endMeeting();
|
||||
var btn = $(this);
|
||||
btn.prop("disabled", true);
|
||||
jqxhr.fail(function() {
|
||||
console.info("meeting end failed");
|
||||
});
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on ('click', '.meeting-url-copy', function () {
|
||||
var meetingURLInput = $('.meeting-url');
|
||||
|
||||
// copy URL
|
||||
meetingURLInput.select();
|
||||
try {
|
||||
var success = document.execCommand("copy");
|
||||
if (success) {
|
||||
meetingURLInput.blur();
|
||||
$(this).trigger('hint', [$(this).data('copied-hint')]);
|
||||
} else {
|
||||
$(this).trigger('hint', [$(this).data('copy-error')]);
|
||||
}
|
||||
} catch (err) {
|
||||
$(this).trigger('hint', [$(this).data('copy-error')]);
|
||||
}
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on('hint', '.meeting-url-copy', function (event, msg) {
|
||||
$(this).focus();
|
||||
$(this).attr('title', msg)
|
||||
.tooltip('fixTitle')
|
||||
.tooltip('show')
|
||||
.attr('title', $(this).data('copy-hint'))
|
||||
.tooltip('fixTitle');
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on('mouseleave', '.meeting-url-copy', function () {
|
||||
$(this).blur();
|
||||
});
|
||||
|
||||
// button used to send invitations to the meeting (i.e. "mailto:" link)
|
||||
$('.center-panel-wrapper').on('click', '.meeting-invite', function () {
|
||||
var meetingURL = Meeting.getInstance().getURL();
|
||||
var subject = $(this).data("invite-subject");
|
||||
var body = $(this).data("invite-body").replace("&&URL&&", meetingURL);
|
||||
var mailto = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body);
|
||||
window.open(mailto);
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on ('click', '.meeting-url-qrcode', function () {
|
||||
var meetingURL;
|
||||
|
||||
try {
|
||||
meetingURL = $('.meeting-url').val();
|
||||
if ($('.meeting-url-qrcode-group').is(':empty')) {
|
||||
// generate code
|
||||
qrcode = new QRCode($(".meeting-url-qrcode-group")[0], {
|
||||
text: meetingURL,
|
||||
width: 128,
|
||||
height: 128,
|
||||
colorDark : "#000000",
|
||||
colorLight : "#ffffff",
|
||||
correctLevel : QRCode.CorrectLevel.H
|
||||
});
|
||||
} else {
|
||||
// clear the code.
|
||||
qrcode.clear();
|
||||
// make another code.
|
||||
qrcode.makeCode(meetingURL);
|
||||
}
|
||||
$(this).trigger('hint', [$(this).data('qrcode-generated-hint')]);
|
||||
} catch (err) {
|
||||
$(this).trigger('hint', [$(this).data('qrcode-generate-error')]);
|
||||
}
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on('hint', '.meeting-url-qrcode', function (event, msg) {
|
||||
$(this).focus();
|
||||
$(this).attr('title', msg)
|
||||
.tooltip('fixTitle')
|
||||
.tooltip('show')
|
||||
.attr('title', $(this).data('qrcode-generate-hint'))
|
||||
.tooltip('fixTitle');
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on('mouseleave', '.meeting-url-qrcode', function () {
|
||||
$(this).blur();
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on('focus', '.meeting-url', function () {
|
||||
$(this).select();
|
||||
});
|
||||
|
||||
// only allow ctrl commands
|
||||
$('.center-panel-wrapper').on('keydown', '.meeting-url', function (event) {
|
||||
if(!event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// enable tooltips
|
||||
var options = {
|
||||
selector: '.has-tooltip',
|
||||
container: 'body'
|
||||
};
|
||||
$(document).tooltip(options);
|
||||
options = {
|
||||
selector: '.bottom-tooltip',
|
||||
container: 'body',
|
||||
placement: 'bottom'
|
||||
};
|
||||
$(document).tooltip(options);
|
||||
|
||||
// focus name input or join button
|
||||
if ($('.meeting-name').is(':visible')) {
|
||||
$('.meeting-name').focus();
|
||||
} else if ($('.meeting-user-name').is(':visible')) {
|
||||
$('.meeting-user-name').focus();
|
||||
} else {
|
||||
$('.meeting-join').focus();
|
||||
}
|
||||
};
|
||||
|
||||
var initIndex = function() {
|
||||
|
||||
$('.center-panel-wrapper').on('input', '.meeting-name', function () {
|
||||
var newId = $(this).val();
|
||||
Meeting.getInstance().setMeetingId(newId);
|
||||
$(".page-wrapper.meetings").data('id', newId);
|
||||
$('.meeting-url').val(Meeting.getInstance().getURL());
|
||||
$('.join-meeting-title').text('"'+newId+'"');
|
||||
if (newId === '') {
|
||||
$('.invite-join-wrapper').addClass('hidden');
|
||||
} else {
|
||||
$('.invite-join-wrapper').removeClass('hidden');
|
||||
}
|
||||
if (!$('.meeting-url-qrcode-group').is(':empty')) {
|
||||
$('.meeting-url-qrcode-group').empty();
|
||||
}
|
||||
});
|
||||
|
||||
PreviousMeetings.init('joinedMeetings');
|
||||
};
|
||||
|
||||
var initMeetings = function() {
|
||||
$('.meeting-url').val(Meeting.getInstance().getURL());
|
||||
};
|
||||
|
||||
var initRooms = function() {
|
||||
displayRoomURL();
|
||||
var roomAdmin = $('.page-wrapper.rooms').data('admin-id');
|
||||
|
||||
$('.center-panel-wrapper').on('input', '.meeting-name', function () {
|
||||
var newId = $(this).val();
|
||||
Meeting.getInstance().setMeetingId(newId);
|
||||
$('.meeting-url').val(Meeting.getInstance().getURL());
|
||||
$('.join-meeting-title').text('"'+newId+'"');
|
||||
if (newId === '') {
|
||||
$('.invite-join-wrapper').addClass('hidden');
|
||||
} else {
|
||||
$('.invite-join-wrapper').removeClass('hidden');
|
||||
}
|
||||
if (!$('.meeting-url-qrcode-group').is(':empty')) {
|
||||
$('.meeting-url-qrcode-group').empty();
|
||||
}
|
||||
});
|
||||
|
||||
if ($(".page-wrapper.rooms").data('main-room')) {
|
||||
PreviousMeetings.init('joinedRooms-'+roomAdmin);
|
||||
|
||||
if ($('input.meeting-name').val() !== '') {
|
||||
$('input.meeting-name').trigger('input');
|
||||
}
|
||||
}
|
||||
|
||||
Recordings.getInstance().refresh();
|
||||
Recordings.getInstance().setupActionHandlers();
|
||||
};
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
if ($("body[data-controller=landing]").get(0)) {
|
||||
init();
|
||||
if ($("body[data-action=index]").get(0)) {
|
||||
initIndex();
|
||||
} else if ($("body[data-action=meetings]").get(0)) {
|
||||
initMeetings();
|
||||
} else if ($("body[data-action=rooms]").get(0)) {
|
||||
initRooms();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).call(this);
|
|
@ -0,0 +1,51 @@
|
|||
// 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/>.
|
||||
|
||||
$(document).on('turbolinks:load', function(){
|
||||
$.rails.refreshCSRFTokens();
|
||||
})
|
||||
|
||||
document.addEventListener("turbolinks:before-cache", function() {
|
||||
$(".alert").remove()
|
||||
})
|
||||
|
||||
// Gets the localized string
|
||||
function getLocalizedString(key) {
|
||||
var keyArr = key.split(".")
|
||||
var translated = I18n
|
||||
|
||||
// Search current language for the key
|
||||
try {
|
||||
keyArr.forEach(function(k) {
|
||||
translated = translated[k]
|
||||
})
|
||||
} catch (e) {
|
||||
// Key is missing in selected language so default to english
|
||||
translated = undefined;
|
||||
}
|
||||
|
||||
|
||||
// If key is not found, search the fallback language for the key
|
||||
if (translated === null || translated === undefined) {
|
||||
translated = I18nFallback
|
||||
|
||||
keyArr.forEach(function(k) {
|
||||
translated = translated[k]
|
||||
})
|
||||
}
|
||||
|
||||
return translated
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 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/>.
|
||||
|
||||
# Meeting class
|
||||
|
||||
_meetingInstance = null
|
||||
|
||||
class @Meeting
|
||||
constructor: (@meetingId, @type, @userName, @adminId) ->
|
||||
|
||||
# Gets the current instance or creates a new one
|
||||
@getInstance: ->
|
||||
if _meetingInstance
|
||||
return _meetingInstance
|
||||
meetingId = $(".page-wrapper").data('id')
|
||||
type = $("body").data('resource')
|
||||
name = $('.meeting-user-name').val()
|
||||
adminId = $(".page-wrapper").data('admin-id')
|
||||
_meetingInstance = new Meeting(meetingId, type, name, adminId)
|
||||
return _meetingInstance
|
||||
|
||||
@clear: ->
|
||||
_meetingInstance = null
|
||||
|
||||
@buildMeetingURL: (meetingId, type, adminId) ->
|
||||
fullId = ''
|
||||
if adminId
|
||||
fullId = encodeURIComponent(adminId) + '/' + encodeURIComponent(meetingId)
|
||||
else
|
||||
fullId = encodeURIComponent(meetingId)
|
||||
return @buildRootURL() + '/' + type + '/' + fullId
|
||||
|
||||
@buildRootURL: ->
|
||||
url = location.protocol + '//' + location.hostname
|
||||
if location.port
|
||||
url += ':' + location.port
|
||||
if GreenLight.RELATIVE_ROOT
|
||||
url += GreenLight.RELATIVE_ROOT
|
||||
return url
|
||||
|
||||
# Sends the end meeting request
|
||||
# Returns a response object
|
||||
endMeeting: ->
|
||||
return $.ajax({
|
||||
url: @getURL() + "/end",
|
||||
type: 'DELETE'
|
||||
})
|
||||
|
||||
# Makes a call to get the join meeting url
|
||||
# Returns a response object
|
||||
# The response object contains the URL to join the meeting
|
||||
getJoinMeetingResponse: ->
|
||||
if !@userName
|
||||
showAlert(I18n.enter_name, 4000, 'danger')
|
||||
return false
|
||||
return $.get @getURL() + "/join?name=" + @userName, (data) =>
|
||||
# update name used to join meeting
|
||||
localStorage.setItem('lastJoinedName', @getUserName())
|
||||
|
||||
if data.messageKey == 'ok'
|
||||
key = ''
|
||||
if @type == 'meetings'
|
||||
key = 'joinedMeetings'
|
||||
else if @type == 'rooms'
|
||||
key = 'joinedRooms-'+@adminId
|
||||
|
||||
# update previously joined meetings/rooms on client
|
||||
try
|
||||
joinedMeetings = localStorage.getItem(key) || ''
|
||||
joinedMeetings = joinedMeetings.split(',')
|
||||
joinedMeetings = joinedMeetings.filter (item) => item != @meetingId.toString()
|
||||
if joinedMeetings.length >= 5
|
||||
joinedMeetings.splice(0, 1)
|
||||
joinedMeetings.push(@meetingId)
|
||||
localStorage.setItem(key, joinedMeetings.join(','))
|
||||
catch err
|
||||
localStorage.setItem(key, @meetingId)
|
||||
|
||||
getMeetingId: ->
|
||||
return @meetingId
|
||||
|
||||
setMeetingId: (id) ->
|
||||
@meetingId = id
|
||||
return this
|
||||
|
||||
getAdminId: ->
|
||||
return @adminId
|
||||
|
||||
setAdminId: (id) ->
|
||||
@adminId = id
|
||||
return this
|
||||
|
||||
getType: ->
|
||||
return @type
|
||||
|
||||
setType: (type) ->
|
||||
@type = type
|
||||
return this
|
||||
|
||||
getURL: ->
|
||||
return Meeting.buildMeetingURL(@meetingId, @type, @adminId)
|
||||
|
||||
getUserName: ->
|
||||
return @userName
|
||||
|
||||
setUserName: (name) ->
|
||||
@userName = name
|
||||
return this
|
||||
|
||||
getModJoined: ->
|
||||
return @modJoined
|
||||
|
||||
setModJoined: (modJoined) ->
|
||||
@modJoined = modJoined
|
||||
return this
|
||||
|
||||
getWaitingForMod: ->
|
||||
return @waitingForMod
|
||||
|
||||
setWaitingForMod: (wMod) ->
|
||||
@waitingForMod = wMod
|
||||
return this
|
|
@ -1,55 +0,0 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 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/>.
|
||||
|
||||
# Previous Meetings class
|
||||
|
||||
class @PreviousMeetings
|
||||
MAX_MEETINGS = 5
|
||||
|
||||
# initializes and populates previous meetings list with entries from localStorage
|
||||
@init: (type) ->
|
||||
$('.center-panel-wrapper').off 'click', '.fill-meeting-name'
|
||||
$('.center-panel-wrapper').on 'click', '.fill-meeting-name', (event, msg) ->
|
||||
name = $(this).text()
|
||||
$('input.meeting-name').val(name).trigger('input')
|
||||
|
||||
$('ul.previously-joined').empty()
|
||||
joinedMeetings = localStorage.getItem(type)
|
||||
if joinedMeetings && joinedMeetings.length > 0
|
||||
joinedMeetings = joinedMeetings.split(',')
|
||||
PreviousMeetings.append(joinedMeetings.reverse())
|
||||
|
||||
# adds to previous meetings list if its unique
|
||||
@uniqueAdd: (names) ->
|
||||
meetings = $('ul.previously-joined > li').toArray().map( (li) ->
|
||||
return li.innerText
|
||||
)
|
||||
index = meetings.indexOf('')
|
||||
if index > 1
|
||||
meetings.splice(index, 1)
|
||||
if Array.isArray(names)
|
||||
names = names.filter( (value) ->
|
||||
return $.inArray(value, meetings) == -1
|
||||
)
|
||||
PreviousMeetings.append(names)
|
||||
|
||||
@append: (meeting_names) ->
|
||||
for m in meeting_names
|
||||
if $('ul.previously-joined > li').length > MAX_MEETINGS
|
||||
break
|
||||
$('ul.previously-joined').append('<li><a class="fill-meeting-name">'+m+'</a></li>')
|
||||
|
||||
$('.center-panel-wrapper .previously-joined-wrapper').removeClass('hidden')
|
|
@ -0,0 +1,48 @@
|
|||
// 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() + " " + getLocalizedString('javascript.room.mailer.subject');
|
||||
var body = getLocalizedString('javascript.room.mailer.body') + "\n\n" + $(this).attr("data-pres-link");
|
||||
var autogenerated = getLocalizedString('javascript.room.mailer.autogenerated') + "\n";
|
||||
var footer = getLocalizedString('javascript.room.mailer.footer');
|
||||
|
||||
var url = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body) + encodeURIComponent(autogenerated) + encodeURIComponent(footer);
|
||||
var win = window.open(url, '_blank');
|
||||
|
||||
win.focus();
|
||||
});
|
||||
});
|
||||
|
||||
// Handle recording delete modal
|
||||
$(".delete-rec").click(function() {
|
||||
$("#delete-rec-confirm").parent().attr("action", $(this).data("path"))
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
});
|
|
@ -1,430 +0,0 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 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/>.
|
||||
|
||||
# Recordings class
|
||||
|
||||
_recordingsInstance = null
|
||||
|
||||
class @Recordings
|
||||
# adding or removing a column will require updates to all subsequent column positions
|
||||
COLUMNS = [
|
||||
'DATE',
|
||||
'NAME',
|
||||
'PREVIEW',
|
||||
'DURATION',
|
||||
'PARTICIPANTS',
|
||||
'PLAYBACK',
|
||||
'VISIBILITY',
|
||||
'LISTED',
|
||||
'ACTION'
|
||||
]
|
||||
COLUMN = {}
|
||||
i = 0
|
||||
for c in COLUMNS
|
||||
COLUMN[c] = i++
|
||||
|
||||
constructor: ->
|
||||
recordingsObject = this
|
||||
|
||||
# configure the datatable for recordings
|
||||
this.table = $('#recordings').dataTable({
|
||||
data: [],
|
||||
rowId: 'id',
|
||||
paging: false,
|
||||
dom: 't',
|
||||
info: false,
|
||||
order: [[ COLUMN.DATE, "desc" ]],
|
||||
language: {
|
||||
emptyTable: '<h3>'+I18n.no_recordings_yet+'</h3>',
|
||||
zeroRecords: '<h3>'+I18n.no_recordings+'</h3>'
|
||||
},
|
||||
columns: [
|
||||
{ data: "start_time" },
|
||||
{ data: "name", visible: $(".page-wrapper.rooms").data('main-room') },
|
||||
{ data: "previews", orderable: false },
|
||||
{ data: "duration" },
|
||||
{ data: "participants" },
|
||||
{ data: "playbacks", orderable: false },
|
||||
{ data: "published" },
|
||||
{ data: "listed", visible: false },
|
||||
{ data: "id", orderable: false }
|
||||
],
|
||||
columnDefs: [
|
||||
{
|
||||
targets: COLUMN.DATE,
|
||||
render: (data, type, row) ->
|
||||
if type == 'display'
|
||||
date = new Date(data)
|
||||
title = date
|
||||
.toLocaleString($('html').attr('lang'),
|
||||
{month: 'long', day: 'numeric', year: 'numeric',
|
||||
hour12: 'true', hour: '2-digit', minute: '2-digit'})
|
||||
timeago = '<time datetime="'+date.toISOString()+'" data-time-ago="'+date.toISOString()+'">'+date.toISOString()+'</time>'
|
||||
return title+'<span class="timeago">('+timeago+')</span>'
|
||||
return data
|
||||
},
|
||||
{
|
||||
targets: COLUMN.PREVIEW,
|
||||
render: (data, type, row) ->
|
||||
if type == 'display'
|
||||
str = ''
|
||||
if row.published
|
||||
for d in data
|
||||
str += '<img height="50" width="50" class="img-thumbnail" src="'+d.url+'" alt="'+d.alt+'"></img><img class="img-thumbnail large" src="'+d.url+'"></img>'
|
||||
return str
|
||||
return data
|
||||
},
|
||||
{
|
||||
targets: COLUMN.PLAYBACK,
|
||||
render: (data, type, row) ->
|
||||
if type == 'display'
|
||||
str = ''
|
||||
if row.published
|
||||
if data.length == 1
|
||||
str = '<a class="btn btn-default play-tooltip" href="'+data[0].url+'" target="_blank"><i class="fa fa-play-circle"></i></a>'
|
||||
else
|
||||
for d in data
|
||||
str += '<a href="'+d.url+'" target="_blank">'+d.type_i18n+'</a> '
|
||||
return str
|
||||
return data
|
||||
},
|
||||
{
|
||||
targets: COLUMN.VISIBILITY,
|
||||
render: (data, type, row) ->
|
||||
visibility = ['unpublished', 'unlisted', 'published']
|
||||
if row.published
|
||||
if row.listed
|
||||
state = visibility[2]
|
||||
else
|
||||
state = visibility[1]
|
||||
else
|
||||
state = visibility[0]
|
||||
if type == 'display'
|
||||
return I18n[state]
|
||||
return state
|
||||
},
|
||||
{
|
||||
targets: COLUMN.ACTION,
|
||||
render: (data, type, row) ->
|
||||
if type == 'display'
|
||||
roomName = Meeting.getInstance().getMeetingId()
|
||||
recordingActions = $('.hidden-elements').find('.recording-actions')
|
||||
classes = ['recording-unpublished', 'recording-unlisted', 'recording-published']
|
||||
if row.published
|
||||
if row.listed
|
||||
cls = classes[2]
|
||||
else
|
||||
cls = classes[1]
|
||||
else
|
||||
cls = classes[0]
|
||||
trigger = recordingActions.find('.recording-update-trigger')
|
||||
trigger.removeClass(classes.join(' '))
|
||||
trigger.addClass(cls)
|
||||
|
||||
return recordingActions.html()
|
||||
return data
|
||||
}
|
||||
]
|
||||
})
|
||||
options = {
|
||||
selector: '.delete-tooltip',
|
||||
placement: 'bottom',
|
||||
title: I18n.delete_recording
|
||||
};
|
||||
$('#recordings').tooltip(options)
|
||||
|
||||
options.selector = '.visibility-tooltip'
|
||||
options.title = I18n.change_visibility
|
||||
$('#recordings').tooltip(options)
|
||||
|
||||
options.selector = '.play-tooltip'
|
||||
options.title = I18n.play_recording
|
||||
$('#recordings').tooltip(options)
|
||||
|
||||
options.selector = '.youtube-tooltip'
|
||||
options.title = I18n.upload_youtube
|
||||
$('#recordings').tooltip(options)
|
||||
|
||||
options.selector = '.upload-tooltip'
|
||||
options.title = I18n.share
|
||||
$('#recordings').tooltip(options)
|
||||
|
||||
options.selector = '.mail-tooltip'
|
||||
options.title = I18n.mail_recording
|
||||
$('#recordings').tooltip(options)
|
||||
|
||||
$(document).one "turbolinks:before-cache", =>
|
||||
@getTable().api().clear().draw().destroy()
|
||||
|
||||
# enable popovers
|
||||
# can't use trigger:'focus' because it doesn't work will with buttons inside
|
||||
# the popover
|
||||
options = {
|
||||
selector: '.has-popover',
|
||||
html: true,
|
||||
trigger: 'click',
|
||||
title: ->
|
||||
return $(this).data("popover-title");
|
||||
content: ->
|
||||
bodySelector = $(this).data("popover-body")
|
||||
return $(bodySelector).html()
|
||||
}
|
||||
$('#recordings').popover(options)
|
||||
|
||||
# close popovers manually when clicking outside of them or in buttons
|
||||
# with [data-dismiss="popover"]
|
||||
# careful to hide only the open popover and not all of them, otherwise they won't reopen
|
||||
$('body').on 'click', (e) ->
|
||||
$('.has-popover').each ->
|
||||
if !$(this).is(e.target) and $(this).has(e.target).length == 0 and $('.popover.in').has(e.target).length == 0
|
||||
if $(this).next(".popover.in").length > 0
|
||||
$(this).popover('hide')
|
||||
$(document).on 'click', '[data-dismiss="popover"]', (e) ->
|
||||
$('.has-popover').each ->
|
||||
if $(this).next(".popover.in").length > 0
|
||||
$(this).popover('hide')
|
||||
|
||||
# Gets the current instance or creates a new one
|
||||
@getInstance: ->
|
||||
if _recordingsInstance && Recordings.initialized()
|
||||
return _recordingsInstance
|
||||
_recordingsInstance = new Recordings()
|
||||
return _recordingsInstance
|
||||
|
||||
@initialize: ->
|
||||
Recordings.getInstance()
|
||||
|
||||
@initialized: ->
|
||||
return $.fn.DataTable.isDataTable('#recordings') && _recordingsInstance
|
||||
|
||||
draw: ->
|
||||
if !@isOwner()
|
||||
@table.api().columns(COLUMN.LISTED).search('true')
|
||||
@table.api().columns.adjust().draw()
|
||||
|
||||
# refresh the recordings from the server
|
||||
refresh: ->
|
||||
table_api = this.table.api()
|
||||
$.get @getRecordingsURL(), (data) =>
|
||||
@setOwner(data.is_owner)
|
||||
if !@owner
|
||||
table_api.column(COLUMN.ACTION).visible(false)
|
||||
table_api.column(COLUMN.VISIBILITY).visible(false)
|
||||
for recording in data.recordings
|
||||
# NOTE: Temporary fix for the minutes to milliseconds bug some recordings may have
|
||||
# experienced when transitioning from BigBlueButton 1.1 to BigBlueButton 2.0.
|
||||
recording.duration = if recording.duration < 1000 then recording.duration else parseInt(recording.length / 60000)
|
||||
data.recordings.sort (a,b) ->
|
||||
return new Date(b.start_time) - new Date(a.start_time)
|
||||
table_api.clear()
|
||||
table_api.rows.add(data.recordings)
|
||||
@draw()
|
||||
|
||||
if $(".page-wrapper.rooms").data('main-room')
|
||||
recording_names = data.recordings.map (r) ->
|
||||
return r.name
|
||||
output = {}
|
||||
for key in [0...recording_names.length]
|
||||
output[recording_names[key]] = recording_names[key]
|
||||
PreviousMeetings.uniqueAdd(value for key, value of output)
|
||||
|
||||
|
||||
# setup click handlers for the action buttons
|
||||
setupActionHandlers: ->
|
||||
table_api = this.table.api()
|
||||
recordingsObject = this
|
||||
selectedUpload = null
|
||||
canUpload = false
|
||||
|
||||
@getTable().on 'click', '.recording-update', (event) ->
|
||||
btn = $(this)
|
||||
row = table_api.row($(this).closest('tr')).data()
|
||||
url = recordingsObject.getRecordingsURL()
|
||||
id = row.id
|
||||
|
||||
published = btn.data('visibility') == "unlisted" ||
|
||||
btn.data('visibility') == "published"
|
||||
listed = btn.data('visibility') == "published"
|
||||
|
||||
btn.prop('disabled', true)
|
||||
|
||||
data = { published: published.toString() }
|
||||
data["meta_" + GreenLight.META_LISTED] = listed.toString();
|
||||
$.ajax({
|
||||
method: 'PATCH',
|
||||
url: url+'/'+id,
|
||||
data: data
|
||||
}).done((data) ->
|
||||
btn.prop('disabled', false)
|
||||
).fail((xhr, text) ->
|
||||
btn.prop('disabled', false)
|
||||
)
|
||||
|
||||
@getTable().on 'click', '.recording-delete', (event) ->
|
||||
btn = $(this)
|
||||
row = table_api.row($(this).closest('tr'))
|
||||
url = recordingsObject.getRecordingsURL()
|
||||
id = row.data().id
|
||||
btn.prop('disabled', true)
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: url+'/'+id
|
||||
}).done((data) ->
|
||||
btn.prop('disabled', false)
|
||||
).fail((xhr, text) ->
|
||||
btn.prop('disabled', false)
|
||||
if xhr.status == 404
|
||||
row.remove();
|
||||
recordingsObject.draw()
|
||||
showAlert(I18n.recording_deleted, 4000);
|
||||
)
|
||||
|
||||
@getTable().on 'click', '.upload-button', (event) ->
|
||||
btn = $(this)
|
||||
row = table_api.row($(this).closest('tr')).data()
|
||||
url = recordingsObject.getRecordingsURL()
|
||||
id = row.id
|
||||
|
||||
title = $('#video-title').val()
|
||||
privacy_status = $('input[name=privacy_status]:checked').val()
|
||||
|
||||
if title == ''
|
||||
title = row.name
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: url+'/'+id
|
||||
data: {video_title: title, privacy_status: privacy_status}
|
||||
success: (data) ->
|
||||
|
||||
if data['url'] != null
|
||||
window.location.href = data['url']
|
||||
else
|
||||
cloud = selectedUpload.find('.cloud-blue')
|
||||
check = selectedUpload.find('.green-check')
|
||||
spinner = selectedUpload.find('.load-spinner')
|
||||
|
||||
showAlert(I18n.successful_upload, 4000);
|
||||
|
||||
spinner.hide()
|
||||
check.show()
|
||||
|
||||
setTimeout ( ->
|
||||
cloud.show()
|
||||
check.hide()
|
||||
), 2500
|
||||
})
|
||||
|
||||
selectedUpload.find('.cloud-blue').hide()
|
||||
selectedUpload.find('.load-spinner').show()
|
||||
|
||||
@getTable().on 'click', '.mail-recording', (event) ->
|
||||
btn = $(this)
|
||||
row = table_api.row($(this).closest('tr')).data()
|
||||
url = recordingsObject.getRecordingsURL()
|
||||
id = row.id
|
||||
|
||||
# Take the username from the header.
|
||||
username = $('#title-header').text().replace('Welcome ', '').trim()
|
||||
|
||||
recording_url = row.playbacks[0].url
|
||||
webcams_url = getHostName(recording_url) + '/presentation/' + id + '/video/webcams.webm'
|
||||
subject = username + I18n.recording_mail_subject
|
||||
body = I18n.recording_mail_body + "\n\n" + recording_url + "\n\n" + I18n.email_footer_1 + "\n" + I18n.email_footer_2
|
||||
|
||||
mailto = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body);
|
||||
window.open(mailto);
|
||||
|
||||
@getTable().on 'click', '.youtube-upload', (event) ->
|
||||
row = table_api.row($(this).closest('tr')).data()
|
||||
$('#video-title').attr('value', row.name)
|
||||
|
||||
@getTable().on 'click', '.cloud-upload', (event) ->
|
||||
btn = $(this)
|
||||
row = table_api.row($(this).closest('tr')).data()
|
||||
id = row.id
|
||||
|
||||
selectedUpload = $(this)
|
||||
|
||||
# Determine if the recording can be uploaded to Youtube.
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
data: {'rec_id': id},
|
||||
async: false,
|
||||
url: recordingsObject.getRecordingsURL() + '/can_upload'
|
||||
}).success((res_data) ->
|
||||
canUpload = res_data['uploadable']
|
||||
)
|
||||
|
||||
youtube_button = $('.share-popover').find('.youtube-upload')
|
||||
|
||||
attr = $(this).attr('data-popover-body');
|
||||
|
||||
# Check if the cloud button has a popover body.
|
||||
if (typeof attr == typeof undefined || attr == false)
|
||||
switch canUpload
|
||||
# We can upload the recording.
|
||||
when 'true'
|
||||
youtube_button.attr('title', I18n.upload_youtube)
|
||||
youtube_button.removeClass('disabled-button')
|
||||
youtube_button.addClass('has-popover')
|
||||
youtube_button.show()
|
||||
# Can't upload because uploading is disabled.
|
||||
when 'uploading_disabled'
|
||||
youtube_button.hide()
|
||||
# Can't upload because account is not authenticated with Google.
|
||||
when 'invalid_provider'
|
||||
youtube_button.attr('title', I18n.invalid_provider)
|
||||
youtube_button.addClass('disabled-button')
|
||||
youtube_button.removeClass('has-popover')
|
||||
youtube_button.show()
|
||||
# Can't upload because recording does not contain video.
|
||||
else
|
||||
youtube_button.attr('title', I18n.no_video)
|
||||
youtube_button.addClass('disabled-button')
|
||||
youtube_button.removeClass('has-popover')
|
||||
youtube_button.show()
|
||||
|
||||
$(this).attr('data-popover-body', '.share-popover')
|
||||
$(this).popover('show')
|
||||
else
|
||||
$(this).popover('hide')
|
||||
$(this).removeAttr('data-popover-body')
|
||||
|
||||
|
||||
@getTable().on 'draw.dt', (event) ->
|
||||
$('time[data-time-ago]').timeago();
|
||||
|
||||
getTable: ->
|
||||
@table
|
||||
|
||||
getHostName = (url) ->
|
||||
parser = document.createElement('a');
|
||||
parser.href = url;
|
||||
parser.origin;
|
||||
|
||||
getRecordingsURL: ->
|
||||
if $(".page-wrapper.rooms").data('main-room')
|
||||
base_url = Meeting.buildRootURL()+'/'+$('body').data('resource')+'/'+Meeting.getInstance().getAdminId()
|
||||
else
|
||||
base_url = $('.meeting-url').val()
|
||||
base_url+'/recordings'
|
||||
|
||||
isOwner: ->
|
||||
@owner
|
||||
|
||||
setOwner: (owner) ->
|
||||
@owner = owner
|
|
@ -0,0 +1,162 @@
|
|||
// 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/>.
|
||||
|
||||
$(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"){
|
||||
|
||||
// Set a room header rename event
|
||||
var configure_room_header = function(room_title){
|
||||
|
||||
function register_room_title_event(e){
|
||||
// Remove current window events
|
||||
$(window).off('mousedown keydown');
|
||||
|
||||
if(e.type == 'focusout'){
|
||||
submit_rename_request(room_title);
|
||||
return;
|
||||
}
|
||||
|
||||
room_title.addClass("dotted_underline");
|
||||
room_title.find('#user-text').fadeTo('medium', 0.7);
|
||||
room_title.find('#user-text').attr("contenteditable", true);
|
||||
room_title.find('#user-text').focus();
|
||||
|
||||
// Stop automatic refresh
|
||||
e.preventDefault();
|
||||
|
||||
register_window_event(room_title, 'user-text', '#edit-room', 'edit-room');
|
||||
}
|
||||
|
||||
room_title.find('#user-text').on('dblclick focusout', function(e){
|
||||
if(room_title.find('#edit-room').length){
|
||||
register_room_title_event(e);
|
||||
}
|
||||
});
|
||||
|
||||
room_title.find('.fa-edit').on('click', function(e){
|
||||
register_room_title_event(e);
|
||||
});
|
||||
}
|
||||
|
||||
// Set a recording row rename event
|
||||
var configure_recording_row = function(recording_title){
|
||||
|
||||
function register_recording_title_event(e){
|
||||
// Remove current window events
|
||||
$(window).off('mousedown keydown');
|
||||
|
||||
if(e.type == 'focusout'){
|
||||
submit_rename_request(recording_title);
|
||||
return;
|
||||
}
|
||||
|
||||
recording_title.addClass("dotted_underline");
|
||||
recording_title.fadeTo('medium', 0.7);
|
||||
recording_title.find('span').attr("contenteditable", true);
|
||||
recording_title.find('span').focus();
|
||||
|
||||
// Stop automatic refresh
|
||||
e.preventDefault();
|
||||
|
||||
register_window_event(recording_title, 'recording-text', '#edit-record', 'edit-recordid');
|
||||
}
|
||||
|
||||
recording_title.find('a').on('click focusout', function(e){
|
||||
register_recording_title_event(e);
|
||||
});
|
||||
|
||||
recording_title.find('#recording-text').on('dblclick focusout', function(e){
|
||||
register_recording_title_event(e);
|
||||
});
|
||||
}
|
||||
|
||||
// Register window event to submit new name
|
||||
// upon click or upon pressing the enter key
|
||||
var register_window_event = function(element, textfield_id, edit_button_id, edit_button_data){
|
||||
$(window).on('mousedown keydown', function(clickEvent){
|
||||
|
||||
// Return if the text is clicked
|
||||
if(clickEvent.type == "mousedown" && clickEvent.target.id == textfield_id){
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if the edit icon is clicked
|
||||
if(clickEvent.type == "mousedown" && $(clickEvent.target).is(edit_button_id) &&
|
||||
$(clickEvent.target).data(edit_button_data) === element.find(edit_button_id).data(edit_button_data)){
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if event is keydown and enter key is not pressed
|
||||
if(clickEvent.type == "keydown" && clickEvent.which !== 13){
|
||||
return;
|
||||
}
|
||||
|
||||
clickEvent.preventDefault();
|
||||
submit_rename_request(element);
|
||||
|
||||
// Remove window event when ajax call to update name is submitted
|
||||
$(window).off('mousedown keydown');
|
||||
});
|
||||
}
|
||||
|
||||
// Apply ajax request depending on the element that triggered the event
|
||||
var submit_rename_request = function(element){
|
||||
if(element.is('#room-title')){
|
||||
submit_update_request({
|
||||
setting: "rename_header",
|
||||
name: element.find('#user-text').text(),
|
||||
}, element.data('path'), "POST");
|
||||
}
|
||||
else if(element.is('#recording-title')){
|
||||
submit_update_request({
|
||||
setting: "rename_recording",
|
||||
record_id: element.data('recordid'),
|
||||
record_name: element.find('span').text(),
|
||||
room_uid: element.data('room-uid'),
|
||||
}, element.data('path'), "PATCH");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for submitting ajax requests
|
||||
var submit_update_request = function(data, path, action){
|
||||
// Send ajax request for update
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: action,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// Elements that can be renamed
|
||||
var room_title = $('#room-title');
|
||||
var recording_rows = $('#recording-table').find('tr');
|
||||
|
||||
// Configure renaming for room header
|
||||
configure_room_header(room_title);
|
||||
|
||||
// Configure renaming for recording rows
|
||||
recording_rows.each(function(){
|
||||
var recording_title = $(this).find('#recording-title');
|
||||
configure_recording_row(recording_title);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,502 @@
|
|||
// 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/>.
|
||||
|
||||
// Room specific js for copy button and email link.
|
||||
$(document).on('turbolinks:load', function(){
|
||||
var controller = $("body").data('controller');
|
||||
var action = $("body").data('action');
|
||||
|
||||
// highlight current room
|
||||
$('.room-block').removeClass('current');
|
||||
$('a[href="' + window.location.pathname + '"] .room-block').addClass('current');
|
||||
|
||||
// Only run on room pages.
|
||||
if (controller == "rooms" && action == "show"){
|
||||
// Display and update all fields related to creating a room in the createRoomModal
|
||||
$("#create-room-block").click(function(){
|
||||
showCreateRoom(this)
|
||||
})
|
||||
|
||||
checkIfAutoJoin()
|
||||
}
|
||||
|
||||
// Autofocus on the Room Name label when creating a room only
|
||||
$('#createRoomModal').on('shown.bs.modal', function (){
|
||||
if ($(".create-only").css("display") == "block"){
|
||||
$('#create-room-name').focus()
|
||||
}
|
||||
})
|
||||
|
||||
if (controller == "rooms" && action == "show" || controller == "admins" && action == "server_rooms"){
|
||||
// Display and update all fields related to creating a room in the createRoomModal
|
||||
$(".update-room").click(function(){
|
||||
showUpdateRoom(this)
|
||||
})
|
||||
|
||||
// share room pop up accessibility
|
||||
manageAccessAccessibility();
|
||||
|
||||
$(".delete-room").click(function() {
|
||||
showDeleteRoom(this)
|
||||
})
|
||||
|
||||
// For keyboard users to be able to generate access code
|
||||
generateAccessCodeAccessibility()
|
||||
|
||||
$('.selectpicker').selectpicker({
|
||||
liveSearchPlaceholder: getLocalizedString('javascript.search.start')
|
||||
});
|
||||
// Fixes turbolinks issue with bootstrap select
|
||||
$(window).trigger('load.bs.select.data-api');
|
||||
|
||||
$(".share-room").click(function() {
|
||||
// Update the path of save button
|
||||
$("#save-access").attr("data-path", $(this).data("path"))
|
||||
$("#room-owner-uid").val($(this).data("owner"))
|
||||
|
||||
// Get list of users shared with and display them
|
||||
displaySharedUsers($(this).data("users-path"))
|
||||
})
|
||||
|
||||
$("#shareRoomModal").on("show.bs.modal", function() {
|
||||
$(".selectpicker").selectpicker('val','')
|
||||
})
|
||||
|
||||
$(".bootstrap-select").on("click", function() {
|
||||
$(".bs-searchbox").siblings().hide()
|
||||
})
|
||||
|
||||
$("#share-room-select ~ button").on("click", function() {
|
||||
$(".bs-searchbox").siblings().hide()
|
||||
})
|
||||
|
||||
$(".bs-searchbox input").on("input", function() {
|
||||
if ($(".bs-searchbox input").val() == '' || $(".bs-searchbox input").val().length < 3) {
|
||||
$(".select-options").remove()
|
||||
$(".bs-searchbox").siblings().hide()
|
||||
} else {
|
||||
// Manually populate the dropdown
|
||||
$.get($("#share-room-select").data("path"), { search: $(".bs-searchbox input").val(), owner_uid: $("#room-owner-uid").val() }, function(users) {
|
||||
$(".select-options").remove()
|
||||
if (users.length > 0) {
|
||||
users.forEach(function(user) {
|
||||
let opt = document.createElement("option")
|
||||
$(opt).val(user.uid)
|
||||
$(opt).text(user.name)
|
||||
$(opt).addClass("select-options")
|
||||
$(opt).attr("data-subtext", user.uid)
|
||||
$("#share-room-select").append(opt)
|
||||
})
|
||||
// Only refresh the select dropdown if there are results to show
|
||||
$('#share-room-select').selectpicker('refresh');
|
||||
}
|
||||
$(".bs-searchbox").siblings().show()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
$(".remove-share-room").click(function() {
|
||||
$("#remove-shared-confirm").parent().attr("action", $(this).data("path"))
|
||||
})
|
||||
|
||||
// User selects an option from the Room Access dropdown
|
||||
$(".bootstrap-select").on("changed.bs.select", function(){
|
||||
// Get the uid of the selected user
|
||||
let uid = $(".selectpicker").selectpicker('val')
|
||||
|
||||
// If the value was changed to blank, ignore it
|
||||
if (uid == "") return
|
||||
|
||||
let currentListItems = $("#user-list li").toArray().map(user => $(user).data("uid"))
|
||||
|
||||
// Check to make sure that the user is not already there
|
||||
if (!currentListItems.includes(uid)) {
|
||||
// Create the faded list item and display it
|
||||
let option = $("option[value='" + uid + "']")
|
||||
|
||||
let listItem = document.createElement("li")
|
||||
listItem.setAttribute('class', 'list-group-item text-left not-saved add-access');
|
||||
listItem.setAttribute("data-uid", uid)
|
||||
|
||||
let spanItemAvatar = document.createElement("span"),
|
||||
spanItemName = document.createElement("span"),
|
||||
spanItemUser = document.createElement("span");
|
||||
spanItemAvatar.setAttribute('class', 'avatar float-left mr-2');
|
||||
spanItemAvatar.innerText = option.text().charAt(0);
|
||||
spanItemName.setAttribute('class', 'shared-user');
|
||||
spanItemName.innerText = option.text();
|
||||
spanItemUser.setAttribute('class', 'text-muted');
|
||||
spanItemUser.innerText = option.data('subtext');
|
||||
spanItemName.append(spanItemUser);
|
||||
|
||||
listItem.innerHTML = "<span class='text-primary float-right shared-user cursor-pointer' onclick='removeSharedUser(this)'><i class='fas fa-times'></i></span>"
|
||||
listItem.prepend(spanItemName);
|
||||
listItem.prepend(spanItemAvatar);
|
||||
|
||||
$("#user-list").append(listItem)
|
||||
}
|
||||
})
|
||||
|
||||
$("#presentation-upload").change(function(data) {
|
||||
var file = data.target.files[0]
|
||||
|
||||
// Check file type and size to make sure they aren't over the limit
|
||||
if (validFileUpload(file)) {
|
||||
$("#presentation-upload-label").text(file.name)
|
||||
} else {
|
||||
$("#invalid-file-type").show()
|
||||
$("#presentation-upload").val("")
|
||||
$("#presentation-upload-label").text($("#presentation-upload-label").data("placeholder"))
|
||||
}
|
||||
})
|
||||
|
||||
$(".preupload-room").click(function() {
|
||||
updatePreuploadPresentationModal(this)
|
||||
})
|
||||
|
||||
$("#remove-presentation").click(function(data) {
|
||||
removePreuploadPresentation($(this).data("remove"))
|
||||
})
|
||||
|
||||
// trigger initial room filter
|
||||
filterRooms();
|
||||
}
|
||||
});
|
||||
|
||||
function copyInvite() {
|
||||
$('#invite-url').select()
|
||||
if (document.execCommand("copy")) {
|
||||
$('#invite-url').blur();
|
||||
copy = $("#copy-invite")
|
||||
copy.addClass('btn-success');
|
||||
copy.html("<i class='fas fa-check mr-1'></i>" + getLocalizedString("copied"))
|
||||
setTimeout(function(){
|
||||
copy.removeClass('btn-success');
|
||||
copy.html("<i class='fas fa-copy mr-1'></i>" + getLocalizedString("copy"))
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
function copyAccess(target) {
|
||||
input = target ? $("#copy-" + target + "-code") : $("#copy-code")
|
||||
input.attr("type", "text")
|
||||
input.select()
|
||||
if (document.execCommand("copy")) {
|
||||
input.attr("type", "hidden")
|
||||
copy = target ? $("#copy-" + target + "-access") : $("#copy-access")
|
||||
copy.addClass('btn-success');
|
||||
copy.html("<i class='fas fa-check mr-1'></i>" + getLocalizedString("copied"))
|
||||
setTimeout(function(){
|
||||
copy.removeClass('btn-success');
|
||||
originalString = target ? getLocalizedString("room.copy_" + target + "_access") : getLocalizedString("room.copy_access")
|
||||
copy.html("<i class='fas fa-copy mr-1'></i>" + originalString)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
function showCreateRoom(target) {
|
||||
$("#create-room-name").val("")
|
||||
$("#create-room-access-code").text(getLocalizedString("modal.create_room.access_code_placeholder"))
|
||||
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code_placeholder"))
|
||||
$("#room_access_code").val(null)
|
||||
$("#room_moderator_access_code").val(null)
|
||||
|
||||
$("#createRoomModal form").attr("action", $("body").data('relative-root'))
|
||||
$("#room_mute_on_join").prop("checked", $("#room_mute_on_join").data("default"))
|
||||
$("#room_require_moderator_approval").prop("checked", $("#room_require_moderator_approval").data("default"))
|
||||
$("#room_anyone_can_start").prop("checked", $("#room_anyone_can_start").data("default"))
|
||||
$("#room_all_join_moderator").prop("checked", $("#room_all_join_moderator").data("default"))
|
||||
$("#room_recording").prop("checked", $("#room_recording").data("default"))
|
||||
|
||||
//show all elements & their children with a create-only class
|
||||
$(".create-only").each(function() {
|
||||
$(this).show()
|
||||
if($(this).children().length > 0) { $(this).children().show() }
|
||||
})
|
||||
|
||||
//hide all elements & their children with a update-only class
|
||||
$(".update-only").each(function() {
|
||||
$(this).attr('style',"display:none !important")
|
||||
if($(this).children().length > 0) { $(this).children().attr('style',"display:none !important") }
|
||||
})
|
||||
}
|
||||
|
||||
function showUpdateRoom(target) {
|
||||
var modal = $(target)
|
||||
var update_path = modal.closest(".room-block").data("path")
|
||||
var settings_path = modal.data("settings-path")
|
||||
$("#create-room-name").val(modal.closest(".room-block").find(".room-name-text").text().trim())
|
||||
$("#createRoomModal form").attr("action", update_path)
|
||||
|
||||
//show all elements & their children with a update-only class
|
||||
$(".update-only").each(function() {
|
||||
$(this).show()
|
||||
if($(this).children().length > 0) { $(this).children().show() }
|
||||
})
|
||||
|
||||
//hide all elements & their children with a create-only class
|
||||
$(".create-only").each(function() {
|
||||
$(this).attr('style',"display:none !important")
|
||||
if($(this).children().length > 0) { $(this).children().attr('style',"display:none !important") }
|
||||
})
|
||||
|
||||
updateCurrentSettings(settings_path)
|
||||
|
||||
var accessCode = modal.closest(".room-block").data("room-access-code")
|
||||
|
||||
if(accessCode){
|
||||
$("#create-room-access-code").text(getLocalizedString("modal.create_room.access_code") + ": " + accessCode)
|
||||
$("#room_access_code").val(accessCode)
|
||||
} else {
|
||||
$("#create-room-access-code").text(getLocalizedString("modal.create_room.access_code_placeholder"))
|
||||
$("#room_access_code").val(null)
|
||||
}
|
||||
|
||||
var moderatorAccessCode = modal.closest(".room-block").data("room-moderator-access-code")
|
||||
|
||||
if(moderatorAccessCode){
|
||||
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code") + ": " + moderatorAccessCode)
|
||||
$("#room_moderator_access_code").val(moderatorAccessCode)
|
||||
} else {
|
||||
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code_placeholder"))
|
||||
$("#room_moderator_access_code").val(null)
|
||||
}
|
||||
}
|
||||
|
||||
function showDeleteRoom(target) {
|
||||
$("#delete-header").text(getLocalizedString("modal.delete_room.confirm").replace("%{room}", $(target).data("name")))
|
||||
$("#delete-confirm").parent().attr("action", $(target).data("path"))
|
||||
}
|
||||
|
||||
//Update the createRoomModal to show the correct current settings
|
||||
function updateCurrentSettings(settings_path){
|
||||
// Get current room settings and set checkbox
|
||||
$.get(settings_path, function(settings) {
|
||||
$("#room_mute_on_join").prop("checked", $("#room_mute_on_join").data("default") || settings.muteOnStart)
|
||||
$("#room_require_moderator_approval").prop("checked", $("#room_require_moderator_approval").data("default") || settings.requireModeratorApproval)
|
||||
$("#room_anyone_can_start").prop("checked", $("#room_anyone_can_start").data("default") || settings.anyoneCanStart)
|
||||
$("#room_all_join_moderator").prop("checked", $("#room_all_join_moderator").data("default") || settings.joinModerator)
|
||||
$("#room_recording").prop("checked", $("#room_recording").data("default") || Boolean(settings.recording))
|
||||
})
|
||||
}
|
||||
|
||||
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(getLocalizedString("modal.create_room.access_code") + ": " + accessCode)
|
||||
$("#room_access_code").val(accessCode)
|
||||
}
|
||||
|
||||
function ResetAccessCode(){
|
||||
$("#create-room-access-code").text(getLocalizedString("modal.create_room.access_code_placeholder"))
|
||||
$("#room_access_code").val(null)
|
||||
}
|
||||
|
||||
function generateModeratorAccessCode(){
|
||||
const accessCodeLength = 6
|
||||
var validCharacters = "abcdefghijklmopqrstuvwxyz"
|
||||
var accessCode = ""
|
||||
|
||||
for( var i = 0; i < accessCodeLength; i++){
|
||||
accessCode += validCharacters.charAt(Math.floor(Math.random() * validCharacters.length));
|
||||
}
|
||||
|
||||
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code") + ": " + accessCode)
|
||||
$("#room_moderator_access_code").val(accessCode)
|
||||
}
|
||||
|
||||
function ResetModeratorAccessCode(){
|
||||
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code_placeholder"))
|
||||
$("#room_moderator_access_code").val(null)
|
||||
}
|
||||
|
||||
function saveAccessChanges() {
|
||||
let listItemsToAdd = $("#user-list li:not(.remove-shared)").toArray().map(user => $(user).data("uid"))
|
||||
|
||||
$.post($("#save-access").data("path"), {add: listItemsToAdd})
|
||||
}
|
||||
|
||||
// Get list of users shared with and display them
|
||||
function displaySharedUsers(path) {
|
||||
$.get(path, function(users) {
|
||||
// Create list element and add to user list
|
||||
var user_list_html = ""
|
||||
|
||||
users.forEach(function(user) {
|
||||
user_list_html += "<li class='list-group-item text-left' data-uid='" + user.uid + "'>"
|
||||
|
||||
if (user.image) {
|
||||
user_list_html += "<img id='user-image' class='avatar float-left mr-2' src='" + user.image + "'></img>"
|
||||
} else {
|
||||
user_list_html += "<span class='avatar float-left mr-2'>" + user.name.charAt(0) + "</span>"
|
||||
}
|
||||
user_list_html += "<span class='shared-user'>" + user.name + "<span class='text-muted ml-1'>" + user.uid + "</span></span>"
|
||||
user_list_html += "<span class='text-primary float-right shared-user cursor-pointer' onclick='removeSharedUser(this)'><i class='fas fa-times'></i></span>"
|
||||
user_list_html += "</li>"
|
||||
})
|
||||
|
||||
$("#user-list").html(user_list_html)
|
||||
});
|
||||
}
|
||||
|
||||
// Removes the user from the list of shared users
|
||||
function removeSharedUser(target) {
|
||||
let parentLI = target.closest("li")
|
||||
|
||||
if (parentLI.classList.contains("not-saved")) {
|
||||
parentLI.parentNode.removeChild(parentLI)
|
||||
} else {
|
||||
parentLI.removeChild(target)
|
||||
parentLI.classList.add("remove-shared")
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreuploadPresentationModal(target) {
|
||||
$.get($(target).data("settings-path"), function(presentation) {
|
||||
if(presentation.attached) {
|
||||
$("#current-presentation").show()
|
||||
$("#presentation-name").text(presentation.name)
|
||||
$("#change-pres").show()
|
||||
$("#use-pres").hide()
|
||||
} else {
|
||||
$("#current-presentation").hide()
|
||||
$("#change-pres").hide()
|
||||
$("#use-pres").show()
|
||||
}
|
||||
});
|
||||
|
||||
$("#preuploadPresentationModal form").attr("action", $(target).data("path"))
|
||||
$("#remove-presentation").data("remove", $(target).data("remove"))
|
||||
|
||||
// Reset values to original to prevent confusion
|
||||
$("#presentation-upload").val("")
|
||||
$("#presentation-upload-label").text($("#presentation-upload-label").data("placeholder"))
|
||||
$("#invalid-file-type").hide()
|
||||
}
|
||||
|
||||
function removePreuploadPresentation(path) {
|
||||
$.post(path, {})
|
||||
}
|
||||
|
||||
function validFileUpload(file) {
|
||||
return file.size/1024/1024 <= 30
|
||||
}
|
||||
|
||||
// Automatically click the join button if this is an action cable reload
|
||||
function checkIfAutoJoin() {
|
||||
var url = new URL(window.location.href)
|
||||
|
||||
if (url.searchParams.get("reload") == "true") {
|
||||
$("#joiner-consent").click()
|
||||
$("#room-join").click()
|
||||
}
|
||||
}
|
||||
|
||||
function filterRooms() {
|
||||
let search = $('#room-search').val()
|
||||
|
||||
if (search == undefined) { return }
|
||||
|
||||
let search_term = search.toLowerCase(),
|
||||
rooms = $('#room_block_container > div:not(:last-child)');
|
||||
clear_room_search = $('#clear-room-search');
|
||||
|
||||
if (search_term) {
|
||||
clear_room_search.show();
|
||||
} else {
|
||||
clear_room_search.hide();
|
||||
}
|
||||
|
||||
rooms.each(function(i, room) {
|
||||
let text = $(this).find('h4').text();
|
||||
room.style.display = (text.toLowerCase().indexOf(search_term) < 0) ? 'none' : 'block';
|
||||
})
|
||||
}
|
||||
|
||||
function clearRoomSearch() {
|
||||
$('#room-search').val('');
|
||||
filterRooms()
|
||||
}
|
||||
|
||||
function manageAccessAccessibility() {
|
||||
// share room pop up accessibility
|
||||
var holdModal = false;
|
||||
$("#shareRoomModal").on("show.bs.modal", function() {
|
||||
// for screen reader to be able to read results
|
||||
$("#shareRoomModal .form-control").attr("aria-atomic", true);
|
||||
$("#shareRoomModal .dropdown-menu div.inner").attr("role", "alert");
|
||||
$("#shareRoomModal ul.dropdown-menu").attr("role", "listbox");
|
||||
$("#shareRoomModal div.dropdown-menu").find("*").keyup(function(event) {
|
||||
$("#shareRoomModal ul.dropdown-menu li").attr("aria-selected", false);
|
||||
$("#shareRoomModal ul.dropdown-menu li.active").attr("aria-selected", true);
|
||||
$("#shareRoomModal ul.dropdown-menu li.active a").attr("aria-selected", true);
|
||||
});
|
||||
// for keyboard support
|
||||
// so that it can escape / close search user without closing the modal
|
||||
$("#shareRoomModal div.dropdown-menu input").keydown(function(event) {
|
||||
if (event.keyCode === 27) {
|
||||
holdModal = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// reset escape button if the search is closed / done
|
||||
$("#shareRoomModal").on("hide.bs.modal", function(e) {
|
||||
if (holdModal) {
|
||||
holdModal = false;
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateAccessCodeAccessibility() {
|
||||
// For keyboard users to be able to generate access code
|
||||
$("#generate-room-access-code").keyup(function(event) {
|
||||
if (event.keyCode === 13 || event.keyCode === 32) {
|
||||
generateAccessCode();
|
||||
}
|
||||
})
|
||||
|
||||
// For keyboard users to be able to reset access code
|
||||
$("#reset-access-code").keyup(function(event) {
|
||||
if (event.keyCode === 13 || event.keyCode === 32) {
|
||||
ResetAccessCode();
|
||||
}
|
||||
})
|
||||
|
||||
// For keyboard users to be able to generate access code
|
||||
// for moderator
|
||||
$("#generate-moderator-room-access-code").keyup(function(event) {
|
||||
if (event.keyCode === 13 || event.keyCode === 32) {
|
||||
generateModeratorAccessCode();
|
||||
}
|
||||
})
|
||||
|
||||
// For keyboard users to be able to reset access code
|
||||
// for moderator
|
||||
$("#reset-moderator-access-code").keyup(function(event) {
|
||||
if (event.keyCode === 13 || event.keyCode === 32) {
|
||||
ResetModeratorAccessCode();
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// 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/>.
|
||||
|
||||
$(document).on('turbolinks:load', function(){
|
||||
var controller = $("body").data('controller');
|
||||
var action = $("body").data('action');
|
||||
|
||||
if ((controller == "admins" && action == "index") ||
|
||||
(controller == "rooms" && action == "show") ||
|
||||
(controller == "rooms" && action == "update") ||
|
||||
(controller == "rooms" && action == "join") ||
|
||||
(controller == "users" && action == "recordings") ||
|
||||
(controller == "admins" && action == "server_recordings") ||
|
||||
(controller == "admins" && action == "server_rooms")) {
|
||||
// Submit search if the user hits enter
|
||||
$("#search-input").keypress(function(key) {
|
||||
if (key.which == 13) {
|
||||
searchPage()
|
||||
}
|
||||
})
|
||||
|
||||
// Add listeners for sort
|
||||
$("th[data-order]").click(function(data){
|
||||
var header_elem = $(data.target)
|
||||
|
||||
if(header_elem.data('order') === 'asc'){ // asc
|
||||
header_elem.data('order', 'desc');
|
||||
}
|
||||
else if(header_elem.data('order') === 'desc'){ // desc
|
||||
header_elem.data('order', 'none');
|
||||
}
|
||||
else{ // none
|
||||
header_elem.data('order', 'asc');
|
||||
}
|
||||
|
||||
var search = $("#search-input").val();
|
||||
|
||||
var url = window.location.pathname + "?page=1&search=" + search + "&column=" + header_elem.data("header") +
|
||||
"&direction=" + header_elem.data('order')
|
||||
|
||||
window.location.replace(addRecordingTable(url))
|
||||
})
|
||||
|
||||
if(controller === "rooms" && action === "show"){
|
||||
$(".page-item > a").each(function(){
|
||||
if(!$(this).attr('href').endsWith("#")){
|
||||
$(this).attr('href', $(this).attr('href') + "#recordings-table")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Searches the user table for the given string
|
||||
function searchPage() {
|
||||
var search = $("#search-input").val();
|
||||
|
||||
// Check if the user filtered by role
|
||||
var role = new URL(location.href).searchParams.get('role')
|
||||
var tab = new URL(location.href).searchParams.get('tab')
|
||||
|
||||
var url = window.location.pathname + "?page=1&search=" + search
|
||||
|
||||
if (role) { url += "&role=" + role }
|
||||
if (tab) { url += "&tab=" + tab }
|
||||
|
||||
window.location.replace(addRecordingTable(url));
|
||||
}
|
||||
|
||||
// Clears the search bar
|
||||
function clearSearch() {
|
||||
var role = new URL(location.href).searchParams.get('role')
|
||||
var tab = new URL(location.href).searchParams.get('tab')
|
||||
|
||||
var url = window.location.pathname + "?page=1"
|
||||
|
||||
if (role) { url += "&role=" + role }
|
||||
if (tab) { url += "&tab=" + tab }
|
||||
|
||||
window.location.replace(addRecordingTable(url));
|
||||
|
||||
var search_params = new URLSearchParams(window.location.search)
|
||||
}
|
||||
|
||||
function addRecordingTable(url) {
|
||||
if($("body").data('controller') === "rooms" && $("body").data('action') === "show") {
|
||||
url += "#recordings-table"
|
||||
}
|
||||
return url
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// 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');
|
||||
|
||||
// Only run on the settings page.
|
||||
if ((controller == "users" && action == "edit") || (controller == "users" && action == "update")){
|
||||
var settingsButtons = $('.setting-btn');
|
||||
var settingsViews = $('.setting-view');
|
||||
|
||||
settingsButtons.each(function(i, btn) {
|
||||
if(!$(btn).hasClass("active")){ $(settingsViews[i]).hide(); }
|
||||
$(btn).click(function(){
|
||||
$(btn).addClass("active");
|
||||
settingsViews.each(function(i, view){
|
||||
if($(view).attr("id") == $(btn).attr("id")){
|
||||
$(view).show();
|
||||
} else {
|
||||
$(settingsButtons[i]).removeClass("active");
|
||||
$(view).hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,93 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
|
||||
}
|
||||
});
|
||||
|
||||
var loopJoin = function() {
|
||||
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
||||
jqxhr.done(function(data) {
|
||||
if (data.messageKey === 'wait_for_moderator') {
|
||||
setTimeout(loopJoin, 5000);
|
||||
} else {
|
||||
$(location).attr("href", data.response.join_url);
|
||||
}
|
||||
});
|
||||
jqxhr.fail(function(xhr, status, error) {
|
||||
console.info("meeting join failed");
|
||||
});
|
||||
};
|
||||
|
||||
var alertTimeout = null;
|
||||
var showAlert = function(html, timeout_delay, type) {
|
||||
type = type || 'success'
|
||||
|
||||
if (!html) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.alert-template .alert-message').html(html);
|
||||
$('.alert-template .alert').removeClass('alert-success alert-danger alert-info alert-warning').addClass('alert-'+type);
|
||||
$('#alerts').html($('.alert-template').html());
|
||||
|
||||
if (timeout_delay) {
|
||||
clearTimeout(alertTimeout);
|
||||
alertTimeout = setTimeout(function() {
|
||||
$('#alerts > .alert').alert('close');
|
||||
}, timeout_delay);
|
||||
}
|
||||
};
|
||||
|
||||
// remove flash alerts after 5 seconds
|
||||
var flashAlertDelayedDismiss = function() {
|
||||
setTimeout(function(){
|
||||
$('.flash-alerts').remove();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
var displayRoomURL = function() {
|
||||
$('.meeting-url').val(Meeting.getInstance().getURL());
|
||||
};
|
||||
|
||||
var showNotification = function(title, options) {
|
||||
if (Notification.permission === "granted") {
|
||||
var icon = '<%= asset_path("bbb-logo.png") %>';
|
||||
options = $.extend(options, {
|
||||
icon: icon,
|
||||
tag: 'UserWaiting'
|
||||
});
|
||||
var notification = new Notification(title, options);
|
||||
notification.onclick = function() {
|
||||
window.focus();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var isRoomOwner = function() {
|
||||
return $('body').data('current-user') === $('.page-wrapper.rooms').data('admin-id')
|
||||
}
|
||||
|
||||
// For now there are notifications only for users signed in and when they
|
||||
// are in their room's page
|
||||
$(document).on("turbolinks:load", function() {
|
||||
if (isRoomOwner()) {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
flashAlertDelayedDismiss();
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
// 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/>.
|
||||
|
||||
$(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"){
|
||||
|
||||
// Choose active header
|
||||
// (Name, Length or Users)
|
||||
$('th').each(function(){
|
||||
if($(this).data("header")){
|
||||
$(this).on('click', function(){
|
||||
set_active_header($(this).data("header"));
|
||||
sort_by($(this).data("header"), $(this).data('order'));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Based on the header (Name, Length or Users) clicked,
|
||||
// Modify the ui for the tables
|
||||
var set_active_header = function(active_header){
|
||||
$('th').each(function(){
|
||||
if($(this).data("header") == active_header){
|
||||
configure_order($(this));
|
||||
}
|
||||
else{
|
||||
$(this).text($(this).data("header"));
|
||||
$(this).data('order', 'none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Based on the header (Name, Length or Users) clicked,
|
||||
// Modify the ui for the tables
|
||||
var configure_order = function(header_elem){
|
||||
if(header_elem.data('order') === 'asc'){ // asc
|
||||
header_elem.data('order', 'desc');
|
||||
}
|
||||
else if(header_elem.data('order') === 'desc'){ // desc
|
||||
header_elem.data('order', 'none');
|
||||
}
|
||||
else{ // none
|
||||
header_elem.data('order', 'asc');
|
||||
}
|
||||
}
|
||||
|
||||
// Given a label and an order, sort recordings by order
|
||||
// under a given label
|
||||
var sort_by = function(label, order){
|
||||
var recording_list_tbody = $('.table-responsive').find('tbody');
|
||||
if(label === "Name"){
|
||||
sort_recordings(recording_list_tbody, order, "#recording-title");
|
||||
}
|
||||
else if(label === "Length"){
|
||||
sort_recordings(recording_list_tbody, order, "#recording-length");
|
||||
}
|
||||
else if(label === "Users"){
|
||||
sort_recordings(recording_list_tbody, order, "#recording-users");
|
||||
}
|
||||
}
|
||||
|
||||
// Generalized function for sorting recordings
|
||||
var sort_recordings = function(recording_list_tbody, order, recording_id){
|
||||
recording_list_tbody.find('tr').sort(function(a, b){
|
||||
var a_val, b_val;
|
||||
if (recording_id == "#recording-length") {
|
||||
a_val = $.trim($(a).find(recording_id).data("full-length"));
|
||||
b_val = $.trim($(b).find(recording_id).data("full-length"));
|
||||
} else {
|
||||
a_val = $.trim($(a).find(recording_id).text());
|
||||
b_val = $.trim($(b).find(recording_id).text());
|
||||
}
|
||||
|
||||
if(order === "asc"){
|
||||
return a_val.localeCompare(b_val);
|
||||
}
|
||||
else if(order === "desc"){
|
||||
return b_val.localeCompare(a_val);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
}).appendTo(recording_list_tbody);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
// 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/>.
|
||||
|
||||
$(document).on('turbolinks:load', function(){
|
||||
var controller = $("body").data('controller');
|
||||
var action = $("body").data('action');
|
||||
if ((controller == "admins" && action == "edit_user") || (controller == "users" && action == "edit")) {
|
||||
// Hack to make it play nice with turbolinks
|
||||
if ($("#role-dropdown:visible").length == 0){
|
||||
$(window).trigger('load.bs.select.data-api')
|
||||
}
|
||||
|
||||
// Check to see if the role dropdown was set up
|
||||
if ($("#role-dropdown").length != 0){
|
||||
$("#role-dropdown").selectpicker('val', $("#user_role_id").val())
|
||||
}
|
||||
|
||||
// Update hidden field with new value
|
||||
$("#role-dropdown").on("changed.bs.select", function(){
|
||||
$("#user_role_id").val($("#role-dropdown").selectpicker('val'))
|
||||
})
|
||||
|
||||
// Update hidden field with new value
|
||||
// $("#language-dropdown").on("show.bs.select", function(){
|
||||
// $("#language-dropdown").selectpicker('val', $("#user_language").val())
|
||||
// })
|
||||
|
||||
// Update hidden field with new value
|
||||
$("#language-dropdown").on("changed.bs.select", function(){
|
||||
$("#user_language").val($("#language-dropdown").selectpicker('val'))
|
||||
})
|
||||
}
|
||||
})
|
|
@ -0,0 +1,67 @@
|
|||
// 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 client request to join when meeting starts.
|
||||
$(document).on("turbolinks:load", function(){
|
||||
var controller = $("body").data('controller');
|
||||
var action = $("body").data('action');
|
||||
|
||||
if(controller == "rooms" && action == "join"){
|
||||
App.waiting = App.cable.subscriptions.create({
|
||||
channel: "WaitingChannel",
|
||||
roomuid: $(".background").attr("room"),
|
||||
useruid: $(".background").attr("user")
|
||||
}, {
|
||||
connected: function() {
|
||||
console.log("connected");
|
||||
setTimeout(startRefreshTimeout, 120000);
|
||||
},
|
||||
|
||||
disconnected: function(data) {
|
||||
console.log("disconnected");
|
||||
console.log(data);
|
||||
},
|
||||
|
||||
rejected: function() {
|
||||
console.log("rejected");
|
||||
},
|
||||
|
||||
received: function(data){
|
||||
console.log(data);
|
||||
if(data.action == "started"){
|
||||
request_to_join_meeting();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var join_attempts = 0;
|
||||
|
||||
function request_to_join_meeting() {
|
||||
$.post(window.location.pathname, { join_name: $(".background").attr("join-name") }, function() {
|
||||
//Successful post - set up retry incase
|
||||
if(join_attempts < 4){ setTimeout(request_to_join_meeting, 10000); }
|
||||
join_attempts++;
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh the page after 2 mins and attempt to reconnect to ActionCable
|
||||
function startRefreshTimeout() {
|
||||
var url = new URL(window.location.href)
|
||||
url.searchParams.set("reload","true")
|
||||
window.location.href = url.href
|
||||
}
|
|
@ -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/>.
|
||||
*/
|
||||
|
||||
@import "tabler/functions";
|
||||
|
||||
@import "tabler/core";
|
||||
@import "tabler/type";
|
||||
@import "tabler/grid";
|
||||
|
||||
@import "tabler/layout";
|
||||
@import "tabler/aside";
|
||||
@import "tabler/header";
|
||||
@import "tabler/footer";
|
||||
@import "tabler/colors";
|
||||
@import "tabler/text";
|
||||
@import "tabler/utilities";
|
||||
|
||||
@import "tabler/nav";
|
||||
@import "tabler/button";
|
||||
@import "tabler/alert";
|
||||
//@import "tabler/close";
|
||||
//@import "tabler/badge";
|
||||
@import "tabler/tables";
|
||||
//@import "tabler/breadcrumb";
|
||||
//@import "tabler/pagination";
|
||||
@import "tabler/cards";
|
||||
//@import "tabler/popover";
|
||||
@import "tabler/dropdown";
|
||||
@import "tabler/list";
|
||||
@import "tabler/list-group";
|
||||
@import "tabler/avatar";
|
||||
//@import "tabler/product";
|
||||
@import "tabler/progress";
|
||||
@import "tabler/icon";
|
||||
//@import "tabler/image";
|
||||
//@import "tabler/link";
|
||||
//@import "tabler/media";
|
||||
@import "tabler/form";
|
||||
//@import "tabler/sparkline";
|
||||
@import "tabler/social";
|
||||
//@import "tabler/maps";
|
||||
//@import "tabler/statuses";
|
||||
//@import "tabler/charts";
|
||||
//@import "tabler/chips";
|
||||
@import "tabler/stamp";
|
||||
//@import "tabler/chat";
|
||||
//@import "tabler/example";
|
||||
@import "tabler/tag";
|
||||
//@import "tabler/syntax";
|
||||
//@import "tabler/infobox";
|
||||
//@import "tabler/carousel";
|
||||
|
||||
//@import "tabler/forms/custom-range";
|
||||
//@import "tabler/forms/custom-selectgroup";
|
||||
@import "tabler/forms/custom-switch";
|
||||
//@import "tabler/forms/custom-imagecheck";
|
||||
@import "tabler/forms/custom-colorinput";
|
||||
|
||||
//@import "tabler/timeline";
|
||||
|
||||
//@import "tabler/browser";
|
||||
//@import "tabler/flag";
|
||||
//@import "tabler/payments";
|
||||
//@import "tabler/jvectormap";
|
||||
//@import "tabler/selectize";
|
||||
|
||||
//@import "tabler/fonts/feather";
|
|
@ -0,0 +1,105 @@
|
|||
// 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/>.
|
||||
|
||||
#users-table {
|
||||
.user-role {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.user-email {
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
#clear-search {
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
right: 55px;
|
||||
top: 8px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tag i {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
#branding-image{
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
.authentication-required{
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
#site_settings {
|
||||
.colorinput-color {
|
||||
text-align: center;
|
||||
padding-top: 4px;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sort-disabled{
|
||||
background: #e6e6e6 !important;
|
||||
color: rgb(110, 118, 135) !important;
|
||||
opacity: 0.75;
|
||||
&:hover{
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.form-disable{
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.role-colour-picker{
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.custom-role-tag{
|
||||
color: white !important;
|
||||
// Make it consistent with the manage users tab tags
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.user-role-tag{
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
#manage-users-nav.nav-tabs .nav-item {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
#merge-account-arrow {
|
||||
position: absolute;
|
||||
top: 47%;
|
||||
right: 47%;
|
||||
z-index: 999;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.admin-tabs {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
#rooms-table-div {
|
||||
overflow: visible;
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
*
|
||||
* Copyright (c) 2016 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*= require jquery-ui
|
||||
*= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
|
||||
*= require_tree ./main
|
||||
*= require_self
|
||||
*/
|
|
@ -0,0 +1,193 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*= require_self
|
||||
*/
|
||||
|
||||
@import "tabler/variables";
|
||||
@import "bootstrap";
|
||||
@import "jquery-ui/sortable";
|
||||
@import "tabler-custom";
|
||||
@import "font-awesome-sprockets";
|
||||
@import "font-awesome";
|
||||
@import "monolith.min.scss";
|
||||
@import "bootstrap-select.min";
|
||||
|
||||
@import "utilities/variables";
|
||||
@import "admins";
|
||||
@import "main";
|
||||
@import "rooms";
|
||||
@import "sessions";
|
||||
@import "utilities/fonts";
|
||||
@import "users";
|
||||
|
||||
* {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: "Source Sans Pro" !important;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dotted_underline {
|
||||
border-bottom: 3px dashed #d3d3d3;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.disable-click {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: $header-height;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
height: auto;
|
||||
min-height: calc(100% - #{$header-height} - #{$footer-height});
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: $footer-height;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.background {
|
||||
background-color: $background-color;
|
||||
}
|
||||
|
||||
.error-section {
|
||||
background-color: $error-background-color;
|
||||
}
|
||||
|
||||
.font-weight-400 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.force-bottom {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.invite-link-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-border-top {
|
||||
td {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.force-text-normal {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.terms {
|
||||
overflow: scroll;
|
||||
height: 55vh;
|
||||
}
|
||||
|
||||
[contenteditable]:focus {
|
||||
outline: 0px solid transparent;
|
||||
}
|
||||
|
||||
.cookies-banner {
|
||||
color: white;
|
||||
background-color: $button-color-blue;
|
||||
#cookies-agree-button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: $primary !important;
|
||||
}
|
||||
|
||||
.input-group button:focus {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.list-group-item-action.active {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.header .header-nav {
|
||||
color: $text-muted !important;
|
||||
|
||||
&:hover {
|
||||
padding-bottom: 21px;
|
||||
border-bottom: 1px solid $primary;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $primary !important;
|
||||
padding-bottom: 21px;
|
||||
border-bottom: 1px solid $primary;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
thead {
|
||||
th[data-order]:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cursor-pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#delete-confirm:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn i {
|
||||
transition: all .15s;
|
||||
}
|
||||
|
||||
.nav-icon i {
|
||||
width: 35px;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Place all the styles related to the Errors controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -1,18 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
@import "font-awesome-sprockets";
|
||||
@import "font-awesome";
|
|
@ -0,0 +1,178 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Place all the styles related to the Main controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
|
||||
.responsive-header {
|
||||
font-size: 3.3vw;
|
||||
}
|
||||
|
||||
.landing-section {
|
||||
position: relative;
|
||||
height: 40%;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size:18px;
|
||||
}
|
||||
|
||||
.feature-stamp {
|
||||
.stamp {
|
||||
padding:1em 1.5em;
|
||||
height:auto;
|
||||
}
|
||||
}
|
||||
|
||||
.or-line {
|
||||
div {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
text-align: center;
|
||||
span {
|
||||
font-size: 120%;
|
||||
background-color: #ffffff;
|
||||
padding: 0 10px;
|
||||
color: #d3d3d3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.display-4 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.customBtn {
|
||||
display: block;
|
||||
text-align: center;
|
||||
background: #cccccc;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,.25);
|
||||
-webkit-transition: background-color .218s,border-color .218s,box-shadow .218s;
|
||||
transition: background-color .218s,border-color .218s,box-shadow .218s;
|
||||
white-space: nowrap;
|
||||
border-radius: 2px;
|
||||
border: 1px solid transparent;
|
||||
margin-bottom:20px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: #ffffff;
|
||||
-webkit-box-shadow: 0 0 3px 3px rgba(66,133,244,.3);
|
||||
box-shadow: 0 0 3px 3px rgba(66,133,244,.3);
|
||||
}
|
||||
|
||||
.customBtn-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding:13px 15px 13px 15px;
|
||||
float:left;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.customBtn-text {
|
||||
font-size: 19px;
|
||||
line-height: 48px;
|
||||
font-weight: 600;
|
||||
letter-spacing: .21px;
|
||||
padding:0 25px;
|
||||
}
|
||||
|
||||
.customBtn-image {
|
||||
background: #ffffff;
|
||||
background-size: 18px 18px;
|
||||
padding:10px 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.customBtn-google {
|
||||
@extend .customBtn;
|
||||
background: #4688f1;
|
||||
|
||||
.customBtn-image {
|
||||
background: #ffffff image-url("google-logo.png") no-repeat left top;
|
||||
background-size: 18px 18px;
|
||||
padding:10px 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.customBtn-twitter {
|
||||
@extend .customBtn;
|
||||
background: #1da1f2;
|
||||
|
||||
.customBtn-image {
|
||||
background: #ffffff image-url("twitter-logo.png") no-repeat left top;
|
||||
background-size: 18px 18px;
|
||||
padding:10px 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.customBtn-office365 {
|
||||
@extend .customBtn;
|
||||
background: #f65314;
|
||||
|
||||
.customBtn-image {
|
||||
background: #ffffff image-url("office365-logo.jpeg") no-repeat left top;
|
||||
background-size: 18px 18px;
|
||||
padding:10px 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.customBtn-microsoft_windows {
|
||||
@extend .customBtn;
|
||||
background: #00a1f1;
|
||||
|
||||
.customBtn-image {
|
||||
background: #ffffff image-url("windows-logo.png") no-repeat left top;
|
||||
background-size: 18px 18px;
|
||||
padding:10px 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.customBtn-ldap {
|
||||
@extend .customBtn;
|
||||
background: #d61515;
|
||||
|
||||
.customBtn-image {
|
||||
background: #ffffff image-url("ldap-logo.png") no-repeat left top;
|
||||
background-size: 18px 18px;
|
||||
padding:10px 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.customBtn-openid_connect {
|
||||
@extend .customBtn;
|
||||
background: #ef8e1f;
|
||||
|
||||
.customBtn-image {
|
||||
background: #ffffff image-url("openid-logo.png") no-repeat left top;
|
||||
background-size: 18px 18px;
|
||||
padding:10px 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.signin-button {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.table-responsive tbody td:first-child > *:first-child {
|
||||
max-height: 3em;
|
||||
overflow: hidden;
|
||||
max-width: 200px;
|
||||
display: flex;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
|
@ -1,133 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
.meeting-url-button-group {
|
||||
padding-top: 5px;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.meeting-url-qrcode-group {
|
||||
padding-top: 5px;
|
||||
width: 128px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.previously-joined, .actives {
|
||||
list-style-type: none;
|
||||
margin:auto;
|
||||
width: 200px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.meetings {
|
||||
|
||||
}
|
||||
|
||||
.rooms {
|
||||
|
||||
.table-wrapper {
|
||||
padding: 40px 50px 10px 50px;
|
||||
|
||||
#recordings {
|
||||
thead {
|
||||
th:after {
|
||||
content: none; //removes the sort icon in table header
|
||||
}
|
||||
}
|
||||
.dataTables_empty {
|
||||
text-align: center;
|
||||
}
|
||||
.timeago {
|
||||
margin-left: 5px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img-thumbnail{
|
||||
padding: 4px;
|
||||
background-color: white;
|
||||
border: 1px solid #dddddd;
|
||||
border-radius: 4px;
|
||||
|
||||
&.large{
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover + &.large{
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.meeting-url-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.recording-update-trigger {
|
||||
&.recording-unpublished {
|
||||
color: red;
|
||||
}
|
||||
|
||||
&.recording-unlisted {
|
||||
color: #e3a91e;
|
||||
}
|
||||
|
||||
&.recording-published {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-spinner {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.disabled-button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.youtube-red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cloud-blue {
|
||||
color: cornflowerblue;
|
||||
}
|
||||
|
||||
.green-check {
|
||||
color: limegreen;
|
||||
}
|
||||
|
||||
.top-buffer {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tooltip-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#youtube-footer{
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 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/>.
|
||||
|
||||
@import "bootstrap-sprockets";
|
||||
@import "bootstrap";
|
||||
@import "bootstrap-social";
|
||||
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.background {
|
||||
height: 350px;
|
||||
width: 100%;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body[data-controller=landing].app-background {
|
||||
@extend .background;
|
||||
}
|
||||
|
||||
#alerts {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
margin-left: -250px;
|
||||
width: 500px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 40px;
|
||||
margin-bottom: 160px;
|
||||
.logo {
|
||||
max-width: 150px;
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.center-block {
|
||||
float: none;
|
||||
}
|
||||
.center-panel-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
.center-panel {
|
||||
height: 100%;
|
||||
.center-panel-size {
|
||||
max-width: 1200px
|
||||
}
|
||||
.center-panel-content-size {
|
||||
height: 100%;
|
||||
max-width: 1100px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input-spacing {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.title-wrapper {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.join-form-wrapper {
|
||||
.center-block {
|
||||
max-width: 400px;
|
||||
}
|
||||
.join-form {
|
||||
width: 100%;
|
||||
}
|
||||
.btn {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.meeting-url-wrapper {
|
||||
.meeting-url {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
a.signin-link {
|
||||
&:hover, &:focus {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.signin-link {
|
||||
.signin-icon {
|
||||
vertical-align: middle;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
.signin-button {
|
||||
background: white;
|
||||
width: 250px;
|
||||
border: thin solid #888;
|
||||
border-radius: 2px;
|
||||
white-space: nowrap;
|
||||
padding: 5px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.signin-icon-wrapper {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
}
|
||||
.signin-text-wrapper {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
.signin-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
}
|
||||
}
|
||||
|
||||
.verticle-line {
|
||||
// parent must be position relative to work
|
||||
width: 1px;
|
||||
background-color: lightgray;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.invite-join-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.help-link {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding-right: 3px;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Place all the styles related to the Rooms controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
|
||||
.start-button {
|
||||
font-size: 26px !important;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
height: 60px !important;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.join-form {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
|
||||
.moderator-code-label {
|
||||
margin-top: 150px !important;
|
||||
}
|
||||
|
||||
.home-indicator {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
|
||||
.btn-del-room {
|
||||
width: 70% !important;
|
||||
}
|
||||
|
||||
.edit_hover_class a{
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.edit_hover_class:hover a {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#room-settings-dropdown-label {
|
||||
vertical-align: middle;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.room-block {
|
||||
&:not(.current) {
|
||||
.stamp {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#create-room-block {
|
||||
border: 1px dashed lightgray;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.allow-icon-click{
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.cant-create-rooms-title{
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-xxxl{
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
line-height: 8rem;
|
||||
max-width: 8rem;
|
||||
margin-top: -6rem;
|
||||
font-size: 5rem;
|
||||
}
|
||||
|
||||
.bootstrap-select .dropdown-menu li.active small.text-muted{
|
||||
color: #9aa0ac !important
|
||||
}
|
||||
|
||||
.not-saved {
|
||||
color: grey;
|
||||
background: rgba(0, 40, 100, 0.12);
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
min-height: 0px !important;
|
||||
}
|
||||
|
||||
.remove-shared {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.enabled-setting {
|
||||
background: lightgray;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#recording-table .edit_hover_class {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#room-owner-name {
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.create-room-button {
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
#presentation-upload-label {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 75px;
|
||||
}
|
||||
|
||||
#clear-room-search {
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 8px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Place all the styles related to the Sessions controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
|
||||
.login {
|
||||
.center-panel {
|
||||
.center-panel-size {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.signin-link {
|
||||
&:hover, &:focus {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.signin-link {
|
||||
.signin-icon {
|
||||
vertical-align: middle;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
.signin-button {
|
||||
background: white;
|
||||
width: 250px;
|
||||
border: thin solid #888;
|
||||
border-radius: 2px;
|
||||
white-space: nowrap;
|
||||
padding: 5px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.signin-icon-wrapper {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
}
|
||||
.signin-text-wrapper {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
.signin-text {
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Place all the styles related to the Users controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
|
||||
.user-role-tag{
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.shared-user {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.bootstrap-select {
|
||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||
}
|
|
@ -0,0 +1,504 @@
|
|||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-LightItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-LightItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-LightItalic.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-LightItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-LightItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-LightItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-LightItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Italic.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Italic.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Italic.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Italic.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Italic.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Italic.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Italic.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-BoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-BoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-BoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-BoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-BoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-BoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-BoldItalic.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Light.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Light.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Light.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Light.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Light.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Light.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Light.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Regular.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Regular.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Regular.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Regular.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Regular.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Regular.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Regular.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBold.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBold.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBold.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBold.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBold.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBold.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-SemiBold.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Bold.ttf')) format('truetype');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Bold.ttf')) format('truetype');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Bold.ttf')) format('truetype');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Bold.ttf')) format('truetype');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Bold.ttf')) format('truetype');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Bold.ttf')) format('truetype');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: asset-url(font-path('SourceSansPro/SourceSansPro-Bold.ttf')) format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
// Declare all variables here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
$background-color: #F5F7FB;
|
||||
$error-background-color: #EFE6E6;
|
||||
$button-color-blue: #467FCF;
|
||||
$header-height: 65px;
|
||||
$footer-height: 65px;
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
|
@ -14,8 +16,8 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class RefreshMeetingsChannel < ApplicationCable::Channel
|
||||
class WaitingChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from "refresh_meetings"
|
||||
stream_from "#{params[:roomuid]}_waiting_channel"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
# 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/>.
|
||||
|
||||
class AccountActivationsController < ApplicationController
|
||||
include Emailer
|
||||
include Authenticator
|
||||
|
||||
before_action :ensure_unauthenticated
|
||||
before_action :find_user_by_token, only: :edit
|
||||
before_action :find_user_by_digest, only: :resend
|
||||
|
||||
# GET /account_activations
|
||||
def show
|
||||
end
|
||||
|
||||
# GET /account_activations/edit
|
||||
def edit
|
||||
# If the user exists and is not verified and provided the correct token
|
||||
if @user && !@user.activated?
|
||||
# Verify user
|
||||
@user.set_role(initial_user_role(@user.email)) if @user.role.nil?
|
||||
@user.activate
|
||||
|
||||
# Redirect user to root with account pending flash if account is still pending
|
||||
return redirect_to root_path,
|
||||
flash: { success: I18n.t("registration.approval.signup") } if @user.has_role?(:pending)
|
||||
|
||||
# Redirect user to sign in path with success flash
|
||||
redirect_to signin_path, flash: { success: "#{I18n.t('verify.activated')} #{I18n.t('verify.signin')}" }
|
||||
else
|
||||
redirect_to root_path, flash: { alert: I18n.t("verify.invalid") }
|
||||
end
|
||||
end
|
||||
|
||||
# POST /account_activations/resend
|
||||
def resend
|
||||
if @user.activated?
|
||||
# User is already verified
|
||||
flash[:alert] = I18n.t("verify.already_verified")
|
||||
else
|
||||
# Resend
|
||||
send_activation_email(@user, @user.create_activation_token)
|
||||
end
|
||||
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user_by_token
|
||||
return redirect_to root_path, flash: { alert: I18n.t("verify.invalid") } unless params[:token].present?
|
||||
|
||||
@user = User.find_by!(activation_digest: User.hash_token(params[:token]), provider: @user_domain)
|
||||
end
|
||||
|
||||
def find_user_by_digest
|
||||
@user = User.find_by!(activation_digest: params[:digest], provider: @user_domain)
|
||||
end
|
||||
|
||||
def ensure_unauthenticated
|
||||
redirect_to current_user.main_room || root_path if current_user
|
||||
end
|
||||
end
|
|
@ -0,0 +1,379 @@
|
|||
# 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/>.
|
||||
|
||||
class AdminsController < ApplicationController
|
||||
include Pagy::Backend
|
||||
include Themer
|
||||
include Emailer
|
||||
include Recorder
|
||||
include Rolify
|
||||
include Populator
|
||||
|
||||
manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve, :reset, :merge_user]
|
||||
manage_deleted_users = [:undelete]
|
||||
authorize_resource class: false
|
||||
before_action :find_user, only: manage_users
|
||||
before_action :find_deleted_user, only: manage_deleted_users
|
||||
before_action :verify_admin_of_user, only: [manage_users, manage_deleted_users]
|
||||
|
||||
# GET /admins
|
||||
def index
|
||||
# Initializa the data manipulation variables
|
||||
@search = params[:search] || ""
|
||||
@order_column = params[:column] && params[:direction] != "none" ? params[:column] : "created_at"
|
||||
@order_direction = params[:direction] && params[:direction] != "none" ? params[:direction] : "DESC"
|
||||
@tab = params[:tab] || "active"
|
||||
@role = params[:role] ? Role.find_by(name: params[:role], provider: @user_domain) : nil
|
||||
|
||||
users = if @tab == "invited"
|
||||
invited_users_list
|
||||
else
|
||||
manage_users_list
|
||||
end
|
||||
|
||||
@pagy, @users = pagy(users)
|
||||
end
|
||||
|
||||
# GET /admins/site_settings
|
||||
def site_settings
|
||||
@tab = params[:tab] || "appearance"
|
||||
end
|
||||
|
||||
# GET /admins/server_recordings
|
||||
def server_recordings
|
||||
@search = params[:search] || ""
|
||||
|
||||
if @search.present?
|
||||
if @search.include? "@"
|
||||
user_email = @search
|
||||
else
|
||||
room_uid = @search
|
||||
end
|
||||
else
|
||||
@latest = true
|
||||
end
|
||||
|
||||
@pagy, @recordings = pagy_array(recordings_to_show(user_email, room_uid))
|
||||
end
|
||||
|
||||
# GET /admins/rooms
|
||||
def server_rooms
|
||||
@search = params[:search] || ""
|
||||
@order_column = params[:column] && params[:direction] != "none" ? params[:column] : "status"
|
||||
@order_direction = params[:direction] && params[:direction] != "none" ? params[:direction] : "DESC"
|
||||
|
||||
begin
|
||||
meetings = all_running_meetings[:meetings]
|
||||
rescue BigBlueButton::BigBlueButtonException
|
||||
flash[:alert] = I18n.t("administrator.rooms.timeout", server: I18n.t("bigbluebutton"))
|
||||
meetings = []
|
||||
end
|
||||
|
||||
@order_column = "created_at" if meetings.empty?
|
||||
@running_room_bbb_ids = meetings.pluck(:meetingID)
|
||||
|
||||
@participants_count = {}
|
||||
meetings.each do |meet|
|
||||
@participants_count[meet[:meetingID]] = meet[:participantCount]
|
||||
end
|
||||
|
||||
@pagy, @rooms = pagy_array(server_rooms_list)
|
||||
end
|
||||
|
||||
# GET /admins/room_configuration
|
||||
def room_configuration
|
||||
end
|
||||
|
||||
# MANAGE USERS
|
||||
|
||||
# GET /admins/edit/:user_uid
|
||||
def edit_user
|
||||
session[:prev_url] = request.referer if request.referer.present?
|
||||
end
|
||||
|
||||
# POST /admins/ban/:user_uid
|
||||
def ban_user
|
||||
@user.set_role :denied
|
||||
|
||||
redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.banned") }
|
||||
end
|
||||
|
||||
# POST /admins/unban/:user_uid
|
||||
def unban_user
|
||||
@user.set_role :user
|
||||
|
||||
redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.unbanned") }
|
||||
end
|
||||
|
||||
# POST /admins/approve/:user_uid
|
||||
def approve
|
||||
@user.set_role :user
|
||||
|
||||
send_user_approved_email(@user)
|
||||
|
||||
redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.approved") }
|
||||
end
|
||||
|
||||
# POST /admins/approve/:user_uid
|
||||
def undelete
|
||||
# Undelete the user and all of his rooms
|
||||
@user.undelete!
|
||||
@user.rooms.deleted.each(&:undelete!)
|
||||
|
||||
redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.restored") }
|
||||
end
|
||||
|
||||
# POST /admins/invite
|
||||
def invite
|
||||
emails = params[:invite_user][:email].split(",")
|
||||
|
||||
emails.each do |email|
|
||||
invitation = create_or_update_invite(email)
|
||||
|
||||
send_invitation_email(current_user.name, email, invitation)
|
||||
end
|
||||
|
||||
redirect_back fallback_location: admins_path,
|
||||
flash: { success: I18n.t("administrator.flash.invite", email: emails.join(", ")) }
|
||||
end
|
||||
|
||||
# GET /admins/reset
|
||||
def reset
|
||||
send_password_reset_email(@user, @user.create_reset_digest)
|
||||
|
||||
if session[:prev_url].present?
|
||||
redirect_path = session[:prev_url]
|
||||
session.delete(:prev_url)
|
||||
else
|
||||
redirect_path = admins_path
|
||||
end
|
||||
|
||||
redirect_to redirect_path, flash: { success: I18n.t("administrator.flash.reset_password") }
|
||||
end
|
||||
|
||||
# POST /admins/merge/:user_uid
|
||||
def merge_user
|
||||
begin
|
||||
# Get uid of user that will be merged into the other account
|
||||
uid_to_merge = params[:merge]
|
||||
logger.info "#{current_user.uid} is attempting to merge #{uid_to_merge} into #{@user.uid}"
|
||||
|
||||
# Check to make sure the 2 users are unique
|
||||
raise "Can not merge the user into themself" if uid_to_merge == @user.uid
|
||||
|
||||
# Find user to merge
|
||||
user_to_merge = User.find_by(uid: uid_to_merge)
|
||||
|
||||
# Move over user's rooms
|
||||
user_to_merge.rooms.each do |room|
|
||||
room.owner = @user
|
||||
|
||||
room.name = "(#{I18n.t('merged')}) #{room.name}"
|
||||
|
||||
room.save!
|
||||
end
|
||||
|
||||
# Reload user to update merge rooms
|
||||
user_to_merge.reload
|
||||
|
||||
# Delete merged user
|
||||
user_to_merge.destroy(true)
|
||||
rescue => e
|
||||
logger.info "Failed to merge #{uid_to_merge} into #{@user.uid}: #{e}"
|
||||
flash[:alert] = I18n.t("administrator.flash.merge_fail")
|
||||
else
|
||||
logger.info "#{current_user.uid} successfully merged #{uid_to_merge} into #{@user.uid}"
|
||||
flash[:success] = I18n.t("administrator.flash.merge_success")
|
||||
end
|
||||
|
||||
redirect_back fallback_location: admins_path
|
||||
end
|
||||
|
||||
# GET /admins/merge_list
|
||||
def merge_list
|
||||
# Returns a list of users that can merged into another user
|
||||
initial_list = User.without_role(:super_admin)
|
||||
.where.not(uid: current_user.uid)
|
||||
.merge_list_search(params[:search])
|
||||
|
||||
initial_list = initial_list.where(provider: @user_domain) if Rails.configuration.loadbalanced_configuration
|
||||
|
||||
# Respond with JSON object of users
|
||||
respond_to do |format|
|
||||
format.json { render body: initial_list.pluck_to_hash(:uid, :name, :email).to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# SITE SETTINGS
|
||||
|
||||
# POST /admins/update_settings
|
||||
def update_settings
|
||||
tab = params[:tab] || "settings"
|
||||
@settings.update_value(params[:setting], params[:value])
|
||||
|
||||
flash_message = I18n.t("administrator.flash.settings")
|
||||
|
||||
if params[:value] == "Default Recording Visibility"
|
||||
flash_message += ". #{I18n.t('administrator.site_settings.recording_visibility.warning')}"
|
||||
end
|
||||
|
||||
redirect_to admin_site_settings_path(tab: tab), flash: { success: flash_message }
|
||||
end
|
||||
|
||||
# POST /admins/color
|
||||
def coloring
|
||||
@settings.update_value("Primary Color", params[:value])
|
||||
@settings.update_value("Primary Color Lighten", color_lighten(params[:value]))
|
||||
@settings.update_value("Primary Color Darken", color_darken(params[:value]))
|
||||
redirect_to admin_site_settings_path(tab: "appearance"), flash: { success: I18n.t("administrator.flash.settings") }
|
||||
end
|
||||
|
||||
# POST /admins/registration_method/:method
|
||||
def registration_method
|
||||
new_method = Rails.configuration.registration_methods[params[:value].to_sym]
|
||||
|
||||
# Only allow change to Join by Invitation if user has emails enabled
|
||||
if !Rails.configuration.enable_email_verification && new_method == Rails.configuration.registration_methods[:invite]
|
||||
redirect_to admin_site_settings_path(tab: "settings"),
|
||||
flash: { alert: I18n.t("administrator.flash.invite_email_verification") }
|
||||
else
|
||||
@settings.update_value("Registration Method", new_method)
|
||||
redirect_to admin_site_settings_path(tab: "settings"),
|
||||
flash: { success: I18n.t("administrator.flash.registration_method_updated") }
|
||||
end
|
||||
end
|
||||
|
||||
# POST /admins/clear_auth
|
||||
def clear_auth
|
||||
User.include_deleted.where(provider: @user_domain).update_all(social_uid: nil)
|
||||
|
||||
redirect_to admin_site_settings_path(tab: "settings"), flash: { success: I18n.t("administrator.flash.settings") }
|
||||
end
|
||||
|
||||
# POST /admins/clear_cache
|
||||
def clear_cache
|
||||
Rails.cache.delete("#{@user_domain}/getUser")
|
||||
Rails.cache.delete("#{@user_domain}/getUserGreenlightCredentials")
|
||||
|
||||
redirect_to admin_site_settings_path(tab: "settings"), flash: { success: I18n.t("administrator.flash.settings") }
|
||||
end
|
||||
|
||||
# POST /admins/log_level
|
||||
def log_level
|
||||
Rails.logger.level = params[:value].to_i
|
||||
|
||||
redirect_to admin_site_settings_path(tab: "administration"), flash: { success: I18n.t("administrator.flash.settings") }
|
||||
end
|
||||
|
||||
# ROOM CONFIGURATION
|
||||
# POST /admins/update_room_configuration
|
||||
def update_room_configuration
|
||||
@settings.update_value(params[:setting], params[:value])
|
||||
|
||||
flash_message = I18n.t("administrator.flash.room_configuration")
|
||||
|
||||
redirect_to admin_room_configuration_path, flash: { success: flash_message }
|
||||
end
|
||||
|
||||
# ROLES
|
||||
|
||||
# GET /admins/roles
|
||||
def roles
|
||||
@roles = all_roles(params[:selected_role])
|
||||
end
|
||||
|
||||
# POST /admins/role
|
||||
# This method creates a new role scoped to the users provider
|
||||
def new_role
|
||||
new_role = create_role(params[:role][:name])
|
||||
|
||||
return redirect_to admin_roles_path, flash: { alert: I18n.t("administrator.roles.invalid_create") } if new_role.nil?
|
||||
|
||||
redirect_to admin_roles_path(selected_role: new_role.id)
|
||||
end
|
||||
|
||||
# PATCH /admin/roles/order
|
||||
# This updates the priority of a site's roles
|
||||
# Note: A lower priority role will always get used before a higher priority one
|
||||
def change_role_order
|
||||
unless update_priority(params[:role])
|
||||
redirect_to admin_roles_path, flash: { alert: I18n.t("administrator.roles.invalid_order") }
|
||||
end
|
||||
end
|
||||
|
||||
# POST /admin/role/:role_id
|
||||
# This method updates the permissions assigned to a role
|
||||
def update_role
|
||||
role = Role.find(params[:role_id])
|
||||
flash[:alert] = I18n.t("administrator.roles.invalid_update") unless update_permissions(role)
|
||||
redirect_to admin_roles_path(selected_role: role.id)
|
||||
end
|
||||
|
||||
# DELETE admins/role/:role_id
|
||||
# This deletes a role
|
||||
def delete_role
|
||||
role = Role.find(params[:role_id])
|
||||
|
||||
# Make sure no users are assigned to the role and the role isn't a reserved role
|
||||
# before deleting
|
||||
if role.users.count.positive?
|
||||
flash[:alert] = I18n.t("administrator.roles.role_has_users", user_count: role.users.count)
|
||||
return redirect_to admin_roles_path(selected_role: role.id)
|
||||
elsif Role::RESERVED_ROLE_NAMES.include?(role) || role.provider != @user_domain ||
|
||||
role.priority <= current_user.role.priority
|
||||
return redirect_to admin_roles_path(selected_role: role.id)
|
||||
else
|
||||
role.role_permissions.delete_all
|
||||
role.delete
|
||||
end
|
||||
|
||||
redirect_to admin_roles_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user
|
||||
@user = User.find_by(uid: params[:user_uid])
|
||||
end
|
||||
|
||||
def find_deleted_user
|
||||
@user = User.deleted.find_by(uid: params[:user_uid])
|
||||
end
|
||||
|
||||
# Verifies that admin is an administrator of the user in the action
|
||||
def verify_admin_of_user
|
||||
redirect_to admins_path,
|
||||
flash: { alert: I18n.t("administrator.flash.unauthorized") } unless current_user.admin_of?(@user, "can_manage_users")
|
||||
end
|
||||
|
||||
# Creates the invite if it doesn't exist, or updates the updated_at time if it does
|
||||
def create_or_update_invite(email)
|
||||
invite = Invitation.find_by(email: email, provider: @user_domain)
|
||||
|
||||
# Invite already exists
|
||||
if invite.present?
|
||||
# Updates updated_at to now
|
||||
invite.touch
|
||||
else
|
||||
# Creates invite
|
||||
invite = Invitation.create(email: email, provider: @user_domain)
|
||||
end
|
||||
|
||||
invite
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
|
@ -14,46 +16,301 @@
|
|||
# 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 'bigbluebutton_api'
|
||||
require 'digest/sha1'
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery with: :exception
|
||||
before_action :set_locale
|
||||
MEETING_NAME_LIMIT = 200
|
||||
USER_NAME_LIMIT = 100
|
||||
include BbbServer
|
||||
include Errors
|
||||
|
||||
def set_locale
|
||||
I18n.locale = http_accept_language.language_region_compatible_from(I18n.available_locales)
|
||||
before_action :block_unknown_hosts, :redirect_to_https, :set_user_domain, :set_user_settings, :maintenance_mode?,
|
||||
:migration_error?, :user_locale, :check_admin_password, :check_user_role
|
||||
|
||||
protect_from_forgery with: :exceptions
|
||||
|
||||
# Retrieves the current user.
|
||||
def current_user
|
||||
@current_user ||= User.includes(:role, :main_room).find_by(id: session[:user_id])
|
||||
|
||||
if Rails.configuration.loadbalanced_configuration && (@current_user && !@current_user.has_role?(:super_admin) &&
|
||||
@current_user.provider != @user_domain)
|
||||
@current_user = nil
|
||||
session.clear
|
||||
end
|
||||
|
||||
def current_user
|
||||
@current_user ||= User.find_by(id: session[:user_id])
|
||||
@current_user
|
||||
end
|
||||
helper_method :current_user
|
||||
|
||||
def bbb_server
|
||||
@bbb_server ||= Rails.configuration.loadbalanced_configuration ? bbb(@user_domain) : bbb("greenlight")
|
||||
end
|
||||
|
||||
# Block unknown hosts to mitigate host header injection attacks
|
||||
def block_unknown_hosts
|
||||
return if Rails.configuration.hosts.blank?
|
||||
raise UnsafeHostError, "#{request.host} is not a safe host" unless Rails.configuration.hosts.include?(request.host)
|
||||
end
|
||||
|
||||
# Force SSL
|
||||
def redirect_to_https
|
||||
if Rails.configuration.loadbalanced_configuration && request.headers["X-Forwarded-Proto"] == "http"
|
||||
redirect_to protocol: "https://"
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the user domain variable
|
||||
def set_user_domain
|
||||
if Rails.env.test? || !Rails.configuration.loadbalanced_configuration
|
||||
@user_domain = "greenlight"
|
||||
else
|
||||
@user_domain = parse_user_domain(request.host)
|
||||
|
||||
check_provider_exists
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the settinfs variable
|
||||
def set_user_settings
|
||||
@settings = Setting.includes(:features).find_or_create_by(provider: @user_domain)
|
||||
end
|
||||
|
||||
# Redirects the user to a Maintenance page if turned on
|
||||
def maintenance_mode?
|
||||
if ENV["MAINTENANCE_MODE"] == "true"
|
||||
render "errors/greenlight_error", status: 503, formats: :html,
|
||||
locals: {
|
||||
status_code: 503,
|
||||
message: I18n.t("errors.maintenance.message"),
|
||||
help: I18n.t("errors.maintenance.help"),
|
||||
}
|
||||
end
|
||||
|
||||
maintenance_string = @settings.get_value("Maintenance Banner").presence || Rails.configuration.maintenance_window
|
||||
if maintenance_string.present? && cookies[:maintenance_window] != maintenance_string
|
||||
flash.now[:maintenance] = maintenance_string
|
||||
end
|
||||
end
|
||||
|
||||
# Show an information page when migration fails and there is a version error.
|
||||
def migration_error?
|
||||
render :migration_error, status: 500 unless ENV["DB_MIGRATE_FAILED"].blank?
|
||||
end
|
||||
|
||||
# Determines proper locale to be used by calling user_locale with params based on if room owner exists
|
||||
def determine_locale(user)
|
||||
if user && user.language != 'default'
|
||||
user.language
|
||||
else
|
||||
Rails.configuration.default_locale.presence || http_accept_language.language_region_compatible_from(I18n.available_locales)
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the appropriate locale.
|
||||
def user_locale(user = current_user)
|
||||
locale = determine_locale(user)
|
||||
begin
|
||||
I18n.locale = locale.tr('-', '_') unless locale.nil?
|
||||
rescue
|
||||
# Default to English if there are any issues in language
|
||||
logger.error("Support: User locale is not supported (#{locale}")
|
||||
I18n.locale = "en"
|
||||
end
|
||||
end
|
||||
helper_method :user_locale
|
||||
|
||||
# Checks to make sure that the admin has changed his password from the default
|
||||
def check_admin_password
|
||||
if current_user&.has_role?(:admin) && current_user.email == "admin@example.com" &&
|
||||
current_user&.greenlight_account? && current_user&.authenticate(Rails.configuration.admin_password_default)
|
||||
|
||||
flash.now[:alert] = I18n.t("default_admin",
|
||||
edit_link: change_password_path(user_uid: current_user.uid)).html_safe
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the user is banned and logs him out if he is
|
||||
def check_user_role
|
||||
if current_user&.has_role? :denied
|
||||
session.delete(:user_id)
|
||||
redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") }
|
||||
elsif current_user&.has_role? :pending
|
||||
session.delete(:user_id)
|
||||
redirect_to root_path, flash: { alert: I18n.t("registration.approval.fail") }
|
||||
end
|
||||
end
|
||||
|
||||
# Relative root helper (when deploying to subdirectory).
|
||||
def relative_root
|
||||
Rails.configuration.relative_url_root || ""
|
||||
end
|
||||
helper_method :relative_root
|
||||
|
||||
def meeting_name_limit
|
||||
MEETING_NAME_LIMIT
|
||||
end
|
||||
helper_method :meeting_name_limit
|
||||
|
||||
def user_name_limit
|
||||
USER_NAME_LIMIT
|
||||
end
|
||||
helper_method :user_name_limit
|
||||
|
||||
# Determines if the BigBlueButton endpoint is configured (or set to default).
|
||||
def bigbluebutton_endpoint_default?
|
||||
return false if Rails.configuration.loadbalanced_configuration
|
||||
Rails.configuration.bigbluebutton_endpoint_default == Rails.configuration.bigbluebutton_endpoint
|
||||
end
|
||||
helper_method :bigbluebutton_endpoint_default?
|
||||
|
||||
def qrcode_generation_enabled?
|
||||
Rails.configuration.enable_qrcode_generation
|
||||
def allow_greenlight_accounts?
|
||||
return Rails.configuration.allow_user_signup unless Rails.configuration.loadbalanced_configuration
|
||||
return false unless @user_domain && !@user_domain.empty? && Rails.configuration.allow_user_signup
|
||||
return false if @user_domain == "greenlight"
|
||||
# Proceed with retrieving the provider info
|
||||
begin
|
||||
provider_info = retrieve_provider_info(@user_domain, 'api2', 'getUserGreenlightCredentials')
|
||||
provider_info['provider'] == 'greenlight'
|
||||
rescue => e
|
||||
logger.error "Error in checking if greenlight accounts are allowed: #{e}"
|
||||
false
|
||||
end
|
||||
end
|
||||
helper_method :allow_greenlight_accounts?
|
||||
|
||||
# Determine if Greenlight is configured to allow user signups.
|
||||
def allow_user_signup?
|
||||
Rails.configuration.allow_user_signup
|
||||
end
|
||||
helper_method :allow_user_signup?
|
||||
|
||||
# Gets all configured omniauth providers.
|
||||
def configured_providers
|
||||
Rails.configuration.providers.select do |provider|
|
||||
Rails.configuration.send("omniauth_#{provider}")
|
||||
end
|
||||
end
|
||||
helper_method :configured_providers
|
||||
|
||||
# Indicates whether users are allowed to share rooms
|
||||
def shared_access_allowed
|
||||
@settings.get_value("Shared Access") == "true"
|
||||
end
|
||||
helper_method :shared_access_allowed
|
||||
|
||||
# Indicates whether users should consent recoding when joining rooms
|
||||
def recording_consent_required?
|
||||
@settings.get_value("Require Recording Consent") == "true"
|
||||
end
|
||||
helper_method :recording_consent_required?
|
||||
|
||||
# Indicates whether users are allowed to add moderator access codes to rooms
|
||||
def moderator_code_allowed?
|
||||
@settings.get_value("Room Configuration Moderator Access Codes") == "optional"
|
||||
end
|
||||
helper_method :moderator_code_allowed?
|
||||
|
||||
# Returns a list of allowed file types
|
||||
def allowed_file_types
|
||||
Rails.configuration.allowed_file_types
|
||||
end
|
||||
helper_method :allowed_file_types
|
||||
|
||||
# Allows admins to edit a user's details
|
||||
def can_edit_user?(user_to_edit, editting_user)
|
||||
return user_to_edit.greenlight_account? if user_to_edit == editting_user
|
||||
|
||||
editting_user.admin_of?(user_to_edit, "can_manage_users")
|
||||
end
|
||||
helper_method :can_edit_user?
|
||||
|
||||
# Returns the page that the logo redirects to when clicked on
|
||||
def home_page
|
||||
return admins_path if current_user.has_role? :super_admin
|
||||
return current_user.main_room if current_user.role.get_permission("can_create_rooms")
|
||||
cant_create_rooms_path
|
||||
end
|
||||
helper_method :home_page
|
||||
|
||||
# Parses the url for the user domain
|
||||
def parse_user_domain(hostname)
|
||||
return hostname.split('.').first if Rails.configuration.url_host.empty?
|
||||
Rails.configuration.url_host.split(',').each do |url_host|
|
||||
return hostname.chomp(url_host).chomp('.') if hostname.include?(url_host)
|
||||
end
|
||||
''
|
||||
end
|
||||
|
||||
# Include user domain in lograge logs
|
||||
def append_info_to_payload(payload)
|
||||
super
|
||||
payload[:host] = @user_domain
|
||||
end
|
||||
|
||||
# Manually handle BigBlueButton errors
|
||||
rescue_from BigBlueButton::BigBlueButtonException do |ex|
|
||||
logger.error "BigBlueButtonException: #{ex}"
|
||||
render "errors/bigbluebutton_error"
|
||||
end
|
||||
|
||||
# Manually deal with 401 errors
|
||||
rescue_from CanCan::AccessDenied do |_exception|
|
||||
if current_user
|
||||
render "errors/greenlight_error"
|
||||
else
|
||||
# Store the current url as a cookie to redirect to after sigining in
|
||||
cookies[:return_to] = request.url
|
||||
|
||||
# Get the correct signin path
|
||||
path = if allow_greenlight_accounts?
|
||||
signin_path
|
||||
elsif Rails.configuration.loadbalanced_configuration
|
||||
"#{Rails.configuration.relative_url_root}/auth/bn_launcher"
|
||||
else
|
||||
signin_path
|
||||
end
|
||||
|
||||
redirect_to path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_provider_exists
|
||||
# Checks to see if the user exists
|
||||
begin
|
||||
# Check if the session has already checked that the user exists
|
||||
# and return true if they did for this domain
|
||||
return if session[:provider_exists] == @user_domain
|
||||
|
||||
retrieve_provider_info(@user_domain, 'api2', 'getUserGreenlightCredentials')
|
||||
|
||||
# Add a session variable if the provider exists
|
||||
session[:provider_exists] = @user_domain
|
||||
rescue => e
|
||||
logger.error "Error in retrieve provider info: #{e}"
|
||||
@hide_signin = true
|
||||
case e.message
|
||||
when "No user with that id exists"
|
||||
set_default_settings
|
||||
|
||||
render "errors/greenlight_error", locals: { message: I18n.t("errors.not_found.user_not_found.message"),
|
||||
help: I18n.t("errors.not_found.user_not_found.help") }
|
||||
when "Provider not included."
|
||||
set_default_settings
|
||||
|
||||
render "errors/greenlight_error", locals: { message: I18n.t("errors.not_found.user_missing.message"),
|
||||
help: I18n.t("errors.not_found.user_missing.help") }
|
||||
when "That user has no configured provider."
|
||||
if Setting.exists?(provider: @user_domain)
|
||||
# Keep the branding
|
||||
@settings = Setting.find_by(provider: @user_domain)
|
||||
else
|
||||
set_default_settings
|
||||
end
|
||||
|
||||
render "errors/greenlight_error", locals: { status_code: 501,
|
||||
message: I18n.t("errors.no_provider.message"),
|
||||
help: I18n.t("errors.no_provider.help") }
|
||||
else
|
||||
set_default_settings
|
||||
|
||||
render "errors/greenlight_error", locals: { status_code: 500, message: I18n.t("errors.internal.message"),
|
||||
help: I18n.t("errors.internal.help"), display_back: true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_default_settings
|
||||
# Use the default site settings
|
||||
@user_domain = "greenlight"
|
||||
@settings = Setting.find_or_create_by(provider: @user_domain)
|
||||
end
|
||||
helper_method :qrcode_generation_enabled?
|
||||
end
|
||||
|
|
|
@ -1,383 +0,0 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 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/>.
|
||||
|
||||
class BbbController < ApplicationController
|
||||
include BbbApi
|
||||
|
||||
before_action :authorize_recording_owner!, only: [:update_recordings, :delete_recordings]
|
||||
before_action :load_and_authorize_room_owner!, only: [:end]
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: :callback
|
||||
|
||||
# GET /:resource/:id/join
|
||||
# GET /:resource/:room_id/:id/join
|
||||
def join
|
||||
if params[:name].blank?
|
||||
return render_bbb_response(
|
||||
messageKey: "missing_parameter",
|
||||
message: "user name was not included",
|
||||
status: :unprocessable_entity
|
||||
)
|
||||
elsif params[:name].size > user_name_limit
|
||||
return render_bbb_response(
|
||||
messageKey: "invalid_parameter",
|
||||
message: "user name is too long",
|
||||
status: :unprocessable_entity
|
||||
)
|
||||
elsif params[:id].size > meeting_name_limit
|
||||
return render_bbb_response(
|
||||
messageKey: "invalid_parameter",
|
||||
message: "meeting name is too long",
|
||||
status: :unprocessable_entity
|
||||
)
|
||||
else
|
||||
if params[:room_id]
|
||||
user = User.find_by encrypted_id: params[:room_id]
|
||||
if !user
|
||||
return render_bbb_response(
|
||||
messageKey: "not_found",
|
||||
message: "User Not Found",
|
||||
status: :not_found
|
||||
)
|
||||
end
|
||||
|
||||
meeting_id = "#{params[:room_id]}-#{params[:id]}"
|
||||
meeting_name = params[:id]
|
||||
meeting_path = "#{URI.encode(params[:room_id])}/#{URI.encode(params[:id]).gsub('/','%2F')}"
|
||||
else
|
||||
user = User.find_by encrypted_id: params[:id]
|
||||
meeting_id = params[:id]
|
||||
meeting_path = URI.encode(meeting_id).gsub('/','%2F')
|
||||
end
|
||||
|
||||
options = if user
|
||||
{
|
||||
wait_for_moderator: true,
|
||||
meeting_recorded: true,
|
||||
meeting_name: meeting_name,
|
||||
room_owner: params[:room_id],
|
||||
user_is_moderator: current_user == user
|
||||
}
|
||||
else
|
||||
{
|
||||
user_is_moderator: true
|
||||
}
|
||||
end
|
||||
|
||||
base_url = "#{request.base_url}#{relative_root}/#{params[:resource]}/#{meeting_path}"
|
||||
options[:meeting_logout_url] = base_url
|
||||
options[:hook_url] = "#{base_url}/callback"
|
||||
options[:moderator_message] = t('moderator_default_message', url: "<a href=\"#{base_url}\" target=\"_blank\"><u>#{base_url}</u></a>")
|
||||
|
||||
bbb_res = bbb_join_url(
|
||||
meeting_id,
|
||||
params[:name],
|
||||
options
|
||||
)
|
||||
|
||||
# the user can join the meeting
|
||||
if user
|
||||
if bbb_res[:returncode] && current_user == user
|
||||
JoinMeetingJob.perform_later(user, params[:id], base_url)
|
||||
WaitingList.empty(options[:room_owner], options[:meeting_name])
|
||||
# the user can't join because they are on mobile and HTML5 is not enabled.
|
||||
elsif bbb_res[:messageKey] == 'unable_to_join'
|
||||
NotifyUserCantJoinJob.perform_later(user.encrypted_id, params[:id], params[:name])
|
||||
# user will be waiting for a moderator
|
||||
else
|
||||
NotifyUserWaitingJob.perform_later(user.encrypted_id, params[:id], params[:name])
|
||||
end
|
||||
end
|
||||
|
||||
render_bbb_response bbb_res, bbb_res[:response]
|
||||
end
|
||||
end
|
||||
|
||||
# POST /:resource/:room_id/:id/callback
|
||||
# Endpoint for webhook calls from BigBlueButton
|
||||
def callback
|
||||
# respond with 200 anyway so BigBlueButton knows the hook call was ok
|
||||
# but abort execution
|
||||
head(:ok) && return unless validate_checksum
|
||||
|
||||
begin
|
||||
event = params['event']
|
||||
data = event.is_a?(String) ? JSON.parse(params['event']) : event
|
||||
treat_callback_event(data)
|
||||
rescue Exception => e
|
||||
logger.error "Error parsing webhook data. Data: #{data}, exception: #{e.inspect}"
|
||||
|
||||
# respond with 200 anyway so BigBlueButton knows the hook call was ok
|
||||
head(:ok) && return
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /rooms/:room_id/:id/end
|
||||
def end
|
||||
load_and_authorize_room_owner!
|
||||
|
||||
bbb_res = bbb_end_meeting "#{@user.encrypted_id}-#{params[:id]}"
|
||||
if bbb_res[:returncode] || bbb_res[:status] == :not_found
|
||||
EndMeetingJob.perform_later(@user.encrypted_id, params[:id])
|
||||
bbb_res[:status] = :ok
|
||||
end
|
||||
render_bbb_response bbb_res
|
||||
end
|
||||
|
||||
# GET /rooms/:room_id/recordings
|
||||
# GET /rooms/:room_id/:id/recordings
|
||||
def recordings
|
||||
load_room!
|
||||
|
||||
# bbb_res = bbb_get_recordings "#{@user.encrypted_id}-#{params[:id]}"
|
||||
options = { "meta_room-id": @user.encrypted_id }
|
||||
if params[:id]
|
||||
options["meta_meeting-name"] = params[:id]
|
||||
end
|
||||
bbb_res = bbb_get_recordings(options)
|
||||
render_bbb_response bbb_res, bbb_res[:recordings]
|
||||
end
|
||||
|
||||
# PATCH /rooms/:room_id/recordings/:record_id
|
||||
# PATCH /rooms/:room_id/:id/recordings/:record_id
|
||||
def update_recordings
|
||||
published = params[:published] == 'true'
|
||||
metadata = params.select{ |k, v| k.match(/^meta_/) }
|
||||
bbb_res = bbb_update_recordings(params[:record_id], published, metadata)
|
||||
if bbb_res[:returncode]
|
||||
RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id])
|
||||
end
|
||||
render_bbb_response bbb_res
|
||||
end
|
||||
|
||||
# DELETE /rooms/:room_id/recordings/:record_id
|
||||
# DELETE /rooms/:room_id/:id/recordings/:record_id
|
||||
def delete_recordings
|
||||
recording = bbb_get_recordings({recordID: params[:record_id]})[:recordings].first
|
||||
bbb_res = bbb_delete_recordings(params[:record_id])
|
||||
if bbb_res[:returncode]
|
||||
RecordingDeletesJob.perform_later(@user.encrypted_id, params[:record_id], recording[:metadata][:"meeting-name"])
|
||||
end
|
||||
render_bbb_response bbb_res
|
||||
end
|
||||
|
||||
# POST /rooms/:room_id/recordings/:record_id
|
||||
# POST /rooms/:room_id/:id/recordings/:record_id
|
||||
def youtube_publish
|
||||
# If we can't get the client, then they don't have a Youtube account.
|
||||
begin
|
||||
client = Yt::Account.new(access_token: current_user.token)
|
||||
video = client.upload_video(get_webcams_url(params[:record_id]),
|
||||
title: params[:video_title],
|
||||
description: t('youtube_description', url: 'https://bigbluebutton.org/'),
|
||||
privacy_status: params[:privacy_status])
|
||||
rescue => e
|
||||
errors = e.response_body['error']['errors']
|
||||
# Many complications, start by getting them to refresh their token.
|
||||
if errors.length > 1
|
||||
redirect_url = user_login_url
|
||||
else
|
||||
error = errors[0]
|
||||
if error['message'] == "Unauthorized"
|
||||
redirect_url = 'https://m.youtube.com/create_channel'
|
||||
else
|
||||
# In this case, they don't have a youtube channel connected to their account, so prompt to create one.
|
||||
redirect_url = user_login_url
|
||||
end
|
||||
end
|
||||
end
|
||||
render json: {:url => redirect_url}
|
||||
end
|
||||
|
||||
# POST /rooms/:room_id/recordings/can_upload
|
||||
def can_upload
|
||||
# The recording is uploadable if it contains webcam data and they are logged in through Google.
|
||||
if Rails.configuration.enable_youtube_uploading == false then
|
||||
uploadable = 'uploading_disabled'
|
||||
elsif current_user.provider != 'google'
|
||||
uploadable = 'invalid_provider'
|
||||
else
|
||||
uploadable = (Faraday.head(get_webcams_url(params[:rec_id])).status == 200 && current_user.provider == 'google').to_s
|
||||
end
|
||||
render json: {:uploadable => uploadable}
|
||||
end
|
||||
|
||||
def get_webcams_url(recording_id)
|
||||
uri = URI.parse(Rails.configuration.bigbluebutton_endpoint)
|
||||
uri.scheme + '://' + uri.host + '/presentation/' + recording_id + '/video/webcams.webm'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_room!
|
||||
@user = User.find_by encrypted_id: params[:room_id]
|
||||
if !@user
|
||||
render head(:not_found) && return
|
||||
end
|
||||
end
|
||||
|
||||
def load_and_authorize_room_owner!
|
||||
load_room!
|
||||
|
||||
if !current_user || current_user != @user
|
||||
render head(:unauthorized) && return
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_recording_owner!
|
||||
load_and_authorize_room_owner!
|
||||
|
||||
recordings = bbb_get_recordings({recordID: params[:record_id]})[:recordings]
|
||||
recordings.each do |recording|
|
||||
if recording[:recordID] == params[:record_id]
|
||||
return true
|
||||
end
|
||||
end
|
||||
render head(:not_found) && return
|
||||
end
|
||||
|
||||
def render_bbb_response(bbb_res, response={})
|
||||
@messageKey = bbb_res[:messageKey]
|
||||
@message = bbb_res[:message]
|
||||
@status = bbb_res[:status]
|
||||
@response = response
|
||||
render status: @status
|
||||
end
|
||||
|
||||
def read_body(request)
|
||||
request.body.read.force_encoding("UTF-8")
|
||||
end
|
||||
|
||||
def treat_callback_event(event)
|
||||
# Check if the event is a BigBlueButton 2.0 event.
|
||||
if event.has_key?('envelope')
|
||||
eventName = (event.present? && event['envelope'].present?) ? event['envelope']['name'] : nil
|
||||
else # The event came from BigBlueButton 1.1 (or earlier).
|
||||
eventName = (event.present? && event['header'].present?) ? event['header']['name'] : nil
|
||||
end
|
||||
|
||||
# a recording is ready
|
||||
if eventName == "publish_ended"
|
||||
if event['payload'] && event['payload']['metadata'] && event['payload']['meeting_id']
|
||||
token = event['payload']['metadata'][META_TOKEN]
|
||||
room_id = event['payload']['metadata']['room-id']
|
||||
record_id = event['payload']['meeting_id']
|
||||
duration_data = event['payload']['duration']
|
||||
|
||||
# the webhook event doesn't have all the data we need, so we need
|
||||
# to send a getRecordings anyway
|
||||
# TODO: if the webhooks included all data in the event we wouldn't need this
|
||||
rec_info = bbb_get_recordings({recordID: record_id})
|
||||
rec_info = rec_info[:recordings].first
|
||||
RecordingCreatedJob.perform_later(token, room_id, parse_recording_for_view(rec_info))
|
||||
|
||||
rec_info[:duration] = duration_data.to_json
|
||||
|
||||
# send an email to the owner of this recording, if defined
|
||||
if Rails.configuration.mail_notifications
|
||||
owner = User.find_by(encrypted_id: room_id)
|
||||
RecordingReadyEmailJob.perform_later(owner, parse_recording_for_view(rec_info)) if owner.present?
|
||||
end
|
||||
else
|
||||
logger.error "Bad format for event #{event}, won't process"
|
||||
end
|
||||
elsif eventName == "meeting_created_message" || eventName == "MeetingCreatedEvtMsg"
|
||||
# Fire an Actioncable event that updates _previously_joined for the client.
|
||||
actioncable_event('create')
|
||||
elsif eventName == "meeting_destroyed_event" || eventName == "MeetingEndedEvtMsg"
|
||||
actioncable_event('destroy')
|
||||
|
||||
# Since the meeting is destroyed we have no way get the callback url to remove the meeting, so we must build it.
|
||||
remove_url = build_callback_url(params[:id], params[:room_id])
|
||||
|
||||
# Remove webhook for the meeting.
|
||||
webhook_remove(remove_url)
|
||||
elsif eventName == "user_joined_message"
|
||||
actioncable_event('join', {user_id: event['payload']['user']['extern_userid'], user: event['payload']['user']['name'], role: event['payload']['user']['role']})
|
||||
elsif eventName == "UserJoinedMeetingEvtMsg"
|
||||
actioncable_event('join', {user_id: event['core']['body']['intId'], user: event['core']['body']['name'], role: event['core']['body']['role']})
|
||||
elsif eventName == "user_left_message"
|
||||
actioncable_event('leave', {user_id: event['payload']['user']['extern_userid']})
|
||||
elsif eventName == "UserLeftMeetingEvtMsg"
|
||||
actioncable_event('leave', {user_id: event['core']['body']['intId']})
|
||||
else
|
||||
logger.info "Callback event will not be treated. Event name: #{eventName}"
|
||||
end
|
||||
|
||||
render head(:ok) && return
|
||||
end
|
||||
|
||||
def build_callback_url(id, room_id)
|
||||
"#{request.base_url}#{relative_root}/rooms/#{room_id}/#{URI.encode(id)}/callback"
|
||||
end
|
||||
|
||||
def actioncable_event(method, data = {})
|
||||
data = {method: method, meeting: params[:id], room: params[:room_id]}.merge(data)
|
||||
ActionCable.server.broadcast('refresh_meetings', data)
|
||||
end
|
||||
|
||||
# Validates the checksum received in a callback call.
|
||||
# If the checksum doesn't match, renders an ok and aborts execution.
|
||||
def validate_checksum
|
||||
secret = ENV['BIGBLUEBUTTON_SECRET']
|
||||
checksum = params["checksum"]
|
||||
return false unless checksum
|
||||
|
||||
# Message is only encoded if it comes from the bbb-webhooks node application.
|
||||
# The post process script does not encode it's response body.
|
||||
begin
|
||||
# Decode and break the body into parts.
|
||||
parts = URI.decode_www_form(read_body(request))
|
||||
|
||||
# Convert the data into the correct checksum format, replace ruby hash arrows.
|
||||
converted_data = {parts[0][0]=>parts[0][1],parts[1][0]=>parts[1][1].to_i}.to_s.gsub!('=>', ':')
|
||||
|
||||
# Manually remove the space between the two elements.
|
||||
converted_data[converted_data.rindex("timestamp") - 2] = ''
|
||||
callback_url = uri_remove_param(request.original_url, "checksum")
|
||||
checksum_str = "#{callback_url}#{converted_data}#{secret}"
|
||||
rescue
|
||||
# Data was not recieved encoded (sent from post process script).
|
||||
data = read_body(request)
|
||||
callback_url = uri_remove_param(request.original_url, "checksum")
|
||||
checksum_str = "#{callback_url}#{data}#{secret}"
|
||||
end
|
||||
|
||||
calculated_checksum = Digest::SHA1.hexdigest(checksum_str)
|
||||
|
||||
if calculated_checksum != checksum
|
||||
logger.error "Checksum did not match. Calculated: #{calculated_checksum}, received: #{checksum}"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Removes parameters from an URI
|
||||
def uri_remove_param(uri, params = nil)
|
||||
return uri unless params
|
||||
params = Array(params)
|
||||
uri_parsed = URI.parse(uri)
|
||||
return uri unless uri_parsed.query
|
||||
new_params = uri_parsed.query.gsub(/&/, '&').split('&').reject { |q| params.include?(q.split('=').first) }
|
||||
uri = uri.split('?').first
|
||||
if new_params.count > 0
|
||||
"#{uri}?#{new_params.join('&')}"
|
||||
else
|
||||
uri
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,130 @@
|
|||
# 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/>.
|
||||
|
||||
module Authenticator
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Logs a user into GreenLight.
|
||||
def login(user)
|
||||
migrate_twitter_user(user)
|
||||
|
||||
session[:user_id] = user.id
|
||||
user.update(last_login: Time.zone.now)
|
||||
|
||||
logger.info("Support: #{user.email} has successfully logged in.")
|
||||
|
||||
# If there are not terms, or the user has accepted them, check for email verification
|
||||
if !Rails.configuration.terms || user.accepted_terms
|
||||
check_email_verified(user)
|
||||
else
|
||||
redirect_to terms_path
|
||||
end
|
||||
end
|
||||
|
||||
# If email verification is disabled, or the user has verified, go to their room
|
||||
def check_email_verified(user)
|
||||
# Admin users should be redirected to the admin page
|
||||
if user.has_role? :super_admin
|
||||
redirect_to admins_path
|
||||
elsif user.activated?
|
||||
# Dont redirect to any of these urls
|
||||
dont_redirect_to = [root_url, signin_url, ldap_signin_url, ldap_callback_url, signup_url, unauthorized_url,
|
||||
internal_error_url, not_found_url]
|
||||
|
||||
unless ENV['OAUTH2_REDIRECT'].nil?
|
||||
dont_redirect_to.push(File.join(ENV['OAUTH2_REDIRECT'], "auth", "openid_connect", "callback"))
|
||||
end
|
||||
|
||||
url = if cookies[:return_to] && !dont_redirect_to.include?(cookies[:return_to])
|
||||
cookies[:return_to]
|
||||
elsif user.role.get_permission("can_create_rooms")
|
||||
user.main_room
|
||||
else
|
||||
cant_create_rooms_path
|
||||
end
|
||||
|
||||
# Delete the cookie if it exists
|
||||
cookies.delete :return_to if cookies[:return_to]
|
||||
|
||||
redirect_to url
|
||||
else
|
||||
session[:user_id] = nil
|
||||
user.create_activation_token
|
||||
redirect_to account_activation_path(digest: user.activation_digest)
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_unauthenticated_except_twitter
|
||||
redirect_to current_user.main_room || root_path if current_user && params[:old_twitter_user_id].nil?
|
||||
end
|
||||
|
||||
# Logs current user out of GreenLight.
|
||||
def logout
|
||||
session.delete(:user_id) if current_user
|
||||
end
|
||||
|
||||
# Check if the user is using local accounts
|
||||
def auth_changed_to_local?(user)
|
||||
Rails.configuration.loadbalanced_configuration && user.social_uid.present? && allow_greenlight_accounts?
|
||||
end
|
||||
|
||||
# Check if the user exists under the same email with no social uid and that social accounts are allowed
|
||||
def auth_changed_to_social?(email)
|
||||
Rails.configuration.loadbalanced_configuration &&
|
||||
User.exists?(email: email, provider: @user_domain, social_uid: nil) &&
|
||||
!allow_greenlight_accounts?
|
||||
end
|
||||
|
||||
# Sets the initial user role based on the email mapping
|
||||
def initial_user_role(email)
|
||||
mapping = @settings.get_value("Email Mapping")
|
||||
return "user" unless mapping.present?
|
||||
|
||||
mapping.split(",").each do |map|
|
||||
email_role = map.split("=")
|
||||
return email_role[1] if email.ends_with?(email_role[0])
|
||||
end
|
||||
|
||||
"user" # default to user if role not found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Migrates all of the twitter users rooms to the new account
|
||||
def migrate_twitter_user(user)
|
||||
if !session["old_twitter_user_id"].nil? && user.provider != "twitter"
|
||||
old_user = User.find(session["old_twitter_user_id"])
|
||||
|
||||
old_user.rooms.each do |room|
|
||||
room.owner = user
|
||||
|
||||
room.name = "Old #{room.name}" if room.id == old_user.main_room.id
|
||||
|
||||
room.save!
|
||||
end
|
||||
|
||||
# Query for the old user again so the migrated rooms don't get deleted
|
||||
old_user.reload
|
||||
old_user.destroy!
|
||||
|
||||
session["old_twitter_user_id"] = nil
|
||||
|
||||
flash[:success] = I18n.t("registration.deprecated.merge_success")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,146 @@
|
|||
# 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 'bigbluebutton_api'
|
||||
|
||||
module BbbServer
|
||||
extend ActiveSupport::Concern
|
||||
include BbbApi
|
||||
|
||||
META_LISTED = "gl-listed"
|
||||
|
||||
# Checks if a room is running on the BigBlueButton server.
|
||||
def room_running?(bbb_id)
|
||||
bbb_server.is_meeting_running?(bbb_id)
|
||||
end
|
||||
|
||||
# Returns a list of all running meetings
|
||||
def all_running_meetings
|
||||
bbb_server.get_meetings
|
||||
end
|
||||
|
||||
def get_recordings(meeting_id)
|
||||
bbb_server.get_recordings(meetingID: meeting_id)
|
||||
end
|
||||
|
||||
def get_multiple_recordings(meeting_ids)
|
||||
bbb_server.get_recordings(meetingID: meeting_ids)
|
||||
end
|
||||
|
||||
# Returns a URL to join a user into a meeting.
|
||||
def join_path(room, name, options = {}, uid = nil)
|
||||
# Create the meeting, even if it's running
|
||||
start_session(room, options)
|
||||
|
||||
# Determine the password to use when joining.
|
||||
password = options[:user_is_moderator] ? room.moderator_pw : room.attendee_pw
|
||||
|
||||
# Generate the join URL.
|
||||
join_opts = {}
|
||||
join_opts[:userID] = uid if uid
|
||||
join_opts[:join_via_html5] = true
|
||||
join_opts[:avatarURL] = options[:avatarURL] if options[:avatarURL].present?
|
||||
join_opts[:createTime] = room.last_session.to_datetime.strftime("%Q") if room.last_session
|
||||
|
||||
bbb_server.join_meeting_url(room.bbb_id, name, password, join_opts)
|
||||
end
|
||||
|
||||
# Creates a meeting on the BigBlueButton server.
|
||||
def start_session(room, options = {})
|
||||
create_options = {
|
||||
record: options[:record].to_s,
|
||||
logoutURL: options[:meeting_logout_url] || '',
|
||||
moderatorPW: room.moderator_pw,
|
||||
attendeePW: room.attendee_pw,
|
||||
moderatorOnlyMessage: options[:moderator_message],
|
||||
"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]
|
||||
}
|
||||
|
||||
create_options[:muteOnStart] = options[:mute_on_start] if options[:mute_on_start]
|
||||
create_options[:guestPolicy] = "ASK_MODERATOR" if options[:require_moderator_approval]
|
||||
|
||||
# Send the create request.
|
||||
begin
|
||||
meeting = if room.presentation.attached?
|
||||
modules = BigBlueButton::BigBlueButtonModules.new
|
||||
url = rails_blob_url(room.presentation).gsub("&", "%26")
|
||||
logger.info("Support: Room #{room.uid} starting using presentation: #{url}")
|
||||
modules.add_presentation(:url, url)
|
||||
bbb_server.create_meeting(room.name, room.bbb_id, create_options, modules)
|
||||
else
|
||||
bbb_server.create_meeting(room.name, room.bbb_id, create_options)
|
||||
end
|
||||
|
||||
unless meeting[:messageKey] == 'duplicateWarning'
|
||||
room.update_attributes(sessions: room.sessions + 1, last_session: DateTime.strptime(meeting[:createTime].to_s, "%Q"))
|
||||
end
|
||||
rescue BigBlueButton::BigBlueButtonException => e
|
||||
puts "BigBlueButton failed on create: #{e.key}: #{e.message}"
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the number of recordings for this room
|
||||
def recording_count(bbb_id)
|
||||
bbb_server.get_recordings(meetingID: bbb_id)[:recordings].length
|
||||
end
|
||||
|
||||
# Update a recording from a room
|
||||
def update_recording(record_id, meta)
|
||||
meta[:recordID] = record_id
|
||||
bbb_server.send_api_request("updateRecordings", meta)
|
||||
end
|
||||
|
||||
# Update a recording from a room
|
||||
def publish_recording(record_id)
|
||||
bbb_server.publish_recordings(record_id, true)
|
||||
end
|
||||
|
||||
# Update a recording from a room
|
||||
def unpublish_recording(record_id)
|
||||
bbb_server.publish_recordings(record_id, false)
|
||||
end
|
||||
|
||||
# Protect a recording
|
||||
def protect_recording(record_id, meta = {})
|
||||
meta[:recordID] = record_id
|
||||
meta[:protect] = true
|
||||
bbb_server.send_api_request("updateRecordings", meta)
|
||||
end
|
||||
|
||||
# Unprotect a recording
|
||||
def unprotect_recording(record_id, meta = {})
|
||||
meta[:recordID] = record_id
|
||||
meta[:protect] = false
|
||||
bbb_server.send_api_request("updateRecordings", meta)
|
||||
end
|
||||
|
||||
# Deletes a recording from a room.
|
||||
def delete_recording(record_id)
|
||||
bbb_server.delete_recordings(record_id)
|
||||
end
|
||||
|
||||
# Deletes all recordings associated with the room.
|
||||
def delete_all_recordings(bbb_id)
|
||||
record_ids = bbb_server.get_recordings(meetingID: bbb_id)[:recordings].pluck(:recordID)
|
||||
bbb_server.delete_recordings(record_ids) unless record_ids.empty?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,150 @@
|
|||
# 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/>.
|
||||
|
||||
module Emailer
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Sends account activation email.
|
||||
def send_activation_email(user, token)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
|
||||
UserMailer.verify_email(user, user_verification_link(token), @settings).deliver
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
else
|
||||
flash[:success] = I18n.t("email_sent", email_type: t("verify.verification"))
|
||||
end
|
||||
end
|
||||
|
||||
# Sends password reset email.
|
||||
def send_password_reset_email(user, token)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
|
||||
UserMailer.password_reset(user, reset_link(token), @settings).deliver_now
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
else
|
||||
flash[:success] = I18n.t("email_sent", email_type: t("reset_password.subtitle"))
|
||||
end
|
||||
end
|
||||
|
||||
def send_user_promoted_email(user, role)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
|
||||
UserMailer.user_promoted(user, role, root_url, @settings).deliver_now
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
end
|
||||
end
|
||||
|
||||
def send_user_demoted_email(user, role)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
|
||||
UserMailer.user_demoted(user, role, root_url, @settings).deliver_now
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
end
|
||||
end
|
||||
|
||||
# Sends inivitation to join
|
||||
def send_invitation_email(name, email, invite)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
|
||||
UserMailer.invite_email(name, email, invite.updated_at, invitation_link(invite.invite_token), @settings).deliver_now
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
end
|
||||
end
|
||||
|
||||
def send_user_approved_email(user)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
|
||||
UserMailer.approve_user(user, root_url, @settings).deliver_now
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
else
|
||||
flash[:success] = I18n.t("email_sent", email_type: t("verify.verification"))
|
||||
end
|
||||
end
|
||||
|
||||
def send_approval_user_signup_email(user)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
admin_emails = admin_emails()
|
||||
UserMailer.approval_user_signup(user, admins_url(tab: "pending"),
|
||||
admin_emails, @settings).deliver_now unless admin_emails.empty?
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
end
|
||||
end
|
||||
|
||||
def send_invite_user_signup_email(user)
|
||||
begin
|
||||
return unless Rails.configuration.enable_email_verification
|
||||
|
||||
admin_emails = admin_emails()
|
||||
UserMailer.invite_user_signup(user, admins_url, admin_emails, @settings).deliver_now unless admin_emails.empty?
|
||||
rescue => e
|
||||
logger.error "Support: Error in email delivery: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the link the user needs to click to verify their account
|
||||
def user_verification_link(token)
|
||||
edit_account_activation_url(token: token)
|
||||
end
|
||||
|
||||
def admin_emails
|
||||
roles = Role.where(provider: @user_domain, role_permissions: { name: "can_manage_users", value: "true" })
|
||||
.pluck(:name)
|
||||
|
||||
admins = User.with_role(roles - ["super_admin"])
|
||||
|
||||
admins = admins.where(provider: @user_domain) if Rails.configuration.loadbalanced_configuration
|
||||
|
||||
admins.collect(&:email).join(",")
|
||||
end
|
||||
|
||||
def reset_link(token)
|
||||
edit_password_reset_url(token)
|
||||
end
|
||||
|
||||
def invitation_link(token)
|
||||
if allow_greenlight_accounts?
|
||||
signup_url(invite_token: token)
|
||||
else
|
||||
root_url(invite_token: token)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Errors
|
||||
class UnsafeHostError < StandardError; end
|
||||
end
|