Skip to content
Snippets Groups Projects
Unverified Commit 8f430316 authored by Samuel Couillard's avatar Samuel Couillard Committed by GitHub
Browse files

add(feature): SuperAdmin [3] (#4925)


* Add Tenant to AdminPanel

* Rubocop

* esf

* Fix layout

* Add Tenant destroy

* Add pagination to Tenants

* Fix props

* Remove translations

* Improve specs

* remove byebug

---------

Co-authored-by: default avatarAhmad Farhat <ahmad.af.farhat@gmail.com>
parent d64c15ea
No related branches found
No related tags found
No related merge requests found
Showing with 362 additions and 7 deletions
......@@ -699,6 +699,12 @@ input[type="range"]:focus::-moz-range-thumb {
}
}
.tenants-icons {
svg:hover {
color: var(--brand-color) !important;
}
}
.Toastify {
.Toastify__toast-icon {
width: 25px;
......
......@@ -24,6 +24,7 @@ module Api
# TODO: - ahmad: Add role check
end
# GET /api/v1/admin/tenants
def index
sort_config = config_sorting(allowed_columns: %w[name])
......@@ -42,7 +43,7 @@ module Api
if tenant.save
create_roles(tenant.name)
create_site_settings(tenant.name)
create_meeting_options(tenant.name)
create_rooms_configs_options(tenant.name)
create_role_permissions(tenant.name)
render_data status: :created
else
......@@ -50,6 +51,20 @@ module Api
end
end
# DELETE /api/v1/admin/tenants/:id
def destroy
tenant = Tenant.find(params[:id])
if tenant.destroy
delete_roles(tenant.name)
delete_site_settings(tenant.name)
delete_rooms_configs_options(tenant.name)
render_data status: :ok
else
render_error errors: tenant.errors.to_a, status: :bad_request
end
end
def cache; end
private
......@@ -80,7 +95,7 @@ module Api
]
end
def create_meeting_options(provider)
def create_rooms_configs_options(provider)
RoomsConfiguration.create! [
{ meeting_option: MeetingOption.find_by(name: 'record'), value: 'default_enabled', provider: },
{ meeting_option: MeetingOption.find_by(name: 'muteOnStart'), value: 'optional', provider: },
......@@ -141,6 +156,18 @@ module Api
]
end
def delete_roles(provider)
Role.where(provider:).destroy_all
end
def delete_site_settings(provider)
SiteSetting.where(provider:).destroy_all
end
def delete_rooms_configs_options(provider)
RoomsConfiguration.where(provider:).destroy_all
end
def tenant_params
params.require(:tenant).permit(:name, :client_secret)
end
......
......@@ -16,6 +16,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import { TrashIcon } from '@heroicons/react/24/outline';
import Modal from '../../shared_components/modals/Modal';
import DeleteTenantForm from './forms/DeleteTenantForm';
export default function TenantRow({ tenant }) {
return (
......@@ -23,9 +27,19 @@ export default function TenantRow({ tenant }) {
<td className="py-4 border-0">
<strong> {tenant?.name} </strong>
</td>
<td className="py-4 border-start-0">
<td className="py-4 border-0">
{tenant?.client_secret}
</td>
<td className="border-start-0 text-end tenants-icons">
<Modal
modalButton={<Button variant="icon"><TrashIcon className="hi-s" /></Button>}
body={(
<DeleteTenantForm
tenantId={tenant?.id}
/>
)}
/>
</td>
</tr>
);
}
......
......@@ -31,6 +31,7 @@ export default function TenantsTable({
<tr className="text-muted small">
<th className="fw-normal border-0">Tenant<SortBy fieldName="name" /></th>
<th className="fw-normal border-0">Client Secret</th>
<th className="border-start-0" aria-label="options" />
</tr>
</thead>
<tbody className="border-top-0">
......
// 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 <http://www.gnu.org/licenses/>.
import React from 'react';
import { useForm } from 'react-hook-form';
import {
Button, Stack,
} from 'react-bootstrap';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import Form from '../../../shared_components/forms/Form';
import Spinner from '../../../shared_components/utilities/Spinner';
import useDeleteTenant from '../../../../hooks/mutations/admin/tenants/useDeleteTenant';
export default function DeleteTenantForm({ tenantId, handleClose }) {
const { t } = useTranslation();
const deleteTenantAPI = useDeleteTenant({ tenantId, onSettled: handleClose });
const methods = useForm();
return (
<>
<Stack direction="horizontal" className="mb-3">
<ExclamationTriangleIcon className="text-danger hi-xl" />
<Stack direction="vertical" className="ps-3">
<h3>Delete Tenant</h3>
<p className="mb-0">Are you sure you want to delete this tenant?</p>
<p className="mt-0"><strong> { t('action_permanent') } </strong></p>
</Stack>
</Stack>
<Form methods={methods} onSubmit={deleteTenantAPI.mutate}>
<Stack direction="horizontal" gap={1} className="float-end">
<Button variant="neutral" onClick={handleClose}>
{ t('close') }
</Button>
<Button variant="danger" type="submit" disabled={deleteTenantAPI.isLoading}>
{deleteTenantAPI.isLoading && <Spinner className="me-2" />}
{ t('delete') }
</Button>
</Stack>
</Form>
</>
);
}
DeleteTenantForm.propTypes = {
handleClose: PropTypes.func,
tenantId: PropTypes.string.isRequired,
};
DeleteTenantForm.defaultProps = {
handleClose: () => { },
};
// 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 <http://www.gnu.org/licenses/>.
import { useMutation, useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next';
import axios from '../../../../helpers/Axios';
export default function useDeleteTenant({ tenantId, onSettled }) {
const { t } = useTranslation();
const queryClient = useQueryClient();
return useMutation(
() => axios.delete(`/admin/tenants/${tenantId}.json`),
{
onError: () => {
toast.error(t('toast.error.problem_completing_action'));
},
onSuccess: () => {
queryClient.invalidateQueries('tenants');
toast.success('Tenant was successfully deleted.');
},
onSettled,
},
);
}
......@@ -17,5 +17,5 @@
# frozen_string_literal: true
class TenantSerializer < ApplicationSerializer
attributes :name, :client_secret
attributes :id, :name, :client_secret
end
......@@ -107,7 +107,7 @@ Rails.application.routes.draw do
post '/', to: 'role_permissions#update'
end
end
resources :tenants, only: %i[index create]
resources :tenants, only: %i[index create destroy]
end
namespace :migrations do
......
# 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 <http://www.gnu.org/licenses/>.
# frozen_string_literal: true
# rubocop:disable Rails/ReversibleMigration
class ChangeTenantIdTypeToUuid < ActiveRecord::Migration[7.0]
def change
add_column :tenants, :uuid, :uuid, default: 'gen_random_uuid()', null: false
change_table :tenants do |t|
t.remove :id
t.rename :uuid, :id
end
execute 'ALTER TABLE tenants ADD PRIMARY KEY (id);'
end
end
# rubocop:enable Rails/ReversibleMigration
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_03_01_203522) do
ActiveRecord::Schema[7.0].define(version: 2023_03_07_184209) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
......@@ -173,7 +173,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_01_203522) do
t.index ["setting_id"], name: "index_site_settings_on_setting_id"
end
create_table "tenants", force: :cascade do |t|
create_table "tenants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name", null: false
t.string "client_secret", null: false
t.datetime "created_at", null: false
......
......@@ -74,6 +74,58 @@ RSpec.describe Api::V1::Admin::TenantsController, type: :controller do
end
end
describe 'destroy' do
let!(:og_tenant) { create(:tenant) }
let!(:new_tenant) { create(:tenant) }
before do
create_roles([og_tenant.name, new_tenant.name])
create_site_settings([og_tenant.name, new_tenant.name])
create_room_configs_options([og_tenant.name, new_tenant.name])
create_role_permissions([og_tenant.name, new_tenant.name])
end
it 'deletes the tenant' do
delete :destroy, params: { id: og_tenant.id }
expect(Tenant.exists?(name: og_tenant.name)).to be false
end
it 'does not delete the other tenants' do
delete :destroy, params: { id: og_tenant.id }
expect(Tenant.exists?(name: new_tenant.name)).to be true
end
it 'deletes the roles for the tenant' do
delete :destroy, params: { id: og_tenant.id }
expect(Role.exists?(provider: og_tenant.name)).to be false
end
it 'does not delete the roles for the other tenants' do
delete :destroy, params: { id: og_tenant.id }
expect(Role.exists?(provider: new_tenant.name)).to be true
end
it 'deletes the site settings for the tenant' do
delete :destroy, params: { id: og_tenant.id }
expect(SiteSetting.exists?(provider: og_tenant.name)).to be false
end
it 'does not delete the site settings for the other tenants' do
delete :destroy, params: { id: og_tenant.id }
expect(SiteSetting.exists?(provider: new_tenant.name)).to be true
end
it 'deletes the rooms configuration for the tenant' do
delete :destroy, params: { id: og_tenant.id }
expect(RoomsConfiguration.exists?(provider: og_tenant.name)).to be false
end
it 'does not delete the rooms configuration for the other tenants' do
delete :destroy, params: { id: og_tenant.id }
expect(RoomsConfiguration.exists?(provider: new_tenant.name)).to be true
end
end
private
def create_settings_permissions_meetingoptions
......@@ -109,4 +161,99 @@ RSpec.describe Api::V1::Admin::TenantsController, type: :controller do
MeetingOption.find_or_create_by(name: 'glModeratorAccessCode', default_value: '')
MeetingOption.find_or_create_by(name: 'glViewerAccessCode', default_value: '')
end
def create_roles(providers)
providers.each do |provider|
Role.create! [
{ name: 'Administrator', provider: },
{ name: 'User', provider: },
{ name: 'Guest', provider: }
]
end
end
def create_site_settings(providers)
providers.each do |provider|
SiteSetting.create! [
{ setting: Setting.find_by(name: 'PrimaryColor'), value: '#467fcf', provider: },
{ setting: Setting.find_by(name: 'PrimaryColorLight'), value: '#e8eff9', provider: },
{ setting: Setting.find_by(name: 'PrimaryColorDark'), value: '#316cbe', provider: },
{ setting: Setting.find_by(name: 'BrandingImage'),
value: ActionController::Base.helpers.image_path('bbb_logo.png'),
provider: },
{ setting: Setting.find_by(name: 'Terms'), value: '', provider: },
{ setting: Setting.find_by(name: 'PrivacyPolicy'), value: '', provider: },
{ setting: Setting.find_by(name: 'RegistrationMethod'), value: SiteSetting::REGISTRATION_METHODS[:open], provider: },
{ setting: Setting.find_by(name: 'ShareRooms'), value: 'true', provider: },
{ setting: Setting.find_by(name: 'PreuploadPresentation'), value: 'true', provider: },
{ setting: Setting.find_by(name: 'RoleMapping'), value: '', provider: },
{ setting: Setting.find_by(name: 'DefaultRole'), provider:, value: 'User' }
]
end
end
def create_room_configs_options(providers)
providers.each do |provider|
RoomsConfiguration.create! [
{ meeting_option: MeetingOption.find_by(name: 'record'), value: 'default_enabled', provider: },
{ meeting_option: MeetingOption.find_by(name: 'muteOnStart'), value: 'optional', provider: },
{ meeting_option: MeetingOption.find_by(name: 'guestPolicy'), value: 'optional', provider: },
{ meeting_option: MeetingOption.find_by(name: 'glAnyoneCanStart'), value: 'optional', provider: },
{ meeting_option: MeetingOption.find_by(name: 'glAnyoneJoinAsModerator'), value: 'optional', provider: },
{ meeting_option: MeetingOption.find_by(name: 'glRequireAuthentication'), value: 'optional', provider: },
{ meeting_option: MeetingOption.find_by(name: 'glViewerAccessCode'), value: 'optional', provider: },
{ meeting_option: MeetingOption.find_by(name: 'glModeratorAccessCode'), value: 'optional', provider: }
]
end
end
def create_role_permissions(providers)
providers.each do |provider|
admin = Role.find_by(name: 'Administrator', provider:)
user = Role.find_by(name: 'User', provider:)
guest = Role.find_by(name: 'Guest', provider:)
create_room = Permission.find_by(name: 'CreateRoom')
manage_users = Permission.find_by(name: 'ManageUsers')
manage_rooms = Permission.find_by(name: 'ManageRooms')
manage_recordings = Permission.find_by(name: 'ManageRecordings')
manage_site_settings = Permission.find_by(name: 'ManageSiteSettings')
manage_roles = Permission.find_by(name: 'ManageRoles')
shared_list = Permission.find_by(name: 'SharedList')
can_record = Permission.find_by(name: 'CanRecord')
room_limit = Permission.find_by(name: 'RoomLimit')
RolePermission.create! [
{ role: admin, permission: create_room, value: 'true' },
{ role: admin, permission: manage_users, value: 'true' },
{ role: admin, permission: manage_rooms, value: 'true' },
{ role: admin, permission: manage_recordings, value: 'true' },
{ role: admin, permission: manage_site_settings, value: 'true' },
{ role: admin, permission: manage_roles, value: 'true' },
{ role: admin, permission: shared_list, value: 'true' },
{ role: admin, permission: can_record, value: 'true' },
{ role: admin, permission: room_limit, value: '100' },
{ role: user, permission: create_room, value: 'true' },
{ role: user, permission: manage_users, value: 'false' },
{ role: user, permission: manage_rooms, value: 'false' },
{ role: user, permission: manage_recordings, value: 'false' },
{ role: user, permission: manage_site_settings, value: 'false' },
{ role: user, permission: manage_roles, value: 'false' },
{ role: user, permission: shared_list, value: 'true' },
{ role: user, permission: can_record, value: 'true' },
{ role: user, permission: room_limit, value: '100' },
{ role: guest, permission: create_room, value: 'false' },
{ role: guest, permission: manage_users, value: 'false' },
{ role: guest, permission: manage_rooms, value: 'false' },
{ role: guest, permission: manage_recordings, value: 'false' },
{ role: guest, permission: manage_site_settings, value: 'false' },
{ role: guest, permission: manage_roles, value: 'false' },
{ role: guest, permission: shared_list, value: 'true' },
{ role: guest, permission: can_record, value: 'true' },
{ role: guest, permission: room_limit, value: '100' }
]
end
end
end
# 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 <http://www.gnu.org/licenses/>.
# frozen_string_literal: true
FactoryBot.define do
factory :tenant do
name { Faker::Company.unique.name }
client_secret { Faker::Lorem.characters(number: 10) }
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment