]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: Add table component
authorStephan Müller <smueller@suse.com>
Thu, 1 Feb 2018 15:00:52 +0000 (16:00 +0100)
committerRicardo Dias <rdias@suse.com>
Mon, 5 Mar 2018 13:07:05 +0000 (13:07 +0000)
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 <smueller@suse.com>
Signed-off-by: Tiago Melo <tmelo@suse.com>
src/pybind/mgr/dashboard_v2/frontend/package.json
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/components.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table-details.directive.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/table/table.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/defaults.scss
src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss

index 35fcc2d0d339e9c5769c801d564d68c67bd9e3c0..632ea6f85b1b607d4fd8f192318c1c56c5437e54 100644 (file)
@@ -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 (file)
index 0000000..7baba7a
--- /dev/null
@@ -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 (file)
index 0000000..b3e2684
--- /dev/null
@@ -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 (file)
index 0000000..d3b2263
--- /dev/null
@@ -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 (file)
index 0000000..aec7a25
--- /dev/null
@@ -0,0 +1,68 @@
+<div class="dataTables_wrapper">
+  <div class="dataTables_header clearfix"
+       *ngIf="header">
+    <!-- actions -->
+    <div class="oadatatableactions">
+        <ng-content select="table-actions"></ng-content>
+    </div>
+    <!-- end actions -->
+
+    <!-- search -->
+    <div class="input-group">
+      <span class="input-group-addon">
+        <i class="glyphicon glyphicon-search"></i>
+      </span>
+      <input
+             class="form-control"
+             type="text"
+             [(ngModel)]="search"
+             (keyup)='updateFilter($event)'>
+      <span class="input-group-btn">
+        <button type="button"
+                class="btn btn-default clear-input tc_clearInputBtn"
+                (click)="updateFilter()">
+          <i class="icon-prepend fa fa-remove"></i>
+        </button>
+      </span>
+    </div>
+    <!-- end search -->
+
+    <!-- pagination limit -->
+    <div class="dataTables_length widget-toolbar">
+      <input type="number"
+             min="1"
+             max="9999"
+             [value]="limit"
+             (click)="setLimit($event)"
+             (keyup)="setLimit($event)"
+             (blur)="setLimit($event)">
+    </div>
+    <!-- end pagination limit-->
+
+    <!-- refresh button -->
+    <div class="widget-toolbar tc_refreshBtn">
+      <a (click)="reloadData()">
+        <i class="fa fa-lg fa-refresh"></i>
+      </a>
+    </div>
+    <!-- end refresh button -->
+  </div>
+  <ngx-datatable #table
+                 class="bootstrap oadatatable"
+                 [cssClasses]="paginationClasses"
+                 [selectionType]="selectable"
+                 [selected]="selected"
+                 (select)="toggleExpandRow($event)"
+                 [columns]="columns"
+                 [columnMode]="'force'"
+                 [rows]="rows"
+                 [footerHeight]="'auto'"
+                 [limit]="limit"
+                 [loadingIndicator]="true"
+                 [rowHeight]="'auto'">
+    <!-- Row Detail Template -->
+    <ngx-datatable-row-detail (toggle)="updateDetailView($event)">
+    </ngx-datatable-row-detail>
+  </ngx-datatable>
+</div>
+<ng-template cdTableDetails></ng-template>
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 (file)
index 0000000..6cce942
--- /dev/null
@@ -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 (file)
index 0000000..c063b01
--- /dev/null
@@ -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<TableComponent>;
+  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 (file)
index 0000000..6751ac9
--- /dev/null
@@ -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 = <Type<any>>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;
+  }
+}
index 5c2e80731b4b2b2285bebfcbf036140e1b47680e..083e658c60224c711dae82366747513c862166ba 100644 (file)
@@ -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: [
index 3000628b02967a048f85d4a57f682add1e8c9433..fd0d6d44b6d622e1a56537fb4485be58c7fc36ea 100644 (file)
@@ -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;
+}
index 54e1d90acdcdfc0e1642d8acb1a87bf4c5ee04f4..1e9de270941fb0b1eb976ab86b0f36ffae6fb857 100755 (executable)
@@ -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;