forked from External/greenlight
initial commit
This commit is contained in:
3
app/assets/config/manifest.js
Normal file
3
app/assets/config/manifest.js
Normal file
@ -0,0 +1,3 @@
|
||||
//= link_tree ../images
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
0
app/assets/images/.keep
Normal file
0
app/assets/images/.keep
Normal file
BIN
app/assets/images/background.png
Normal file
BIN
app/assets/images/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
BIN
app/assets/images/bbb_logo.png
Normal file
BIN
app/assets/images/bbb_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
app/assets/images/google_logo.png
Normal file
BIN
app/assets/images/google_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 771 B |
BIN
app/assets/images/twitter_logo.png
Normal file
BIN
app/assets/images/twitter_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
16
app/assets/javascripts/application.js
Normal file
16
app/assets/javascripts/application.js
Normal file
@ -0,0 +1,16 @@
|
||||
// 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 jquery
|
||||
//= require jquery_ujs
|
||||
//= require turbolinks
|
||||
//= require_tree .
|
13
app/assets/javascripts/cable.js
Normal file
13
app/assets/javascripts/cable.js
Normal file
@ -0,0 +1,13 @@
|
||||
// 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() {
|
||||
this.App || (this.App = {});
|
||||
|
||||
App.cable = ActionCable.createConsumer();
|
||||
|
||||
}).call(this);
|
0
app/assets/javascripts/channels/.keep
Normal file
0
app/assets/javascripts/channels/.keep
Normal file
3
app/assets/javascripts/main.coffee
Normal file
3
app/assets/javascripts/main.coffee
Normal file
@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
3
app/assets/javascripts/meetings.coffee
Normal file
3
app/assets/javascripts/meetings.coffee
Normal file
@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
3
app/assets/javascripts/rooms.coffee
Normal file
3
app/assets/javascripts/rooms.coffee
Normal file
@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
3
app/assets/javascripts/sessions.coffee
Normal file
3
app/assets/javascripts/sessions.coffee
Normal file
@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
161
app/assets/stylesheets/application.scss
Normal file
161
app/assets/stylesheets/application.scss
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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_tree .
|
||||
*= require_self
|
||||
*/
|
||||
|
||||
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
//
|
||||
// Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the GNU Lesser General Public License as published by the Free Software
|
||||
// Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License along
|
||||
// with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
@import "bootstrap";
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.background {
|
||||
height: 350px;
|
||||
width: 100%;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body[data-controller=landing].app-background {
|
||||
@extend .background;
|
||||
}
|
||||
|
||||
#alerts {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
margin-left: -250px;
|
||||
width: 500px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 40px;
|
||||
margin-bottom: 160px;
|
||||
.logo {
|
||||
max-width: 150px;
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.center-block {
|
||||
float: none;
|
||||
}
|
||||
.center-panel-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
.center-panel {
|
||||
height: 100%;
|
||||
.center-panel-size {
|
||||
max-width: 1200px
|
||||
}
|
||||
.center-panel-content-size {
|
||||
height: 100%;
|
||||
max-width: 1100px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input-spacing {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.title-wrapper {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.join-form-wrapper {
|
||||
.center-block {
|
||||
max-width: 400px;
|
||||
}
|
||||
.join-form {
|
||||
width: 100%;
|
||||
}
|
||||
.btn {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.meeting-url-wrapper {
|
||||
.meeting-url {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.verticle-line {
|
||||
// parent must be position relative to work
|
||||
width: 1px;
|
||||
background-color: lightgray;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.invite-join-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.help-link {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding-right: 3px;
|
||||
}
|
121
app/assets/stylesheets/main.scss
Normal file
121
app/assets/stylesheets/main.scss
Normal file
@ -0,0 +1,121 @@
|
||||
// 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/
|
||||
|
||||
.meeting-url-button-group {
|
||||
padding-top: 5px;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.meeting-url-qrcode-group {
|
||||
padding-top: 5px;
|
||||
width: 128px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.previously-joined, .actives {
|
||||
list-style-type: none;
|
||||
margin:auto;
|
||||
width: 200px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.meetings {
|
||||
|
||||
}
|
||||
|
||||
.rooms {
|
||||
|
||||
.table-wrapper {
|
||||
padding: 40px 50px 10px 50px;
|
||||
|
||||
#recordings {
|
||||
thead {
|
||||
th:after {
|
||||
content: none; //removes the sort icon in table header
|
||||
}
|
||||
}
|
||||
.dataTables_empty {
|
||||
text-align: center;
|
||||
}
|
||||
.timeago {
|
||||
margin-left: 5px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img-thumbnail{
|
||||
padding: 4px;
|
||||
background-color: white;
|
||||
border: 1px solid #dddddd;
|
||||
border-radius: 4px;
|
||||
|
||||
&.large{
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover + &.large{
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.meeting-url-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.recording-update-trigger {
|
||||
&.recording-unpublished {
|
||||
color: red;
|
||||
}
|
||||
|
||||
&.recording-unlisted {
|
||||
color: #e3a91e;
|
||||
}
|
||||
|
||||
&.recording-published {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-spinner {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.disabled-button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.youtube-red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cloud-blue {
|
||||
color: cornflowerblue;
|
||||
}
|
||||
|
||||
.green-check {
|
||||
color: limegreen;
|
||||
}
|
||||
|
||||
.top-buffer {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tooltip-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#youtube-footer{
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
3
app/assets/stylesheets/meetings.scss
Normal file
3
app/assets/stylesheets/meetings.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the Meetings controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
3
app/assets/stylesheets/rooms.scss
Normal file
3
app/assets/stylesheets/rooms.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// 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/
|
50
app/assets/stylesheets/sessions.scss
Normal file
50
app/assets/stylesheets/sessions.scss
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
}
|
||||
}
|
4
app/channels/application_cable/channel.rb
Normal file
4
app/channels/application_cable/channel.rb
Normal file
@ -0,0 +1,4 @@
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
4
app/channels/application_cable/connection.rb
Normal file
4
app/channels/application_cable/connection.rb
Normal file
@ -0,0 +1,4 @@
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
end
|
||||
end
|
41
app/controllers/application_controller.rb
Normal file
41
app/controllers/application_controller.rb
Normal file
@ -0,0 +1,41 @@
|
||||
require 'bigbluebutton_api'
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
include SessionsHelper
|
||||
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
MEETING_NAME_LIMIT = 90
|
||||
USER_NAME_LIMIT = 30
|
||||
|
||||
def meeting_name_limit
|
||||
MEETING_NAME_LIMIT
|
||||
end
|
||||
helper_method :meeting_name_limit
|
||||
|
||||
def user_name_limit
|
||||
USER_NAME_LIMIT
|
||||
end
|
||||
helper_method :user_name_limit
|
||||
|
||||
# Determines if the BigBlueButton endpoint is configured (or set to default).
|
||||
def bigbluebutton_endpoint_default?
|
||||
Rails.configuration.bigbluebutton_endpoint_default == Rails.configuration.bigbluebutton_endpoint
|
||||
end
|
||||
helper_method :bigbluebutton_endpoint_default?
|
||||
|
||||
private
|
||||
|
||||
# Ensure the user is logged into the room they are accessing.
|
||||
def verify_room_ownership
|
||||
return unless params.include?(:room_uid)
|
||||
@room = Room.find_by(uid: params[:room_uid])
|
||||
|
||||
# Redirect to correct room or root if not logged in.
|
||||
if current_user.nil?
|
||||
redirect_to root_path
|
||||
elsif @room.nil? || current_user != @room.user
|
||||
redirect_to room_path(current_user.room.uid)
|
||||
end
|
||||
end
|
||||
end
|
0
app/controllers/concerns/.keep
Normal file
0
app/controllers/concerns/.keep
Normal file
9
app/controllers/main_controller.rb
Normal file
9
app/controllers/main_controller.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class MainController < ApplicationController
|
||||
|
||||
# GET /
|
||||
def index
|
||||
# If the user is logged in already, move them along to their room.
|
||||
redirect_to room_path(current_user.room.uid) if current_user
|
||||
end
|
||||
|
||||
end
|
82
app/controllers/meetings_controller.rb
Normal file
82
app/controllers/meetings_controller.rb
Normal file
@ -0,0 +1,82 @@
|
||||
class MeetingsController < ApplicationController
|
||||
|
||||
before_action :verify_room_ownership
|
||||
skip_before_action :verify_room_ownership, only: [:join, :wait]
|
||||
|
||||
# GET /rooms/:room_uid/meetings
|
||||
def index
|
||||
|
||||
end
|
||||
|
||||
# GET /rooms/:room_uid/meetings/:meeting_uid
|
||||
def show
|
||||
|
||||
end
|
||||
|
||||
# POST /rooms/:room_uid/meetings
|
||||
def create
|
||||
@meeting = Meeting.new(meeting_params(@room))
|
||||
|
||||
if @meeting.save
|
||||
# Create the meeting on the BigBlueButton server and join the user into the meeting.
|
||||
redirect_to join_meeting_path(room_uid: @room.uid, meeting_uid: @meeting.uid)
|
||||
else
|
||||
# Meeting couldn't be created, handle error.
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# GET /rooms/:room_uid/meetings/:meeting_uid/join
|
||||
def join
|
||||
@meeting = Meeting.find_by(uid: params[:meeting_uid])
|
||||
|
||||
if @meeting
|
||||
opts = default_meeting_options
|
||||
if @meeting.is_running?
|
||||
if current_user
|
||||
# Check if the current user is the room/session owner.
|
||||
opts[:user_is_moderator] = @meeting.room.owned_by?(current_user)
|
||||
redirect_to @meeting.join_path(current_user.name, opts)
|
||||
else
|
||||
# If the unauthenticated user has supplied a join name.
|
||||
if params[:join_name]
|
||||
redirect_to @meeting.join_path(params[:join_name], opts)
|
||||
else
|
||||
# Render the join page so they can supploy their name.
|
||||
render :join
|
||||
end
|
||||
end
|
||||
else
|
||||
# Only start the meeting if owner is joining first.
|
||||
if current_user && @meeting.room.owned_by?(current_user)
|
||||
opts[:user_is_moderator] = true
|
||||
redirect_to @meeting.join_path(current_user.name, opts)
|
||||
else
|
||||
# Send the user to a polling page that will auto join them when it starts.
|
||||
# The wait action/page handles input of name for unauthenticated users.
|
||||
render :wait
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET /rooms/:room_uid/meetings/:meeting_uid/wait
|
||||
def wait
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def meeting_params(room)
|
||||
params.require(:meeting).permit(:name).merge!(room_id: room.id)
|
||||
end
|
||||
|
||||
def default_meeting_options
|
||||
{
|
||||
user_is_moderator: false,
|
||||
meeting_logout_url: request.base_url + room_path(room_uid: @meeting.room.uid),
|
||||
moderator_message: "To invite someone to the meeting, send them this link:
|
||||
#{request.base_url + join_meeting_path(room_uid: @meeting.room.uid, meeting_uid: @meeting.uid)}"
|
||||
}
|
||||
end
|
||||
end
|
9
app/controllers/rooms_controller.rb
Normal file
9
app/controllers/rooms_controller.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class RoomsController < ApplicationController
|
||||
|
||||
before_action :verify_room_ownership
|
||||
|
||||
# GET /rooms/:room_uid
|
||||
def index
|
||||
@meeting = Meeting.new
|
||||
end
|
||||
end
|
22
app/controllers/sessions_controller.rb
Normal file
22
app/controllers/sessions_controller.rb
Normal file
@ -0,0 +1,22 @@
|
||||
class SessionsController < ApplicationController
|
||||
|
||||
# GET /login
|
||||
def new
|
||||
end
|
||||
|
||||
# GET /logout
|
||||
def destroy
|
||||
logout
|
||||
end
|
||||
|
||||
# GET/POST /auth/:provider/callback
|
||||
def create
|
||||
user = User.from_omniauth(request.env['omniauth.auth'])
|
||||
login(user)
|
||||
end
|
||||
|
||||
# POST /auth/failure
|
||||
def fail
|
||||
|
||||
end
|
||||
end
|
14
app/helpers/application_helper.rb
Normal file
14
app/helpers/application_helper.rb
Normal file
@ -0,0 +1,14 @@
|
||||
module ApplicationHelper
|
||||
|
||||
# Gets all configured omniauth providers.
|
||||
def configured_providers
|
||||
Rails.configuration.providers.select do |provider|
|
||||
Rails.configuration.send("omniauth_#{provider}")
|
||||
end
|
||||
end
|
||||
|
||||
# Generates the login URL for a specific provider.
|
||||
def omniauth_login_url(provider)
|
||||
"/auth/#{provider}"
|
||||
end
|
||||
end
|
149
app/helpers/big_blue_helper.rb
Normal file
149
app/helpers/big_blue_helper.rb
Normal file
@ -0,0 +1,149 @@
|
||||
module BigBlueHelper
|
||||
|
||||
META_LISTED = "gl-listed"
|
||||
META_TOKEN = "gl-token"
|
||||
|
||||
def bbb_endpoint
|
||||
Rails.configuration.bigbluebutton_endpoint
|
||||
end
|
||||
|
||||
def bbb_secret
|
||||
Rails.configuration.bigbluebutton_secret
|
||||
end
|
||||
|
||||
def bbb
|
||||
@bbb ||= BigBlueButton::BigBlueButtonApi.new(bbb_endpoint + "api", bbb_secret, "0.8")
|
||||
end
|
||||
|
||||
# Generates a BigBlueButton meeting id from a meeting token.
|
||||
def bbb_meeting_id(id)
|
||||
Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base] + id).to_s
|
||||
end
|
||||
|
||||
# Generates a random password for a meeting.
|
||||
def random_password(length)
|
||||
o = ([('a'..'z'), ('A'..'Z')].map do |i| i.to_a end).flatten
|
||||
((0...length).map do o[rand(o.length)] end).join
|
||||
end
|
||||
|
||||
# Checks if a meeting is running on the BigBlueButton server.
|
||||
def meeting_is_running?(id)
|
||||
begin
|
||||
bbb.get_meeting_info(id, nil)
|
||||
return true
|
||||
rescue BigBlueButton::BigBlueButtonException => exc
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def start_meeting(options)
|
||||
meeting_id = bbb_meeting_id(name)
|
||||
|
||||
# Need to create the meeting on the BigBlueButton server.
|
||||
create_options = {
|
||||
record: options[:meeting_recorded].to_s,
|
||||
#logoutURL: options[:meeting_logout_url] || request.base_url,
|
||||
moderatorPW: random_password(12),
|
||||
attendeePW: random_password(12),
|
||||
moderatorOnlyMessage: options[:moderator_message],
|
||||
"meta_#{BigBlueHelper::META_LISTED}": false,
|
||||
"meta_#{BigBlueHelper::META_TOKEN}": name
|
||||
}
|
||||
|
||||
#meeting_options.merge!(
|
||||
#{ "meta_room-id": options[:room_owner],
|
||||
# "meta_meeting-name": options[:meeting_name]}
|
||||
#) if options[:room_owner]
|
||||
|
||||
# Send the create request.
|
||||
begin
|
||||
bbb.create_meeting(name, meeting_id, create_options)
|
||||
rescue BigBlueButton::BigBlueButtonException => exc
|
||||
puts "BigBlueButton failed on create: #{exc.key}: #{exc.message}"
|
||||
end
|
||||
|
||||
# Get the meeting info.
|
||||
#bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil)
|
||||
|
||||
meeting_id
|
||||
end
|
||||
|
||||
# Generates a URL to join a BigBlueButton session.
|
||||
def join_url(meeting_id, username, options = {})
|
||||
options[:meeting_recorded] ||= false
|
||||
options[:user_is_moderator] ||= false
|
||||
options[:wait_for_moderator] ||= false
|
||||
options[:meeting_logout_url] ||= nil
|
||||
options[:meeting_name] ||= name
|
||||
options[:room_owner] ||= nil
|
||||
options[:moderator_message] ||= ''
|
||||
|
||||
return call_invalid_res if !bbb
|
||||
|
||||
# Get the meeting info.
|
||||
meeting_info = bbb.get_meeting_info(meeting_id, nil)
|
||||
|
||||
# Determine the password to use when joining.
|
||||
password = if options[:user_is_moderator]
|
||||
meeting_info[:moderatorPW]
|
||||
else
|
||||
meeting_info[:attendeePW]
|
||||
end
|
||||
|
||||
# Generate the join URL.
|
||||
bbb.join_meeting_url(meeting_id, username, password)
|
||||
end
|
||||
|
||||
# Generates a URL to join a BigBlueButton session.
|
||||
def join_url_old(meeting_token, full_name, options={})
|
||||
options[:meeting_recorded] ||= false
|
||||
options[:user_is_moderator] ||= false
|
||||
options[:wait_for_moderator] ||= false
|
||||
options[:meeting_logout_url] ||= nil
|
||||
options[:meeting_name] ||= meeting_token
|
||||
options[:room_owner] ||= nil
|
||||
options[:moderator_message] ||= ''
|
||||
|
||||
return call_invalid_res if !bbb
|
||||
|
||||
meeting_id = bbb_meeting_id(meeting_token)
|
||||
|
||||
unless meeting_is_running?(meeting_id)
|
||||
# Need to create the meeting on the BigBlueButton server.
|
||||
create_options = {
|
||||
record: options[:meeting_recorded].to_s,
|
||||
logoutURL: options[:meeting_logout_url] || request.base_url,
|
||||
moderatorPW: random_password(12),
|
||||
attendeePW: random_password(12),
|
||||
moderatorOnlyMessage: options[:moderator_message],
|
||||
"meta_#{BigBlueHelper::META_LISTED}": false,
|
||||
"meta_#{BigBlueHelper::META_TOKEN}": meeting_token
|
||||
}
|
||||
|
||||
#meeting_options.merge!(
|
||||
#{ "meta_room-id": options[:room_owner],
|
||||
# "meta_meeting-name": options[:meeting_name]}
|
||||
#) if options[:room_owner]
|
||||
|
||||
# Send the create request.
|
||||
begin
|
||||
bbb.create_meeting(options[:meeting_name], meeting_id, create_options)
|
||||
rescue BigBlueButton::BigBlueButtonException => exc
|
||||
puts "BigBlueButton failed on create: #{exc.key}: #{exc.message}"
|
||||
end
|
||||
|
||||
# Get the meeting info.
|
||||
bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil)
|
||||
end
|
||||
|
||||
# Determine the password to use when joining.
|
||||
password = if options[:user_is_moderator]
|
||||
bbb_meeting_info[:moderatorPW]
|
||||
else
|
||||
bbb_meeting_info[:attendeePW]
|
||||
end
|
||||
|
||||
# Generate the join URL.
|
||||
bbb.join_meeting_url(meeting_id, full_name, password)
|
||||
end
|
||||
end
|
2
app/helpers/main_helper.rb
Normal file
2
app/helpers/main_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module MainHelper
|
||||
end
|
2
app/helpers/meetings_helper.rb
Normal file
2
app/helpers/meetings_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module MeetingsHelper
|
||||
end
|
2
app/helpers/rooms_helper.rb
Normal file
2
app/helpers/rooms_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module RoomsHelper
|
||||
end
|
18
app/helpers/sessions_helper.rb
Normal file
18
app/helpers/sessions_helper.rb
Normal file
@ -0,0 +1,18 @@
|
||||
module SessionsHelper
|
||||
# Logs a user into GreenLight.
|
||||
def login(user)
|
||||
session[:user_id] = user.id
|
||||
redirect_to room_path(user.room.uid)
|
||||
end
|
||||
|
||||
# Logs current user out of GreenLight.
|
||||
def logout
|
||||
session.delete(:user_id) if current_user
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
# Retrieves the current user.
|
||||
def current_user
|
||||
@current_user ||= User.find_by(id: session[:user_id])
|
||||
end
|
||||
end
|
2
app/jobs/application_job.rb
Normal file
2
app/jobs/application_job.rb
Normal file
@ -0,0 +1,2 @@
|
||||
class ApplicationJob < ActiveJob::Base
|
||||
end
|
4
app/mailers/application_mailer.rb
Normal file
4
app/mailers/application_mailer.rb
Normal file
@ -0,0 +1,4 @@
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'from@example.com'
|
||||
layout 'mailer'
|
||||
end
|
3
app/models/application_record.rb
Normal file
3
app/models/application_record.rb
Normal file
@ -0,0 +1,3 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
0
app/models/concerns/.keep
Normal file
0
app/models/concerns/.keep
Normal file
99
app/models/meeting.rb
Normal file
99
app/models/meeting.rb
Normal file
@ -0,0 +1,99 @@
|
||||
class Meeting < ApplicationRecord
|
||||
|
||||
before_create :generate_meeting_id
|
||||
|
||||
belongs_to :room
|
||||
|
||||
# Creates a meeting on the BigBlueButton server.
|
||||
def create(options = {})
|
||||
create_options = {
|
||||
record: options[:meeting_recorded].to_s,
|
||||
logoutURL: options[:meeting_logout_url] || '',
|
||||
moderatorPW: random_password(12),
|
||||
attendeePW: random_password(12),
|
||||
moderatorOnlyMessage: options[:moderator_message],
|
||||
"meta_#{BigBlueHelper::META_LISTED}": false,
|
||||
"meta_#{BigBlueHelper::META_TOKEN}": name
|
||||
}
|
||||
|
||||
#meeting_options.merge!(
|
||||
#{ "meta_room-id": options[:room_owner],
|
||||
# "meta_meeting-name": options[:meeting_name]}
|
||||
#) if options[:room_owner]
|
||||
|
||||
# Send the create request.
|
||||
begin
|
||||
bbb.create_meeting(name, uid, create_options)
|
||||
rescue BigBlueButton::BigBlueButtonException => exc
|
||||
puts "BigBlueButton failed on create: #{exc.key}: #{exc.message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a URL to join a user into a meeting.
|
||||
def join_path(username, options = {})
|
||||
# Create the meeting if it isn't running.
|
||||
create(options) unless is_running?
|
||||
|
||||
# Set meeting options.
|
||||
options[:meeting_logout_url] ||= nil
|
||||
options[:moderator_message] ||= ''
|
||||
options[:user_is_moderator] ||= false
|
||||
|
||||
options[:meeting_recorded] ||= false
|
||||
|
||||
#options[:wait_for_moderator] ||= false
|
||||
#options[:meeting_name] ||= name
|
||||
#options[:room_owner] ||= nil
|
||||
|
||||
return call_invalid_res if !bbb
|
||||
|
||||
# Get the meeting info.
|
||||
meeting_info = bbb.get_meeting_info(uid, nil)
|
||||
|
||||
# Determine the password to use when joining.
|
||||
password = if options[:user_is_moderator]
|
||||
meeting_info[:moderatorPW]
|
||||
else
|
||||
meeting_info[:attendeePW]
|
||||
end
|
||||
|
||||
# Generate the join URL.
|
||||
bbb.join_meeting_url(uid, username, password)
|
||||
end
|
||||
|
||||
# Checks if a meeting is running on the BigBlueButton server.
|
||||
def is_running?
|
||||
begin
|
||||
bbb.get_meeting_info(uid, nil)
|
||||
return true
|
||||
rescue BigBlueButton::BigBlueButtonException => exc
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bbb_endpoint
|
||||
Rails.configuration.bigbluebutton_endpoint
|
||||
end
|
||||
|
||||
def bbb_secret
|
||||
Rails.configuration.bigbluebutton_secret
|
||||
end
|
||||
|
||||
# Use one common instance of the BigBlueButton API for all meetings.
|
||||
def bbb
|
||||
@@bbb ||= BigBlueButton::BigBlueButtonApi.new(bbb_endpoint + "api", bbb_secret, "0.8")
|
||||
end
|
||||
|
||||
# Generates a BigBlueButton meeting id from a meeting token.
|
||||
def generate_meeting_id
|
||||
self.uid = Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base] + name + Time.now.to_i.to_s).to_s
|
||||
end
|
||||
|
||||
# Generates a random password for a meeting.
|
||||
def random_password(length)
|
||||
o = ([('a'..'z'), ('A'..'Z')].map do |i| i.to_a end).flatten
|
||||
((0...length).map do o[rand(o.length)] end).join
|
||||
end
|
||||
end
|
17
app/models/room.rb
Normal file
17
app/models/room.rb
Normal file
@ -0,0 +1,17 @@
|
||||
class Room < ApplicationRecord
|
||||
|
||||
before_create :set_uid
|
||||
|
||||
belongs_to :user
|
||||
has_many :meetings
|
||||
|
||||
def owned_by?(user)
|
||||
user.room == self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_uid
|
||||
self.uid = Digest::SHA1.hexdigest(user.uid + user.provider + user.username)[0..12]
|
||||
end
|
||||
end
|
51
app/models/user.rb
Normal file
51
app/models/user.rb
Normal file
@ -0,0 +1,51 @@
|
||||
class User < ApplicationRecord
|
||||
|
||||
has_one :room
|
||||
|
||||
class << self
|
||||
|
||||
# Generates a user from omniauth.
|
||||
def from_omniauth(auth)
|
||||
user = find_or_initialize_by(uid: auth['uid'], provider: auth['provider'])
|
||||
user.name = send("#{auth['provider']}_name", auth)
|
||||
user.username = send("#{auth['provider']}_username", auth)
|
||||
user.email = send("#{auth['provider']}_email", auth)
|
||||
#user.token = auth['credentials']['token']
|
||||
|
||||
# Create a room for the user if they don't have one.
|
||||
user.room = Room.create unless user.room
|
||||
|
||||
user.save!
|
||||
user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Provider attributes.
|
||||
def twitter_name(auth)
|
||||
auth['info']['name']
|
||||
end
|
||||
|
||||
def twitter_username(auth)
|
||||
auth['info']['nickname']
|
||||
end
|
||||
|
||||
def twitter_email(auth)
|
||||
auth['info']['email']
|
||||
end
|
||||
|
||||
def google_name(auth)
|
||||
auth['info']['name']
|
||||
end
|
||||
|
||||
def google_username(auth)
|
||||
auth['info']['email'].split('@').first
|
||||
end
|
||||
|
||||
def google_email(auth)
|
||||
auth['info']['email']
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
43
app/views/layouts/application.html.erb
Normal file
43
app/views/layouts/application.html.erb
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Greenlight20</title>
|
||||
<%= csrf_meta_tags %>
|
||||
|
||||
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
|
||||
</head>
|
||||
|
||||
<body class="app-background" data-controller="<%= params[:controller] %>" data-action="<%= params[:action] %>"
|
||||
data-resource="<%= params[:resource] %>"
|
||||
data-current-user="<%= current_user.try(:encrypted_id) %>"
|
||||
style="background-image:url(<%= image_path('background.png') if params[:controller] == 'main' %>);">
|
||||
|
||||
<!-- Messages -->
|
||||
<div id='alerts'>
|
||||
<div class='flash-alerts'>
|
||||
<% flash.each do |name, msg| %>
|
||||
<div class="alert alert-<%= name %> alert-dismissible fade in" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<span class="alert-message"><%= msg %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class='header'>
|
||||
<span class="logo-wrapper pull-left">
|
||||
<% if current_user %>
|
||||
<%= link_to image_tag("bbb_logo.png", :alt => "BigBlueButton", :class => "logo"), room_path(current_user) %>
|
||||
<% else %>
|
||||
<%= link_to image_tag("bbb_logo.png", :alt => "BigBlueButton", :class => "logo"), root_path %>
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
13
app/views/layouts/mailer.html.erb
Normal file
13
app/views/layouts/mailer.html.erb
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style>
|
||||
/* Email styles need to be inline */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
1
app/views/layouts/mailer.text.erb
Normal file
1
app/views/layouts/mailer.text.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= yield %>
|
15
app/views/main/_invite_join.html.erb
Normal file
15
app/views/main/_invite_join.html.erb
Normal file
@ -0,0 +1,15 @@
|
||||
<div class="row invite-join-wrapper hidden">
|
||||
<div class="col-xs-6">
|
||||
<%= render 'shared/title', title: t('invite') do %>
|
||||
<span><%= t('invite_description') %></span>
|
||||
<% end %>
|
||||
<%= render 'shared/meeting_url', hidden: false %>
|
||||
</div>
|
||||
<div class="verticle-line"></div>
|
||||
<div class="col-xs-6">
|
||||
<%= render 'shared/title', title: t('join'), title_class: 'join-meeting-title' %>
|
||||
<button type="button" class="btn btn-primary center-block meeting-start has-tooltip" data-placement="top" title="<%= t('start_meeting') %>">
|
||||
<%= t('start_join') %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
34
app/views/main/index.html.erb
Normal file
34
app/views/main/index.html.erb
Normal file
@ -0,0 +1,34 @@
|
||||
<% content_for :title do %>
|
||||
<div class="title">
|
||||
<h2>Landing Title</h2>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="page-wrapper meetings">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="center-panel-wrapper">
|
||||
<%= render layout: 'shared/center_panel' do %>
|
||||
<div class="center-block center-panel-content-size col-xs-12">
|
||||
<%= render 'shared/meeting_name_form' %>
|
||||
|
||||
<div class="row">
|
||||
<%= render 'main/invite_join' %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% unless configured_providers.empty? %>
|
||||
<div class="center-block col-xs-6 login">
|
||||
<%= render 'shared/signup' %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="text-center" style="padding-top:20px;">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/yGX3JCv7OVM" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
7
app/views/meetings/join.html.erb
Normal file
7
app/views/meetings/join.html.erb
Normal file
@ -0,0 +1,7 @@
|
||||
<p>The join the meeting, enter a name.</p>
|
||||
|
||||
<p>Enter a name to start a session.</p>
|
||||
<%= form_tag join_meeting_path(room_uid: @meeting.room.uid, meeting_uid: @meeting.uid) do %>
|
||||
<%= text_field_tag "join_name" %>
|
||||
<%= submit_tag "Join" %>
|
||||
<% end %>
|
1
app/views/meetings/wait.html.erb
Normal file
1
app/views/meetings/wait.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<p>waiting for meeting to start...</p>
|
22
app/views/rooms/index.html.erb
Normal file
22
app/views/rooms/index.html.erb
Normal file
@ -0,0 +1,22 @@
|
||||
<p>This is a room.</p>
|
||||
|
||||
<p><%= @room.user.name %><p>
|
||||
|
||||
<%= link_to 'Sessions', root_path %>
|
||||
<%= link_to 'Recordings', root_path %>
|
||||
<%= link_to 'Settings', root_path %>
|
||||
|
||||
<p>Enter a name to start a session.</p>
|
||||
<%= form_for @meeting do |f| %>
|
||||
<%= f.text_field :name %>
|
||||
<%= f.submit "Start" %>
|
||||
<% end %>
|
||||
|
||||
<br><br><br><br><br>
|
||||
<p>Previous Sessions</p>
|
||||
<% current_user.room.meetings.each do |m| %>
|
||||
<p><%= m.name + " " + m.is_running?.to_s %></p>
|
||||
<% end %>
|
||||
<br>
|
||||
|
||||
<%= link_to 'Logout', user_logout_path %>
|
32
app/views/sessions/new.html.erb
Normal file
32
app/views/sessions/new.html.erb
Normal file
@ -0,0 +1,32 @@
|
||||
<% content_for :title do %>
|
||||
<div class="title">
|
||||
<h2><%= "Login" %></h2>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="page-wrapper login">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="center-panel-wrapper">
|
||||
<%= render layout: 'shared/center_panel' do %>
|
||||
<div class="center-block center-panel-content-size col-xs-12">
|
||||
|
||||
<% configured_providers.each do |provider| %>
|
||||
<%= link_to omniauth_login_url(provider), class: "signin-link signin-link-#{provider}" do %>
|
||||
<div class="signin-button center-block">
|
||||
<div class="signin-icon-wrapper">
|
||||
<%= image_tag("#{provider}_logo.png", alt: "T", class: "signin-icon") %>
|
||||
</div>
|
||||
<div class="signin-text-wrapper text-center">
|
||||
<span class="signin-text"><%= "Login with #{provider.capitalize}" %></span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
21
app/views/shared/_center_panel.html.erb
Normal file
21
app/views/shared/_center_panel.html.erb
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="center-panel">
|
||||
<div class="row">
|
||||
<div class="center-block center-panel-size col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<% if bigbluebutton_endpoint_default? %>
|
||||
<div class="panel-alert alert alert-danger"><%= t('warning_bigbluebutton_endpoint_default') %></div>
|
||||
<% end %>
|
||||
<h3 class="title-wrapper text-center">
|
||||
<%= yield :title %>
|
||||
</h3>
|
||||
<%= yield %>
|
||||
</div>
|
||||
<%= yield :footer %>
|
||||
<div class="help-link">
|
||||
<%= link_to t('help'), 'http://bigbluebutton.org/videos/', target: '_blank' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
3
app/views/shared/_meeting_name_form.html.erb
Normal file
3
app/views/shared/_meeting_name_form.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<div class="meeting-name-form-wrapper">
|
||||
<input type="text" class="form-control meeting-name" placeholder="Enter meeting name." maxlength="<%= meeting_name_limit %>">
|
||||
</div>
|
42
app/views/shared/_meeting_url.html.erb
Normal file
42
app/views/shared/_meeting_url.html.erb
Normal file
@ -0,0 +1,42 @@
|
||||
<div <%= "hidden" if hidden %> class="meeting-url-wrapper">
|
||||
<div class="meeting-url-group">
|
||||
<input type="text" class="form-control meeting-url"/>
|
||||
</div>
|
||||
|
||||
<% if current_user %>
|
||||
<% body = t('meeting_invite.signed_in.body', user: current_user.name) %>
|
||||
<% subject = t('meeting_invite.signed_in.subject', user: current_user.name) %>
|
||||
<% else %>
|
||||
<% body = t('meeting_invite.not_signed_in.body') %>
|
||||
<% subject = t('meeting_invite.not_signed_in.subject') %>
|
||||
<% end %>
|
||||
|
||||
<div class="meeting-url-button-group center-block">
|
||||
<button type="button" class="btn btn-default meeting-url-copy has-tooltip"
|
||||
title="<%= t('url_copy_explanation') %>"
|
||||
data-copied-hint="<%= t('copied') %>"
|
||||
data-copy-error="<%= t('copy_error') %>"
|
||||
data-copy-hint="<%= t('url_copy_explanation') %>"
|
||||
>
|
||||
<%= icon('clipboard') %>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-default meeting-invite has-tooltip"
|
||||
title="<%= t('meeting_invite.explanation') %>"
|
||||
data-invite-body="<%= body %>"
|
||||
data-invite-subject="<%= subject %>"
|
||||
>
|
||||
<%= icon('envelope-o') %>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-default meeting-url-qrcode has-tooltip"
|
||||
title="<%= t('qrcode.explanation') %>"
|
||||
data-qrcode-generated-hint="<%= t('qrcode.generated') %>"
|
||||
data-qrcode-generate-error="<%= t('qrcode.generate_error') %>"
|
||||
data-qrcode-generate-hint="<%= t('qrcode.explanation') %>"
|
||||
>
|
||||
<%= icon('qrcode') %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="meeting-url-qrcode-group center-block has-tooltip"></div>
|
||||
</div>
|
17
app/views/shared/_signup.html.erb
Normal file
17
app/views/shared/_signup.html.erb
Normal file
@ -0,0 +1,17 @@
|
||||
<span class="signup">
|
||||
<% if current_user %>
|
||||
<div class="text-center">
|
||||
<% if @main_room %>
|
||||
<span><%= t('logged_in_description_html', link: link_to(current_user.name, room_path(current_user))) %></span>
|
||||
<% else %>
|
||||
<%= link_to(t('return_to_room'), room_path(current_user)) %>
|
||||
<% end %>
|
||||
<div><%= link_to t('logout'), user_logout_url %></div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-center">
|
||||
<span><%= t('login_description') %></span>
|
||||
<div><%= link_to t('login'), user_login_url %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</span>
|
7
app/views/shared/_title.html.erb
Normal file
7
app/views/shared/_title.html.erb
Normal file
@ -0,0 +1,7 @@
|
||||
<% title_class ||= '' %>
|
||||
<div class="title-wrapper text-center">
|
||||
<div class="title">
|
||||
<h2 class="<%= title_class if title_class %>"><%= title %></h2>
|
||||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
Reference in New Issue
Block a user