From f1239460b09aa637933cd7a90332d229547486e2 Mon Sep 17 00:00:00 2001 From: Jan Kessler Date: Wed, 24 Jul 2024 20:04:47 +0200 Subject: [PATCH 01/35] Support for Scalelite Tagged Servers (#5790) * init server tags prototype with config variable and corresponding dropdown in room settings * moved ServerTag row to separate file + rubocop * eslint happiness * add db migration for room option meta_server-tag * split the meta_server-tag parameter into two 'virtual' room options in GL: serverTag and serverTagRequired * dynamically render the server tag Dropdown from the config string + hide it entirely when the string is empty * now actually reflect and mutate the serverTag setting in DB via the Dropdown selection + tweaks&fixes * now use server tag room option as actual parameter on create call * rename config SERVER_TAGS_MAP->SERVER_TAG_NAMES and now handle it in Rails config * add config variable for tags<->roles and document the full server tag config in sample.env * fully implemented the tag<->role restriction as per config variable * add configurable default tag name for untagged servers * added checkbox to set if server tag is required * add logic to handle serverTagRequired in meeting_starter service * rubocop + change tagRequired toggle to react-bootstrap Radio buttons inside Cols * eslint + add description above server tag row * make tags Dropdown drop up + cleanpu * finalized DB migration for the server tags options * final polish (including translated strings) * add rake task server_tags_sync to clear invalid/disallowed server tags from the database (after changing the config) * adds translated string for default tag name / DEFAULT_TAG_NAME env now serves as override * show a specific error message when a required server type is unavailable (based on https://github.com/blindsidenetworks/scalelite/pull/1091) * move tag fetching logic from CurrentUserSerializer to new ServerTagsController * refactor ServerTagRow and remove tags-related env variables completely from react * remove DEFAULT_TAG_NAME from sample.env * now silently fall back to untagged if an invalid/forbidden tag is used in meeting_starter.rb (which matches what the user would see in terms of UI, namely the default tag name) * check room owner's role_id instead of current user's for role restrictions of tags / list of allowed tags * improve guarding for the cases of disabled tags or invalid room_ids in ServerTagsController * avoid checking serverTags.isLoading * streamline server tags migration * massively increase performance of server tags migration if database is in the expected state (i.e. no tags related options present) --------- Co-authored-by: Ahmad Farhat --- app/assets/locales/en.json | 5 + app/controllers/api/v1/meetings_controller.rb | 2 +- .../api/v1/server_tags_controller.rb | 37 ++++++ .../rooms/room/room_settings/RoomSettings.jsx | 12 ++ .../rooms/room/room_settings/ServerTagRow.jsx | 118 ++++++++++++++++++ .../hooks/mutations/rooms/useStartMeeting.jsx | 8 +- .../hooks/queries/rooms/useServerTags.jsx | 25 ++++ app/services/meeting_starter.rb | 19 +++ config/application.rb | 5 + config/routes.rb | 1 + ...0240423162700_create_server_tags_option.rb | 73 +++++++++++ db/data_schema.rb | 2 +- esbuild.dev.mjs | 2 +- lib/tasks/server_tags_sync.rake | 45 +++++++ sample.env | 8 ++ 15 files changed, 357 insertions(+), 5 deletions(-) create mode 100644 app/controllers/api/v1/server_tags_controller.rb create mode 100644 app/javascript/components/rooms/room/room_settings/ServerTagRow.jsx create mode 100644 app/javascript/hooks/queries/rooms/useServerTags.jsx create mode 100644 db/data/20240423162700_create_server_tags_option.rb create mode 100644 lib/tasks/server_tags_sync.rake 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) From 71b8e9b662a326b9a2a3b97cc8500bd28033e229 Mon Sep 17 00:00:00 2001 From: Ahmad Farhat Date: Thu, 25 Jul 2024 13:19:02 -0400 Subject: [PATCH 02/35] Small adjustment to role uniqueness check (#5896) --- app/models/role.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/role.rb b/app/models/role.rb index c270bb1499..ed6df213da 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -71,7 +71,7 @@ def set_role_color "##{SecureRandom.hex(3)}" end - raise if Role.exists?(color:) # Ensure uniqueness + raise if Role.exists?(color:, provider:) # Ensure uniqueness self.color = color rescue StandardError From 994ab7b603c82b165bb46200d1ce681a393dc608 Mon Sep 17 00:00:00 2001 From: Ali Hadi Mazeh <91922430+alihadimazeh@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:20:32 -0400 Subject: [PATCH 03/35] Greenlight version number only appears for admins (#5893) * Greenlight version number only appears for admins - Changes to App.jsx: passed in currentUser to the Footer - Footer renders the version number if the current user is an admin * - currentUser is no longer passed as a prop to the Footer component. Footer component imports useAuth to get the current user --- app/javascript/components/shared_components/Footer.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/components/shared_components/Footer.jsx b/app/javascript/components/shared_components/Footer.jsx index 30a3b5b2b4..2b30506e3c 100644 --- a/app/javascript/components/shared_components/Footer.jsx +++ b/app/javascript/components/shared_components/Footer.jsx @@ -19,17 +19,20 @@ import { useTranslation } from 'react-i18next'; import { Container } from 'react-bootstrap'; import useEnv from '../../hooks/queries/env/useEnv'; import useSiteSetting from '../../hooks/queries/site_settings/useSiteSetting'; +import { useAuth } from '../../contexts/auth/AuthProvider'; export default function Footer() { const { t } = useTranslation(); const { data: env } = useEnv(); const { data: links } = useSiteSetting(['Terms', 'PrivacyPolicy']); + const currentUser = useAuth(); + const isAdmin = currentUser && currentUser.role && currentUser?.role.name === 'Administrator'; return (