diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 4a5b7441..293c7912 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -85,8 +85,6 @@ $secondary: $california-gold;
@import "custom_bootstrap";
@import "font-awesome";
-@import "datatables/dataTables.bootstrap5.css";
-@import "datatables/responsive.bootstrap5.css";
@each $name, $value in $colors {
.bg-#{$name} {
diff --git a/app/javascript/controllers/assignment_controller.js b/app/javascript/controllers/assignment_controller.js
index 6d38e150..cf86090e 100644
--- a/app/javascript/controllers/assignment_controller.js
+++ b/app/javascript/controllers/assignment_controller.js
@@ -1,6 +1,6 @@
import { Controller } from "@hotwired/stimulus"
import DataTable from "datatables.net-bs5";
-import { pollUntilDone } from "./sync_poller";
+import { pollUntilDone } from "controllers/sync_poller";
import "datatables.net-responsive";
import "datatables.net-responsive-bs5";
diff --git a/app/javascript/controllers/enrollments_controller.js b/app/javascript/controllers/enrollments_controller.js
index 8a8e2402..2d72d058 100644
--- a/app/javascript/controllers/enrollments_controller.js
+++ b/app/javascript/controllers/enrollments_controller.js
@@ -1,6 +1,6 @@
import { Controller } from "@hotwired/stimulus";
import DataTable from "datatables.net-bs5";
-import { pollUntilDone } from "./sync_poller";
+import { pollUntilDone } from "controllers/sync_poller";
import "datatables.net-responsive";
import "datatables.net-responsive-bs5";
diff --git a/app/views/layouts/_navbar.html.erb b/app/views/layouts/_navbar.html.erb
index f1090860..28666e5d 100644
--- a/app/views/layouts/_navbar.html.erb
+++ b/app/views/layouts/_navbar.html.erb
@@ -29,7 +29,13 @@
<%= session[:username] %>
-
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index c5b63edb..4ed53afc 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -8,6 +8,8 @@
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application" %>
+
+
<%= javascript_importmap_tags %>
<%= render "layouts/components/color_mode_critical" %>
diff --git a/features/navigation.feature b/features/navigation.feature
index d67167af..5ba8f581 100644
--- a/features/navigation.feature
+++ b/features/navigation.feature
@@ -40,3 +40,19 @@ Feature: Navigation
And I should see "Login with bCourses" in the navbar
And I should not see "Offerings" in the navbar
And I should not see "Logout" in the navbar
+
+ Scenario: Admin user sees admin tools in navbar dropdown
+ Given a course exists
+ And I am logged in as an admin
+ And I am on the "Courses page"
+ Then I should see "Admin Tools" in the navbar dropdown
+ And I should see "Dashboards" in the navbar dropdown
+ And I should see "Background Jobs" in the navbar dropdown
+
+ Scenario: Non-admin user does not see admin tools in navbar dropdown
+ Given a course exists
+ And I am logged in as a teacher
+ And I am on the "Courses page"
+ Then I should not see "Admin Tools" in the navbar dropdown
+ And I should not see "Dashboards" in the navbar dropdown
+ And I should not see "Background Jobs" in the navbar dropdown
diff --git a/features/step_definitions/custom_steps.rb b/features/step_definitions/custom_steps.rb
index e1b33e54..d52a918d 100644
--- a/features/step_definitions/custom_steps.rb
+++ b/features/step_definitions/custom_steps.rb
@@ -14,6 +14,11 @@
end
end
+Given(/^I am logged in as an admin$/) do
+ user = create(:admin, email: 'admin@berkeley.edu', canvas_uid: 'canvas_uid_admin')
+ page.set_rack_session(user_id: user.canvas_uid, username: user.name)
+end
+
Given(/^(?:I am|I'm|I) (?:logged|log) in as a (teacher|ta|student)$/i) do |role|
emails = {
'teacher' => 'user1@berkeley.edu',
diff --git a/features/step_definitions/navigation_steps.rb b/features/step_definitions/navigation_steps.rb
index 4711ac16..10685c40 100644
--- a/features/step_definitions/navigation_steps.rb
+++ b/features/step_definitions/navigation_steps.rb
@@ -14,6 +14,18 @@
end
end
+Then(/^I should see "(.*?)" in the navbar dropdown$/) do |text|
+ within('#user-dropdown-menu') do
+ expect(page).to have_content(text)
+ end
+end
+
+Then(/^I should not see "(.*?)" in the navbar dropdown$/) do |text|
+ within('#user-dropdown-menu') do
+ expect(page).not_to have_content(text)
+ end
+end
+
When(/^I navigate to any page other than the "(.*?)"$/) do |excluded_page|
# Currently included home and courses page
case excluded_page
diff --git a/public/datatables/dataTables.bootstrap5.css b/public/datatables/dataTables.bootstrap5.css
new file mode 100644
index 00000000..ed11e6ea
--- /dev/null
+++ b/public/datatables/dataTables.bootstrap5.css
@@ -0,0 +1,569 @@
+:root {
+ --dt-row-selected: 13, 110, 253;
+ --dt-row-selected-text: 255, 255, 255;
+ --dt-row-selected-link: 9, 10, 11;
+ --dt-row-stripe: 0, 0, 0;
+ --dt-row-hover: 0, 0, 0;
+ --dt-column-ordering: 0, 0, 0;
+ --dt-html-background: white;
+}
+:root.dark {
+ --dt-html-background: rgb(33, 37, 41);
+}
+
+table.dataTable td.dt-control {
+ text-align: center;
+ cursor: pointer;
+}
+table.dataTable td.dt-control:before {
+ display: inline-block;
+ box-sizing: border-box;
+ content: "";
+ border-top: 5px solid transparent;
+ border-left: 10px solid rgba(0, 0, 0, 0.5);
+ border-bottom: 5px solid transparent;
+ border-right: 0px solid transparent;
+}
+table.dataTable tr.dt-hasChild td.dt-control:before {
+ border-top: 10px solid rgba(0, 0, 0, 0.5);
+ border-left: 5px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 5px solid transparent;
+}
+table.dataTable tfoot:empty {
+ display: none;
+}
+
+html.dark table.dataTable td.dt-control:before,
+:root[data-bs-theme=dark] table.dataTable td.dt-control:before,
+:root[data-theme=dark] table.dataTable td.dt-control:before {
+ border-left-color: rgba(255, 255, 255, 0.5);
+}
+html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
+:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before,
+:root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
+ border-top-color: rgba(255, 255, 255, 0.5);
+ border-left-color: transparent;
+}
+
+div.dt-scroll {
+ width: 100%;
+}
+
+div.dt-scroll-body thead tr,
+div.dt-scroll-body tfoot tr {
+ height: 0;
+}
+div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td,
+div.dt-scroll-body tfoot tr th,
+div.dt-scroll-body tfoot tr td {
+ height: 0 !important;
+ padding-top: 0px !important;
+ padding-bottom: 0px !important;
+ border-top-width: 0px !important;
+ border-bottom-width: 0px !important;
+}
+div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing,
+div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,
+div.dt-scroll-body tfoot tr td div.dt-scroll-sizing {
+ height: 0 !important;
+ overflow: hidden !important;
+}
+
+table.dataTable thead > tr > th:active,
+table.dataTable thead > tr > td:active {
+ outline: none;
+}
+table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before {
+ position: absolute;
+ display: block;
+ bottom: 50%;
+ content: "\25B2";
+ content: "\25B2"/"";
+}
+table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
+ position: absolute;
+ display: block;
+ top: 50%;
+ content: "\25BC";
+ content: "\25BC"/"";
+}
+table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
+ position: relative;
+ width: 12px;
+ height: 20px;
+}
+table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
+ left: 0;
+ opacity: 0.125;
+ line-height: 9px;
+ font-size: 0.8em;
+}
+table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc,
+table.dataTable thead > tr > td.dt-orderable-asc,
+table.dataTable thead > tr > td.dt-orderable-desc {
+ cursor: pointer;
+}
+table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover,
+table.dataTable thead > tr > td.dt-orderable-asc:hover,
+table.dataTable thead > tr > td.dt-orderable-desc:hover {
+ outline: 2px solid rgba(0, 0, 0, 0.05);
+ outline-offset: -2px;
+}
+table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
+ opacity: 0.6;
+}
+table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
+table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
+table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
+ display: none;
+}
+table.dataTable thead > tr > th:active,
+table.dataTable thead > tr > td:active {
+ outline: none;
+}
+
+table.dataTable thead > tr > th div.dt-column-header,
+table.dataTable thead > tr > th div.dt-column-footer,
+table.dataTable thead > tr > td div.dt-column-header,
+table.dataTable thead > tr > td div.dt-column-footer,
+table.dataTable tfoot > tr > th div.dt-column-header,
+table.dataTable tfoot > tr > th div.dt-column-footer,
+table.dataTable tfoot > tr > td div.dt-column-header,
+table.dataTable tfoot > tr > td div.dt-column-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 4px;
+}
+table.dataTable thead > tr > th div.dt-column-header span.dt-column-title,
+table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title,
+table.dataTable thead > tr > td div.dt-column-header span.dt-column-title,
+table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title,
+table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title,
+table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title,
+table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title,
+table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title {
+ flex-grow: 1;
+}
+table.dataTable thead > tr > th div.dt-column-header span.dt-column-title:empty,
+table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title:empty,
+table.dataTable thead > tr > td div.dt-column-header span.dt-column-title:empty,
+table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title:empty,
+table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title:empty,
+table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title:empty,
+table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title:empty,
+table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title:empty {
+ display: none;
+}
+
+div.dt-scroll-body > table.dataTable > thead > tr > th,
+div.dt-scroll-body > table.dataTable > thead > tr > td {
+ overflow: hidden;
+}
+
+:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover,
+:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover,
+:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover {
+ outline: 2px solid rgba(255, 255, 255, 0.05);
+}
+
+div.dt-processing {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 200px;
+ margin-left: -100px;
+ margin-top: -22px;
+ text-align: center;
+ padding: 2px;
+ z-index: 10;
+}
+div.dt-processing > div:last-child {
+ position: relative;
+ width: 80px;
+ height: 15px;
+ margin: 1em auto;
+}
+div.dt-processing > div:last-child > div {
+ position: absolute;
+ top: 0;
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ background: rgb(13, 110, 253);
+ background: rgb(var(--dt-row-selected));
+ animation-timing-function: cubic-bezier(0, 1, 1, 0);
+}
+div.dt-processing > div:last-child > div:nth-child(1) {
+ left: 8px;
+ animation: datatables-loader-1 0.6s infinite;
+}
+div.dt-processing > div:last-child > div:nth-child(2) {
+ left: 8px;
+ animation: datatables-loader-2 0.6s infinite;
+}
+div.dt-processing > div:last-child > div:nth-child(3) {
+ left: 32px;
+ animation: datatables-loader-2 0.6s infinite;
+}
+div.dt-processing > div:last-child > div:nth-child(4) {
+ left: 56px;
+ animation: datatables-loader-3 0.6s infinite;
+}
+
+@keyframes datatables-loader-1 {
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+@keyframes datatables-loader-3 {
+ 0% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(0);
+ }
+}
+@keyframes datatables-loader-2 {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 100% {
+ transform: translate(24px, 0);
+ }
+}
+table.dataTable.nowrap th, table.dataTable.nowrap td {
+ white-space: nowrap;
+}
+table.dataTable th,
+table.dataTable td {
+ box-sizing: border-box;
+}
+table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
+table.dataTable td.dt-type-numeric,
+table.dataTable td.dt-type-date {
+ text-align: right;
+}
+table.dataTable th.dt-type-numeric div.dt-column-header,
+table.dataTable th.dt-type-numeric div.dt-column-footer, table.dataTable th.dt-type-date div.dt-column-header,
+table.dataTable th.dt-type-date div.dt-column-footer,
+table.dataTable td.dt-type-numeric div.dt-column-header,
+table.dataTable td.dt-type-numeric div.dt-column-footer,
+table.dataTable td.dt-type-date div.dt-column-header,
+table.dataTable td.dt-type-date div.dt-column-footer {
+ flex-direction: row-reverse;
+}
+table.dataTable th.dt-left,
+table.dataTable td.dt-left {
+ text-align: left;
+}
+table.dataTable th.dt-center,
+table.dataTable td.dt-center {
+ text-align: center;
+}
+table.dataTable th.dt-right,
+table.dataTable td.dt-right {
+ text-align: right;
+}
+table.dataTable th.dt-right div.dt-column-header,
+table.dataTable th.dt-right div.dt-column-footer,
+table.dataTable td.dt-right div.dt-column-header,
+table.dataTable td.dt-right div.dt-column-footer {
+ flex-direction: row-reverse;
+}
+table.dataTable th.dt-justify,
+table.dataTable td.dt-justify {
+ text-align: justify;
+}
+table.dataTable th.dt-nowrap,
+table.dataTable td.dt-nowrap {
+ white-space: nowrap;
+}
+table.dataTable th.dt-empty,
+table.dataTable td.dt-empty {
+ text-align: center;
+ vertical-align: top;
+}
+table.dataTable thead th,
+table.dataTable thead td,
+table.dataTable tfoot th,
+table.dataTable tfoot td {
+ text-align: left;
+}
+table.dataTable thead th.dt-head-left,
+table.dataTable thead td.dt-head-left,
+table.dataTable tfoot th.dt-head-left,
+table.dataTable tfoot td.dt-head-left {
+ text-align: left;
+}
+table.dataTable thead th.dt-head-center,
+table.dataTable thead td.dt-head-center,
+table.dataTable tfoot th.dt-head-center,
+table.dataTable tfoot td.dt-head-center {
+ text-align: center;
+}
+table.dataTable thead th.dt-head-right,
+table.dataTable thead td.dt-head-right,
+table.dataTable tfoot th.dt-head-right,
+table.dataTable tfoot td.dt-head-right {
+ text-align: right;
+}
+table.dataTable thead th.dt-head-right div.dt-column-header,
+table.dataTable thead th.dt-head-right div.dt-column-footer,
+table.dataTable thead td.dt-head-right div.dt-column-header,
+table.dataTable thead td.dt-head-right div.dt-column-footer,
+table.dataTable tfoot th.dt-head-right div.dt-column-header,
+table.dataTable tfoot th.dt-head-right div.dt-column-footer,
+table.dataTable tfoot td.dt-head-right div.dt-column-header,
+table.dataTable tfoot td.dt-head-right div.dt-column-footer {
+ flex-direction: row-reverse;
+}
+table.dataTable thead th.dt-head-justify,
+table.dataTable thead td.dt-head-justify,
+table.dataTable tfoot th.dt-head-justify,
+table.dataTable tfoot td.dt-head-justify {
+ text-align: justify;
+}
+table.dataTable thead th.dt-head-nowrap,
+table.dataTable thead td.dt-head-nowrap,
+table.dataTable tfoot th.dt-head-nowrap,
+table.dataTable tfoot td.dt-head-nowrap {
+ white-space: nowrap;
+}
+table.dataTable tbody th.dt-body-left,
+table.dataTable tbody td.dt-body-left {
+ text-align: left;
+}
+table.dataTable tbody th.dt-body-center,
+table.dataTable tbody td.dt-body-center {
+ text-align: center;
+}
+table.dataTable tbody th.dt-body-right,
+table.dataTable tbody td.dt-body-right {
+ text-align: right;
+}
+table.dataTable tbody th.dt-body-justify,
+table.dataTable tbody td.dt-body-justify {
+ text-align: justify;
+}
+table.dataTable tbody th.dt-body-nowrap,
+table.dataTable tbody td.dt-body-nowrap {
+ white-space: nowrap;
+}
+
+/*! Bootstrap 5 integration for DataTables
+ *
+ * ©2020 SpryMedia Ltd, all rights reserved.
+ * License: MIT datatables.net/license/mit
+ */
+table.table.dataTable {
+ clear: both;
+ margin-bottom: 0;
+ max-width: none;
+ border-spacing: 0;
+}
+table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
+ box-shadow: none;
+}
+table.table.dataTable > :not(caption) > * > * {
+ background-color: var(--bs-table-bg);
+}
+table.table.dataTable > tbody > tr {
+ background-color: transparent;
+}
+table.table.dataTable > tbody > tr.selected > * {
+ box-shadow: inset 0 0 0 9999px rgb(13, 110, 253);
+ box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected));
+ color: rgb(255, 255, 255);
+ color: rgb(var(--dt-row-selected-text));
+}
+table.table.dataTable > tbody > tr.selected a {
+ color: rgb(9, 10, 11);
+ color: rgb(var(--dt-row-selected-link));
+}
+table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
+ box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
+}
+table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * {
+ box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
+ box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
+}
+table.table.dataTable.table-hover > tbody > tr:hover > * {
+ box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
+}
+table.table.dataTable.table-hover > tbody > tr.selected:hover > * {
+ box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
+ box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
+}
+
+div.dt-container div.dt-layout-start > *:not(:last-child) {
+ margin-right: 1em;
+}
+div.dt-container div.dt-layout-end > *:not(:first-child) {
+ margin-left: 1em;
+}
+div.dt-container div.dt-layout-full {
+ width: 100%;
+}
+div.dt-container div.dt-layout-full > *:only-child {
+ margin-left: auto;
+ margin-right: auto;
+}
+div.dt-container div.dt-layout-table > div {
+ display: block !important;
+}
+
+@media screen and (max-width: 767px) {
+ div.dt-container div.dt-layout-start > *:not(:last-child) {
+ margin-right: 0;
+ }
+ div.dt-container div.dt-layout-end > *:not(:first-child) {
+ margin-left: 0;
+ }
+}
+div.dt-container {
+ position: relative;
+}
+div.dt-container div.dt-length label {
+ font-weight: normal;
+ text-align: left;
+ white-space: nowrap;
+}
+div.dt-container div.dt-length select {
+ width: auto;
+ display: inline-block;
+ margin-right: 0.5em;
+}
+div.dt-container div.dt-search {
+ text-align: right;
+}
+div.dt-container div.dt-search label {
+ font-weight: normal;
+ white-space: nowrap;
+ text-align: left;
+}
+div.dt-container div.dt-search input {
+ margin-left: 0.5em;
+ display: inline-block;
+ width: auto;
+}
+div.dt-container div.dt-paging {
+ margin: 0;
+}
+div.dt-container div.dt-paging ul.pagination {
+ margin: 2px 0;
+ flex-wrap: wrap;
+}
+div.dt-container div.dt-row {
+ position: relative;
+}
+
+div.dt-scroll-head table.dataTable {
+ margin-bottom: 0 !important;
+}
+
+div.dt-scroll-body {
+ border-bottom-color: var(--bs-border-color);
+ border-bottom-width: var(--bs-border-width);
+ border-bottom-style: solid;
+}
+div.dt-scroll-body > table {
+ border-top: none;
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+}
+div.dt-scroll-body > table > tbody > tr:first-child {
+ border-top-width: 0;
+}
+div.dt-scroll-body > table > thead > tr {
+ border-width: 0 !important;
+}
+div.dt-scroll-body > table > tbody > tr:last-child > * {
+ border-bottom: none;
+}
+
+div.dt-scroll-foot > .dt-scroll-footInner {
+ box-sizing: content-box;
+}
+div.dt-scroll-foot > .dt-scroll-footInner > table {
+ margin-top: 0 !important;
+ border-top: none;
+}
+div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child {
+ border-top-width: 0 !important;
+}
+
+@media screen and (max-width: 767px) {
+ div.dt-container div.dt-length,
+ div.dt-container div.dt-search,
+ div.dt-container div.dt-info,
+ div.dt-container div.dt-paging {
+ text-align: center;
+ }
+ div.dt-container .row {
+ --bs-gutter-y: 0.5rem;
+ }
+ div.dt-container div.dt-paging ul.pagination {
+ justify-content: center !important;
+ }
+}
+table.dataTable.table-sm > thead > tr th.dt-orderable-asc, table.dataTable.table-sm > thead > tr th.dt-orderable-desc, table.dataTable.table-sm > thead > tr th.dt-ordering-asc, table.dataTable.table-sm > thead > tr th.dt-ordering-desc,
+table.dataTable.table-sm > thead > tr td.dt-orderable-asc,
+table.dataTable.table-sm > thead > tr td.dt-orderable-desc,
+table.dataTable.table-sm > thead > tr td.dt-ordering-asc,
+table.dataTable.table-sm > thead > tr td.dt-ordering-desc {
+ padding-right: 20px;
+}
+table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order {
+ right: 5px;
+}
+
+div.dt-scroll-head table.table-bordered {
+ border-bottom-width: 0;
+}
+
+div.table-responsive > div.dt-container > div.row {
+ margin: 0;
+}
+div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child {
+ padding-left: 0;
+}
+div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child {
+ padding-right: 0;
+}
+
+:root[data-bs-theme=dark] {
+ --dt-row-hover: 255, 255, 255;
+ --dt-row-stripe: 255, 255, 255;
+ --dt-column-ordering: 255, 255, 255;
+}
diff --git a/public/datatables/responsive.bootstrap5.css b/public/datatables/responsive.bootstrap5.css
new file mode 100644
index 00000000..ce6fcb54
--- /dev/null
+++ b/public/datatables/responsive.bootstrap5.css
@@ -0,0 +1,203 @@
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.child,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th.child,
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty {
+ cursor: default !important;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before {
+ display: none !important;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control {
+ cursor: pointer;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before {
+ margin-right: 0.5em;
+ display: inline-block;
+ box-sizing: border-box;
+ content: "";
+ border-top: 5px solid transparent;
+ border-left: 10px solid rgba(0, 0, 0, 0.5);
+ border-bottom: 5px solid transparent;
+ border-right: 0px solid transparent;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control.arrow-right::before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control.arrow-right::before {
+ border-top: 5px solid transparent;
+ border-left: 0px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-right: 10px solid rgba(0, 0, 0, 0.5);
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > td.dtr-control:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > th.dtr-control:before {
+ border-top: 10px solid rgba(0, 0, 0, 0.5);
+ border-left: 5px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 5px solid transparent;
+}
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control,
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control {
+ padding-left: 0.333em;
+}
+table.dataTable.dtr-column > tbody > tr > td.dtr-control,
+table.dataTable.dtr-column > tbody > tr > th.dtr-control,
+table.dataTable.dtr-column > tbody > tr > td.control,
+table.dataTable.dtr-column > tbody > tr > th.control {
+ cursor: pointer;
+}
+table.dataTable.dtr-column > tbody > tr > td.dtr-control:before,
+table.dataTable.dtr-column > tbody > tr > th.dtr-control:before,
+table.dataTable.dtr-column > tbody > tr > td.control:before,
+table.dataTable.dtr-column > tbody > tr > th.control:before {
+ display: inline-block;
+ box-sizing: border-box;
+ content: "";
+ border-top: 5px solid transparent;
+ border-left: 10px solid rgba(0, 0, 0, 0.5);
+ border-bottom: 5px solid transparent;
+ border-right: 0px solid transparent;
+}
+table.dataTable.dtr-column > tbody > tr > td.dtr-control.arrow-right::before,
+table.dataTable.dtr-column > tbody > tr > th.dtr-control.arrow-right::before,
+table.dataTable.dtr-column > tbody > tr > td.control.arrow-right::before,
+table.dataTable.dtr-column > tbody > tr > th.control.arrow-right::before {
+ border-top: 5px solid transparent;
+ border-left: 0px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-right: 10px solid rgba(0, 0, 0, 0.5);
+}
+table.dataTable.dtr-column > tbody > tr.dtr-expanded td.dtr-control:before,
+table.dataTable.dtr-column > tbody > tr.dtr-expanded th.dtr-control:before,
+table.dataTable.dtr-column > tbody > tr.dtr-expanded td.control:before,
+table.dataTable.dtr-column > tbody > tr.dtr-expanded th.control:before {
+ border-top: 10px solid rgba(0, 0, 0, 0.5);
+ border-left: 5px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 5px solid transparent;
+}
+table.dataTable > tbody > tr.child {
+ padding: 0.5em 1em;
+}
+table.dataTable > tbody > tr.child:hover {
+ background: transparent !important;
+}
+table.dataTable > tbody > tr.child ul.dtr-details {
+ display: inline-block;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+table.dataTable > tbody > tr.child ul.dtr-details > li {
+ border-bottom: 1px solid #efefef;
+ padding: 0.5em 0;
+}
+table.dataTable > tbody > tr.child ul.dtr-details > li:first-child {
+ padding-top: 0;
+}
+table.dataTable > tbody > tr.child ul.dtr-details > li:last-child {
+ padding-bottom: 0;
+ border-bottom: none;
+}
+table.dataTable > tbody > tr.child span.dtr-title {
+ display: inline-block;
+ min-width: 75px;
+ font-weight: bold;
+}
+
+div.dtr-modal {
+ position: fixed;
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ z-index: 100;
+ padding: 10em 1em;
+}
+div.dtr-modal div.dtr-modal-display {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ width: 50%;
+ height: fit-content;
+ max-height: 75%;
+ overflow: auto;
+ margin: auto;
+ z-index: 102;
+ overflow: auto;
+ background-color: #f5f5f7;
+ border: 1px solid black;
+ border-radius: 0.5em;
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
+}
+div.dtr-modal div.dtr-modal-content {
+ position: relative;
+ padding: 2.5em;
+}
+div.dtr-modal div.dtr-modal-content h2 {
+ margin-top: 0;
+}
+div.dtr-modal div.dtr-modal-close {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ width: 22px;
+ height: 22px;
+ text-align: center;
+ border-radius: 3px;
+ cursor: pointer;
+ z-index: 12;
+}
+div.dtr-modal div.dtr-modal-background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 101;
+ background: rgba(0, 0, 0, 0.6);
+}
+
+@media screen and (max-width: 767px) {
+ div.dtr-modal div.dtr-modal-display {
+ width: 95%;
+ }
+}
+html.dark table.dataTable > tbody > tr > td.dtr-control:before,
+html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control:before {
+ border-left-color: rgba(255, 255, 255, 0.5) !important;
+}
+html.dark table.dataTable > tbody > tr > td.dtr-control.arrow-right::before,
+html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control.arrow-right::before {
+ border-right-color: rgba(255, 255, 255, 0.5) !important;
+}
+html.dark table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before,
+html.dark table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before,
+html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before,
+html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before {
+ border-top-color: rgba(255, 255, 255, 0.5) !important;
+ border-left-color: transparent !important;
+ border-right-color: transparent !important;
+}
+html.dark table.dataTable > tbody > tr.child ul.dtr-details > li,
+html[data-bs-theme=dark] table.dataTable > tbody > tr.child ul.dtr-details > li {
+ border-bottom-color: rgb(64, 67, 70);
+}
+html.dark div.dtr-modal div.dtr-modal-display,
+html[data-bs-theme=dark] div.dtr-modal div.dtr-modal-display {
+ background-color: rgb(33, 37, 41);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+}
+
+div.dtr-bs-modal table.table tr:first-child td {
+ border-top: none;
+}
+
+table.dataTable.table-bordered th.dtr-control.dtr-hidden + *,
+table.dataTable.table-bordered td.dtr-control.dtr-hidden + * {
+ border-left-width: 1px;
+}