first commit
29
.dockerignore
Normal file
@@ -0,0 +1,29 @@
|
||||
# git
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
SECURITY.md
|
||||
.github
|
||||
|
||||
# Rails
|
||||
.env
|
||||
*.rbc
|
||||
capybara-*.html
|
||||
.rspec
|
||||
log
|
||||
tmp
|
||||
/db/**/*.sqlite3
|
||||
/db/**/*.sqlite3-journal
|
||||
/db/production
|
||||
/db/production-postgres
|
||||
public/assets
|
||||
public/b
|
||||
coverage/
|
||||
.rvmrc
|
||||
vendor/bundle
|
||||
.bundle
|
||||
Dockerfile
|
||||
.gitlab-ci.yml
|
||||
.rubocop.yml
|
||||
spec
|
||||
test
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Greenlight Setup
|
||||
url: https://groups.google.com/g/bigbluebutton-greenlight
|
||||
about: Get help with the installation, setup and configuration of Greenlight.
|
||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for Greenlight
|
||||
title: ''
|
||||
labels: 'feature request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for Greenlight related issues.
|
||||
Search for existing feature requests to avoid creating duplicates.-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
34
.github/ISSUE_TEMPLATE/greenlight-issue.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Report a Bug
|
||||
about: Template for creating an issue with Greenlight
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for Greenlight related issues.
|
||||
Search for existing issues to avoid creating duplicates.-->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -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. -->
|
||||
95
.github/workflows/ci.build.prerelease.yml
vendored
Normal file
@@ -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
|
||||
105
.github/workflows/ci.build.push.yml
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
env:
|
||||
RUBY_VERSION: 2.7
|
||||
|
||||
name: CI Build Push
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- v1
|
||||
- v2
|
||||
- translations*
|
||||
- snyk*
|
||||
|
||||
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') && github.ref != 'refs/heads/master'
|
||||
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 with amazonlinux
|
||||
- name: Alternate Amazon Linux Build and Push
|
||||
if: contains(env.DOCKER_BUILD_ENABLED, 'true') && contains(env.DOCKER_BUILD_ALTERNATE_ENABLED, 'true') && github.ref != 'refs/heads/master'
|
||||
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
|
||||
110
.github/workflows/ci.build.release.yml
vendored
Normal file
@@ -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 Version
|
||||
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: 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_version.outputs.tag}} | 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_version.outputs.tag}} | 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 }}:v${{ steps.ci_tag_release_version.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=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 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 }}: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 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 }}: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
|
||||
71
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
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
|
||||
- v1
|
||||
- v2
|
||||
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
|
||||
|
||||
- name: Run RSpec
|
||||
run: bundle exec rspec
|
||||
26
.github/workflows/mirror.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Mirroring
|
||||
|
||||
on: [push, delete]
|
||||
|
||||
jobs:
|
||||
to_codecommit:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CODECOMMIT_MIRROR_ENABLED: ${{ secrets.CODECOMMIT_MIRROR_ENABLED }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: CodeCommit Mirroring
|
||||
if: contains(env.CODECOMMIT_MIRROR_ENABLED, 'true')
|
||||
uses: pixta-dev/repository-mirroring-action@v1
|
||||
with:
|
||||
target_repo_url:
|
||||
${{ secrets.CODECOMMIT_TARGET_REPO_URL }}
|
||||
ssh_private_key:
|
||||
${{ secrets.CODECOMMIT_SSH_PRIVATE_KEY }}
|
||||
ssh_username:
|
||||
${{ secrets.CODECOMMIT_SSH_PRIVATE_KEY_ID }}
|
||||
|
||||
53
.gitignore
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore the default SQLite database.
|
||||
/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
|
||||
!/log/.keep
|
||||
|
||||
# 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
|
||||
|
||||
# Needed for rspec's --only-failures command
|
||||
spec/failures.txt
|
||||
71
.gitlab-ci.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
before_script:
|
||||
|
||||
test:
|
||||
stage: test
|
||||
image: ruby:2.5
|
||||
script:
|
||||
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
|
||||
- bundle config path vendor/bundle
|
||||
- bundle install --jobs 4 --retry 3
|
||||
- bundle exec rake db:create RAILS_ENV=test
|
||||
- bundle exec rake test & bundle exec rspec & bundle exec rubocop --parallel
|
||||
cache:
|
||||
paths:
|
||||
- vendor/bundle
|
||||
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
|
||||
42
.rake_tasks~
Normal file
@@ -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
|
||||
191
.rubocop.yml
Normal file
@@ -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: 65
|
||||
|
||||
# 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
|
||||
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
||||
2.7.6
|
||||
77
Dockerfile
Normal file
@@ -0,0 +1,77 @@
|
||||
FROM ruby:2.7.7-alpine3.16 AS base
|
||||
|
||||
# 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"
|
||||
|
||||
# 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
|
||||
RUN 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 base
|
||||
|
||||
# Set a variable for the install location.
|
||||
ARG RAILS_ROOT=/usr/src/app
|
||||
ARG PACKAGES="tzdata curl postgresql-client sqlite-libs yarn nodejs bash gcompat"
|
||||
|
||||
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
|
||||
|
||||
|
||||
COPY --from=base $RAILS_ROOT $RAILS_ROOT
|
||||
|
||||
# Expose port 80.
|
||||
EXPOSE 80
|
||||
|
||||
# Sets the footer of greenlight application with current build version
|
||||
ARG version_code
|
||||
ENV VERSION_CODE=$version_code
|
||||
|
||||
# Set executable permission to start file
|
||||
RUN chmod +x bin/start
|
||||
# Update HTTPClient cacert.pem with the latest Mozilla cacert.pem
|
||||
RUN wget https://curl.se/ca/cacert.pem https://curl.se/ca/cacert.pem.sha256 -P /tmp
|
||||
RUN cd /tmp && sha256sum cacert.pem > cacert.pem.sha256sum && cd ${RAILS_ROOT}
|
||||
RUN diff /tmp/cacert.pem.sha256sum /tmp/cacert.pem.sha256
|
||||
RUN mv -v /tmp/cacert.pem $(bundle info httpclient --path)/lib/httpclient/ && rm -v /tmp/cacert*
|
||||
|
||||
# Update Openssl certs [This is for Faraday adapter for Net::HTTP]
|
||||
RUN [[ $(id -u) -eq 0 ]] && update-ca-certificates
|
||||
# Start the application.
|
||||
CMD ["bin/start"]
|
||||
80
Gemfile
Normal file
@@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
git_source(:github) do |repo_name|
|
||||
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
||||
"https://github.com/#{repo_name}.git"
|
||||
end
|
||||
|
||||
# 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', '~> 1.9', '>= 1.9.0'
|
||||
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.5'
|
||||
gem 'jquery-rails', '~> 4.4.0'
|
||||
gem 'local_time', '~> 2.1.0'
|
||||
gem 'net-ldap', '~> 0.17.0'
|
||||
gem 'omniauth', '~> 2.1.0'
|
||||
gem 'omniauth-bn-launcher', '~> 0.1.5'
|
||||
gem 'omniauth-bn-office365', '~> 0.1.2'
|
||||
gem 'omniauth-google-oauth2', '~> 1.0.1'
|
||||
gem 'omniauth_openid_connect', '~> 0.4.0'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0.1'
|
||||
gem 'omniauth-twitter', '~> 1.4.0'
|
||||
gem 'pagy', '~> 3.11.0'
|
||||
gem 'pluck_to_hash', '~> 1.0.2'
|
||||
gem 'puma', '~> 4.3.12'
|
||||
gem 'rails', '~> 5.2.8.1'
|
||||
gem 'random_password', '~> 0.1.1'
|
||||
gem "recaptcha", '~> 5.7.0'
|
||||
gem 'redcarpet', '~> 3.5.1'
|
||||
gem 'remote_syslog_logger', '~> 1.0.4'
|
||||
gem 'repost', '~> 0.3.8'
|
||||
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.5'
|
||||
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.8', '>= 2.8.1'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'action-cable-testing', '~> 0.6', '>= 0.6.1'
|
||||
gem "factory_bot_rails", "~> 6.2", ">= 6.2.0"
|
||||
gem 'faker', '~> 2.16'
|
||||
gem 'rails-controller-testing', '~> 1.0', '>= 1.0.5'
|
||||
gem 'rspec-rails', '~> 3.9', '>= 3.9.1'
|
||||
gem 'shoulda-matchers', '~> 3.1', '>= 3.1.3'
|
||||
gem 'webmock', '~> 3.11'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'listen', '~> 3.0'
|
||||
gem 'spring', '~> 2.1'
|
||||
gem 'spring-watcher-listen', '~> 2.0'
|
||||
gem 'web-console', '~> 3.7', '>= 3.7.0'
|
||||
end
|
||||
557
Gemfile.lock
Normal file
@@ -0,0 +1,557 @@
|
||||
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)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
action-cable-testing (0.6.1)
|
||||
actioncable (>= 5.0)
|
||||
actioncable (5.2.8.1)
|
||||
actionpack (= 5.2.8.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.8.1)
|
||||
actionpack (= 5.2.8.1)
|
||||
actionview (= 5.2.8.1)
|
||||
activejob (= 5.2.8.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.8.1)
|
||||
actionview (= 5.2.8.1)
|
||||
activesupport (= 5.2.8.1)
|
||||
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.2.8.1)
|
||||
activesupport (= 5.2.8.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
activejob (5.2.8.1)
|
||||
activesupport (= 5.2.8.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.8.1)
|
||||
activesupport (= 5.2.8.1)
|
||||
activerecord (5.2.8.1)
|
||||
activemodel (= 5.2.8.1)
|
||||
activesupport (= 5.2.8.1)
|
||||
arel (>= 9.0)
|
||||
activestorage (5.2.8.1)
|
||||
actionpack (= 5.2.8.1)
|
||||
activerecord (= 5.2.8.1)
|
||||
marcel (~> 1.0.0)
|
||||
activesupport (5.2.8.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
arel (9.0.0)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.1)
|
||||
autoprefixer-rails (10.4.7.0)
|
||||
execjs (~> 2)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.592.0)
|
||||
aws-sdk-core (3.131.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.57.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.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.5.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
bcrypt (3.1.18)
|
||||
bigbluebutton-api-ruby (1.9.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)
|
||||
bindata (2.4.13)
|
||||
bindex (0.8.1)
|
||||
bn-ldap-authentication (0.1.4)
|
||||
net-ldap (~> 0)
|
||||
bootsnap (1.7.7)
|
||||
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.1.0)
|
||||
concurrent-ruby (1.1.10)
|
||||
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)
|
||||
diff-lcs (1.5.0)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
docile (1.4.0)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
erubi (1.11.0)
|
||||
execjs (2.8.1)
|
||||
factory_bot (6.2.1)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (2.21.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.2)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
ffi (1.15.5)
|
||||
font-awesome-sass (5.9.0)
|
||||
sassc (>= 1.11)
|
||||
globalid (1.0.1)
|
||||
activesupport (>= 5.0)
|
||||
google-apis-core (0.5.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.10.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.14.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.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.17.1)
|
||||
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.15)
|
||||
hashdiff (1.0.1)
|
||||
hashie (5.0.0)
|
||||
hiredis (0.6.3)
|
||||
http_accept_language (2.1.1)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-language-mapping (0.1.3.1)
|
||||
jbuilder (2.11.5)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.6.1)
|
||||
jquery-rails (4.4.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.6.2)
|
||||
json-jwt (1.15.3)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
httpclient
|
||||
jwt (2.5.0)
|
||||
listen (3.7.1)
|
||||
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.19.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (1.0.2)
|
||||
memoist (0.16.2)
|
||||
method_source (1.0.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.17.0)
|
||||
msgpack (1.5.1)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.2.3)
|
||||
net-ldap (0.17.0)
|
||||
net-protocol (0.1.3)
|
||||
timeout
|
||||
net-smtp (0.3.2)
|
||||
net-protocol
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.10)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
oauth (1.1.0)
|
||||
oauth-tty (~> 1.0, >= 1.0.1)
|
||||
snaky_hash (~> 2.0)
|
||||
version_gem (~> 1.1)
|
||||
oauth-tty (1.0.5)
|
||||
version_gem (~> 1.1, >= 1.1.1)
|
||||
oauth2 (1.4.11)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 4)
|
||||
omniauth (2.1.0)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 2.2.3)
|
||||
rack-protection
|
||||
omniauth-bn-launcher (0.1.5)
|
||||
omniauth (~> 2.1, >= 2.1.0)
|
||||
omniauth-oauth2 (= 1.7.2)
|
||||
omniauth-bn-office365 (0.1.2)
|
||||
omniauth (~> 2.1, >= 2.1.0)
|
||||
omniauth-oauth2 (= 1.7.2)
|
||||
omniauth-google-oauth2 (1.0.1)
|
||||
jwt (>= 2.0)
|
||||
oauth2 (~> 1.1)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-oauth2 (~> 1.7.1)
|
||||
omniauth-oauth (1.2.0)
|
||||
oauth
|
||||
omniauth (>= 1.0, < 3)
|
||||
omniauth-oauth2 (1.7.2)
|
||||
oauth2 (~> 1.4)
|
||||
omniauth (>= 1.9, < 3)
|
||||
omniauth-rails_csrf_protection (1.0.1)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
omniauth_openid_connect (0.4.0)
|
||||
addressable (~> 2.5)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 1.1)
|
||||
openid_connect (1.4.2)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
json-jwt (>= 1.15.0)
|
||||
net-smtp
|
||||
rack-oauth2 (~> 1.21)
|
||||
swd (~> 1.3)
|
||||
tzinfo
|
||||
validate_email
|
||||
validate_url
|
||||
webfinger (~> 1.2)
|
||||
os (1.1.4)
|
||||
pagy (3.11.0)
|
||||
parallel (1.22.1)
|
||||
parser (3.1.2.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.1)
|
||||
public_suffix (5.0.0)
|
||||
puma (4.3.12)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.6.1)
|
||||
rack (2.2.6.2)
|
||||
rack-oauth2 (1.21.3)
|
||||
activesupport
|
||||
attr_required
|
||||
httpclient
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (3.0.2)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
rack (>= 1.3)
|
||||
rails (5.2.8.1)
|
||||
actioncable (= 5.2.8.1)
|
||||
actionmailer (= 5.2.8.1)
|
||||
actionpack (= 5.2.8.1)
|
||||
actionview (= 5.2.8.1)
|
||||
activejob (= 5.2.8.1)
|
||||
activemodel (= 5.2.8.1)
|
||||
activerecord (= 5.2.8.1)
|
||||
activestorage (= 5.2.8.1)
|
||||
activesupport (= 5.2.8.1)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.2.8.1)
|
||||
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.4.4)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
railties (5.2.8.1)
|
||||
actionpack (= 5.2.8.1)
|
||||
activesupport (= 5.2.8.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
random_password (0.1.1)
|
||||
rb-fsevent (0.11.1)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
recaptcha (5.7.0)
|
||||
json
|
||||
redcarpet (3.5.1)
|
||||
redis (4.2.5)
|
||||
regexp_parser (2.4.0)
|
||||
remote_syslog_logger (1.0.4)
|
||||
syslog_protocol
|
||||
repost (0.3.8)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
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.18.0)
|
||||
parser (>= 3.1.1.0)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
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.16.1)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.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)
|
||||
snaky_hash (2.0.1)
|
||||
hashie
|
||||
version_gem (~> 1.1, >= 1.1.1)
|
||||
spring (2.1.1)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.3.13)
|
||||
swd (1.3.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.2.1)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.11)
|
||||
timeout (0.3.0)
|
||||
tins (1.31.1)
|
||||
sync
|
||||
trailblazer-option (0.1.2)
|
||||
turbolinks (5.2.1)
|
||||
turbolinks-source (~> 5.2)
|
||||
turbolinks-source (5.2.0)
|
||||
tzinfo (1.2.10)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2021.5)
|
||||
tzinfo (>= 1.0.0)
|
||||
uber (0.1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (2.1.0)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
version_gem (1.1.1)
|
||||
web-console (3.7.0)
|
||||
actionview (>= 5.0)
|
||||
activemodel (>= 5.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 5.0)
|
||||
webfinger (1.2.0)
|
||||
activesupport
|
||||
httpclient (>= 2.4)
|
||||
webmock (3.14.0)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.7.0)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xml-simple (1.1.9)
|
||||
rexml
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
action-cable-testing (~> 0.6, >= 0.6.1)
|
||||
aws-sdk-s3 (~> 1.88.1)
|
||||
bcrypt (~> 3.1.7)
|
||||
bigbluebutton-api-ruby (~> 1.9, >= 1.9.0)
|
||||
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.8, >= 2.8.1)
|
||||
factory_bot_rails (~> 6.2, >= 6.2.0)
|
||||
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.5)
|
||||
jquery-rails (~> 4.4.0)
|
||||
listen (~> 3.0)
|
||||
local_time (~> 2.1.0)
|
||||
lograge (~> 0.11.2)
|
||||
net-ldap (~> 0.17.0)
|
||||
omniauth (~> 2.1.0)
|
||||
omniauth-bn-launcher (~> 0.1.5)
|
||||
omniauth-bn-office365 (~> 0.1.2)
|
||||
omniauth-google-oauth2 (~> 1.0.1)
|
||||
omniauth-rails_csrf_protection (~> 1.0.1)
|
||||
omniauth-twitter (~> 1.4.0)
|
||||
omniauth_openid_connect (~> 0.4.0)
|
||||
pagy (~> 3.11.0)
|
||||
pg (~> 0.18)
|
||||
pluck_to_hash (~> 1.0.2)
|
||||
puma (~> 4.3.12)
|
||||
rails (~> 5.2.8.1)
|
||||
rails-controller-testing (~> 1.0, >= 1.0.5)
|
||||
random_password (~> 0.1.1)
|
||||
recaptcha (~> 5.7.0)
|
||||
redcarpet (~> 3.5.1)
|
||||
redis (~> 4.2.5)
|
||||
remote_syslog_logger (~> 1.0.4)
|
||||
repost (~> 0.3.8)
|
||||
rspec-rails (~> 3.9, >= 3.9.1)
|
||||
rubocop (~> 1.10.0)
|
||||
sassc-rails (~> 2.1.2)
|
||||
sequel (~> 5.41.0)
|
||||
shoulda-matchers (~> 3.1, >= 3.1.3)
|
||||
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.5)
|
||||
uglifier (~> 4.2.0)
|
||||
web-console (~> 3.7, >= 3.7.0)
|
||||
webmock (~> 3.11)
|
||||
165
LICENSE
Normal file
@@ -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.
|
||||
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
<img width="1012" alt="bbb-greenlight-banner" src="https://user-images.githubusercontent.com/1273169/141143584-684766cf-8633-4d66-b35e-f134a368e4c8.png">
|
||||
|
||||
|
||||
# Greenlight
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||
* 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.
|
||||
|
||||
Interested? Try Greenlight out on our [demo server](https://demo.bigbluebutton.org/gl)!
|
||||
|
||||
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).
|
||||
|
||||
For a overview of how Greenlight works, checkout our Introduction to Greenlight Video:
|
||||
|
||||
[](https://youtu.be/Hso8yLzkqj8)
|
||||
|
||||
## Installation on a BigBlueButton Server
|
||||
|
||||
Greenlight is designed to work on a [BigBlueButton 2.0](https://github.com/bigbluebutton/bigbluebutton) (or later) server.
|
||||
|
||||
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.
|
||||
|
||||
## Source Code & Contributing
|
||||
|
||||
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 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.
|
||||
15
Rakefile
Normal file
@@ -0,0 +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
|
||||
23
SECURITY.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We actively support Greenlight through the community forums and through security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| v1.x | :x: |
|
||||
| v2.x | :white_check_mark: |
|
||||
| > v2.x | :x: |
|
||||
|
||||
Since we released v2 to the community all our support efforts are now transitioned to the subsequent releases v2.x.
|
||||
|
||||
All the releases are managed as a Cloud version where release tags are used only as a reference. As such, we highly recommend that all administrators deploy the latest available release.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vunerability in 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 with as much detail as possible.
|
||||
|
||||
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.
|
||||
|
||||
Regards,... [BigBlueButton Team](https://docs.bigbluebutton.org/support/faq.html#bigbluebutton-committer)
|
||||
19
app/assets/config/manifest.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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/>.
|
||||
|
||||
//= link_tree ../images
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
||||
93
app/assets/fonts/SourceSansPro/OFL.txt
Normal file
@@ -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.
|
||||
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf
Normal file
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-BoldItalic.ttf
Normal file
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf
Normal file
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-Light.ttf
Normal file
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-LightItalic.ttf
Normal file
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf
Normal file
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-SemiBold.ttf
Normal file
BIN
app/assets/fonts/SourceSansPro/SourceSansPro-SemiBoldItalic.ttf
Normal file
0
app/assets/images/.keep
Normal file
BIN
app/assets/images/background.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
app/assets/images/bbb_logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/assets/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/assets/images/google-logo.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
app/assets/images/landing/bbb.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
app/assets/images/landing/mobile.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
app/assets/images/landing/world.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
app/assets/images/ldap-logo.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
app/assets/images/logo_with_text.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
app/assets/images/office365-logo.jpeg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
app/assets/images/openid-logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
app/assets/images/placeholder-user.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/assets/images/twitter-logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/assets/images/windows-logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
328
app/assets/javascripts/admins.js
Normal file
@@ -0,0 +1,328 @@
|
||||
// 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"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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());
|
||||
});
|
||||
}
|
||||
}
|
||||
37
app/assets/javascripts/application.js
Normal file
@@ -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/>.
|
||||
|
||||
// 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 jquery3
|
||||
//= require tabler
|
||||
//= require tabler.plugins
|
||||
//= require jquery_ujs
|
||||
//= require pickr.min.js
|
||||
//= require bootstrap-select.min.js
|
||||
//= require local-time
|
||||
//= require_tree .
|
||||
32
app/assets/javascripts/cable.js
Normal file
@@ -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/>.
|
||||
|
||||
// Action Cable provides the framework to deal with WebSockets in Rails.
|
||||
// You can generate new channels where WebSocket features live using the rails generate channel command.
|
||||
//
|
||||
//= require action_cable
|
||||
//= require_self
|
||||
//= require_tree ./channels
|
||||
|
||||
(function() {
|
||||
var protocol = (window.location.protocol === "https:" ? "wss://" : "ws://");
|
||||
var host = window.GreenLight.WEBSOCKET_HOST || window.location.host + window.GreenLight.RELATIVE_ROOT;
|
||||
var url = protocol + host + '/cable';
|
||||
|
||||
this.App || (this.App = {});
|
||||
|
||||
App.cable = ActionCable.createConsumer(url);
|
||||
}).call(this);
|
||||
0
app/assets/javascripts/channels/.keep
Normal file
37
app/assets/javascripts/cookies.js
Normal file
@@ -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() + ";"
|
||||
})
|
||||
})
|
||||
57
app/assets/javascripts/delete.js
Normal file
@@ -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")
|
||||
}
|
||||
})
|
||||
})
|
||||
36
app/assets/javascripts/header.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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(){
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
51
app/assets/javascripts/main.js
Normal file
@@ -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
|
||||
}
|
||||
48
app/assets/javascripts/recording.js
Normal file
@@ -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"))
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
162
app/assets/javascripts/rename.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
517
app/assets/javascripts/room.js
Normal file
@@ -0,0 +1,517 @@
|
||||
// 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) {
|
||||
|
||||
$("#user-list").html("") // Clear current inputs
|
||||
|
||||
users.forEach(function(user) {
|
||||
|
||||
listName = document.createElement("li"),
|
||||
spanAvatar = document.createElement("span"),
|
||||
spanName = document.createElement("span"),
|
||||
spanUid = document.createElement("span"),
|
||||
spanRemove = document.createElement("span"),
|
||||
spanRemoveIcon = document.createElement("i");
|
||||
|
||||
listName.setAttribute('class', 'list-group-item text-left')
|
||||
listName.setAttribute('data-uid', user.uid)
|
||||
spanAvatar.innerText = user.name.charAt(0)
|
||||
spanAvatar.setAttribute('class', 'avatar float-left mr-2')
|
||||
spanName.setAttribute('class', 'shared-user')
|
||||
spanName.innerText = user.name
|
||||
spanUid.setAttribute('class', 'text-muted ml-1')
|
||||
spanUid.innerText = user.uid
|
||||
spanRemove.setAttribute('class', 'text-primary float-right shared-user cursor-pointer')
|
||||
spanRemove.setAttribute('onclick', 'removeSharedUser(this)')
|
||||
spanRemoveIcon.setAttribute('class', 'fas fa-times')
|
||||
|
||||
listName.appendChild(spanAvatar)
|
||||
listName.appendChild(spanName)
|
||||
spanName.appendChild(spanUid)
|
||||
listName.appendChild(spanRemove)
|
||||
spanRemove.appendChild(spanRemoveIcon)
|
||||
|
||||
$("#user-list").append(listName)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
})
|
||||
}
|
||||
103
app/assets/javascripts/search.js
Normal file
@@ -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
|
||||
}
|
||||
42
app/assets/javascripts/settings.js
Normal file
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
105
app/assets/javascripts/sort.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
46
app/assets/javascripts/user_edit.js
Normal file
@@ -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'))
|
||||
})
|
||||
}
|
||||
})
|
||||
67
app/assets/javascripts/wait.js
Normal file
@@ -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
|
||||
}
|
||||
81
app/assets/stylesheets/_tabler-custom.scss
Normal file
@@ -0,0 +1,81 @@
|
||||
/* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
*
|
||||
* Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@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";
|
||||
105
app/assets/stylesheets/admins.scss
Normal file
@@ -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;
|
||||
}
|
||||
192
app/assets/stylesheets/application.scss
Normal file
@@ -0,0 +1,192 @@
|
||||
/* 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 "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;
|
||||
}
|
||||
20
app/assets/stylesheets/errors.scss
Normal file
@@ -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/
|
||||
178
app/assets/stylesheets/main.scss
Executable file
@@ -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;
|
||||
}
|
||||
146
app/assets/stylesheets/rooms.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
66
app/assets/stylesheets/sessions.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
32
app/assets/stylesheets/users.scss
Normal file
@@ -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);
|
||||
}
|
||||
504
app/assets/stylesheets/utilities/_fonts.scss
Normal file
@@ -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;
|
||||
}
|
||||
24
app/assets/stylesheets/utilities/_variables.scss
Normal file
@@ -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;
|
||||
22
app/channels/application_cable/channel.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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 ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
||||
22
app/channels/application_cable/connection.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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 ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
end
|
||||
end
|
||||
23
app/channels/waiting_channel.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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 WaitingChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from "#{params[:roomuid]}_waiting_channel"
|
||||
end
|
||||
end
|
||||
78
app/controllers/account_activations_controller.rb
Normal file
@@ -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
|
||||
379
app/controllers/admins_controller.rb
Normal file
@@ -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
|
||||
335
app/controllers/application_controller.rb
Normal file
@@ -0,0 +1,335 @@
|
||||
# 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 ApplicationController < ActionController::Base
|
||||
include BbbServer
|
||||
include Errors
|
||||
|
||||
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)
|
||||
reset_session
|
||||
return nil # This stops the session validation checks for loadbalanced configurations.
|
||||
end
|
||||
# For backward compatibility and for seamless integration with existing and running deployments:
|
||||
# The active sessions will be declared as active on first interaction after the update.
|
||||
|
||||
# This keeps alive the already active sessions before the upgrade for accounts having no password updates.
|
||||
session[:activated_at] ||= Time.zone.now.to_i if @current_user&.last_pwd_update.nil?
|
||||
|
||||
# Once a request is issued back to the server with a session that had been active before
|
||||
# the last password update it will automatically get invalidated and the request will get
|
||||
# redirected back to the root path.
|
||||
# This solves #3086.
|
||||
unless session[:activated_at].to_i >= @current_user&.last_pwd_update.to_i
|
||||
# For backward compatibility and for seamless integration with existing and running deployments:
|
||||
# The last_pwd_update attribute will default to nil and nil.to_i will always be 0.
|
||||
# This with the activated_at fallback to the first connection after the upgrade will result in
|
||||
# keeping alive old sessions and ensuring a seamless intergation.
|
||||
# In cases where the account has a password update after the upgrade, all of old the active sessions
|
||||
# which haven't updated their state and all the other active updated sessions before the password
|
||||
# update event will be cought and declared as invalid where users will get unauthenticated and redirected to root path.
|
||||
reset_session
|
||||
redirect_to root_path, flash: { alert: I18n.t("session.expired") } and return
|
||||
end
|
||||
@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
|
||||
|
||||
# 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 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
|
||||
end
|
||||
0
app/controllers/concerns/.keep
Normal file
133
app/controllers/concerns/authenticator.rb
Normal file
@@ -0,0 +1,133 @@
|
||||
# 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
|
||||
# Upon login the session metadata activated_at has to match to login event timestamp.
|
||||
session[:activated_at] = user.last_login.to_i if user.without_terms_acceptance { 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
|
||||
|
||||
valid_url = cookies[:return_to] && URI.parse(cookies[:return_to]).host == URI.parse(request.original_url).host
|
||||
|
||||
url = if cookies[:return_to] && valid_url && !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)
|
||||
return true if Rails.configuration.social_switching
|
||||
|
||||
Rails.configuration.loadbalanced_configuration &&
|
||||
User.exists?(email: email, provider: @user_domain) &&
|
||||
!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
|
||||
146
app/controllers/concerns/bbb_server.rb
Normal file
@@ -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
|
||||
150
app/controllers/concerns/emailer.rb
Normal file
@@ -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
|
||||
5
app/controllers/concerns/errors.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Errors
|
||||
class UnsafeHostError < StandardError; end
|
||||
end
|
||||
122
app/controllers/concerns/joiner.rb
Normal file
@@ -0,0 +1,122 @@
|
||||
# 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 Joiner
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Displays the join room page to the user
|
||||
def show_user_join
|
||||
# Get users name
|
||||
@name = if current_user
|
||||
current_user.name
|
||||
elsif cookies.encrypted[:greenlight_name]
|
||||
cookies.encrypted[:greenlight_name]
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
@search, @order_column, @order_direction, pub_recs =
|
||||
public_recordings(@room.bbb_id, params.permit(:search, :column, :direction), true)
|
||||
|
||||
@pagy, @public_recordings = pagy_array(pub_recs)
|
||||
|
||||
render :join
|
||||
end
|
||||
|
||||
# create or update cookie to track the three most recent rooms a user joined
|
||||
def save_recent_rooms
|
||||
if current_user
|
||||
recently_joined_rooms = cookies.encrypted["#{current_user.uid}_recently_joined_rooms"].to_a
|
||||
cookies.encrypted["#{current_user.uid}_recently_joined_rooms"] =
|
||||
recently_joined_rooms.prepend(@room.id).uniq[0..2]
|
||||
end
|
||||
end
|
||||
|
||||
def valid_avatar?(url)
|
||||
return false if URI::DEFAULT_PARSER.make_regexp(%w[http https]).match(url).nil?
|
||||
uri = URI(url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true if uri.scheme == 'https'
|
||||
response = http.request_head(uri)
|
||||
return false if response.code != "200"
|
||||
response['content-length'].to_i < Rails.configuration.max_avatar_size
|
||||
end
|
||||
|
||||
def join_room(opts)
|
||||
@room_settings = JSON.parse(@room[:room_settings])
|
||||
|
||||
moderator_privileges = @room.owned_by?(current_user) || valid_moderator_access_code(session[:moderator_access_code])
|
||||
if room_running?(@room.bbb_id) || room_setting_with_config("anyoneCanStart") || moderator_privileges
|
||||
|
||||
# Determine if the user needs to join as a moderator.
|
||||
opts[:user_is_moderator] = room_setting_with_config("joinModerator") || @shared_room || moderator_privileges
|
||||
opts[:record] = record_meeting
|
||||
opts[:require_moderator_approval] = room_setting_with_config("requireModeratorApproval")
|
||||
opts[:mute_on_start] = room_setting_with_config("muteOnStart")
|
||||
|
||||
if current_user
|
||||
redirect_to join_path(@room, current_user.name, opts, current_user.uid)
|
||||
else
|
||||
join_name = params[:join_name] || params[@room.invite_path][:join_name]
|
||||
|
||||
redirect_to join_path(@room, join_name, opts, fetch_guest_id)
|
||||
end
|
||||
else
|
||||
search_params = params[@room.invite_path] || params
|
||||
@search, @order_column, @order_direction, pub_recs =
|
||||
public_recordings(@room.bbb_id, search_params.permit(:search, :column, :direction), true)
|
||||
|
||||
@pagy, @public_recordings = pagy_array(pub_recs)
|
||||
|
||||
# They need to wait until the meeting begins.
|
||||
render :wait
|
||||
end
|
||||
end
|
||||
|
||||
def incorrect_user_domain
|
||||
Rails.configuration.loadbalanced_configuration && @room.owner.provider != @user_domain
|
||||
end
|
||||
|
||||
# Default, unconfigured meeting options.
|
||||
def default_meeting_options
|
||||
moderator_message = "#{I18n.t('invite_message')}<br> #{request.base_url + room_path(@room)}"
|
||||
moderator_message += "<br> #{I18n.t('modal.create_room.access_code')}: #{@room.access_code}" if @room.access_code.present?
|
||||
{
|
||||
user_is_moderator: false,
|
||||
meeting_logout_url: request.base_url + logout_room_path(@room),
|
||||
moderator_message: moderator_message,
|
||||
host: request.host,
|
||||
recording_default_visibility: @settings.get_value("Default Recording Visibility") == "public"
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_guest_id
|
||||
return cookies[:guest_id] if cookies[:guest_id].present?
|
||||
|
||||
guest_id = "gl-guest-#{SecureRandom.hex(12)}"
|
||||
|
||||
cookies[:guest_id] = {
|
||||
value: guest_id,
|
||||
expires: 1.day.from_now
|
||||
}
|
||||
|
||||
guest_id
|
||||
end
|
||||
end
|
||||
121
app/controllers/concerns/populator.rb
Normal file
@@ -0,0 +1,121 @@
|
||||
# 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 Populator
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Returns a list of users that are in the same context of the current user
|
||||
def manage_users_list
|
||||
initial_list = case @tab
|
||||
when "active"
|
||||
User.without_role([:pending, :denied])
|
||||
when "deleted"
|
||||
User.deleted
|
||||
when "pending"
|
||||
User.with_role(:pending)
|
||||
when "denied"
|
||||
User.with_role(:denied)
|
||||
else
|
||||
User.all
|
||||
end
|
||||
|
||||
initial_list = initial_list.with_role(@role.name) if @role.present?
|
||||
|
||||
initial_list = initial_list.without_role(:super_admin)
|
||||
|
||||
initial_list = initial_list.where(provider: @user_domain) if Rails.configuration.loadbalanced_configuration
|
||||
|
||||
initial_list.where.not(id: current_user.id)
|
||||
.admins_search(@search)
|
||||
.admins_order(@order_column, @order_direction)
|
||||
end
|
||||
|
||||
# Returns a list of rooms that are in the same context of the current user
|
||||
def server_rooms_list
|
||||
if Rails.configuration.loadbalanced_configuration
|
||||
Room.includes(:owner).where(users: { provider: @user_domain })
|
||||
.admins_search(@search)
|
||||
.admins_order(@order_column, @order_direction, @running_room_bbb_ids)
|
||||
else
|
||||
Room.includes(:owner).admins_search(@search).admins_order(@order_column, @order_direction, @running_room_bbb_ids)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the correct recordings based on the users inputs
|
||||
def recordings_to_show(user = nil, room = nil)
|
||||
if user.present?
|
||||
# Find user and get his recordings
|
||||
rooms = User.find_by(email: user)&.rooms&.pluck(:bbb_id)
|
||||
return all_recordings(rooms) if user.present? && !rooms.nil?
|
||||
|
||||
[] # return no recs if room not found
|
||||
elsif room.present?
|
||||
# Find room and get its recordings
|
||||
room = Room.find_by(uid: room)&.bbb_id
|
||||
return all_recordings([room]) if room.present?
|
||||
|
||||
[]
|
||||
else
|
||||
latest_recordings
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a list off all current invitations
|
||||
def invited_users_list
|
||||
list = if Rails.configuration.loadbalanced_configuration
|
||||
Invitation.where(provider: @user_domain)
|
||||
else
|
||||
Invitation.all
|
||||
end
|
||||
|
||||
list.admins_search(@search).order(updated_at: :desc)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns exactly 1 page of the latest recordings
|
||||
def latest_recordings
|
||||
return_length = Rails.configuration.pagination_rows
|
||||
number_of_rooms = Rails.configuration.pagination_number
|
||||
recordings = []
|
||||
counter = 0
|
||||
|
||||
# Manually paginate through the rooms
|
||||
while recordings.length < return_length
|
||||
rooms = if Rails.configuration.loadbalanced_configuration
|
||||
Room.includes(:owner)
|
||||
.where(users: { provider: @user_domain })
|
||||
.order(last_session: :desc)
|
||||
.limit(number_of_rooms)
|
||||
.offset(counter * number_of_rooms)
|
||||
.pluck(:bbb_id)
|
||||
else
|
||||
Room.order(last_session: :desc)
|
||||
.limit(return_length)
|
||||
.offset(counter * return_length)
|
||||
.pluck(:bbb_id)
|
||||
end
|
||||
|
||||
break if rooms.blank?
|
||||
counter += 1
|
||||
recordings.push(*all_recordings(rooms))
|
||||
end
|
||||
|
||||
recordings[0..return_length]
|
||||
end
|
||||
end
|
||||
132
app/controllers/concerns/recorder.rb
Normal file
@@ -0,0 +1,132 @@
|
||||
# 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 Recorder
|
||||
extend ActiveSupport::Concern
|
||||
include RecordingsHelper
|
||||
|
||||
# Fetches all recordings for a room.
|
||||
def recordings(room_bbb_id, search_params = {}, ret_search_params = false)
|
||||
res = get_recordings(room_bbb_id)
|
||||
|
||||
format_recordings(res, search_params, ret_search_params)
|
||||
end
|
||||
|
||||
# Fetches a rooms public recordings.
|
||||
def public_recordings(room_bbb_id, search_params = {}, ret_search_params = false)
|
||||
search, order_col, order_dir, recs = recordings(room_bbb_id, search_params, ret_search_params)
|
||||
[search, order_col, order_dir, recs.select { |r| r[:metadata][:"gl-listed"] == "true" }]
|
||||
end
|
||||
|
||||
# Makes paginated API calls to get recordings
|
||||
def all_recordings(room_bbb_ids, search_params = {}, ret_search_params = false, search_name = false)
|
||||
res = { recordings: [] }
|
||||
|
||||
until room_bbb_ids.empty?
|
||||
# bbb.get_recordings returns an object
|
||||
# take only the array portion of the object that is returned
|
||||
full_res = get_multiple_recordings(room_bbb_ids.pop(Rails.configuration.pagination_number))
|
||||
res[:recordings].push(*full_res[:recordings])
|
||||
end
|
||||
|
||||
format_recordings(res, search_params, ret_search_params, search_name)
|
||||
end
|
||||
|
||||
# Format, filter, and sort recordings to match their current use in the app
|
||||
def format_recordings(api_res, search_params, ret_search_params, search_name = false)
|
||||
search = search_params[:search] || ""
|
||||
order_col = search_params[:column] && search_params[:direction] != "none" ? search_params[:column] : "end_time"
|
||||
order_dir = search_params[:column] && search_params[:direction] != "none" ? search_params[:direction] : "desc"
|
||||
|
||||
search = search.downcase
|
||||
|
||||
api_res[:recordings].each do |r|
|
||||
next if r.key?(:error)
|
||||
# Format playbacks in a more pleasant way.
|
||||
r[:playbacks] = if !r[:playback] || !r[:playback][:format]
|
||||
[]
|
||||
elsif r[:playback][:format].is_a?(Array)
|
||||
r[:playback][:format]
|
||||
else
|
||||
[r[:playback][:format]]
|
||||
end
|
||||
r.delete(:playback)
|
||||
end
|
||||
|
||||
recs = filter_recordings(api_res, search, search_name)
|
||||
recs = sort_recordings(recs, order_col, order_dir)
|
||||
|
||||
if ret_search_params
|
||||
[search, order_col, order_dir, recs]
|
||||
else
|
||||
recs
|
||||
end
|
||||
end
|
||||
|
||||
def filter_recordings(api_res, search, search_name = false)
|
||||
api_res[:recordings].select do |r|
|
||||
(!r[:metadata].nil? && ((!r[:metadata][:name].nil? &&
|
||||
r[:metadata][:name].downcase.include?(search)) ||
|
||||
(r[:metadata][:"gl-listed"] == "true" && search == "public") ||
|
||||
(r[:metadata][:"gl-listed"] == "false" && search == "unlisted"))) ||
|
||||
((r[:metadata].nil? || r[:metadata][:name].nil?) &&
|
||||
r[:name].downcase.include?(search)) ||
|
||||
r[:participants].include?(search) ||
|
||||
!r[:playbacks].select { |p| p[:type].downcase.include?(search) }.empty? ||
|
||||
(search_name && recording_owner(r[:meetingID]).downcase.include?(search))
|
||||
end
|
||||
end
|
||||
|
||||
def sort_recordings(recs, order_col, order_dir)
|
||||
recs = case order_col
|
||||
when "end_time"
|
||||
recs.sort_by { |r| r[:endTime] }
|
||||
when "name"
|
||||
recs.sort_by do |r|
|
||||
if !r[:metadata].nil? && !r[:metadata][:name].nil?
|
||||
r[:metadata][:name].downcase
|
||||
else
|
||||
r[:name].downcase
|
||||
end
|
||||
end
|
||||
when "length"
|
||||
recs.sort_by { |r| r[:playbacks].reject { |p| p[:type] == "statistics" }.first[:length] }
|
||||
when "users"
|
||||
recs.sort_by { |r| r[:participants] }
|
||||
when "visibility"
|
||||
recs.sort_by { |r| r[:metadata][:"gl-listed"] }
|
||||
when "formats"
|
||||
recs.sort_by { |r| r[:playbacks].first[:type].downcase }
|
||||
else
|
||||
recs.sort_by { |r| r[:endTime] }
|
||||
end
|
||||
|
||||
if order_dir == 'asc'
|
||||
recs
|
||||
else
|
||||
recs.reverse
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Gets the email of the room owner to which the recording belongs to
|
||||
def recording_owner(room_id)
|
||||
Room.find_by(bbb_id: room_id).owner.email.presence || Room.find_by(bbb_id: room_id).owner.username
|
||||
end
|
||||
end
|
||||
75
app/controllers/concerns/registrar.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
# 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 Registrar
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def approval_registration
|
||||
@settings.get_value("Registration Method") == Rails.configuration.registration_methods[:approval]
|
||||
end
|
||||
|
||||
def invite_registration
|
||||
@settings.get_value("Registration Method") == Rails.configuration.registration_methods[:invite]
|
||||
end
|
||||
|
||||
# Returns a hash containing whether the user has been invited and if they
|
||||
# signed up with the same email that they were invited with
|
||||
def check_user_invited(email, token, domain)
|
||||
return { present: true, verified: false } unless invite_registration
|
||||
return { present: false, verified: false } if token.nil?
|
||||
|
||||
invite = Invitation.valid.find_by(invite_token: token, provider: domain)
|
||||
if invite.present?
|
||||
# Check if they used the same email to sign up
|
||||
same_email = email.casecmp(invite.email).zero?
|
||||
invite.destroy
|
||||
{ present: true, verified: same_email }
|
||||
else
|
||||
{ present: false, verified: false }
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the user passes the requirements to be invited
|
||||
def passes_invite_reqs
|
||||
# check if user needs to be invited and IS invited
|
||||
invitation = check_user_invited(@user.email, session[:invite_token], @user_domain)
|
||||
|
||||
@user.email_verified = true if invitation[:verified]
|
||||
|
||||
invitation[:present]
|
||||
end
|
||||
|
||||
# Add validation errors to model if they exist
|
||||
def valid_user_or_captcha
|
||||
valid_user = @user.valid?
|
||||
valid_captcha = Rails.configuration.recaptcha_enabled ? verify_recaptcha(model: @user) : true
|
||||
|
||||
logger.error("Support: #{@user.email} creation failed: User params are not valid.") unless valid_user
|
||||
|
||||
valid_user && valid_captcha
|
||||
end
|
||||
|
||||
# Checks if the user trying to sign in with twitter account
|
||||
def check_if_twitter_account(log_out = false)
|
||||
unless params[:old_twitter_user_id].nil? && session[:old_twitter_user_id].nil?
|
||||
logout if log_out
|
||||
flash.now[:alert] = I18n.t("registration.deprecated.new_signin")
|
||||
session[:old_twitter_user_id] = params[:old_twitter_user_id] unless params[:old_twitter_user_id].nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
164
app/controllers/concerns/rolify.rb
Normal file
@@ -0,0 +1,164 @@
|
||||
# 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 Rolify
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Gets all roles
|
||||
def all_roles(selected_role)
|
||||
@roles = Role.editable_roles(@user_domain).by_priority
|
||||
|
||||
if @roles.count.zero?
|
||||
Role.create_default_roles(@user_domain)
|
||||
@roles = Role.editable_roles(@user_domain)
|
||||
end
|
||||
|
||||
@selected_role = if selected_role.nil?
|
||||
@roles.find_by(name: 'user')
|
||||
else
|
||||
@roles.find(selected_role)
|
||||
end
|
||||
|
||||
@roles
|
||||
end
|
||||
|
||||
# Creates a new role
|
||||
def create_role(new_role_name)
|
||||
# Make sure that the role name isn't a duplicate or a reserved name like super_admin or empty
|
||||
return nil if Role.duplicate_name(new_role_name, @user_domain) || new_role_name.strip.empty?
|
||||
|
||||
Role.create_new_role(new_role_name, @user_domain)
|
||||
end
|
||||
|
||||
# Updates a user's roles
|
||||
def update_roles(role_id)
|
||||
return true if role_id.blank?
|
||||
# Check to make sure user can edit roles
|
||||
return false unless current_user.role.get_permission("can_manage_users")
|
||||
|
||||
return true if @user.role_id == role_id.to_i
|
||||
|
||||
new_role = Role.find_by(id: role_id, provider: @user_domain)
|
||||
# Return false if new role doesn't exist
|
||||
return false if new_role.nil?
|
||||
|
||||
return false if new_role.priority < current_user.role.priority
|
||||
|
||||
# Send promoted/demoted emails
|
||||
send_user_promoted_email(@user, new_role) if new_role.get_permission("send_promoted_email")
|
||||
|
||||
@user.set_role(new_role.name)
|
||||
end
|
||||
|
||||
# Updates a roles priority
|
||||
def update_priority(role_to_update)
|
||||
user_role = Role.find_by(name: "user", provider: @user_domain)
|
||||
admin_role = Role.find_by(name: "admin", provider: @user_domain)
|
||||
|
||||
current_user_role = current_user.role
|
||||
|
||||
# Users aren't allowed to update the priority of the admin or user roles
|
||||
return false if role_to_update.include?(user_role.id.to_s) || role_to_update.include?(admin_role.id.to_s)
|
||||
|
||||
# Restrict users to only updating the priority for roles in their domain with a higher
|
||||
# priority
|
||||
role_to_update.each do |id|
|
||||
role = Role.find(id)
|
||||
return false if role.priority <= current_user_role.priority || role.provider != @user_domain
|
||||
end
|
||||
|
||||
# Get the priority of the current user's role and start with 1 higher
|
||||
new_priority = [current_user_role.priority, 0].max + 1
|
||||
|
||||
begin
|
||||
# Save the old priorities incase something fails
|
||||
old_priority = Role.where(id: role_to_update).select(:id, :priority).index_by(&:id)
|
||||
|
||||
# Set all the priorities to nil to avoid unique column issues
|
||||
Role.where(id: role_to_update).update_all(priority: nil)
|
||||
|
||||
# Starting at the starting priority, increase by 1 every time
|
||||
role_to_update.each_with_index do |id, index|
|
||||
Role.find(id).update_attribute(:priority, new_priority + index)
|
||||
end
|
||||
|
||||
true
|
||||
rescue => e
|
||||
# Reset to old prorities
|
||||
role_to_update.each_with_index do |id, _index|
|
||||
Role.find(id).update_attribute(:priority, old_priority[id.to_i].priority)
|
||||
end
|
||||
|
||||
logger.error "#{current_user} failed to update role priorities: #{e}"
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Update Permissions
|
||||
def update_permissions(role)
|
||||
current_user_role = current_user.role
|
||||
|
||||
# Checks that it is valid for the provider to update the role
|
||||
return false if role.priority <= current_user_role.priority || role.provider != @user_domain
|
||||
|
||||
role_params = params.require(:role).permit(:name)
|
||||
permission_params = params.require(:role).permit(:can_create_rooms, :send_promoted_email,
|
||||
:send_demoted_email, :can_edit_site_settings, :can_edit_roles, :can_manage_users,
|
||||
:can_launch_recording, :can_manage_rooms_recordings, :can_appear_in_share_list, :colour)
|
||||
|
||||
permission_params.transform_values! do |v|
|
||||
case v
|
||||
when "0"
|
||||
"false"
|
||||
when "1"
|
||||
"true"
|
||||
else
|
||||
v
|
||||
end
|
||||
end
|
||||
|
||||
# Role is a default role so users can't change the name
|
||||
role_params[:name] = role.name if Role::RESERVED_ROLE_NAMES.include?(role.name)
|
||||
|
||||
# Make sure if the user is updating the role name that the role name is valid
|
||||
if role.name != role_params[:name] && !Role.duplicate_name(role_params[:name], @user_domain) &&
|
||||
!role_params[:name].strip.empty?
|
||||
role.name = role_params[:name]
|
||||
elsif role.name != role_params[:name]
|
||||
return false
|
||||
end
|
||||
|
||||
role.update(colour: permission_params[:colour])
|
||||
role.update_all_role_permissions(permission_params)
|
||||
|
||||
# Create home rooms for all users with this role if users with this role are now able to create rooms
|
||||
create_home_rooms(role.name) if !role.get_permission("can_create_rooms") && permission_params["can_create_rooms"] == "true"
|
||||
|
||||
role.save!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Create home rooms for users since they are now able to create rooms
|
||||
def create_home_rooms(role_name)
|
||||
User.with_role(role_name).each do |user|
|
||||
user.create_home_room if user.main_room.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
45
app/controllers/concerns/themer.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
# 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 Themer
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Lightens a color by 40%
|
||||
def color_lighten(color)
|
||||
# Uses the built in Sass Engine to lighten the color
|
||||
generate_sass("lighten", color, "40%")
|
||||
end
|
||||
|
||||
# Darkens a color by 10%
|
||||
def color_darken(color)
|
||||
# Uses the built in Sass Engine to darken the color
|
||||
generate_sass("darken", color, "10%")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_sass(action, color, percentage)
|
||||
dummy_scss = "h1 { color: $#{action}; }"
|
||||
compiled = SassC::Engine.new("$#{action}:#{action}(#{color}, #{percentage});" + dummy_scss, syntax: :scss).render
|
||||
|
||||
string_locater = 'color: '
|
||||
color_start = compiled.index(string_locater) + string_locater.length
|
||||
|
||||
compiled[color_start..color_start + 6]
|
||||
end
|
||||
end
|
||||
39
app/controllers/errors_controller.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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 ErrorsController < ApplicationController
|
||||
def not_found
|
||||
render "greenlight_error", status: 404, formats: :html
|
||||
end
|
||||
|
||||
def internal_error
|
||||
render "errors/greenlight_error", status: 500, formats: :html,
|
||||
locals: {
|
||||
status_code: 500,
|
||||
message: I18n.t("errors.internal.message"),
|
||||
help: I18n.t("errors.internal.help"),
|
||||
display_back: true,
|
||||
report_issue: true
|
||||
}
|
||||
end
|
||||
|
||||
def unauthorized
|
||||
render "errors/greenlight_error", status: 401, formats: :html, locals: { status_code: 401,
|
||||
message: I18n.t("errors.unauthorized.message"), help: I18n.t("errors.unauthorized.help"), display_back: true }
|
||||
end
|
||||
end
|
||||
74
app/controllers/health_check_controller.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
# 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 HealthCheckController < ApplicationController
|
||||
skip_before_action :redirect_to_https, :set_user_domain, :set_user_settings, :maintenance_mode?, :migration_error?,
|
||||
:user_locale, :check_admin_password, :check_user_role
|
||||
|
||||
# GET /health_check
|
||||
def all
|
||||
response = "success"
|
||||
@cache_expire = 10.seconds
|
||||
|
||||
begin
|
||||
cache_check
|
||||
database_check
|
||||
email_check
|
||||
rescue => e
|
||||
response = "Health Check Failure: #{e}"
|
||||
end
|
||||
|
||||
render plain: response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_check
|
||||
if Rails.cache.write("__health_check_cache_write__", "true", expires_in: @cache_expire)
|
||||
raise "Unable to read from cache" unless Rails.cache.read("__health_check_cache_write__") == "true"
|
||||
else
|
||||
raise "Unable to write to cache"
|
||||
end
|
||||
end
|
||||
|
||||
def database_check
|
||||
raise "Database not responding" if defined?(ActiveRecord) && !ActiveRecord::Migrator.current_version
|
||||
raise "Pending migrations" unless ActiveRecord::Migration.check_pending!.nil?
|
||||
end
|
||||
|
||||
def email_check
|
||||
test_smtp if Rails.configuration.action_mailer.delivery_method == :smtp
|
||||
end
|
||||
|
||||
def test_smtp
|
||||
settings = ActionMailer::Base.smtp_settings
|
||||
|
||||
smtp = Net::SMTP.new(settings[:address], settings[:port])
|
||||
smtp.enable_starttls_auto if settings[:enable_starttls_auto] == ("true") && smtp.respond_to?(:enable_starttls_auto)
|
||||
|
||||
if settings[:authentication].present? && settings[:authentication] != "none"
|
||||
smtp.start(settings[:domain]) do |s|
|
||||
s.authenticate(settings[:user_name], settings[:password], settings[:authentication])
|
||||
end
|
||||
else
|
||||
smtp.start(settings[:domain])
|
||||
end
|
||||
rescue => e
|
||||
raise "Unable to connect to SMTP Server - #{e}"
|
||||
end
|
||||
end
|
||||
28
app/controllers/main_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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 MainController < ApplicationController
|
||||
include Registrar
|
||||
# GET /
|
||||
def index
|
||||
# Store invite token
|
||||
session[:invite_token] = params[:invite_token] if params[:invite_token] && invite_registration
|
||||
|
||||
redirect_to home_page if current_user
|
||||
end
|
||||
end
|
||||
95
app/controllers/password_resets_controller.rb
Normal file
@@ -0,0 +1,95 @@
|
||||
# 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 PasswordResetsController < ApplicationController
|
||||
include Emailer
|
||||
|
||||
before_action :find_user, only: [:edit, :update]
|
||||
before_action :check_expiration, only: [:edit, :update]
|
||||
|
||||
# GET /password_resets/new
|
||||
def new
|
||||
end
|
||||
|
||||
# POST /password_resets
|
||||
def create
|
||||
return redirect_to new_password_reset_path, flash: { alert: I18n.t("reset_password.captcha") } unless valid_captcha
|
||||
|
||||
# Check if user exists and throw an error if he doesn't
|
||||
@user = User.find_by!(email: params[:password_reset][:email].downcase, provider: @user_domain)
|
||||
|
||||
send_password_reset_email(@user, @user.create_reset_digest)
|
||||
redirect_to root_path
|
||||
rescue
|
||||
# User doesn't exist
|
||||
redirect_to root_path, flash: { success: I18n.t("email_sent", email_type: t("reset_password.subtitle")) }
|
||||
end
|
||||
|
||||
# GET /password_resets/:id/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# PATCH /password_resets/:id
|
||||
def update
|
||||
# Check if password is valid
|
||||
if params[:user][:password].empty?
|
||||
flash.now[:alert] = I18n.t("password_empty_notice")
|
||||
elsif params[:user][:password] != params[:user][:password_confirmation]
|
||||
# Password does not match password confirmation
|
||||
flash.now[:alert] = I18n.t("password_different_notice")
|
||||
elsif @user.without_terms_acceptance { @user.update_attributes(user_params) }
|
||||
@user.without_terms_acceptance {
|
||||
# Clear the user's social uid if they are switching from a social to a local account
|
||||
@user.update_attribute(:social_uid, nil) if @user.social_uid.present?
|
||||
# Deactivate the reset digest in use disabling the reset link.
|
||||
@user.update(reset_digest: nil, reset_sent_at: nil, last_pwd_update: Time.zone.now)
|
||||
# For password resets the last_pwd_update has to match the resetting event timestamp.
|
||||
# And the activated_at session metadata has to match it only if the authenticated user
|
||||
# is the user with the account having its password reset.
|
||||
# This keeps that user session only alive while invalidating all others for the same account.
|
||||
session[:activated_at] = @user.last_pwd_update.to_i if current_user&.id == @user.id
|
||||
}
|
||||
# Successfully reset password
|
||||
return redirect_to root_path, flash: { success: I18n.t("password_reset_success") }
|
||||
end
|
||||
render 'edit'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user
|
||||
@user = User.find_by(reset_digest: User.hash_token(params[:id]), provider: @user_domain)
|
||||
|
||||
return redirect_to new_password_reset_url, alert: I18n.t("reset_password.invalid_token") unless @user
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:password, :password_confirmation)
|
||||
end
|
||||
|
||||
# Checks expiration of reset token.
|
||||
def check_expiration
|
||||
redirect_to new_password_reset_url, alert: I18n.t("expired_reset_token") if @user.password_reset_expired?
|
||||
end
|
||||
|
||||
# Checks that the captcha passed is valid
|
||||
def valid_captcha
|
||||
return true unless Rails.configuration.recaptcha_enabled
|
||||
verify_recaptcha
|
||||
end
|
||||
end
|
||||
74
app/controllers/recordings_controller.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
# 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 RecordingsController < ApplicationController
|
||||
before_action :find_room
|
||||
before_action :verify_room_ownership
|
||||
|
||||
META_LISTED = "gl-listed"
|
||||
|
||||
# POST /:meetingID/:record_id
|
||||
def update
|
||||
meta = {
|
||||
"meta_#{META_LISTED}" => (params[:state] == "public"),
|
||||
}
|
||||
|
||||
if params[:state] == "protected"
|
||||
protect_recording(params[:record_id])
|
||||
else
|
||||
unprotect_recording(params[:record_id])
|
||||
end
|
||||
|
||||
if params[:state] == "inaccessible"
|
||||
unpublish_recording(params[:record_id])
|
||||
else
|
||||
publish_recording(params[:record_id])
|
||||
end
|
||||
|
||||
res = update_recording(params[:record_id], meta)
|
||||
|
||||
# Redirects to the page that made the initial request
|
||||
redirect_back fallback_location: root_path if res[:updated]
|
||||
end
|
||||
|
||||
# PATCH /:meetingID/:record_id
|
||||
def rename
|
||||
update_recording(params[:record_id], "meta_name" => params[:record_name])
|
||||
|
||||
redirect_back fallback_location: room_path(@room)
|
||||
end
|
||||
|
||||
# DELETE /:meetingID/:record_id
|
||||
def delete
|
||||
delete_recording(params[:record_id])
|
||||
|
||||
# Redirects to the page that made the initial request
|
||||
redirect_back fallback_location: root_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_room
|
||||
@room = Room.find_by!(bbb_id: params[:meetingID])
|
||||
end
|
||||
|
||||
# Ensure the user is logged into the room they are accessing.
|
||||
def verify_room_ownership
|
||||
redirect_to root_path if !@room.owned_by?(current_user) && !current_user&.role&.get_permission("can_manage_rooms_recordings")
|
||||
end
|
||||
end
|
||||
478
app/controllers/rooms_controller.rb
Normal file
@@ -0,0 +1,478 @@
|
||||
# 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 RoomsController < ApplicationController
|
||||
include Pagy::Backend
|
||||
include Recorder
|
||||
include Joiner
|
||||
include Populator
|
||||
|
||||
before_action :validate_accepted_terms, unless: -> { !Rails.configuration.terms }
|
||||
before_action :validate_verified_email, except: [:show, :join],
|
||||
unless: -> { !Rails.configuration.enable_email_verification }
|
||||
before_action :find_room, except: [:create, :join_specific_room, :cant_create_rooms]
|
||||
before_action :verify_room_ownership_or_admin_or_shared, only: [:start, :shared_access]
|
||||
before_action :verify_room_ownership_or_admin,
|
||||
only: [:room_settings, :update_settings, :destroy, :preupload_presentation, :remove_presentation]
|
||||
before_action :verify_room_ownership_or_shared, only: [:remove_shared_access]
|
||||
before_action :verify_room_owner_verified, only: [:show, :join],
|
||||
unless: -> { !Rails.configuration.enable_email_verification }
|
||||
before_action :verify_room_owner_valid, only: [:show, :join]
|
||||
before_action :verify_user_not_admin, only: [:show]
|
||||
skip_before_action :verify_authenticity_token, only: [:join]
|
||||
|
||||
# POST /
|
||||
def create
|
||||
# Return to root if user is not signed in
|
||||
return redirect_to root_path unless current_user
|
||||
|
||||
# Check if the user has not exceeded the room limit
|
||||
return redirect_to current_user.main_room, flash: { alert: I18n.t("room.room_limit") } if room_limit_exceeded
|
||||
|
||||
# Create room
|
||||
@room = Room.new(name: room_params[:name],
|
||||
access_code: room_params[:access_code],
|
||||
moderator_access_code: room_params[:moderator_access_code])
|
||||
@room.owner = current_user
|
||||
@room.room_settings = create_room_settings_string(room_params)
|
||||
|
||||
# Save the room and redirect if it fails
|
||||
return redirect_to current_user.main_room, flash: { alert: I18n.t("room.create_room_error") } unless @room.save
|
||||
|
||||
logger.info "Support: #{current_user.email} has created a new room #{@room.uid}."
|
||||
|
||||
# Redirect to room is auto join was not turned on
|
||||
return redirect_to @room,
|
||||
flash: { success: I18n.t("room.create_room_success") } unless room_params[:auto_join] == "1"
|
||||
|
||||
# Start the room if auto join was turned on
|
||||
start
|
||||
end
|
||||
|
||||
# GET /:room_uid
|
||||
def show
|
||||
@room_settings = JSON.parse(@room[:room_settings])
|
||||
@anyone_can_start = room_setting_with_config("anyoneCanStart")
|
||||
@room_running = room_running?(@room.bbb_id)
|
||||
@shared_room = room_shared_with_user
|
||||
|
||||
# If its the current user's room
|
||||
if current_user && (@room.owned_by?(current_user) || @shared_room)
|
||||
# If the user is trying to access their own room but is not allowed to
|
||||
if @room.owned_by?(current_user) && !current_user.role.get_permission("can_create_rooms")
|
||||
return redirect_to cant_create_rooms_path
|
||||
end
|
||||
|
||||
# User is allowed to have rooms
|
||||
@search, @order_column, @order_direction, recs =
|
||||
recordings(@room.bbb_id, params.permit(:search, :column, :direction), true)
|
||||
|
||||
@pagy, @recordings = pagy_array(recs)
|
||||
else
|
||||
return redirect_to root_path, flash: { alert: I18n.t("room.invalid_provider") } if incorrect_user_domain
|
||||
|
||||
show_user_join
|
||||
end
|
||||
end
|
||||
|
||||
# GET /rooms
|
||||
def cant_create_rooms
|
||||
return redirect_to root_path unless current_user
|
||||
shared_rooms = current_user.shared_rooms
|
||||
|
||||
if current_user.shared_rooms.empty?
|
||||
# Render view for users that cant create rooms
|
||||
@recent_rooms = Room.where(id: cookies.encrypted["#{current_user.uid}_recently_joined_rooms"])
|
||||
render :cant_create_rooms
|
||||
else
|
||||
redirect_to shared_rooms[0]
|
||||
end
|
||||
end
|
||||
|
||||
# POST /:room_uid
|
||||
def join
|
||||
return redirect_to root_path,
|
||||
flash: { alert: I18n.t("administrator.site_settings.authentication.user-info") } if auth_required
|
||||
|
||||
@shared_room = room_shared_with_user
|
||||
|
||||
unless @room.owned_by?(current_user) || @shared_room
|
||||
# Don't allow users to join unless they have a valid access code or the room doesn't have an access codes
|
||||
valid_access_code = !@room.access_code.present? || @room.access_code == session[:access_code]
|
||||
if !valid_access_code && !valid_moderator_access_code(session[:moderator_access_code])
|
||||
return redirect_to room_path(room_uid: params[:room_uid]), flash: { alert: I18n.t("room.access_code_required") }
|
||||
end
|
||||
|
||||
# Assign join name if passed.
|
||||
if params[@room.invite_path]
|
||||
@join_name = params[@room.invite_path][:join_name]
|
||||
elsif !params[:join_name]
|
||||
# Join name not passed.
|
||||
return redirect_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
# create or update cookie with join name
|
||||
cookies.encrypted[:greenlight_name] = @join_name unless cookies.encrypted[:greenlight_name] == @join_name
|
||||
|
||||
save_recent_rooms
|
||||
|
||||
logger.info "Support: #{current_user.present? ? current_user.email : @join_name} is joining room #{@room.uid}"
|
||||
join_room(default_meeting_options)
|
||||
end
|
||||
|
||||
# DELETE /:room_uid
|
||||
def destroy
|
||||
begin
|
||||
# Don't delete the users home room.
|
||||
raise I18n.t("room.delete.home_room") if @room == @room.owner.main_room
|
||||
|
||||
# Destroy all recordings then permanently delete the room
|
||||
delete_all_recordings(@room.bbb_id)
|
||||
@room.destroy(true)
|
||||
rescue => e
|
||||
flash[:alert] = I18n.t("room.delete.fail", error: e)
|
||||
else
|
||||
flash[:success] = I18n.t("room.delete.success")
|
||||
end
|
||||
|
||||
# Redirect to home room if the redirect_back location is the deleted room
|
||||
return redirect_to @current_user.main_room if request.referer == room_url(@room)
|
||||
|
||||
# Redirect to the location that the user deleted the room from
|
||||
redirect_back fallback_location: current_user.main_room
|
||||
end
|
||||
|
||||
# POST /room/join
|
||||
def join_specific_room
|
||||
room_uid = params[:join_room][:url].split('/').last
|
||||
|
||||
@room = Room.find_by(uid: room_uid)
|
||||
return redirect_to cant_create_rooms_path, alert: I18n.t("room.no_room.invalid_room_uid") unless @room
|
||||
|
||||
redirect_to room_path(@room)
|
||||
end
|
||||
|
||||
# POST /:room_uid/start
|
||||
def start
|
||||
logger.info "Support: #{current_user.email} is starting room #{@room.uid}"
|
||||
|
||||
# Join the user in and start the meeting.
|
||||
opts = default_meeting_options
|
||||
opts[:user_is_moderator] = true
|
||||
|
||||
# Include the user's choices for the room settings
|
||||
@room_settings = JSON.parse(@room[:room_settings])
|
||||
opts[:mute_on_start] = room_setting_with_config("muteOnStart")
|
||||
opts[:require_moderator_approval] = room_setting_with_config("requireModeratorApproval")
|
||||
opts[:record] = record_meeting
|
||||
|
||||
begin
|
||||
redirect_to join_path(@room, current_user.name, opts, current_user.uid)
|
||||
rescue BigBlueButton::BigBlueButtonException => e
|
||||
logger.error("Support: #{@room.uid} start failed: #{e}")
|
||||
|
||||
redirect_to room_path, alert: I18n.t(e.key.to_s.underscore, default: I18n.t("bigbluebutton_exception"))
|
||||
end
|
||||
|
||||
# Notify users that the room has started.
|
||||
# Delay 5 seconds to allow for server start, although the request will retry until it succeeds.
|
||||
NotifyUserWaitingJob.set(wait: 5.seconds).perform_later(@room)
|
||||
end
|
||||
|
||||
# POST /:room_uid/update_settings
|
||||
def update_settings
|
||||
begin
|
||||
options = params[:room].nil? ? params : params[:room]
|
||||
raise "Room name can't be blank" if options[:name].blank?
|
||||
|
||||
# Update the rooms values
|
||||
room_settings_string = create_room_settings_string(options)
|
||||
|
||||
attributes = {
|
||||
name: options[:name],
|
||||
}
|
||||
|
||||
unless params[:setting] == "rename_header"
|
||||
attributes[:room_settings] = room_settings_string
|
||||
attributes[:access_code] = options[:access_code]
|
||||
attributes[:moderator_access_code] = options[:moderator_access_code]
|
||||
end
|
||||
|
||||
@room.update(attributes)
|
||||
|
||||
flash[:success] = I18n.t("room.update_settings_success")
|
||||
rescue => e
|
||||
logger.error "Support: Error in updating room settings: #{e}"
|
||||
flash[:alert] = I18n.t("room.update_settings_error")
|
||||
end
|
||||
|
||||
redirect_back fallback_location: room_path(@room)
|
||||
end
|
||||
|
||||
# GET /:room_uid/current_presentation
|
||||
def current_presentation
|
||||
attached = @room.presentation.attached?
|
||||
|
||||
# Respond with JSON object of presentation name
|
||||
respond_to do |format|
|
||||
format.json { render body: { attached: attached, name: attached ? @room.presentation.filename.to_s : "" }.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# POST /:room_uid/preupload_presenstation
|
||||
def preupload_presentation
|
||||
begin
|
||||
raise "Invalid file type" unless valid_file_type
|
||||
@room.presentation.attach(room_params[:presentation])
|
||||
|
||||
flash[:success] = I18n.t("room.preupload_success")
|
||||
rescue => e
|
||||
logger.error "Support: Error in updating room presentation: #{e}"
|
||||
flash[:alert] = I18n.t("room.preupload_error")
|
||||
end
|
||||
|
||||
redirect_back fallback_location: room_path(@room)
|
||||
end
|
||||
|
||||
# POST /:room_uid/remove_presenstation
|
||||
def remove_presentation
|
||||
begin
|
||||
@room.presentation.purge
|
||||
|
||||
flash[:success] = I18n.t("room.preupload_remove_success")
|
||||
rescue => e
|
||||
logger.error "Support: Error in removing room presentation: #{e}"
|
||||
flash[:alert] = I18n.t("room.preupload_remove_error")
|
||||
end
|
||||
|
||||
redirect_back fallback_location: room_path(@room)
|
||||
end
|
||||
|
||||
# POST /:room_uid/update_shared_access
|
||||
def shared_access
|
||||
begin
|
||||
current_list = @room.shared_users.pluck(:id)
|
||||
new_list = User.where(uid: params[:add]).pluck(:id)
|
||||
|
||||
# Get the list of users that used to be in the list but were removed
|
||||
users_to_remove = current_list - new_list
|
||||
# Get the list of users that are in the new list but not in the current list
|
||||
users_to_add = new_list - current_list
|
||||
|
||||
# Remove users that are removed
|
||||
SharedAccess.where(room_id: @room.id, user_id: users_to_remove).delete_all unless users_to_remove.empty?
|
||||
|
||||
# Add users that are added
|
||||
users_to_add.each do |id|
|
||||
SharedAccess.create(room_id: @room.id, user_id: id)
|
||||
end
|
||||
|
||||
flash[:success] = I18n.t("room.shared_access_success")
|
||||
rescue => e
|
||||
logger.error "Support: Error in updating room shared access: #{e}"
|
||||
flash[:alert] = I18n.t("room.shared_access_error")
|
||||
end
|
||||
|
||||
redirect_back fallback_location: room_path
|
||||
end
|
||||
|
||||
# POST /:room_uid/remove_shared_access
|
||||
def remove_shared_access
|
||||
begin
|
||||
SharedAccess.find_by!(room_id: @room.id, user_id: current_user).destroy
|
||||
flash[:success] = I18n.t("room.remove_shared_access_success")
|
||||
rescue => e
|
||||
logger.error "Support: Error in removing room shared access: #{e}"
|
||||
flash[:alert] = I18n.t("room.remove_shared_access_error")
|
||||
end
|
||||
|
||||
redirect_to current_user.main_room
|
||||
end
|
||||
|
||||
# GET /:room_uid/shared_users
|
||||
def shared_users
|
||||
# Respond with JSON object of users that have access to the room
|
||||
respond_to do |format|
|
||||
format.json { render body: @room.shared_users.pluck_to_hash(:uid, :name, :image).to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /:room_uid/room_settings
|
||||
def room_settings
|
||||
# Respond with JSON object of the room_settings
|
||||
respond_to do |format|
|
||||
format.json { render body: @room.room_settings }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /:room_uid/logout
|
||||
def logout
|
||||
logger.info "Support: #{current_user.present? ? current_user.email : 'Guest'} has left room #{@room.uid}"
|
||||
|
||||
# Redirect the correct page.
|
||||
redirect_to @room
|
||||
end
|
||||
|
||||
# POST /:room_uid/login
|
||||
def login
|
||||
# use same form for access_code and moderator_access_code
|
||||
if valid_moderator_access_code(room_params[:access_code])
|
||||
session[:moderator_access_code] = room_params[:access_code]
|
||||
else
|
||||
session[:access_code] = room_params[:access_code]
|
||||
end
|
||||
|
||||
if session[:access_code] != @room.access_code && !valid_moderator_access_code(session[:moderator_access_code])
|
||||
flash[:alert] = I18n.t("room.access_code_required")
|
||||
end
|
||||
|
||||
redirect_to room_path(@room.uid)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_room_settings_string(options)
|
||||
room_settings = {
|
||||
muteOnStart: options[:mute_on_join] == "1",
|
||||
requireModeratorApproval: options[:require_moderator_approval] == "1",
|
||||
anyoneCanStart: options[:anyone_can_start] == "1",
|
||||
joinModerator: options[:all_join_moderator] == "1",
|
||||
recording: options[:recording] == "1",
|
||||
}
|
||||
|
||||
room_settings.to_json
|
||||
end
|
||||
|
||||
def room_params
|
||||
params.require(:room).permit(:name, :auto_join, :mute_on_join, :access_code,
|
||||
:require_moderator_approval, :anyone_can_start, :all_join_moderator,
|
||||
:recording, :presentation, :moderator_access_code)
|
||||
end
|
||||
|
||||
# Find the room from the uid.
|
||||
def find_room
|
||||
@room = Room.includes(:owner).find_by!(uid: params[:room_uid])
|
||||
end
|
||||
|
||||
# Ensure the user either owns the room or is an admin of the room owner or the room is shared with him
|
||||
def verify_room_ownership_or_admin_or_shared
|
||||
return redirect_to root_path unless @room.owned_by?(current_user) ||
|
||||
room_shared_with_user ||
|
||||
current_user&.admin_of?(@room.owner, "can_manage_rooms_recordings")
|
||||
end
|
||||
|
||||
# Ensure the user either owns the room or is an admin of the room owner
|
||||
def verify_room_ownership_or_admin
|
||||
return redirect_to root_path if !@room.owned_by?(current_user) &&
|
||||
!current_user&.admin_of?(@room.owner, "can_manage_rooms_recordings")
|
||||
end
|
||||
|
||||
# Ensure the user owns the room or is allowed to start it
|
||||
def verify_room_ownership_or_shared
|
||||
return redirect_to root_path unless @room.owned_by?(current_user) || room_shared_with_user
|
||||
end
|
||||
|
||||
def validate_accepted_terms
|
||||
redirect_to terms_path if current_user && !current_user&.accepted_terms
|
||||
end
|
||||
|
||||
def validate_verified_email
|
||||
redirect_to account_activation_path(digest: current_user.activation_digest) if current_user && !current_user&.activated?
|
||||
end
|
||||
|
||||
def verify_room_owner_verified
|
||||
redirect_to root_path, alert: t("room.unavailable") unless @room.owner.activated?
|
||||
end
|
||||
|
||||
# Check to make sure the room owner is not pending or banned
|
||||
def verify_room_owner_valid
|
||||
redirect_to root_path, alert: t("room.owner_banned") if @room.owner.has_role?(:pending) || @room.owner.has_role?(:denied)
|
||||
end
|
||||
|
||||
def verify_user_not_admin
|
||||
redirect_to admins_path if current_user&.has_role?(:super_admin)
|
||||
end
|
||||
|
||||
def auth_required
|
||||
@settings.get_value("Room Authentication") == "true" && current_user.nil?
|
||||
end
|
||||
|
||||
# Checks if the room is shared with the user and room sharing is enabled
|
||||
def room_shared_with_user
|
||||
shared_access_allowed ? @room.shared_with?(current_user) : false
|
||||
end
|
||||
|
||||
def room_limit_exceeded
|
||||
limit = @settings.get_value("Room Limit").to_i
|
||||
|
||||
# Does not apply to admin or users that aren't signed in
|
||||
# 15+ option is used as unlimited
|
||||
return false if current_user&.has_role?(:admin) || limit == 15
|
||||
|
||||
current_user.rooms.length >= limit
|
||||
end
|
||||
helper_method :room_limit_exceeded
|
||||
|
||||
def valid_moderator_access_code(code)
|
||||
code == @room.moderator_access_code && !@room.moderator_access_code.blank? && moderator_code_allowed?
|
||||
end
|
||||
helper_method :valid_moderator_access_code
|
||||
|
||||
def record_meeting
|
||||
# If the require consent setting is checked, then check the room setting, else, set to true
|
||||
user = current_user || @room.owner
|
||||
if recording_consent_required?
|
||||
room_setting_with_config("recording") && user&.role&.get_permission("can_launch_recording")
|
||||
else
|
||||
user&.role&.get_permission("can_launch_recording")
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the file extension is allowed
|
||||
def valid_file_type
|
||||
Rails.configuration.allowed_file_types.split(",")
|
||||
.include?(File.extname(room_params[:presentation].original_filename.downcase))
|
||||
end
|
||||
|
||||
# Gets the room setting based on the option set in the room configuration
|
||||
def room_setting_with_config(name)
|
||||
config = case name
|
||||
when "muteOnStart"
|
||||
"Room Configuration Mute On Join"
|
||||
when "requireModeratorApproval"
|
||||
"Room Configuration Require Moderator"
|
||||
when "joinModerator"
|
||||
"Room Configuration All Join Moderator"
|
||||
when "anyoneCanStart"
|
||||
"Room Configuration Allow Any Start"
|
||||
when "recording"
|
||||
"Room Configuration Recording"
|
||||
end
|
||||
|
||||
case @settings.get_value(config)
|
||||
when "enabled"
|
||||
true
|
||||
when "optional"
|
||||
@room_settings[name]
|
||||
when "disabled"
|
||||
false
|
||||
end
|
||||
end
|
||||
helper_method :room_setting_with_config
|
||||
end
|
||||
293
app/controllers/sessions_controller.rb
Normal file
@@ -0,0 +1,293 @@
|
||||
# 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 SessionsController < ApplicationController
|
||||
include Authenticator
|
||||
include Registrar
|
||||
include Emailer
|
||||
include LdapAuthenticator
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: [:omniauth, :fail]
|
||||
before_action :check_user_signup_allowed, only: [:new]
|
||||
before_action :ensure_unauthenticated_except_twitter, only: [:new, :signin, :ldap_signin]
|
||||
|
||||
# GET /signin
|
||||
def signin
|
||||
check_if_twitter_account
|
||||
|
||||
@providers = configured_providers
|
||||
|
||||
if one_provider
|
||||
provider_path = if Rails.configuration.omniauth_ldap
|
||||
ldap_signin_path
|
||||
else
|
||||
"#{Rails.configuration.relative_url_root}/auth/#{@providers.first}"
|
||||
end
|
||||
|
||||
redirect_post(provider_path, options: { authenticity_token: :auto })
|
||||
end
|
||||
end
|
||||
|
||||
# GET /ldap_signin
|
||||
def ldap_signin
|
||||
end
|
||||
|
||||
# GET /signup
|
||||
def new
|
||||
# Check if the user needs to be invited
|
||||
if invite_registration
|
||||
redirect_to root_path, flash: { alert: I18n.t("registration.invite.no_invite") } unless params[:invite_token]
|
||||
|
||||
session[:invite_token] = params[:invite_token]
|
||||
end
|
||||
|
||||
check_if_twitter_account(true)
|
||||
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
# POST /users/login
|
||||
def create
|
||||
logger.info "Support: #{session_params[:email]} is attempting to login."
|
||||
|
||||
user = User.include_deleted.find_by(email: session_params[:email].downcase)
|
||||
|
||||
is_super_admin = user&.has_role? :super_admin
|
||||
|
||||
# Scope user to domain if the user is not a super admin
|
||||
user = User.include_deleted.find_by(email: session_params[:email].downcase, provider: @user_domain) unless is_super_admin
|
||||
|
||||
# Check user with that email exists
|
||||
return redirect_to(signin_path, alert: I18n.t("invalid_credentials")) unless user
|
||||
|
||||
# Check if authenticators have switched
|
||||
return switch_account_to_local(user) if !is_super_admin && auth_changed_to_local?(user)
|
||||
|
||||
# Check correct password was entered
|
||||
unless user.try(:authenticate, session_params[:password])
|
||||
logger.info "Support: #{session_params[:email]} login failed."
|
||||
return redirect_to(signin_path, alert: I18n.t("invalid_credentials"))
|
||||
end
|
||||
|
||||
# Check that the user is not deleted
|
||||
return redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") } if user.deleted?
|
||||
|
||||
unless is_super_admin
|
||||
# Check that the user is a Greenlight account
|
||||
return redirect_to(root_path, alert: I18n.t("invalid_login_method")) unless user.greenlight_account?
|
||||
# Check that the user has verified their account
|
||||
unless user.activated?
|
||||
user.create_activation_token if user.activation_digest.nil?
|
||||
return redirect_to(account_activation_path(digest: user.activation_digest))
|
||||
end
|
||||
end
|
||||
|
||||
return redirect_to edit_password_reset_path(user.create_reset_digest),
|
||||
flash: { alert: I18n.t("registration.insecure_password") } unless User.secure_password?(session_params[:password])
|
||||
|
||||
login(user)
|
||||
end
|
||||
|
||||
# POST /users/logout
|
||||
def destroy
|
||||
logout
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
# GET/POST /auth/:provider/callback
|
||||
def omniauth
|
||||
@auth = request.env['omniauth.auth']
|
||||
|
||||
begin
|
||||
process_signin
|
||||
rescue => e
|
||||
logger.error "Error authenticating via omniauth: #{e}"
|
||||
omniauth_fail
|
||||
end
|
||||
end
|
||||
|
||||
# POST /auth/failure
|
||||
def omniauth_fail
|
||||
if params[:message].nil?
|
||||
redirect_to root_path, alert: I18n.t("omniauth_error")
|
||||
else
|
||||
redirect_to root_path, alert: I18n.t("omniauth_specific_error", error: params["message"])
|
||||
end
|
||||
end
|
||||
|
||||
# GET /auth/ldap
|
||||
def ldap
|
||||
ldap_config = {}
|
||||
ldap_config[:host] = ENV['LDAP_SERVER']
|
||||
ldap_config[:port] = ENV['LDAP_PORT'].to_i.zero? ? 389 : ENV['LDAP_PORT'].to_i
|
||||
ldap_config[:bind_dn] = ENV['LDAP_BIND_DN']
|
||||
ldap_config[:password] = ENV['LDAP_PASSWORD']
|
||||
ldap_config[:auth_method] = ENV['LDAP_AUTH']
|
||||
ldap_config[:encryption] = case ENV['LDAP_METHOD']
|
||||
when 'ssl'
|
||||
'simple_tls'
|
||||
when 'tls'
|
||||
'start_tls'
|
||||
end
|
||||
ldap_config[:base] = ENV['LDAP_BASE']
|
||||
ldap_config[:filter] = ENV['LDAP_FILTER']
|
||||
ldap_config[:uid] = ENV['LDAP_UID']
|
||||
|
||||
if params[:session][:username].blank? || session_params[:password].blank?
|
||||
return redirect_to(ldap_signin_path, alert: I18n.t("invalid_credentials_external"))
|
||||
end
|
||||
|
||||
result = send_ldap_request(params[:session], ldap_config)
|
||||
|
||||
return redirect_to(ldap_signin_path, alert: I18n.t("invalid_credentials_external")) unless result
|
||||
|
||||
@auth = parse_auth(result.first, ENV['LDAP_ROLE_FIELD'], ENV['LDAP_ATTRIBUTE_MAPPING'])
|
||||
|
||||
begin
|
||||
process_signin
|
||||
rescue => e
|
||||
logger.error "Support: Error authenticating via omniauth: #{e}"
|
||||
omniauth_fail
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Verify that GreenLight is configured to allow user signup.
|
||||
def check_user_signup_allowed
|
||||
redirect_to root_path unless Rails.configuration.allow_user_signup
|
||||
end
|
||||
|
||||
def session_params
|
||||
params.require(:session).permit(:email, :password)
|
||||
end
|
||||
|
||||
def one_provider
|
||||
(!allow_user_signup? || !allow_greenlight_accounts?) && @providers.count == 1 &&
|
||||
!Rails.configuration.loadbalanced_configuration
|
||||
end
|
||||
|
||||
def check_user_exists
|
||||
User.exists?(social_uid: @auth['uid'], provider: current_provider)
|
||||
end
|
||||
|
||||
def check_user_deleted(email)
|
||||
User.deleted.exists?(email: email, provider: @user_domain)
|
||||
end
|
||||
|
||||
def check_auth_deleted
|
||||
User.deleted.exists?(social_uid: @auth['uid'], provider: current_provider)
|
||||
end
|
||||
|
||||
def current_provider
|
||||
@auth['provider'] == "bn_launcher" ? @auth['info']['customer'] : @auth['provider']
|
||||
end
|
||||
|
||||
# Check if the user already exists, if not then check for invitation
|
||||
def passes_invite_reqs
|
||||
return true if @user_exists
|
||||
|
||||
invitation = check_user_invited("", session[:invite_token], @user_domain)
|
||||
invitation[:present]
|
||||
end
|
||||
|
||||
def process_signin
|
||||
@user_exists = check_user_exists
|
||||
|
||||
if !@user_exists && @auth['provider'] == "twitter"
|
||||
return redirect_to root_path, flash: { alert: I18n.t("registration.deprecated.twitter_signup") }
|
||||
end
|
||||
|
||||
# Check if user is deleted
|
||||
return redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") } if check_auth_deleted
|
||||
|
||||
# If using invitation registration method, make sure user is invited
|
||||
return redirect_to root_path, flash: { alert: I18n.t("registration.invite.no_invite") } unless passes_invite_reqs
|
||||
|
||||
# Switch the user to a social account if they exist under the same email with no social uid
|
||||
switch_account_to_social if !@user_exists && auth_changed_to_social?(@auth['info']['email'])
|
||||
|
||||
user = User.from_omniauth(@auth)
|
||||
|
||||
logger.info "Support: Auth user #{user.email} is attempting to login."
|
||||
|
||||
# Add pending role if approval method and is a new user
|
||||
if approval_registration && !@user_exists
|
||||
user.set_role :pending
|
||||
|
||||
# Inform admins that a user signed up if emails are turned on
|
||||
send_approval_user_signup_email(user)
|
||||
|
||||
return redirect_to root_path, flash: { success: I18n.t("registration.approval.signup") }
|
||||
end
|
||||
|
||||
send_invite_user_signup_email(user) if invite_registration && !@user_exists
|
||||
|
||||
user.set_role(initial_user_role(user.email)) if !@user_exists && user.role.nil?
|
||||
|
||||
login(user)
|
||||
|
||||
if @auth['provider'] == "twitter"
|
||||
flash[:alert] = if allow_user_signup? && allow_greenlight_accounts?
|
||||
I18n.t("registration.deprecated.twitter_signin", link: signup_path(old_twitter_user_id: user.id))
|
||||
else
|
||||
I18n.t("registration.deprecated.twitter_signin", link: signin_path(old_twitter_user_id: user.id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Send the user a password reset email to allow them to set their password
|
||||
def switch_account_to_local(user)
|
||||
logger.info "Switching social account to local account for #{user.uid}"
|
||||
|
||||
# Send the user a reset password email
|
||||
send_password_reset_email(user, user.create_reset_digest)
|
||||
|
||||
# Overwrite the flash with a more descriptive message if successful
|
||||
flash[:success] = I18n.t("reset_password.auth_change") if flash[:success].present?
|
||||
|
||||
redirect_to signin_path
|
||||
end
|
||||
|
||||
# Set the user's social id to the new id being passed
|
||||
def switch_account_to_social
|
||||
user = User.find_by({
|
||||
email: @auth['info']['email'],
|
||||
provider: Rails.configuration.loadbalanced_configuration ? @user_domain : nil
|
||||
}.compact)
|
||||
|
||||
logger.info "Switching social account for #{user.uid}"
|
||||
|
||||
# Set the user's social id to the one being returned from auth
|
||||
user.update_attribute(:social_uid, @auth['uid'])
|
||||
end
|
||||
|
||||
def ldap_encryption
|
||||
encryption_method = case ENV['LDAP_METHOD']
|
||||
when 'ssl'
|
||||
'simple_tls'
|
||||
when 'tls'
|
||||
'start_tls'
|
||||
end
|
||||
|
||||
{
|
||||
method: encryption_method,
|
||||
tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
||||
}
|
||||
end
|
||||
end
|
||||
42
app/controllers/themes_controller.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
# 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 ThemesController < ApplicationController
|
||||
skip_before_action :block_unknown_hosts, :redirect_to_https, :maintenance_mode?, :migration_error?, :user_locale,
|
||||
:check_admin_password, :check_user_role
|
||||
|
||||
# GET /primary
|
||||
def index
|
||||
color = @settings.get_value("Primary Color") || Rails.configuration.primary_color_default
|
||||
lighten_color = @settings.get_value("Primary Color Lighten") || Rails.configuration.primary_color_lighten_default
|
||||
darken_color = @settings.get_value("Primary Color Darken") || Rails.configuration.primary_color_darken_default
|
||||
|
||||
file_name = Rails.root.join('lib', 'assets', '_primary_themes.scss')
|
||||
@file_contents = File.read(file_name)
|
||||
|
||||
# Include the variables and covert scss file to css
|
||||
@compiled = SassC::Engine.new("$primary-color:#{color};" \
|
||||
"$primary-color-lighten:#{lighten_color};" \
|
||||
"$primary-color-darken:#{darken_color};" +
|
||||
@file_contents, syntax: :scss).render
|
||||
|
||||
respond_to do |format|
|
||||
format.css { render body: @compiled }
|
||||
end
|
||||
end
|
||||
end
|
||||
257
app/controllers/users_controller.rb
Normal file
@@ -0,0 +1,257 @@
|
||||
# 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 UsersController < ApplicationController
|
||||
include Pagy::Backend
|
||||
include Authenticator
|
||||
include Emailer
|
||||
include Registrar
|
||||
include Recorder
|
||||
include Rolify
|
||||
|
||||
before_action :find_user, only: [:edit, :change_password, :delete_account, :update, :update_password]
|
||||
before_action :ensure_unauthenticated_except_twitter, only: [:create]
|
||||
before_action :check_user_signup_allowed, only: [:create]
|
||||
before_action :check_admin_of, only: [:edit, :change_password, :delete_account]
|
||||
|
||||
# POST /u
|
||||
def create
|
||||
@user = User.new(user_params)
|
||||
@user.provider = @user_domain
|
||||
|
||||
# User or recpatcha is not valid
|
||||
render("sessions/new") && return unless valid_user_or_captcha
|
||||
|
||||
# Redirect to root if user token is either invalid or expired
|
||||
return redirect_to root_path, flash: { alert: I18n.t("registration.invite.fail") } unless passes_invite_reqs
|
||||
|
||||
# User has passed all validations required
|
||||
@user.save
|
||||
|
||||
logger.info "Support: #{@user.email} user has been created."
|
||||
|
||||
# Set user to pending and redirect if Approval Registration is set
|
||||
if approval_registration
|
||||
@user.set_role :pending
|
||||
|
||||
return redirect_to root_path,
|
||||
flash: { success: I18n.t("registration.approval.signup") } unless Rails.configuration.enable_email_verification
|
||||
end
|
||||
|
||||
send_registration_email
|
||||
|
||||
# Sign in automatically if email verification is disabled or if user is already verified.
|
||||
if !Rails.configuration.enable_email_verification || @user.email_verified
|
||||
@user.set_role(initial_user_role(@user.email))
|
||||
|
||||
login(@user) && return
|
||||
end
|
||||
|
||||
send_activation_email(@user, @user.create_activation_token)
|
||||
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
# GET /u/:user_uid/edit
|
||||
def edit
|
||||
redirect_to root_path unless current_user
|
||||
end
|
||||
|
||||
# GET /u/:user_uid/change_password
|
||||
def change_password
|
||||
redirect_to edit_user_path unless current_user.greenlight_account?
|
||||
end
|
||||
|
||||
# GET /u/:user_uid/delete_account
|
||||
def delete_account
|
||||
redirect_to signin_path unless current_user
|
||||
end
|
||||
|
||||
# POST /u/:user_uid/edit
|
||||
def update
|
||||
if session[:prev_url].present?
|
||||
path = session[:prev_url]
|
||||
session.delete(:prev_url)
|
||||
else
|
||||
path = admins_path
|
||||
end
|
||||
|
||||
redirect_path = current_user.admin_of?(@user, "can_manage_users") ? path : edit_user_path(@user)
|
||||
|
||||
unless can_edit_user?(@user, current_user)
|
||||
params[:user][:name] = @user.name
|
||||
params[:user][:email] = @user.email
|
||||
end
|
||||
old_user_email = @user.email
|
||||
if @user.without_terms_acceptance { @user.update_attributes(user_params) }
|
||||
@user.without_terms_acceptance {
|
||||
@user.update_attributes(email_verified: false) if user_params[:email] != old_user_email
|
||||
user_locale(@user)
|
||||
}
|
||||
if @user.without_terms_acceptance { update_roles(params[:user][:role_id]) }
|
||||
return redirect_to redirect_path, flash: { success: I18n.t("info_update_success") }
|
||||
else
|
||||
flash[:alert] = I18n.t("administrator.roles.invalid_assignment")
|
||||
end
|
||||
end
|
||||
|
||||
render :edit
|
||||
end
|
||||
|
||||
# POST /u/:user_uid/change_password
|
||||
def update_password
|
||||
# Update the users password.
|
||||
if @user.authenticate(user_params[:old_password])
|
||||
# Bad UX on client side [FIXED].
|
||||
@user.assign_attributes user_params.slice(:password, :password_confirmation)
|
||||
else
|
||||
# Original password is incorrect, can't update.
|
||||
@user.errors.add(:old_password, I18n.t("old_password_incorrect"))
|
||||
end
|
||||
|
||||
# Notify the user that their account has been updated.
|
||||
if @user.errors.empty? && @user.without_terms_acceptance {
|
||||
@user.save && @user.update(last_pwd_update: Time.zone.now)
|
||||
}
|
||||
# Changing the password has to update the last_pwd_update to match the event timestamp.
|
||||
# The activated_at session metadata has to match it too to reset the state of the active session
|
||||
# making this change password request.
|
||||
# This keeps only this session alive.
|
||||
session[:activated_at] = @user.last_pwd_update.to_i if current_user.id == @user.id
|
||||
return redirect_to change_password_path, flash: { success: I18n.t("info_update_success") }
|
||||
end
|
||||
# redirect_to change_password_path
|
||||
render :change_password
|
||||
end
|
||||
|
||||
# DELETE /u/:user_uid
|
||||
def destroy
|
||||
# Include deleted users in the check
|
||||
admin_path = request.referer.present? ? request.referer : admins_path
|
||||
@user = User.include_deleted.find_by(uid: params[:user_uid])
|
||||
|
||||
logger.info "Support: #{current_user.email} is deleting #{@user.email}."
|
||||
|
||||
self_delete = current_user == @user
|
||||
redirect_url = self_delete ? root_path : admin_path
|
||||
|
||||
begin
|
||||
if current_user && (self_delete || current_user.admin_of?(@user, "can_manage_users"))
|
||||
# Permanently delete if the user is deleting themself
|
||||
perm_delete = self_delete || (params[:permanent].present? && params[:permanent] == "true")
|
||||
|
||||
# Permanently delete the rooms under the user if they have not been reassigned
|
||||
if perm_delete
|
||||
@user.rooms.include_deleted.each do |room|
|
||||
# Destroy all recordings then permanently delete the room
|
||||
delete_all_recordings(room.bbb_id)
|
||||
room.destroy(true)
|
||||
end
|
||||
end
|
||||
|
||||
@user.destroy(perm_delete)
|
||||
|
||||
# Log the user out if they are deleting themself
|
||||
session.delete(:user_id) if self_delete
|
||||
|
||||
return redirect_to redirect_url, flash: { success: I18n.t("administrator.flash.delete") } unless self_delete
|
||||
else
|
||||
flash[:alert] = I18n.t("administrator.flash.delete_fail")
|
||||
end
|
||||
rescue => e
|
||||
logger.error "Support: Error in user deletion: #{e}"
|
||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("administrator.flash.delete_fail"))
|
||||
end
|
||||
|
||||
redirect_to redirect_url
|
||||
end
|
||||
|
||||
# GET /u/:user_uid/recordings
|
||||
def recordings
|
||||
if current_user && current_user.uid == params[:user_uid]
|
||||
@search, @order_column, @order_direction, recs =
|
||||
all_recordings(current_user.rooms.pluck(:bbb_id), params.permit(:search, :column, :direction), true)
|
||||
@pagy, @recordings = pagy_array(recs)
|
||||
else
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
# GET | POST /terms
|
||||
def terms
|
||||
redirect_to '/404' unless Rails.configuration.terms
|
||||
|
||||
if params[:accept] == "true"
|
||||
current_user.update_attributes(accepted_terms: true)
|
||||
login(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
# GET /shared_access_list
|
||||
def shared_access_list
|
||||
# Don't allow searchs unless atleast 3 characters are passed
|
||||
return redirect_to '/404' unless params[:search].strip.length >= 3
|
||||
|
||||
roles_can_appear = []
|
||||
Role.where(provider: @user_domain).each do |role|
|
||||
roles_can_appear << role.name if role.get_permission("can_appear_in_share_list") && role.priority >= 0
|
||||
end
|
||||
|
||||
initial_list = User.where.not(uid: params[:owner_uid])
|
||||
.with_role(roles_can_appear)
|
||||
.shared_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).to_json }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user
|
||||
@user = User.find_by(uid: params[:user_uid])
|
||||
end
|
||||
|
||||
# Verify that GreenLight is configured to allow user signup.
|
||||
def check_user_signup_allowed
|
||||
redirect_to root_path unless Rails.configuration.allow_user_signup
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:name, :email, :image, :password, :password_confirmation,
|
||||
:old_password, :accepted_terms, :language)
|
||||
end
|
||||
|
||||
def send_registration_email
|
||||
if invite_registration
|
||||
send_invite_user_signup_email(@user)
|
||||
elsif approval_registration
|
||||
send_approval_user_signup_email(@user)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks that the user is allowed to edit this user
|
||||
def check_admin_of
|
||||
redirect_to root_path if current_user &&
|
||||
@user != current_user &&
|
||||
!current_user.admin_of?(@user, "can_manage_users")
|
||||
end
|
||||
end
|
||||