From 41c50f4c5257797b9fa8e488952eca2d63e980f4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Thu, 1 Feb 2018 16:00:52 +0100 Subject: [PATCH] mgr/dashboard_v2: Add table component MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The current implementation isn't finished yet, but it's a start. The ngx-datatable is used as the core of the table component. You can use the table to show data in a table. You can search, paginate, sort, refresh the table contents. Multi selection is possible, the details of the selected items will be given to the specified detail component. What will be fixed soon? * Enable the usage of buttons in the table header * Enable details inline not beneath the table * Pagination to use a input field to switch pages * The columns to show can be checked and predefined * The selection made by the user will be saved in the local storage Signed-off-by: Stephan Müller Signed-off-by: Tiago Melo --- .../mgr/dashboard_v2/frontend/package.json | 1 + .../shared/components/components.module.ts | 14 + .../table/table-details.directive.spec.ts | 8 + .../table/table-details.directive.ts | 11 + .../components/table/table.component.html | 68 ++++ .../components/table/table.component.scss | 71 ++++ .../components/table/table.component.spec.ts | 87 ++++ .../components/table/table.component.ts | 98 +++++ .../frontend/src/app/shared/shared.module.ts | 4 +- .../dashboard_v2/frontend/src/defaults.scss | 9 + .../frontend/src/openattic-theme.scss | 371 +++++------------- 11 files changed, 471 insertions(+), 271 deletions(-) create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/components.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.html create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.scss create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.spec.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.ts diff --git a/src/pybind/mgr/dashboard_v2/frontend/package.json b/src/pybind/mgr/dashboard_v2/frontend/package.json index 35fcc2d0d33..632ea6f85b1 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/package.json +++ b/src/pybind/mgr/dashboard_v2/frontend/package.json @@ -22,6 +22,7 @@ "@angular/platform-browser-dynamic": "^5.0.0", "@angular/router": "^5.0.0", "awesome-bootstrap-checkbox": "0.3.7", + "@swimlane/ngx-datatable": "^11.1.7", "bootstrap": "^3.3.7", "core-js": "^2.4.1", "font-awesome": "4.7.0", diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/components.module.ts new file mode 100644 index 00000000000..7baba7a5337 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/components.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TableComponent } from './table/table.component'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { TableDetailsDirective } from './table/table-details.directive'; +import {FormsModule} from '@angular/forms'; + +@NgModule({ + entryComponents: [], + imports: [CommonModule, NgxDatatableModule, FormsModule], + declarations: [TableComponent, TableDetailsDirective], + exports: [TableComponent, NgxDatatableModule] +}) +export class ComponentsModule {} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.spec.ts new file mode 100644 index 00000000000..b3e26843cba --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.spec.ts @@ -0,0 +1,8 @@ +import { TableDetailsDirective } from './table-details.directive'; + +describe('TableDetailsDirective', () => { + it('should create an instance', () => { + const directive = new TableDetailsDirective(null); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.ts new file mode 100644 index 00000000000..d3b226353db --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.ts @@ -0,0 +1,11 @@ +import {Directive, Input, ViewContainerRef} from '@angular/core'; + +@Directive({ + selector: '[cdTableDetails]' +}) +export class TableDetailsDirective { + @Input() selected?: any[]; + + constructor(public viewContainerRef: ViewContainerRef) { } + +} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.html new file mode 100644 index 00000000000..aec7a259971 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.html @@ -0,0 +1,68 @@ +
+
+ +
+ +
+ + + +
+ + + + + + + +
+ + + +
+ +
+ + + +
+ + + +
+ +
+ + + + + +
+ diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.scss new file mode 100644 index 00000000000..6cce9423b4e --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.scss @@ -0,0 +1,71 @@ +.dataTables_wrapper { + margin-bottom: 25px; + .separator { + height: 30px; + border-left: 1px solid rgba(0,0,0,.09); + padding-left: 5px; + margin-left: 5px; + display: inline-block; + vertical-align: middle; + } + .widget-toolbar { + display: inline-block; + float: right; + width: auto; + height: 30px; + line-height: 28px; + position: relative; + border-left: 1px solid rgba(0,0,0,.09); + cursor: pointer; + padding: 0 8px; + text-align: center; + } + .dropdown-menu { + white-space: nowrap; + & > li { + cursor: pointer; + & > label { + width: 100%; + margin-bottom: 0; + padding-left: 20px; + padding-right: 20px; + cursor: pointer; + &:hover { + background-color: #f5f5f5; + } + & > input { + cursor: pointer; + } + } + } + } + th.oadatatablecheckbox { + width: 16px; + } +} +.dataTables_header { + background-color: #f6f6f6; + border: 1px solid #d1d1d1; + border-bottom: none; + padding: 5px; + position: relative; + .oadatatableactions { + display: inline-block; + } + .input-group { + float: right; + border-left: 1px solid rgba(0,0,0,.09); + padding-left: 8px; + width: 40%; + max-width: 350px; + .form-control { + height: 30px; + } + .clear-input { + height: 30px; + i { + vertical-align: text-top; + } + } + } +} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.spec.ts new file mode 100644 index 00000000000..c063b016578 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.spec.ts @@ -0,0 +1,87 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableComponent } from './table.component'; +import {NgxDatatableModule, TableColumn} from '@swimlane/ngx-datatable'; +import {FormsModule} from '@angular/forms'; + +describe('TableComponent', () => { + let component: TableComponent; + let fixture: ComponentFixture; + const columns: TableColumn[] = []; + const createFakeData = (n) => { + const data = []; + for (let i = 0; i < n; i++) { + data.push({ + a: i, + b: i * i, + c: -(i % 10) + }); + } + return data; + }; + + beforeEach( + async(() => { + TestBed.configureTestingModule({ + declarations: [TableComponent], + imports: [NgxDatatableModule, FormsModule] + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(TableComponent); + component = fixture.componentInstance; + }); + + beforeEach(() => { + component.data = createFakeData(100); + component.useData(); + component.columns = [ + {prop: 'a'}, + {prop: 'b'}, + {prop: 'c'} + ]; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have rows', () => { + expect(component.data.length).toBe(100); + expect(component.rows.length).toBe(component.data.length); + }); + + it('should have an int in setLimit parsing a string', () => { + expect(component.limit).toBe(10); + expect(component.limit).toEqual(jasmine.any(Number)); + + const e = {target: {value: '1'}}; + component.setLimit(e); + expect(component.limit).toBe(1); + expect(component.limit).toEqual(jasmine.any(Number)); + e.target.value = '-20'; + component.setLimit(e); + expect(component.limit).toBe(1); + }); + + it('should search for 13', () => { + component.search = '13'; + expect(component.rows.length).toBe(100); + component.updateFilter(true); + expect(component.rows[0].a).toBe(13); + expect(component.rows[1].b).toBe(1369); + expect(component.rows[2].b).toBe(3136); + expect(component.rows.length).toBe(3); + }); + + it('should restore full table after search', () => { + component.search = '13'; + expect(component.rows.length).toBe(100); + component.updateFilter(true); + expect(component.rows.length).toBe(3); + component.updateFilter(); + expect(component.rows.length).toBe(100); + }); +}); diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.ts new file mode 100644 index 00000000000..6751ac9a015 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.ts @@ -0,0 +1,98 @@ +import { + Component, EventEmitter, OnInit, Input, Output, ViewChild, OnChanges, ComponentFactoryResolver, Type +} from '@angular/core'; +import {DatatableComponent, TableColumn} from '@swimlane/ngx-datatable'; +import {TableDetailsDirective} from './table-details.directive'; + +@Component({ + selector: 'cd-table', + templateUrl: './table.component.html', + styleUrls: ['./table.component.scss'] +}) +export class TableComponent implements OnInit, OnChanges { + @ViewChild(DatatableComponent) table: DatatableComponent; + @ViewChild(TableDetailsDirective) detailTemplate: TableDetailsDirective; + + @Input() data: any[]; // This is the array with the items to be shown + @Input() columns: TableColumn[]; // each item -> { prop: 'attribute name', name: 'display name' } + @Input() detailsComponent?: string; // name of the component fe 'TableDetailsComponent' + @Input() header? = true; + + @Output() fetchData = new EventEmitter(); // Should be the function that will update the input data + + selectable: String = undefined; + search = ''; + rows = []; + selected = []; + paginationClasses = { + pagerLeftArrow: 'i fa fa-angle-double-left', + pagerRightArrow: 'i fa fa-angle-double-right', + pagerPrevious: 'i fa fa-angle-left', + pagerNext: 'i fa fa-angle-right' + }; + limit = 10; + + constructor(private componentFactoryResolver: ComponentFactoryResolver) {} + + ngOnInit() { + this.reloadData(); + if (this.detailsComponent) { + this.selectable = 'multi'; + } + } + + ngOnChanges(changes) { + this.useData(); + } + + setLimit(e) { + const value = parseInt(e.target.value, 10); + if (value > 0) { + this.limit = value; + } + } + + reloadData() { + this.fetchData.emit(); + } + + useData() { + this.rows = [...this.data]; + } + + toggleExpandRow() { + if (this.selected.length > 0) { + this.table.rowDetail.toggleExpandRow(this.selected[0]); + } + } + + updateDetailView() { + if (!this.detailsComponent) { + return; + } + const factories = Array.from(this.componentFactoryResolver['_factories'].keys()); + const factoryClass = >factories.find((x: any) => x.name === this.detailsComponent); + this.detailTemplate.viewContainerRef.clear(); + const cmpRef = this.detailTemplate.viewContainerRef.createComponent( + this.componentFactoryResolver.resolveComponentFactory(factoryClass) + ); + cmpRef.instance.selected = this.selected; + } + + updateFilter(event?) { + if (!event) { + this.search = ''; + } + const val = this.search.toLowerCase(); + const columns = this.columns; + // update the rows + this.rows = this.data.filter(function (d) { + return columns.filter((c) => { + return (typeof d[c.prop] === 'string' || typeof d[c.prop] === 'number') + && (d[c.prop] + '').toLowerCase().indexOf(val) !== -1; + }).length > 0; + }); + // Whenever the filter changes, always go back to the first page + this.table.offset = 0; + } +} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts index 5c2e80731b4..083e658c602 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts @@ -5,11 +5,13 @@ import { AuthStorageService } from './services/auth-storage.service'; import { AuthGuardService } from './services/auth-guard.service'; import { PipesModule } from './pipes/pipes.module'; import { HostService } from './services/host.service'; +import { ComponentsModule } from './components/components.module'; @NgModule({ imports: [ CommonModule, - PipesModule + PipesModule, + ComponentsModule ], declarations: [], providers: [ diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/defaults.scss b/src/pybind/mgr/dashboard_v2/frontend/src/defaults.scss index 3000628b029..fd0d6d44b6d 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/defaults.scss +++ b/src/pybind/mgr/dashboard_v2/frontend/src/defaults.scss @@ -1 +1,10 @@ $oa-color-blue: #288cea; +$oa-color-light-blue: #afd9ee; +$bg-color-light-blue: #d9edf7; +$border-color: 1px solid #d1d1d1; +@mixin table-cell { + padding: 5px; + border: none; + border-left: $border-color; + border-bottom: $border-color; +} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss b/src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss index 54e1d90acdc..1e9de270941 100755 --- a/src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss +++ b/src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss @@ -605,7 +605,7 @@ ul.task-queue-pagination { margin-bottom: 0; } .panel-openattic { - border: 1px solid #d1d1d1; + border: $border-color; border-top: 0; border-radius: 0; } @@ -622,131 +622,113 @@ ul.task-queue-pagination { } .panel-openattic>.panel-body { background: #ffffff; - border-top: 1px solid #d1d1d1; + border-top: $border-color; padding: 10px 15px; } .panel-openattic>.panel-footer { background: #ffffff; - border-top: 1px solid #d1d1d1; + border-top: $border-color; } -/* Table */ -table.datatable { +/* Table + * Has to be here because the table component uses ngx-datatable component in + * the template and styles are not inherited by nested components. + */ +.ngx-datatable.oadatatable { + border: $border-color; margin-bottom: 0; - max-width: none!important -} -table.dataTable thead .sorting_asc, -table.dataTable thead .sorting_desc { - color: $oa-color-blue; -} -table.dataTable thead .sorting, -table.dataTable thead .sorting_asc, -table.dataTable thead .sorting_desc { - cursor: pointer; -} -table.datatable thead .sorting:after, -table.datatable thead .sorting_asc:after, -table.datatable thead .sorting_desc:after { - font-family: FontAwesome; - font-weight: 400; - height: 9px; - left: 10px; - line-height: 12px; - position: relative; - vertical-align: baseline; - width: 12px; -} -table.datatable thead .sorting:after { - content: "\f0dc"; -} -table.datatable thead .sorting_asc:after { - content: "\f160"; -} -table.datatable thead .sorting_desc:after { - content: "\f161"; -} -.table>tbody>tr>td, -.table>tbody>tr>th, -.table>tfoot>tr>td, -.table>tfoot>tr>th, -.table>thead>tr>td, -.table>thead>tr>th { - padding: 5px; -} -.table>tbody>tr>td>input, -.table>tbody>tr>th>input, -.table>tfoot>tr>td>input, -.table>tfoot>tr>th>input, -.table>thead>tr>td>input, -.table>thead>tr>th>input { - display: block; -} -.table>thead { - background-clip: padding-box; - background-color: #f9f9f9; - background-image: -webkit-linear-gradient(top,#fafafa 0,#ededed 100%); - background-image: -o-linear-gradient(top,#fafafa 0,#ededed 100%); - background-image: linear-gradient(to bottom,#fafafa 0,#ededed 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0); -} - -.table.header-text-center>thead>tr>th { - text-align: center; -} - -.table-bordered { - border: none; - border-top: 1px solid #d1d1d1; - border-right: 1px solid #d1d1d1; -} - -.table-bordered>tbody>tr>td, -.table-bordered>tbody>tr>th, -.table-bordered>tfoot>tr>td, -.table-bordered>tfoot>tr>th, -.table-bordered>thead>tr>td, -.table-bordered>thead>tr>th { - border: none; - border-left: 1px solid #d1d1d1; - border-bottom: 1px solid #d1d1d1; -} -.table-striped>tbody>tr:nth-of-type(odd) { - background-color: #ffffff; -} -.table-striped>tbody>tr:nth-of-type(even) { - background-color: #f6f6f6; -} -.table-responsive { - overflow-x: auto; - margin-bottom: 0; - min-height: .01%; -} - -.table-no-background>thead { - background: none; -} -.table-no-background>thead>tr>th { - border-bottom: 1px solid #ddd; -} -.table-no-background>tbody>tr>td { - height: 50px; - vertical-align: middle; - border-top: 0px; - border-bottom: 1px solid #ddd; -} - -.table-transparent>thead { - background: none; -} -.table-transparent>thead>tr>th { - border-bottom: 0px; -} -.table-transparent>tbody>tr>td { - height: 50px; - vertical-align: middle; - border-top: 0px; - border-bottom: 0px; + max-width: none!important; + .datatable-header { + background-clip: padding-box; + background-color: #f9f9f9; + background-image: -webkit-linear-gradient(top,#fafafa 0,#ededed 100%); + background-image: -o-linear-gradient(top,#fafafa 0,#ededed 100%); + background-image: linear-gradient(to bottom,#fafafa 0,#ededed 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0); + .sort-asc, .sort-desc { + color: $oa-color-blue; + } + .datatable-header-cell{ + @include table-cell; + text-align: center; + .datatable-header-cell-label { + &:after { + font-family: FontAwesome; + font-weight: 400; + height: 9px; + left: 10px; + line-height: 12px; + position: relative; + vertical-align: baseline; + width: 12px; + } + } + &.sortable { + .datatable-header-cell-label:after { + content: " \f0dc"; + } + &.sort-active { + &.sort-asc .datatable-header-cell-label:after { + content: " \f160"; + } + &.sort-desc .datatable-header-cell-label:after { + content: " \f161"; + } + } + } + &:first-child { + border-left: none; + } + } + } + .datatable-body { + .datatable-body-row { + &.datatable-row-even { + background-color: #ffffff; + } + &.datatable-row-odd { + background-color: #f6f6f6; + } + &:hover { + background-color: $oa-color-light-blue; + } + &.active, &.active:hover { + background-color: $bg-color-light-blue; + } + .datatable-body-cell{ + @include table-cell; + &:first-child { + border-left: none; + } + .datatable-body-cell-label { + display: block; + } + } + } + } + .datatable-footer { + .selected-count .page-count { + padding-left: 5px; + } + .datatable-pager .pager { + margin-right: 5px; + .pages { + & > a, & > span { + display: inline-block; + padding: 5px 10px; + margin-bottom: 5px; + border: none; + } + a:hover { + background-color: $oa-color-light-blue; + } + &.active > a { + background-color: $bg-color-light-blue; + } + } + } + } } /* Typo */ @@ -812,157 +794,6 @@ h6{ color: $oa-color-blue; } -/* Datatables */ -.dataTables_wrapper { - margin-bottom: 25px; -} -.dataTables_wrapper .separator { - height: 30px; - border-left: 1px solid rgba(0,0,0,.09); - padding-left: 5px; - margin-left: 5px; - display: inline-block; - vertical-align: middle; -} -.dataTables_wrapper .widget-toolbar { - display: inline-block; - float: right; - width: auto; - height: 30px; - line-height: 28px; - position: relative; - border-left: 1px solid rgba(0,0,0,.09); - cursor: pointer; - padding: 0 8px; - text-align: center; -} -.dataTables_wrapper .dropdown-menu { - white-space: nowrap; -} -.dataTables_wrapper .dropdown-menu>li { - cursor: pointer; -} -.dataTables_wrapper .dropdown-menu>li>label { - width: 100%; - margin-bottom: 0; - padding-left: 20px; - padding-right: 20px; - cursor: pointer; -} -.dataTables_wrapper .dropdown-menu>li>label:hover { - background-color: #f5f5f5; -} -.dataTables_wrapper .dropdown-menu>li>label>input { - cursor: pointer; -} -.dataTables_wrapper th.oadatatablecheckbox { - width: 16px; -} -.dataTables_header { - background-color: #f6f6f6; - border: 1px solid #d1d1d1; - border-bottom: none; - padding: 5px; - position: relative; -} -.dataTables_header .oadatatableactions { - display: inline-block; -} -.dataTables_header .input-group { - float: right; - border-left: 1px solid rgba(0,0,0,.09); - padding-left: 8px; - width: 40%; - max-width: 350px; -} -.dataTables_header .input-group .input-group-addon { -} -.dataTables_header .input-group .form-control { - height: 30px; -} -.dataTables_header .input-group .clear-input { - height: 30px; -} -.dataTables_header .input-group .clear-input i { - vertical-align: text-top; -} -.dataTables_no-match { - border: 1px solid #d1d1d1; - padding: 10px 0; - text-align: center; - font-weight: bold; - font-style: italic; -} -.dataTables_content .progress { - max-height: 16px; -} -.dataTables_content .progress span { - line-height: 16px; -} -.dataTables_footer { - background-color: #ffffff; - border: 1px solid #d1d1d1; - border-top: none; - padding: 0; - overflow: hidden; -} -.dataTables_footer .dataTables_info { - float: left; - padding-top: 6px; - padding-left: 5px; - font-style: italic; -} -.dataTables_paginate { - background: #fafafa; - float: right; - margin: 0; -} -.dataTables_paginate .pagination { - float: left; - margin: 0; -} -.dataTables_paginate .pagination.paginate-input { - line-height: 1em; - padding: 0.3em 0.5em; -} -.dataTables_paginate .pagination>li.disabled>span { - background: #f5f5f5; - border-left-color: #ececec; - border-right-color: #ececec; -} -.dataTables_paginate .pagination>li.disabled>span, -.dataTables_paginate .pagination>li>span:focus, -.dataTables_paginate .pagination>li>span:hover { - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} -.dataTables_paginate .pagination>li>span { - border-color: #fff #e1e1e1 #f4f4f4; - border-width: 0 1px; - font-size: 1.2em; - font-weight: 400; - padding: 4px; - text-align: center; - width: 31px; -} -.dataTables_paginate .pagination .last>span { - border-right: 0; -} -.oadatatable div.overlay, div.oa-overlay { - position: absolute; - top: 0; - left: 0; - z-index: 10; - height: 100%; - width: 100%; - background-color: rgba(30, 30, 30, 0.2); -} -.oadatatable div.overlay-content, div.oa-overlay-content { - margin: 200px auto; - width: 50px; - height: 50px; - background-color: rgba(30, 30, 30, 0); -} - /* Feedback */ #feedback .feedback-button { position: fixed; -- 2.39.5