diff --git a/app/assets/locales/en.json b/app/assets/locales/en.json
index 60a29c0d66..8e603ad8e0 100644
--- a/app/assets/locales/en.json
+++ b/app/assets/locales/en.json
@@ -164,6 +164,10 @@
"wrong_access_code": "Wrong Access Code",
"generate_viewers_access_code": "Generate access code for viewers",
"generate_mods_access_code": "Generate access code for moderators",
+ "server_tag": "Select a server type for this room",
+ "default_tag_name": "Default",
+ "server_tag_desired": "Desired",
+ "server_tag_required": "Required",
"are_you_sure_delete_room": "Are you sure you want to delete this room?"
}
},
@@ -437,6 +441,7 @@
},
"error": {
"problem_completing_action": "The action can't be completed. \n Please try again.",
+ "server_type_unavailable": "The required server type is unavailable. Please select a different type in the room settings.",
"file_type_not_supported": "The file type is not supported.",
"file_size_too_large": "The file size is too large.",
"file_upload_error": "The file can't be uploaded.",
diff --git a/app/controllers/api/v1/meetings_controller.rb b/app/controllers/api/v1/meetings_controller.rb
index be51924d4b..f0a68b5819 100644
--- a/app/controllers/api/v1/meetings_controller.rb
+++ b/app/controllers/api/v1/meetings_controller.rb
@@ -31,7 +31,7 @@ def start
begin
MeetingStarter.new(room: @room, base_url: request.base_url, current_user:, provider: current_provider).call
rescue BigBlueButton::BigBlueButtonException => e
- return render_error status: :bad_request unless e.key == 'idNotUnique'
+ return render_error status: :bad_request, errors: e.key unless e.key == 'idNotUnique'
end
render_data data: BigBlueButtonApi.new(provider: current_provider).join_meeting(
diff --git a/app/controllers/api/v1/server_tags_controller.rb b/app/controllers/api/v1/server_tags_controller.rb
new file mode 100644
index 0000000000..8692a657f4
--- /dev/null
+++ b/app/controllers/api/v1/server_tags_controller.rb
@@ -0,0 +1,37 @@
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+#
+# Copyright (c) 2022 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.
+#
+# Greenlight 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 Greenlight; if not, see .
+
+# frozen_string_literal: true
+
+module Api
+ module V1
+ class ServerTagsController < ApiController
+ # GET /api/v1/server_tags/:friendly_id
+ # Returns a list of all allowed tags&names for the room's owner
+ def show
+ tag_names = Rails.configuration.server_tag_names
+ tag_roles = Rails.configuration.server_tag_roles
+ return render_data data: {}, status: :ok if tag_names.blank?
+
+ room = Room.find_by(friendly_id: params[:friendly_id])
+ return render_data data: {}, status: :ok if room.nil?
+
+ allowed_tag_names = tag_names.reject { |tag, _| tag_roles.key?(tag) && tag_roles[tag].exclude?(room.user.role_id) }
+ render_data data: allowed_tag_names, status: :ok
+ end
+ end
+ end
+end
diff --git a/app/javascript/components/rooms/room/room_settings/RoomSettings.jsx b/app/javascript/components/rooms/room/room_settings/RoomSettings.jsx
index 159f8ed303..973ca6afc0 100644
--- a/app/javascript/components/rooms/room/room_settings/RoomSettings.jsx
+++ b/app/javascript/components/rooms/room/room_settings/RoomSettings.jsx
@@ -33,6 +33,8 @@ import { useAuth } from '../../../../contexts/auth/AuthProvider';
import UpdateRoomNameForm from './forms/UpdateRoomNameForm';
import useRoom from '../../../../hooks/queries/rooms/useRoom';
import UnshareRoom from './UnshareRoom';
+import useServerTags from '../../../../hooks/queries/rooms/useServerTags';
+import ServerTagRow from './ServerTagRow';
export default function RoomSettings() {
const { t } = useTranslation();
@@ -41,6 +43,7 @@ export default function RoomSettings() {
const roomSetting = useRoomSettings(friendlyId);
const { data: roomConfigs } = useRoomConfigs();
const { data: room } = useRoom(friendlyId);
+ const { data: serverTags } = useServerTags(friendlyId);
const updateMutationWrapper = () => useUpdateRoomSetting(friendlyId);
const deleteMutationWrapper = (args) => useDeleteRoom({ friendlyId, ...args });
@@ -66,6 +69,15 @@ export default function RoomSettings() {
config={roomConfigs?.glModeratorAccessCode}
description={t('room.settings.generate_mods_access_code')}
/>
+ {serverTags && Object.keys(serverTags).length !== 0 && (
+
+ )}
{ t('room.settings.user_settings') }
diff --git a/app/javascript/components/rooms/room/room_settings/ServerTagRow.jsx b/app/javascript/components/rooms/room/room_settings/ServerTagRow.jsx
new file mode 100644
index 0000000000..044df49710
--- /dev/null
+++ b/app/javascript/components/rooms/room/room_settings/ServerTagRow.jsx
@@ -0,0 +1,118 @@
+// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+//
+// Copyright (c) 2022 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.
+//
+// Greenlight 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 Greenlight; if not, see .
+
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import PropTypes from 'prop-types';
+import {
+ Row, Col, Dropdown, ButtonGroup, ToggleButton,
+} from 'react-bootstrap';
+import SimpleSelect from '../../../shared_components/utilities/SimpleSelect';
+
+export default function ServerTagRow({
+ updateMutation: useUpdateAPI, currentTag, tagRequired, serverTags, description,
+}) {
+ const updateAPI = useUpdateAPI();
+ const { t } = useTranslation();
+
+ function getDefaultTagName() {
+ return t('room.settings.default_tag_name');
+ }
+
+ function getTagName(tag) {
+ if (tag in serverTags) {
+ return serverTags[tag];
+ }
+ return getDefaultTagName();
+ }
+
+ const dropdownTags = Object.entries(serverTags).map(([tagString, tagName]) => (
+ (
+ updateAPI.mutate({ settingName: 'serverTag', settingValue: tagString })}
+ >
+ {tagName}
+
+ )
+ ));
+
+ return (
+
+ {description}
+
+
+ {[
+ updateAPI.mutate({ settingName: 'serverTag', settingValue: '' })}
+ >
+ {getDefaultTagName()}
+ ,
+ ].concat(dropdownTags)}
+
+
+
+
+ {
+ updateAPI.mutate({ settingName: 'serverTagRequired', settingValue: false });
+ }}
+ >
+ {t('room.settings.server_tag_desired')}
+
+ {
+ updateAPI.mutate({ settingName: 'serverTagRequired', settingValue: true });
+ }}
+ >
+ {t('room.settings.server_tag_required')}
+
+
+
+
+ );
+}
+
+ServerTagRow.defaultProps = {
+ currentTag: '',
+ tagRequired: false,
+};
+
+ServerTagRow.propTypes = {
+ updateMutation: PropTypes.func.isRequired,
+ currentTag: PropTypes.string,
+ tagRequired: PropTypes.bool,
+ serverTags: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+ description: PropTypes.string.isRequired,
+};
diff --git a/app/javascript/hooks/mutations/rooms/useStartMeeting.jsx b/app/javascript/hooks/mutations/rooms/useStartMeeting.jsx
index bfb944e00d..630d55471b 100644
--- a/app/javascript/hooks/mutations/rooms/useStartMeeting.jsx
+++ b/app/javascript/hooks/mutations/rooms/useStartMeeting.jsx
@@ -28,8 +28,12 @@ export default function useStartMeeting(friendlyId) {
onSuccess: (joinUrl) => {
window.location.href = joinUrl;
},
- onError: () => {
- toast.error(t('toast.error.problem_completing_action'));
+ onError: (error) => {
+ if (error.response.data.errors !== 'serverTagUnavailable') {
+ toast.error(t('toast.error.problem_completing_action'));
+ } else {
+ toast.error(t('toast.error.server_type_unavailable'));
+ }
},
},
);
diff --git a/app/javascript/hooks/queries/rooms/useServerTags.jsx b/app/javascript/hooks/queries/rooms/useServerTags.jsx
new file mode 100644
index 0000000000..8b551088fb
--- /dev/null
+++ b/app/javascript/hooks/queries/rooms/useServerTags.jsx
@@ -0,0 +1,25 @@
+// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+//
+// Copyright (c) 2022 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.
+//
+// Greenlight 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 Greenlight; if not, see .
+
+import { useQuery } from 'react-query';
+import axios from '../../../helpers/Axios';
+
+export default function useServerTags(friendlyId) {
+ return useQuery(
+ ['getServerTags', friendlyId],
+ () => axios.get(`/server_tags/${friendlyId}.json`).then((resp) => resp.data.data),
+ );
+}
diff --git a/app/services/meeting_starter.rb b/app/services/meeting_starter.rb
index 1b7fb26833..99d95e3ffc 100644
--- a/app/services/meeting_starter.rb
+++ b/app/services/meeting_starter.rb
@@ -39,6 +39,8 @@ def call
settings: 'glViewerAccessCode'
).call
+ handle_server_tag(meeting_options: options)
+
options.merge!(computed_options(access_code: viewer_code['glViewerAccessCode']))
retries = 0
@@ -73,6 +75,23 @@ def computed_options(access_code:)
}
end
+ def handle_server_tag(meeting_options:)
+ if meeting_options['serverTag'].present?
+ tag_names = Rails.configuration.server_tag_names
+ tag_roles = Rails.configuration.server_tag_roles
+ tag = meeting_options.delete('serverTag')
+ tag_required = meeting_options.delete('serverTagRequired')
+
+ if tag_names.key?(tag) && !(tag_roles.key?(tag) && tag_roles[tag].exclude?(@room.user.role_id))
+ tag_param = tag_required == 'true' ? "#{tag} !" : tag
+ meeting_options.store('meta_server-tag', tag_param)
+ end
+ else
+ meeting_options.delete('serverTag')
+ meeting_options.delete('serverTagRequired')
+ end
+ end
+
def presentation_url
return unless @room.presentation.attached?
diff --git a/config/application.rb b/config/application.rb
index 5417b3dc75..73f70e7416 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -86,5 +86,10 @@ class Application < Rails::Application
I18n.load_path += Dir[Rails.root.join('config/locales/*.{rb,yml}').to_s]
config.i18n.fallbacks = %i[en]
config.i18n.enforce_available_locales = false
+
+ # Handle server tag config
+ config.server_tag_names = ENV.fetch('SERVER_TAG_NAMES', '').split(',').to_h { |pair| pair.split(':') }
+ config.server_tag_roles = ENV.fetch('SERVER_TAG_ROLES', '').split(',').to_h { |pair| pair.split(':') }
+ config.server_tag_roles = config.server_tag_roles.transform_values! { |v| v.split('/') }
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 6954ac32f7..94e8119910 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -85,6 +85,7 @@
resources :site_settings, only: :index
resources :rooms_configurations, only: %i[index show], param: :name
resources :locales, only: %i[index show], param: :name
+ resources :server_tags, only: :show, param: :friendly_id
namespace :admin do
resources :users, only: %i[update] do
diff --git a/db/data/20240423162700_create_server_tags_option.rb b/db/data/20240423162700_create_server_tags_option.rb
new file mode 100644
index 0000000000..5298e158ea
--- /dev/null
+++ b/db/data/20240423162700_create_server_tags_option.rb
@@ -0,0 +1,73 @@
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+#
+# Copyright (c) 2022 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.
+#
+# Greenlight 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 Greenlight; if not, see .
+
+# frozen_string_literal: true
+
+class CreateServerTagsOption < ActiveRecord::Migration[7.0]
+ def up
+ MeetingOption.create!(name: 'serverTag', default_value: '') unless MeetingOption.exists?(name: 'serverTag')
+ tag_option = MeetingOption.find_by!(name: 'serverTag')
+ MeetingOption.create!(name: 'serverTagRequired', default_value: 'false') unless MeetingOption.exists?(name: 'serverTagRequired')
+ tag_required_option = MeetingOption.find_by!(name: 'serverTagRequired')
+
+ unless RoomsConfiguration.exists?(meeting_option: tag_option, provider: 'greenlight')
+ RoomsConfiguration.create!(meeting_option: tag_option, value: 'optional', provider: 'greenlight')
+ end
+ unless RoomsConfiguration.exists?(meeting_option: tag_required_option, provider: 'greenlight')
+ RoomsConfiguration.create!(meeting_option: tag_required_option, value: 'optional', provider: 'greenlight')
+ end
+ Tenant.all.each do |tenant|
+ unless RoomsConfiguration.exists?(meeting_option: tag_option, provider: tenant.name)
+ RoomsConfiguration.create!(meeting_option: tag_option, value: 'optional', provider: tenant.name)
+ end
+ unless RoomsConfiguration.exists?(meeting_option: tag_required_option, provider: tenant.name)
+ RoomsConfiguration.create!(meeting_option: tag_required_option, value: 'optional', provider: tenant.name)
+ end
+ end
+
+ if RoomMeetingOption.exists?(meeting_option: tag_option) || RoomMeetingOption.exists?(meeting_option: tag_required_option)
+ # slow variant that works with existing tag options
+ Room.find_each do |room|
+ RoomMeetingOption.find_or_create_by!(room:, meeting_option: tag_option)
+ unless RoomMeetingOption.exists?(room:, meeting_option: tag_required_option)
+ RoomMeetingOption.create!(room:, meeting_option: tag_required_option, value: 'false')
+ end
+ end
+ else
+ # much faster variant without checks/validation
+ Room.find_in_batches do |batch|
+ tag_options_batch = batch.map { |room| { room_id: room.id, meeting_option_id: tag_option.id } }
+ tag_required_options_batch = batch.map { |room| { room_id: room.id, meeting_option_id: tag_required_option.id, value: 'false' } }
+ # rubocop:disable Rails/SkipsModelValidations
+ RoomMeetingOption.insert_all!(tag_options_batch)
+ RoomMeetingOption.insert_all!(tag_required_options_batch)
+ # rubocop:enable Rails/SkipsModelValidations
+ end
+ end
+ end
+
+ def down
+ tag_option = MeetingOption.find_by!(name: 'serverTag')
+ RoomMeetingOption.destroy_by(meeting_option: tag_option)
+ RoomsConfiguration.destroy_by(meeting_option: tag_option)
+ tag_option.destroy
+
+ tag_required_option = MeetingOption.find_by!(name: 'serverTagRequired')
+ RoomMeetingOption.destroy_by(meeting_option: tag_required_option)
+ RoomsConfiguration.destroy_by(meeting_option: tag_required_option)
+ tag_required_option.destroy
+ end
+end
diff --git a/db/data_schema.rb b/db/data_schema.rb
index 3eb90691e2..dd44530cd2 100644
--- a/db/data_schema.rb
+++ b/db/data_schema.rb
@@ -1 +1 @@
-DataMigrate::Data.define(version: 20240209155229)
+DataMigrate::Data.define(version: 20240423162700)
diff --git a/esbuild.dev.mjs b/esbuild.dev.mjs
index ded76ccc13..03e9ea90f1 100644
--- a/esbuild.dev.mjs
+++ b/esbuild.dev.mjs
@@ -30,4 +30,4 @@ esbuild.context({
}).catch((e) => {
console.error('build failed:', e);
process.exit(1)
-})
\ No newline at end of file
+})
diff --git a/lib/tasks/server_tags_sync.rake b/lib/tasks/server_tags_sync.rake
new file mode 100644
index 0000000000..7de74604aa
--- /dev/null
+++ b/lib/tasks/server_tags_sync.rake
@@ -0,0 +1,45 @@
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+#
+# Copyright (c) 2022 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.
+#
+# Greenlight 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 Greenlight; if not, see .
+
+# frozen_string_literal: true
+
+desc 'Remove dismissed or disallowed server tags from database'
+
+task server_tags_sync: :environment do
+ tag_option = MeetingOption.find_by!(name: 'serverTag')
+
+ RoomMeetingOption.where(meeting_option: tag_option).find_each do |room_tag|
+ tag_value = room_tag.value
+ next if tag_value.blank?
+
+ role_id = room_tag.room.user.role_id
+ tag_invalid = false
+ if Rails.configuration.server_tag_names.key?(tag_value)
+ role_not_allowed = Rails.configuration.server_tag_roles.key?(tag_value) && Rails.configuration.server_tag_roles[tag_value].exclude?(role_id)
+ tag_invalid = true if role_not_allowed
+ else
+ tag_invalid = true
+ end
+
+ if tag_invalid
+ info "Clearing invalid server tag #{tag_value} for room with id #{room_tag.room}."
+ room_tag.update(value: '')
+ end
+ rescue StandardError => e
+ err "Unable to sync server tag of room:\nID: #{room_tag.room}\nError: #{e}"
+ end
+ success 'Successfully sanitized server tags.'
+end
diff --git a/sample.env b/sample.env
index 21584ca7f4..ecb667e219 100644
--- a/sample.env
+++ b/sample.env
@@ -100,3 +100,11 @@ LOG_LEVEL=info
# to check their file.
#CLAMAV_SCANNING=true
#CLAMAV_DAEMONIZE=true
+
+## Support for Tagged Servers
+# If your Greenlight instance is connected to Scalelite or another Loadbalancer with enabled support for the 'meta_server-tag'
+# parameter on create calls, you can use the following variables to configure support for this feature via the Greenlight UI.
+# When this configuration is changed later, disallowed tags can be removed from the DB via `bundle exec rake server_tags_sync`
+# Example configuration (delimiters are , : and /):
+# SERVER_TAG_NAMES=tag1:Name 1,tag2:Name2 # defines available tags and their friendly names
+# SERVER_TAG_ROLES=tag2:xyz-123-321-aaaa-zyx/abc-321-123-zzzz-cba # allow tag only for given role ids (see role ids in DB)