]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: add initial frontend code
authorTiago Melo <tmelo@suse.com>
Mon, 22 Jan 2018 14:51:58 +0000 (14:51 +0000)
committerRicardo Dias <rdias@suse.com>
Mon, 5 Mar 2018 13:07:00 +0000 (13:07 +0000)
This contains the angular boilerplate, some initial structure and the navigation component.
Signed-off-by: Tiago Melo <tmelo@suse.com>
46 files changed:
src/pybind/mgr/dashboard_v2/README.rst
src/pybind/mgr/dashboard_v2/frontend/.angular-cli.json [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/.editorconfig [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/.gitignore [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/README.md [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/e2e/app.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/e2e/app.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/e2e/tsconfig.e2e.json [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/karma.conf.js [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/package.json [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/protractor.conf.js [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/app.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/core-routing.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/core.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/assets/.gitkeep [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/assets/Ceph_Logo_Standard_RGB_White_120411_fa.png [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/assets/loading.gif [new file with mode: 0755]
src/pybind/mgr/dashboard_v2/frontend/src/assets/logo-mini.png [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/assets/notification-icons.png [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.prod.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/favicon.ico [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/index.html [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/main.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss [new file with mode: 0755]
src/pybind/mgr/dashboard_v2/frontend/src/polyfills.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/styles.scss [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/test.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.app.json [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.spec.json [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/typings.d.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/tsconfig.json [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/tslint.json [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/module.py

index 5c85e8a636f6281cd60c60d3a65cbc70a19c345f..5eed5a11a40c92e24ec7f50a971672b8d50d7fd4 100644 (file)
@@ -55,6 +55,9 @@ You can see currently enabled modules with::
 
   $ ceph mgr module ls
 
+Currently you will need to manually generate the frontend code.
+Instructions can be found in `./frontend/README.md`.
+
 In order to be able to log in, you need to define a username and password, which
 will be stored in the MON's configuration database::
 
diff --git a/src/pybind/mgr/dashboard_v2/frontend/.angular-cli.json b/src/pybind/mgr/dashboard_v2/frontend/.angular-cli.json
new file mode 100644 (file)
index 0000000..9d39081
--- /dev/null
@@ -0,0 +1,61 @@
+{
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+  "project": {
+    "name": "ceph-dashboard"
+  },
+  "apps": [
+    {
+      "root": "src",
+      "outDir": "dist",
+      "assets": [
+        "assets",
+        "favicon.ico"
+      ],
+      "index": "index.html",
+      "main": "main.ts",
+      "polyfills": "polyfills.ts",
+      "test": "test.ts",
+      "tsconfig": "tsconfig.app.json",
+      "testTsconfig": "tsconfig.spec.json",
+      "prefix": "oa",
+      "styles": [
+        "../node_modules/bootstrap/dist/css/bootstrap.css",
+        "styles.scss"
+      ],
+      "scripts": [],
+      "environmentSource": "environments/environment.ts",
+      "environments": {
+        "dev": "environments/environment.ts",
+        "prod": "environments/environment.prod.ts"
+      }
+    }
+  ],
+  "e2e": {
+    "protractor": {
+      "config": "./protractor.conf.js"
+    }
+  },
+  "lint": [
+    {
+      "project": "src/tsconfig.app.json",
+      "exclude": "**/node_modules/**"
+    },
+    {
+      "project": "src/tsconfig.spec.json",
+      "exclude": "**/node_modules/**"
+    },
+    {
+      "project": "e2e/tsconfig.e2e.json",
+      "exclude": "**/node_modules/**"
+    }
+  ],
+  "test": {
+    "karma": {
+      "config": "./karma.conf.js"
+    }
+  },
+  "defaults": {
+    "styleExt": "scss",
+    "component": {}
+  }
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/.editorconfig b/src/pybind/mgr/dashboard_v2/frontend/.editorconfig
new file mode 100644 (file)
index 0000000..6e87a00
--- /dev/null
@@ -0,0 +1,13 @@
+# Editor configuration, see http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/src/pybind/mgr/dashboard_v2/frontend/.gitignore b/src/pybind/mgr/dashboard_v2/frontend/.gitignore
new file mode 100644 (file)
index 0000000..2e55dc6
--- /dev/null
@@ -0,0 +1,50 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node_modules
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+testem.log
+/typings
+
+# e2e
+/e2e/*.js
+/e2e/*.map
+
+# System Files
+.DS_Store
+Thumbs.db
+
+# Package lock files
+yarn.lock
+package-lock.json
+
+# Ceph
+!core
+!*.core
diff --git a/src/pybind/mgr/dashboard_v2/frontend/README.md b/src/pybind/mgr/dashboard_v2/frontend/README.md
new file mode 100644 (file)
index 0000000..518ea55
--- /dev/null
@@ -0,0 +1,52 @@
+# Ceph Dashboard
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.3.
+
+## Installation
+
+Run `npm install` to install the required packages locally.
+
+**Note**
+
+If you do not have installed [Angular CLI](https://github.com/angular/angular-cli) globally, then you need to execute ``ng`` commands with an additional ``npm run`` before it.
+
+## Development server
+
+Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `npm run build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. Navigate to `http://localhost:8080`.
+
+## Running unit tests
+
+Run `npm run test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `npm run e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
+
+## Examples of generator
+
+```
+# Create module 'Core'
+src/app> ng generate module core -m=app --routing
+
+# Create module 'Auth' under module 'Core'
+src/app/core> ng generate module auth -m=core --routing
+or, alternatively:
+src/app> ng generate module core/auth -m=core --routing
+
+# Create component 'Login' under module 'Auth'
+src/app/core/auth> ng generate component login -m=core/auth
+or, alternatively:
+src/app> ng generate component core/auth/login -m=core/auth
+```
diff --git a/src/pybind/mgr/dashboard_v2/frontend/e2e/app.e2e-spec.ts b/src/pybind/mgr/dashboard_v2/frontend/e2e/app.e2e-spec.ts
new file mode 100644 (file)
index 0000000..3e98370
--- /dev/null
@@ -0,0 +1,14 @@
+import { AppPage } from './app.po';
+
+describe('ceph-dashboard App', () => {
+  let page: AppPage;
+
+  beforeEach(() => {
+    page = new AppPage();
+  });
+
+  it('should display welcome message', () => {
+    page.navigateTo();
+    expect(page.getParagraphText()).toEqual('Welcome to oa!');
+  });
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/e2e/app.po.ts b/src/pybind/mgr/dashboard_v2/frontend/e2e/app.po.ts
new file mode 100644 (file)
index 0000000..d9761bb
--- /dev/null
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+  navigateTo() {
+    return browser.get('/');
+  }
+
+  getParagraphText() {
+    return element(by.css('oa-root h1')).getText();
+  }
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/e2e/tsconfig.e2e.json b/src/pybind/mgr/dashboard_v2/frontend/e2e/tsconfig.e2e.json
new file mode 100644 (file)
index 0000000..1d9e5ed
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/e2e",
+    "baseUrl": "./",
+    "module": "commonjs",
+    "target": "es5",
+    "types": [
+      "jasmine",
+      "jasminewd2",
+      "node"
+    ]
+  }
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/karma.conf.js b/src/pybind/mgr/dashboard_v2/frontend/karma.conf.js
new file mode 100644 (file)
index 0000000..af139fa
--- /dev/null
@@ -0,0 +1,33 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+  config.set({
+    basePath: '',
+    frameworks: ['jasmine', '@angular/cli'],
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-chrome-launcher'),
+      require('karma-jasmine-html-reporter'),
+      require('karma-coverage-istanbul-reporter'),
+      require('@angular/cli/plugins/karma')
+    ],
+    client:{
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
+    },
+    coverageIstanbulReporter: {
+      reports: [ 'html', 'lcovonly' ],
+      fixWebpackSourcePaths: true
+    },
+    angularCli: {
+      environment: 'dev'
+    },
+    reporters: ['progress', 'kjhtml'],
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: true,
+    browsers: ['Chrome'],
+    singleRun: false
+  });
+};
diff --git a/src/pybind/mgr/dashboard_v2/frontend/package.json b/src/pybind/mgr/dashboard_v2/frontend/package.json
new file mode 100644 (file)
index 0000000..e7eb975
--- /dev/null
@@ -0,0 +1,51 @@
+{
+  "name": "ceph-dashboard",
+  "version": "0.0.0",
+  "license": "MIT",
+  "scripts": {
+    "ng": "ng",
+    "start": "ng serve",
+    "build": "ng build",
+    "test": "ng test",
+    "lint": "ng lint",
+    "e2e": "ng e2e"
+  },
+  "private": true,
+  "dependencies": {
+    "@angular/animations": "^5.0.0",
+    "@angular/common": "^5.0.0",
+    "@angular/compiler": "^5.0.0",
+    "@angular/core": "^5.0.0",
+    "@angular/forms": "^5.0.0",
+    "@angular/http": "^5.0.0",
+    "@angular/platform-browser": "^5.0.0",
+    "@angular/platform-browser-dynamic": "^5.0.0",
+    "@angular/router": "^5.0.0",
+    "bootstrap": "^3.3.7",
+    "core-js": "^2.4.1",
+    "ngx-bootstrap": "^2.0.1",
+    "rxjs": "^5.5.2",
+    "zone.js": "^0.8.14"
+  },
+  "devDependencies": {
+    "@angular/cli": "^1.6.5",
+    "@angular/compiler-cli": "^5.0.0",
+    "@angular/language-service": "^5.0.0",
+    "@types/jasmine": "~2.5.53",
+    "@types/jasminewd2": "~2.0.2",
+    "@types/node": "~6.0.60",
+    "codelyzer": "^4.0.1",
+    "jasmine-core": "~2.6.2",
+    "jasmine-spec-reporter": "~4.1.0",
+    "karma": "~1.7.0",
+    "karma-chrome-launcher": "~2.1.1",
+    "karma-cli": "~1.0.1",
+    "karma-coverage-istanbul-reporter": "^1.2.1",
+    "karma-jasmine": "~1.1.0",
+    "karma-jasmine-html-reporter": "^0.2.2",
+    "protractor": "~5.1.2",
+    "ts-node": "~3.2.0",
+    "tslint": "~5.7.0",
+    "typescript": "~2.4.2"
+  }
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/protractor.conf.js b/src/pybind/mgr/dashboard_v2/frontend/protractor.conf.js
new file mode 100644 (file)
index 0000000..7ee3b5e
--- /dev/null
@@ -0,0 +1,28 @@
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+exports.config = {
+  allScriptsTimeout: 11000,
+  specs: [
+    './e2e/**/*.e2e-spec.ts'
+  ],
+  capabilities: {
+    'browserName': 'chrome'
+  },
+  directConnect: true,
+  baseUrl: 'http://localhost:4200/',
+  framework: 'jasmine',
+  jasmineNodeOpts: {
+    showColors: true,
+    defaultTimeoutInterval: 30000,
+    print: function() {}
+  },
+  onPrepare() {
+    require('ts-node').register({
+      project: 'e2e/tsconfig.e2e.json'
+    });
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+  }
+};
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts
new file mode 100644 (file)
index 0000000..d425c6f
--- /dev/null
@@ -0,0 +1,10 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+const routes: Routes = [];
+
+@NgModule({
+  imports: [RouterModule.forRoot(routes)],
+  exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.html
new file mode 100644 (file)
index 0000000..41d2dd6
--- /dev/null
@@ -0,0 +1,5 @@
+<oa-navigation></oa-navigation>
+
+<div class="container-fluid">
+  <router-outlet></router-outlet>
+</div>
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts
new file mode 100644 (file)
index 0000000..f99d866
--- /dev/null
@@ -0,0 +1,30 @@
+import { TestBed, async } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+import { NavigationComponent } from './core/navigation/navigation/navigation.component';
+
+describe('AppComponent', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        RouterTestingModule
+      ],
+      declarations: [
+        AppComponent,
+        NavigationComponent
+      ],
+    }).compileComponents();
+  }));
+
+  it('should create the app', async(() => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app).toBeTruthy();
+  }));
+
+  it(`should have as title 'oa'`, async(() => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.title).toEqual('oa');
+  }));
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.ts
new file mode 100644 (file)
index 0000000..3d2afc2
--- /dev/null
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'oa-root',
+  templateUrl: './app.component.html',
+  styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+  title = 'oa';
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.module.ts
new file mode 100644 (file)
index 0000000..4375175
--- /dev/null
@@ -0,0 +1,26 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { AppRoutingModule } from './app-routing.module';
+
+import { AppComponent } from './app.component';
+import { CoreModule } from './core/core.module';
+import { SharedModule } from './shared/shared.module';
+import { CephModule } from './ceph/ceph.module';
+
+@NgModule({
+  declarations: [
+    AppComponent
+  ],
+  imports: [
+    BrowserModule,
+    AppRoutingModule,
+    CoreModule,
+    SharedModule,
+    CephModule
+  ],
+  exports: [SharedModule],
+  providers: [],
+  bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts
new file mode 100644 (file)
index 0000000..9cdda33
--- /dev/null
@@ -0,0 +1,10 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@NgModule({
+  imports: [
+    CommonModule
+  ],
+  declarations: []
+})
+export class CephModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/core-routing.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/core-routing.module.ts
new file mode 100644 (file)
index 0000000..405e5a0
--- /dev/null
@@ -0,0 +1,10 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+const routes: Routes = [];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class CoreRoutingModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/core.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/core.module.ts
new file mode 100644 (file)
index 0000000..a738ccb
--- /dev/null
@@ -0,0 +1,15 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { CoreRoutingModule } from './core-routing.module';
+import { NavigationModule } from './navigation/navigation.module';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    CoreRoutingModule,
+    NavigationModule
+  ],
+  exports: [NavigationModule],
+  declarations: []
+})
+export class CoreModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation.module.ts
new file mode 100644 (file)
index 0000000..bbac877
--- /dev/null
@@ -0,0 +1,12 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { NavigationComponent } from './navigation/navigation.component';
+
+@NgModule({
+  imports: [
+    CommonModule
+  ],
+  declarations: [NavigationComponent],
+  exports: [NavigationComponent]
+})
+export class NavigationModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html
new file mode 100644 (file)
index 0000000..a097b05
--- /dev/null
@@ -0,0 +1,92 @@
+<nav class="navbar navbar-default navbar-openattic">
+  <!-- Brand and toggle get grouped for better mobile display -->
+
+  <div class="navbar-header tc_logo_component">
+    <a class="navbar-brand"
+       href="#">
+      <img src="assets/Ceph_Logo_Standard_RGB_White_120411_fa.png"
+           alt="Ceph">
+    </a>
+
+    <button type="button"
+            class="navbar-toggle collapsed"
+            data-toggle="collapse"
+            data-target="#bs-example-navbar-collapse-1">
+      <span i18n
+            class="sr-only">Toggle navigation
+      </span>
+      <span class="icon-bar"></span>
+      <span class="icon-bar"></span>
+      <span class="icon-bar"></span>
+    </button>
+  </div>
+
+  <!-- Collect the nav links, forms, and other content for toggling -->
+  <div class="collapse navbar-collapse"
+       id="bs-example-navbar-collapse-1">
+    <ul class="nav navbar-nav navbar-primary">
+      <!-- <li data-ui-sref-active="active"
+          class="tc_menuitem tc_menuitem_dashboard">
+        <a i18n
+           routerLink="/dashboard">Dashboard
+        </a>
+      </li>
+      <li data-ui-sref-active="active"
+          class="tc_menuitem tc_menuitem_ceph_osds">
+        <a i18n
+           routerLink="/cephOsds">OSDs
+        </a>
+      </li>
+      <li data-ui-sref-active="active"
+          class="tc_menuitem tc_menuitem_ceph_rbds">
+        <a i18n
+           routerLink="/cephRbds">RBDs
+        </a>
+      </li>
+      <li data-ui-sref-active="active"
+          class="tc_menuitem tc_menuitem_ceph_pools">
+        <a i18n
+           routerLink="/cephPools">Pools
+        </a>
+      </li>
+      <li data-ui-sref-active="active"
+          class="tc_menuitem tc_menuitem_ceph_nodes">
+        <a i18n
+           routerLink="/cephNodes">Nodes
+        </a>
+      </li>
+      <li class="dropdown tc_menuitem tc_menuitem_ceph_rgw">
+        <a href=""
+           class="dropdown-toggle"
+           data-toggle="dropdown"><ng-container i18n>Object Gateway</ng-container> <span class="caret"></span>
+        </a>
+        <ul class="dropdown-menu">
+          <li data-ui-sref-active="active"
+              class="tc_submenuitem tc_submenuitem_ceph_rgw_users">
+            <a i18n
+               routerLink="/ceph-rgw-users">Users
+            </a>
+          </li>
+          <li data-ui-sref-active="active"
+              class="tc_submenuitem tc_submenuitem_ceph_rgw_buckets">
+            <a i18n
+               routerLink="/ceph-rgw-buckets">Buckets
+            </a>
+          </li>
+        </ul>
+      </li>
+      <li data-ui-sref-active="active"
+          class="tc_menuitem tc_submenuitem_settings">
+        <a i18n
+           routerLink="/settings">Settings
+        </a>
+      </li> -->
+    </ul>
+    <!-- /.navbar-primary -->
+
+    <ul class="nav navbar-nav navbar-utility">
+    </ul>
+    <!-- /.navbar-utility -->
+  </div>
+  <!-- /.navbar-collapse -->
+</nav>
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.scss
new file mode 100644 (file)
index 0000000..0f5acfd
--- /dev/null
@@ -0,0 +1,143 @@
+.navbar-openattic {
+  margin-bottom: 0;
+  background: #474544;
+  border: 0;
+  border-radius: 0;
+  border-top: 4px solid #288cea;
+  font-size: 1.2em;
+}
+.navbar-openattic .navbar-header {
+  display: flex;
+  float: none;
+}
+.navbar-openattic .navbar-toggle {
+  margin-left: auto;
+  border: 0;
+}
+.navbar-openattic .navbar-toggle:focus,
+.navbar-openattic .navbar-toggle:hover {
+  background-color: transparent;
+  outline: 0;
+}
+.navbar-openattic .navbar-toggle .icon-bar {
+  background-color: #ececec;
+}
+.navbar-openattic .navbar-toggle:focus .icon-bar,
+.navbar-openattic .navbar-toggle:hover .icon-bar {
+  -webkit-box-shadow: 0 0 3px #fff;
+  box-shadow: 0 0 3px #fff;
+}
+.navbar-openattic .navbar-collapse {
+  padding: 0;
+}
+.navbar-openattic .navbar-nav > li > a,
+.navbar-openattic .navbar-nav > li > .oa-navbar > a {
+  color: #ececec;
+  line-height: 1;
+  padding: 10px 20px;
+  position: relative;
+  display: block;
+  text-decoration: none;
+}
+.navbar-openattic .navbar-nav > li > a:focus,
+.navbar-openattic .navbar-nav > li > a:hover,
+.navbar-openattic .navbar-nav > li > .oa-navbar > a:focus,
+.navbar-openattic .navbar-nav > li > .oa-navbar > a:hover {
+  color: #ececec;
+}
+.navbar-openattic .navbar-nav > li > a:hover,
+.navbar-openattic .navbar-nav > li > .oa-navbar > a:hover {
+  background-color: #505050;
+}
+.navbar-openattic .navbar-nav > .open > a,
+.navbar-openattic .navbar-nav > .open > a:hover,
+.navbar-openattic .navbar-nav > .open > a:focus,
+.navbar-openattic .navbar-nav > .open > .oa-navbar > a,
+.navbar-openattic .navbar-nav > .open > .oa-navbar > a:hover,
+.navbar-openattic .navbar-nav > .open > .oa-navbar > a:focus {
+  color: #ececec;
+  border-color: transparent;
+  background-color: transparent;
+}
+.navbar-openattic .navbar-primary > li > a {
+  border: 0;
+}
+.navbar-openattic .navbar-primary > .active > a,
+.navbar-openattic .navbar-primary > .active > a:hover,
+.navbar-openattic .navbar-primary > .active > a:focus {
+  color: #ececec;
+  background-color: #288cea;
+  border: 0;
+}
+.navbar-openattic .navbar-utility a,
+.navbar-openattic .navbar-utility .fa {
+  font-size: 1em;
+}
+.navbar-openattic .navbar-utility > .active > a {
+  color: #ececec;
+  background-color: #505050;
+}
+.navbar-openattic .navbar-utility > li > .open > a,
+.navbar-openattic .navbar-utility > li > .open > a:hover,
+.navbar-openattic .navbar-utility > li > .open > a:focus {
+  color: #ececec;
+  border-color: transparent;
+  background-color: transparent;
+}
+@media (min-width: 768px) {
+  .navbar-openattic .navbar-primary > li > a {
+    border-bottom: 4px solid transparent;
+  }
+  .navbar-openattic .navbar-primary > .active > a,
+  .navbar-openattic .navbar-primary > .active > a:hover,
+  .navbar-openattic .navbar-primary > .active > a:focus {
+    background-color: transparent;
+    border-bottom: 4px solid #288cea;
+  }
+  .navbar-openattic .navbar-utility {
+    border-bottom: 0;
+    font-size: 11px;
+    position: absolute;
+    right: 0;
+    top: 0;
+  }
+}
+@media (max-width: 767px) {
+  .navbar-openattic .navbar-nav {
+    margin: 0;
+  }
+  .navbar-openattic .navbar-collapse,
+  .navbar-openattic .navbar-form {
+    border-color: #ececec;
+  }
+  .navbar-openattic .navbar-collapse {
+    padding: 0;
+  }
+  .navbar-nav .open .dropdown-menu {
+    padding-top: 0;
+    padding-bottom: 0;
+    background-color: #505050;
+  }
+  .navbar-nav .open .dropdown-menu .dropdown-header,
+  .navbar-nav .open .dropdown-menu > li > a {
+    padding: 5px 15px 5px 35px;
+  }
+  .navbar-openattic .navbar-nav .open .dropdown-menu > li > a {
+    color: #ececec;
+  }
+  .navbar-openattic .navbar-nav .open .dropdown-menu > .active > a {
+    color: #ececec;
+    background-color: #288cea;
+  }
+  .navbar-openattic .navbar-nav > li > a:hover {
+    background-color: #288cea;
+  }
+  .navbar-openattic .navbar-utility {
+    border-top: 1px solid #ececec;
+  }
+  .navbar-openattic .navbar-primary > .active > a,
+  .navbar-openattic .navbar-primary > .active > a:hover,
+  .navbar-openattic .navbar-primary > .active > a:focus {
+    background-color: #288cea;
+  }
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts
new file mode 100644 (file)
index 0000000..3857718
--- /dev/null
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NavigationComponent } from './navigation.component';
+
+describe('NavigationComponent', () => {
+  let component: NavigationComponent;
+  let fixture: ComponentFixture<NavigationComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ NavigationComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NavigationComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.ts
new file mode 100644 (file)
index 0000000..00bc05b
--- /dev/null
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'oa-navigation',
+  templateUrl: './navigation.component.html',
+  styleUrls: ['./navigation.component.scss']
+})
+export class NavigationComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}
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
new file mode 100644 (file)
index 0000000..fffbe5b
--- /dev/null
@@ -0,0 +1,10 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@NgModule({
+  imports: [
+    CommonModule
+  ],
+  declarations: []
+})
+export class SharedModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/assets/.gitkeep b/src/pybind/mgr/dashboard_v2/frontend/src/assets/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/assets/Ceph_Logo_Standard_RGB_White_120411_fa.png b/src/pybind/mgr/dashboard_v2/frontend/src/assets/Ceph_Logo_Standard_RGB_White_120411_fa.png
new file mode 100644 (file)
index 0000000..0f07b83
Binary files /dev/null and b/src/pybind/mgr/dashboard_v2/frontend/src/assets/Ceph_Logo_Standard_RGB_White_120411_fa.png differ
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/assets/loading.gif b/src/pybind/mgr/dashboard_v2/frontend/src/assets/loading.gif
new file mode 100755 (executable)
index 0000000..8fb88de
Binary files /dev/null and b/src/pybind/mgr/dashboard_v2/frontend/src/assets/loading.gif differ
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/assets/logo-mini.png b/src/pybind/mgr/dashboard_v2/frontend/src/assets/logo-mini.png
new file mode 100644 (file)
index 0000000..b3446a8
Binary files /dev/null and b/src/pybind/mgr/dashboard_v2/frontend/src/assets/logo-mini.png differ
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/assets/notification-icons.png b/src/pybind/mgr/dashboard_v2/frontend/src/assets/notification-icons.png
new file mode 100644 (file)
index 0000000..d609a7c
Binary files /dev/null and b/src/pybind/mgr/dashboard_v2/frontend/src/assets/notification-icons.png differ
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.prod.ts b/src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.prod.ts
new file mode 100644 (file)
index 0000000..3612073
--- /dev/null
@@ -0,0 +1,3 @@
+export const environment = {
+  production: true
+};
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.ts b/src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.ts
new file mode 100644 (file)
index 0000000..b7f639a
--- /dev/null
@@ -0,0 +1,8 @@
+// The file contents for the current environment will overwrite these during build.
+// The build system defaults to the dev environment which uses `environment.ts`, but if you do
+// `ng build --env=prod` then `environment.prod.ts` will be used instead.
+// The list of which env maps to which file can be found in `.angular-cli.json`.
+
+export const environment = {
+  production: false
+};
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/favicon.ico b/src/pybind/mgr/dashboard_v2/frontend/src/favicon.ico
new file mode 100644 (file)
index 0000000..90e538b
Binary files /dev/null and b/src/pybind/mgr/dashboard_v2/frontend/src/favicon.ico differ
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/index.html b/src/pybind/mgr/dashboard_v2/frontend/src/index.html
new file mode 100644 (file)
index 0000000..e0db572
--- /dev/null
@@ -0,0 +1,25 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>Ceph</title>
+  <base href="/">
+
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="icon" type="image/x-icon" href="favicon.ico">
+</head>
+<body>
+  <noscript>
+    <div class="noscript container"
+         ng-if="false">
+      <div class="jumbotron alert alert-danger">
+        <h2>JavaScript required!</h2>
+        <p>A browser with JavaScript enabled is required in order to use this service.</p>
+        <p>When using Internet Explorer, please check your security settings and add this address to your trusted sites.</p>
+      </div>
+    </div>
+  </noscript>
+
+  <oa-root></oa-root>
+</body>
+</html>
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/main.ts b/src/pybind/mgr/dashboard_v2/frontend/src/main.ts
new file mode 100644 (file)
index 0000000..91ec6da
--- /dev/null
@@ -0,0 +1,12 @@
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+  enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+  .catch(err => console.log(err));
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss b/src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss
new file mode 100755 (executable)
index 0000000..6810369
--- /dev/null
@@ -0,0 +1,1323 @@
+/*
+  Basics
+  Branding
+  Breadcrumb
+  Buttons
+  Dropdown
+  Grid
+  Modal
+  Navbar
+  Navs
+  Notifications
+  Pagination
+  Panel
+  Table
+  Typo
+
+  Login
+  Statistics
+
+  ApiRecorder
+  Caret
+  Datatables
+  Feedback
+  FlexElement
+  Grafana
+  Graph
+  Progressbar
+  TagForm
+  Trees
+  CSS Fix
+/*
+
+/* Basics */
+html {
+  background-color: #ffffff;
+}
+html,
+body {
+  width: 100%;
+  height: 100%;
+  font-size: 12px;
+}
+optgroup {
+  font-weight: bold;
+  font-style: italic;
+}
+option {
+  font-weight: normal;
+  font-style: normal;
+}
+.full-height {
+  height: 100%;
+}
+.vertical-align {
+  display: flex;
+  align-items: center;
+}
+.loading {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+}
+.bg-color-darken {
+  background-color: #404040!important;
+}
+.bg-color-greenLight {
+  background-color: #71843f!important;
+}
+.bg-color-red {
+  background-color: #a90329!important;
+}
+.no-margin {
+  margin: 0;
+}
+.margin-left-md {
+  margin-left: 15px
+}
+.margin-right-md {
+  margin-right: 15px
+}
+.margin-right-sm {
+  margin-right: 10px
+}
+.margin-bottom-md {
+  margin-bottom: 15px
+}
+.no-padding {
+  padding: 0;
+}
+.small-padding {
+  padding: 5px;
+}
+.no-border {
+  border: 0px;
+  box-shadow: 0px 0px 0px !important;
+}
+.no-wrap {
+  white-space: nowrap;
+}
+.strikethrough {
+  text-decoration: line-through;
+}
+
+.italic {
+  font-style: italic;
+}
+
+/* Branding */
+.navbar-openattic .navbar-brand,
+.navbar-openattic .navbar-brand:hover{
+  color: #ececec;
+  height: auto;
+  margin: 15px 0 15px 20px;
+  padding: 0;
+  -webkit-align-self: flex-start;
+  align-self: flex-start;
+}
+.navbar-openattic .navbar-brand>img {
+  height: 25px;
+}
+
+/* Breadcrumb */
+.breadcrumb {
+  padding: 8px 0;
+  background-color: transparent;
+  border-radius: 0;
+}
+.breadcrumb>li+li:before {
+  padding: 0 5px 0 7px;
+  color: #474544;
+  font-family: "FontAwesome";
+  content: "\f101";
+}
+.breadcrumb>li>span {
+  color: #474544;
+}
+
+/* Icons */
+.icon-warning {
+  color: #f0ad4e;
+}
+.icon-danger {
+  color: #c9302c;
+}
+
+/* Buttons */
+.btn-openattic {
+  color: #ececec;
+  background-color: #288cea;
+  border-color: #288cea;
+}
+.btn-primary {
+  color: #ececec;
+  background-color: #288CEA;
+  border-color: #2172bf;
+}
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  color: #ececec;
+  background-color: #2582D9;
+  border-color: #2172bf;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  background-image: none;
+}
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+  background-color: #288CEA;
+  border-color: #2172bf;
+}
+.btn-primary .badge {
+  color: #288CEA;
+  background-color: #ececec;
+}
+.btn-primary .caret {
+  color: #ececec;
+}
+.btn-group>.btn>i.fa,
+button.btn.btn-label>i.fa {
+  /** Add space between icon and text */
+  padding-right: 5px;
+}
+
+/* Dropdown */
+.dropdown-menu {
+  min-width: 50px;
+}
+.dropdown-menu>li>a {
+  color: #474544;
+  cursor: pointer;
+}
+.dropdown-menu>li>a>i.fa {
+  /** Add space between icon and text */
+  padding-right: 5px;
+}
+.dropdown-menu>.active>a {
+  color: #ececec;
+  background-color: #288cea;
+}
+.dataTables_wrapper .dropdown-menu>li.divider {
+  cursor: auto;
+}
+
+/* Grid */
+.container,
+.container-fluid {
+  padding-left: 30px;
+  padding-right: 30px;
+}
+.row {
+  margin-left: -30px;
+  margin-right: -30px;
+}
+.col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9,
+.col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9,
+.col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9,
+.col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 {
+  padding-left: 30px;
+  padding-right: 30px;
+}
+
+/* Modal */
+.modal-dialog {
+  margin: 30px auto !important;
+}
+.modal .modal-content .openattic-modal-header,
+.modal .modal-content .openattic-modal-content,
+.modal .modal-content .openattic-modal-footer {
+  padding: 10px 20px;
+}
+.modal .modal-content .openattic-modal-header {
+  border-bottom: 1px solid #cccccc;
+  border-radius: 5px 5px 0 0;
+  background-color: #f5f5f5;
+}
+.modal .modal-content .openattic-modal-content {
+  padding: 20px 20px 10px 20px;
+  overflow-x: auto;
+  max-height: 70vh;
+}
+.modal .modal-content .openattic-modal-content p {
+  margin-bottom: 10px;
+}
+.modal .modal-content .openattic-modal-content legend {
+  font-size: 1.833em;
+}
+.modal .modal-content .openattic-modal-footer {
+  border-top: 1px solid #cccccc;
+  border-radius: 0 0 5px 5px;
+  background-color: #f5f5f5;
+}
+.modal .modal-content .openattic-modal-header span {
+  display: block;
+  font-size: 16px; /* Same as .panel-title */
+}
+
+/* Modal Table (Task Queue) */
+table.task-queue-table thead {
+  display: flex;
+  flex-flow: row;
+}
+table.task-queue-table thead tr {
+  display: flex;
+  align-items: stretch;
+  width: 100%;
+}
+table.task-queue-table tbody {
+  display: flex;
+  flex-flow: row wrap;
+}
+table.task-queue-table tbody tr {
+  display: flex;
+  width: 100%
+}
+table.task-queue-table > * > tr > * {
+  flex: 1;
+}
+table.task-queue-table > * > tr > .oadatatablecheckbox {
+  flex: 0;
+}
+div.task-queue-modal-content {
+  height: 40em;
+}
+div.openattic-modal-content div.modal-scroll {
+  max-height: 26em;
+  overflow: auto;
+  border-bottom: 1px solid #e1e1e1;
+}
+div.task-queue-modal-content div.dataTables_wrapper {
+  margin-bottom: 0;
+}
+div.task-queue-modal-content div.dataTables_wrapper th.oadatatablecheckbox {
+  width: 100%;
+}
+div.task-queue-modal-content div.dataTables_wrapper div.widget-toolbar.tc_refreshBtn{
+  width: 36px;
+}
+ul.task-queue-pagination {
+  display: table;
+  margin: auto;
+  padding-top: 10px;
+}
+
+/* Navs */
+.nav-tabs {
+  margin-bottom: 15px;
+}
+.nav-tabs-openattic {
+  margin-top: -15px;
+  margin-bottom: 15px;
+}
+.nav-tabs-openattic>li>a {
+  padding: 7px 15px 4px 15px;
+}
+.nav-tabs-openattic>li.active>a,
+.nav-tabs-openattic>li.active>a:active,
+.nav-tabs-openattic>li.active>a:focus,
+.nav-tabs-openattic>li.active>a:hover {
+  border: 0!important;
+  border-bottom: 3px solid #288cea!important;
+}
+
+/* Notifications */
+#toasty .toast.toasty-theme-bootstrap {
+  opacity: 1
+}
+
+/* Pagination */
+.pagination {
+  display: block;
+  margin: 0;
+}
+.pagination>.disabled>a,
+.pagination>.disabled>a:focus,
+.pagination>.disabled>a:hover,
+.pagination>.disabled>span,
+.pagination>.disabled>span:focus,
+.pagination>.disabled>span:hover {
+  -webkit-box-shadow: none;
+  box-shadow: none;
+  cursor: not-allowed;
+  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%);
+}
+.pagination>.active>a,
+.pagination>.active>a:focus,
+.pagination>.active>a:hover,
+.pagination>.active>span,
+.pagination>.active>span:focus,
+.pagination>.active>span:hover,
+.pagination>.disabled>a,
+.pagination>.disabled>a:focus,
+.pagination>.disabled>a:hover,
+.pagination>.disabled>span,
+.pagination>.disabled>span:focus,
+.pagination>.disabled>span:hover,
+.pagination>li>a,
+.pagination>li>span,
+.panel-group
+.panel-heading {
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);
+}
+.pagination>li>a,
+.pagination>li>span {
+  background-color: #eee;
+  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%);
+  border-color: #b7b7b7;
+  color: #4d5258;
+  cursor: pointer;
+  font-weight: 600;
+  padding: 2px 10px;
+}
+.pagination>.active>span,
+.pagination>.active>span:focus,
+.pagination>.active>span:hover {
+  color: #288cea;
+  border-color: #fff #e1e1e1 #f4f4f4;
+  border-width: 0 1px;
+}
+
+/* Panel */
+.panel .panel-toolbar {
+  float: right;
+}
+.panel .panel-toolbar div {
+  display: inline-block;
+}
+.panel .panel-toolbar>a,
+.panel .panel-toolbar>.dropdown>a {
+  padding-left: 5px;
+}
+.panel-dashboard {
+  height: 100%;
+  padding-top: 60px;
+}
+.panel-dashboard>.panel-heading {
+  cursor: move;
+  position: relative;
+  margin-top: -60px;
+  width: 100%;
+}
+.panel-dashboard>.panel-heading>.panel-title {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  -o-text-overflow: ellipsis;
+}
+.panel-dashboard>.panel-heading>.toolbar a {
+  text-decoration: none;
+}
+.panel-dashboard>.panel-body {
+  height: 100%;
+  overflow: auto;
+}
+.panel-dashboard>.panel-body .indent {
+  margin-top: 10px;
+  margin-left: 10px;
+}
+.panel-dashboard .overlay {
+  position: absolute;
+  bottom: 5px;
+  right: 5px;
+  z-index: 10;
+}
+.panel-dashboard .max-height {
+  height: 100%;
+}
+.panel-dashboard .max-height.alert-is-shown {
+  height: 85%;
+}
+.panel-dashboard .fa-2x{
+  vertical-align: middle;
+  margin-right: 0.5em;
+}
+.panel-dashboard .alert.bottom-margin-zero {
+  margin-bottom: 0;
+}
+.panel-openattic {
+  border: 1px solid #d1d1d1;
+  border-top: 0;
+  border-radius: 0;
+}
+.panel-openattic>.panel-heading {
+  border-top: 2px solid #288cea;
+  border-radius: 0;
+  padding: 20px 15px;
+}
+.panel-openattic>.panel-heading>.panel-title {
+  color: #333333;
+  font-size: 1.333em;
+  margin: 0;
+  padding: 0;
+}
+.panel-openattic>.panel-body {
+  background: #ffffff;
+  border-top: 1px solid #d1d1d1;
+  padding: 10px 15px;
+}
+.panel-openattic>.panel-footer {
+  background: #ffffff;
+  border-top: 1px solid #d1d1d1;
+}
+
+/* Table */
+table.datatable {
+  margin-bottom: 0;
+  max-width: none!important
+}
+table.dataTable thead .sorting_asc,
+table.dataTable thead .sorting_desc {
+  color: #288cea;
+}
+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;
+}
+
+/* Typo */
+a {
+  color: #288cea;
+}
+a:hover,
+a:focus{
+  color: #474544;
+}
+h1 {
+  letter-spacing: -1px;
+  font-size: 2em;
+}
+h2 {
+  letter-spacing: -1px;
+  font-size: 1.833em;
+}
+h3{
+  display: block;
+  font-size: 1.583em;
+  font-weight: 400;
+}
+h3.sub-title {
+  color: #666666;
+  margin-left: 15px;
+}
+h4{
+  font-size: 1.5em;
+  line-height: normal
+}
+h5{
+  font-size: 1.417em;
+  font-weight: 300;
+  line-height: normal;
+}
+h6{
+  font-size: 1.25em;
+  font-weight: 700;
+  line-height: normal;
+}
+
+/*************************************************************/
+
+/* Login */
+.login {
+  height: 100%;
+}
+.login .utility-bar {
+  position: absolute;
+  margin: 0;
+  padding: 0;
+  top: 10px;
+  right: 10px;
+}
+.login .utility-bar>a {
+  color: #ececec;
+  line-height: 1;
+  padding: 10px 20px;
+  position: relative;
+  display: block;
+  text-decoration: none;
+}
+.login .utility-bar>a:focus,
+.login .utility-bar>a:hover {
+  color: #ffffff;
+}
+.login .utility-bar>a:hover {
+  background-color: #505050;
+}
+.login .open>a,
+.login .open>a:hover,
+.login .open>a:focus {
+  color: #ececec;
+  border-color: transparent;
+  background-color: transparent;
+}
+.login .utility-bar .dropdown-menu {
+  left: auto;
+  right: 0;
+}
+.login .icon-bar {
+  position: absolute;
+  margin: 0;
+  padding: 0;
+  bottom: 10px;
+  left: 10px;
+}
+.login .icon-bar>a {
+  color: #ececec;
+  text-decoration: none;
+}
+.login .icon-bar>a .fa-dark {
+  color: #474544;
+}
+.login .row {
+  color: #ececec;
+  background-color: #474544;
+}
+.login h1 {
+  margin-top: 0;
+  margin-bottom: 30px;
+}
+.login .btn-password,
+.login .form-control {
+  color: #ececec;
+  background-color: #555555;
+}
+.login .col-lg-1, .login .col-lg-10, .login .col-lg-11, .login .col-lg-12, .login .col-lg-2, .login .col-lg-3,
+.login .col-lg-4, .login .col-lg-5, .login .col-lg-6, .login .col-lg-7, .login .col-lg-8, .login .col-lg-9 {
+  padding-left: 35px;
+  padding-right: 35px;
+}
+.login .col-md-1, .login .col-md-10, .login .col-md-11, .login .col-md-12, .login .col-md-2, .login .col-md-3,
+.login .col-md-4, .login .col-md-5, .login .col-md-6, .login .col-md-7, .login .col-md-8, .login .col-md-9 {
+  padding-left: 30px;
+  padding-right: 30px;
+}
+.login .col-sm-1, .login .col-sm-10, .login .col-sm-11, .login .col-sm-12, .login .col-sm-2, .login .col-sm-3,
+.login .col-sm-4, .login .col-sm-5, .login .col-sm-6, .login .col-sm-7, .login .col-sm-8, .login .col-sm-9 {
+  padding-left: 25px;
+  padding-right: 25px;
+}
+.login .col-xs-1, .col-xs-10, .login .col-xs-11, .login .col-xs-12, .login .col-xs-2, .login .col-xs-3,
+.login .col-xs-4, .login .col-xs-5, .login .col-xs-6, .login .col-xs-7, .login .col-xs-8, .login .col-xs-9 {
+  padding-left: 15px;
+  padding-right: 15px;
+}
+
+/* Statistics */
+.statistics-content {
+  margin: 0 -20px;
+}
+
+/*************************************************************/
+
+/* ApiRecorder */
+.apirecorder {
+  resize: none;
+  width:100%;
+}
+.apirecorder-enabled {
+  color: red;
+}
+
+/* Caret */
+.caret {
+  color: #288cea;
+}
+
+/* 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;
+  top: 50%;
+  right: 0;
+  padding: 2px 16px;
+  cursor: pointer;
+  color: #ffffff;
+  font-size: 1.2em;
+  font-weight: 700;
+  background-color: #288cea;
+  border-radius: 5px 5px 0 0;
+  z-index: 99999;
+}
+#feedback .feedback-button:hover {
+  background-color: #2172bf;
+}
+#feedback .feedback-button-transform {
+  -webkit-transform: rotate(-90deg) translate(50%, -100%);
+  -moz-transform: rotate(-90deg) translate(50%, -100%);
+  -ms-transform: rotate(-90deg) translate(50%, -100%);
+  -o-transform: rotate(-90deg) translate(50%, -100%);
+  transform: rotate(-90deg) translate(50%, -100%);
+  -webkit-transform-origin: top right;
+  -moz-transform-origin: top right;
+  -ms-transform-origin: top right;
+  -o-transform-origin: top right;
+  transform-origin: top right;
+}
+#feedback .feedback-button-active {
+  right: 299px;
+}
+#feedback .feedback-button .fa,
+#feedback .feedback-button .glyphicon{
+  padding-right: 6px;
+}
+#feedback .feedback-panel {
+  position: fixed;
+  top: 0;
+  right: -300px;
+  padding: 20px;
+  width: 300px;
+  height: 100%;
+  background-color: #ffffff;
+  border-left: 5px solid #288cea;
+  z-index: 99999;
+  overflow-y: auto;
+}
+#feedback .feedback-panel-active {
+  right: 0;
+}
+#feedback .feedback-transition {
+  transition: right 150ms cubic-bezier(0.0, 0.0, 0.2, 1);
+}
+
+/* FlexElement */
+/* Container */
+.flex-container {
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+}
+.flex-wrap {
+  -webkit-flex-wrap: wrap;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+}
+.flex-nowrap {
+  -webkit-flex-wrap: nowrap;
+  -ms-flex-wrap: nowrap;
+  flex-wrap: nowrap;
+}
+.flex-row {
+  -webkit-flex-direction: row;
+  -ms-flex-direction: row;
+  flex-direction: row;
+}
+.flex-column {
+  -webkit-flex-direction: column;
+  -ms-flex-direction: column;
+  flex-direction: column;
+}
+/* Items */
+.flex-item {
+  margin-bottom: 10px;
+  padding: 15px;
+}
+.flex-item-1 { -webkit-flex: 1; -moz-flex: 1; -ms-flex: 1; flex: 1; padding: 0 10px; }
+.flex-item-2 { -webkit-flex: 2; -moz-flex: 2; -ms-flex: 2; flex: 2; padding: 0 10px; }
+.flex-item-3 { -webkit-flex: 3; -moz-flex: 3; -ms-flex: 3; flex: 3; padding: 0 10px; }
+.flex-item-4 { -webkit-flex: 4; -moz-flex: 4; -ms-flex: 4; flex: 4; padding: 0 10px; }
+.flex-item-5 { -webkit-flex: 5; -moz-flex: 5; -ms-flex: 5; flex: 5; padding: 0 10px; }
+.flex-item-6 { -webkit-flex: 6; -moz-flex: 6; -ms-flex: 6; flex: 6; padding: 0 10px; }
+.flex-item-7 { -webkit-flex: 7; -moz-flex: 7; -ms-flex: 7; flex: 7; padding: 0 10px; }
+.flex-item-8 { -webkit-flex: 8; -moz-flex: 8; -ms-flex: 8; flex: 8; padding: 0 10px; }
+.flex-item-9 { -webkit-flex: 9; -moz-flex: 9; -ms-flex: 9; flex: 9; padding: 0 10px; }
+
+/* Grafana */
+.grafana-container {
+  margin-top: 20px;
+  height: 64px;
+  background:url(./assets/loading.gif) center center no-repeat;
+}
+.grafana {
+  width: 100%;
+  min-height: 600px;
+}
+
+/* Progressbar */
+.progress-bar {
+  background-image: none !important;
+}
+.progress-bar-info {
+  background-color: #288cea;
+}
+.progress-bar-freespace {
+  background-color: #ddd;
+}
+.progress-bar-stolenspace {
+  background-color: #aaa;
+}
+.progress-bar-outer{
+  margin-top: 5px !important;
+}
+.progress-bar-outer div {
+  border-radius: 31px;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  box-shadow: 0 0 0 0;
+  -webkit-box-shadow: 0 0 0 0;
+  -moz-box-shadow: 0 0 0 0;
+  margin: 0;
+  height: 16px;
+}
+.progress-bar-outer div div {
+  background-color: #0091d9;
+}
+.progress-bar-outer div div span {
+  position: relative;
+  top: -3px;
+}
+.oaprogress {
+  position: relative;
+  margin-bottom: 0;
+}
+.oaprogress div.progress-bar {
+  position: static;
+}
+.oaprogress span {
+  position: absolute;
+  display: block;
+  width: 100%;
+  color: black;
+  font-weight: normal;
+}
+
+tags-input .tags {
+  border-radius: 4px;
+  border: 1px solid #ccc;
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+}
+
+/* TagForm */
+.tag-form label {
+  display: block;
+  margin-bottom: 6px;
+  line-height: 19px;
+  font-weight: 400;
+  font-size: 13px;
+  color: #333;
+  text-align: left;
+  white-space: normal;
+}
+
+/* Trees */
+.tree {
+  min-height: 20px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.tree>ul {
+  padding-left: 0;
+}
+.tree ul ul {
+  padding-left: 34px;
+  padding-top: 10px;
+}
+.tree li {
+  list-style-type: none;
+  margin: 0;
+  padding: 5px;
+  position: relative;
+}
+.tree li span {
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  border: 1px dotted #999;
+  border-radius: 5px;
+  display: inline-block;
+  padding: 3px 8px;
+  text-decoration: none;
+  -webkit-transition: color .2s ease .1s,background-color .2s ease .1s,border-color .3s ease .2s;
+  -moz-transition: color .2s ease .1s,background-color .2s ease .1s,border-color .3s ease .2s;
+  -o-transition: color .2s ease .1s,background-color .2s ease .1s,border-color .3s ease .2s;
+  transition: color .2s ease .1s,background-color .2s ease .1s,border-color .3s ease .2s;
+}
+.tree>ul>li::after,
+.tree>ul>li:before {
+  border: 0;
+}
+.tree li:after,
+.tree li:before {
+  content: '';
+  left: -20px;
+  position: absolute;
+  right: auto;
+}
+.tree li:before {
+  border-left: 1px solid #999;
+  bottom: 50px;
+  height: 100%;
+  top: -11px;
+  width: 1px;
+  -webkit-transition: "border-color 0.1s ease 0.1s";
+  -moz-transition: "border-color 0.1s ease 0.1s";
+  -o-transition: "border-color 0.1s ease 0.1s";
+  transition: "border-color 0.1s ease 0.1s";
+}
+.tree li:after {
+  border-top: 1px solid #999;
+  height: 20px;
+  top: 18px;
+  width: 25px;
+}
+.tree li:last-child::before {
+  height: 30px;
+}
+
+.scrollable-menu {
+  height: auto;
+  max-height: 200px;
+  overflow-x: hidden;
+}
+
+.toggle, .toggle-on, .toggle-off {
+  border-radius: 20px;
+}
+
+.toggle .toggle-handle {
+  border-radius: 20px;
+}
+
+/* CSS Fix */
+a {
+  cursor: pointer;
+}
+form .input-group-addon {
+  color: #a2a2a2 !important;
+  background-color: transparent;
+}
+uib-accordion .panel-title,
+.panel .accordion-title {
+  font-size: 14px !important;
+}
+.panel-body h2:first-child {
+  margin-top: 0;
+}
+.actions {
+  padding-bottom: 10px;
+}
+.pull-left {
+  float: left;
+}
+.code-clogs {
+  display: block;
+  padding: 9px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.42857143;
+  color: #333;
+  word-break: break-all;
+  word-wrap: break-word;
+  background-color: #f5f5f5;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
+}
+.degree-sign:after {
+  content: "\00B0 C"!important;
+}
+.formactions.well {
+  overflow: auto;
+  padding: 10px 20px;
+}
+.disabled {
+  pointer-events: none;
+}
+.clickable {
+  cursor: pointer;
+}
+.non-clickable {
+  cursor: initial;
+}
+.locked {
+  cursor: default!important;
+}
+.list-nomargin {
+  margin: 0;
+}
+
+.has-error .has-error-btn {
+  background-color: #f2dede;
+  border-color: #a94442;
+}
+
+.has-error .has-error-btn:disabled:hover {
+  background-color: #f2dede;
+  border-color: #a94442;
+}
+
+/* If javascript is disabled. */
+.noscript {
+  padding-top: 5em;
+}
+.noscript p {
+  color: #777;
+}
+
+/* Notifications */
+
+.notification div.img-circle {
+  width: 50px;
+  height: 50px;
+  position: relative;
+}
+.notification.info div.img-circle {
+  background-color: #5bc0de;
+}
+.notification.error div.img-circle {
+  background-color: #d9534f;
+}
+.notification.success div.img-circle {
+  background-color: #5cb85c;
+}
+.notification.warning div.img-circle {
+  background-color: #f0ad4e;
+}
+
+.notification .icon {
+  background-repeat: no-repeat;
+  background-image: url('./assets/notification-icons.png') !important;
+  height: 36px;
+  width: 36px;
+  position: absolute;
+  margin: 7px;
+}
+.notification.info .icon {
+  background-position: -36px 0;
+}
+.notification.error .icon {
+  background-position: -108px 0;
+}
+.notification.success .icon {
+  background-position: 0 0;
+}
+.notification.warning .icon {
+  background-position: -72px 0;
+}
+
+.required {
+  color: #d04437;
+}
+
+/* oa-helper  */
+oa-helper i {
+  color: #288cea;
+  cursor: pointer;
+}
+
+.page-footer {
+  font-size: 12px;
+  color: #777;
+  text-align: center;
+  margin-left: 150px;
+  margin-right: 150px;
+  margin-top: 50px;
+  margin-bottom: 50px;
+}
+
+hr.oa-hr-small {
+  margin-top: 5px;
+  margin-bottom: 5px;
+}
+
+.table>thead>tr>th.rbd-striping-object{
+  min-width: 60px;
+}
+.table>thead>tr>th.rbd-striping-stripe {
+  min-width: 100px;
+}
+.rbd-striping-column-separator {
+  width: 1px;
+}
+
+.table>tbody>tr>td.rbd-striping-cell-top {
+  border-top: 1px solid #ccc;
+  border-left: 1px solid #ccc;
+  border-right: 1px solid #ccc;
+}
+.table>tbody>tr>td.rbd-striping-cell-center {
+  border-top: 1px dashed #ccc;
+  border-left: 1px solid #ccc;
+  border-right: 1px solid #ccc;
+}
+.table>tbody>tr>td.rbd-striping-cell-bottom {
+  border-bottom: 1px solid #ccc;
+  border-left: 1px solid #ccc;
+  border-right: 1px solid #ccc;
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/polyfills.ts b/src/pybind/mgr/dashboard_v2/frontend/src/polyfills.ts
new file mode 100644 (file)
index 0000000..20d4075
--- /dev/null
@@ -0,0 +1,76 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ *      file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+
+/** Evergreen browsers require these. **/
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
+import 'core-js/es7/reflect';
+
+
+/**
+ * Required to support Web Animations `@angular/platform-browser/animations`.
+ * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
+ **/
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
+
+
+
+/***************************************************************************************************
+ * Zone JS is required by Angular itself.
+ */
+import 'zone.js/dist/zone';  // Included with Angular CLI.
+
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
+
+/**
+ * Date, currency, decimal and percent pipes.
+ * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
+ */
+// import 'intl';  // Run `npm install --save intl`.
+/**
+ * Need to import at least one locale-data with intl.
+ */
+// import 'intl/locale-data/jsonp/en';
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/styles.scss b/src/pybind/mgr/dashboard_v2/frontend/src/styles.scss
new file mode 100644 (file)
index 0000000..c10c1ee
--- /dev/null
@@ -0,0 +1,2 @@
+/* You can add global styles to this file, and also import other style files */
+@import './openattic-theme.scss';
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/test.ts b/src/pybind/mgr/dashboard_v2/frontend/src/test.ts
new file mode 100644 (file)
index 0000000..cd612ee
--- /dev/null
@@ -0,0 +1,32 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/long-stack-trace-zone';
+import 'zone.js/dist/proxy.js';
+import 'zone.js/dist/sync-test';
+import 'zone.js/dist/jasmine-patch';
+import 'zone.js/dist/async-test';
+import 'zone.js/dist/fake-async-test';
+import { getTestBed } from '@angular/core/testing';
+import {
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
+declare const __karma__: any;
+declare const require: any;
+
+// Prevent Karma from running prematurely.
+__karma__.loaded = function () {};
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
+// Finally, start Karma to run the tests.
+__karma__.start();
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.app.json b/src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.app.json
new file mode 100644 (file)
index 0000000..39ba8db
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/app",
+    "baseUrl": "./",
+    "module": "es2015",
+    "types": []
+  },
+  "exclude": [
+    "test.ts",
+    "**/*.spec.ts"
+  ]
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.spec.json b/src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.spec.json
new file mode 100644 (file)
index 0000000..63d89ff
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/spec",
+    "baseUrl": "./",
+    "module": "commonjs",
+    "target": "es5",
+    "types": [
+      "jasmine",
+      "node"
+    ]
+  },
+  "files": [
+    "test.ts"
+  ],
+  "include": [
+    "**/*.spec.ts",
+    "**/*.d.ts"
+  ]
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/typings.d.ts b/src/pybind/mgr/dashboard_v2/frontend/src/typings.d.ts
new file mode 100644 (file)
index 0000000..ef5c7bd
--- /dev/null
@@ -0,0 +1,5 @@
+/* SystemJS module definition */
+declare var module: NodeModule;
+interface NodeModule {
+  id: string;
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/tsconfig.json b/src/pybind/mgr/dashboard_v2/frontend/tsconfig.json
new file mode 100644 (file)
index 0000000..a6c016b
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "compileOnSave": false,
+  "compilerOptions": {
+    "outDir": "./dist/out-tsc",
+    "sourceMap": true,
+    "declaration": false,
+    "moduleResolution": "node",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "target": "es5",
+    "typeRoots": [
+      "node_modules/@types"
+    ],
+    "lib": [
+      "es2017",
+      "dom"
+    ]
+  }
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/tslint.json b/src/pybind/mgr/dashboard_v2/frontend/tslint.json
new file mode 100644 (file)
index 0000000..97aef52
--- /dev/null
@@ -0,0 +1,142 @@
+{
+  "rulesDirectory": [
+    "node_modules/codelyzer"
+  ],
+  "rules": {
+    "arrow-return-shorthand": true,
+    "callable-types": true,
+    "class-name": true,
+    "comment-format": [
+      true,
+      "check-space"
+    ],
+    "curly": true,
+    "eofline": true,
+    "forin": true,
+    "import-blacklist": [
+      true,
+      "rxjs",
+      "rxjs/Rx"
+    ],
+    "import-spacing": true,
+    "indent": [
+      true,
+      "spaces"
+    ],
+    "interface-over-type-literal": true,
+    "label-position": true,
+    "max-line-length": [
+      true,
+      140
+    ],
+    "member-access": false,
+    "member-ordering": [
+      true,
+      {
+        "order": [
+          "static-field",
+          "instance-field",
+          "static-method",
+          "instance-method"
+        ]
+      }
+    ],
+    "no-arg": true,
+    "no-bitwise": true,
+    "no-console": [
+      true,
+      "debug",
+      "info",
+      "time",
+      "timeEnd",
+      "trace"
+    ],
+    "no-construct": true,
+    "no-debugger": true,
+    "no-duplicate-super": true,
+    "no-empty": false,
+    "no-empty-interface": true,
+    "no-eval": true,
+    "no-inferrable-types": [
+      true,
+      "ignore-params"
+    ],
+    "no-misused-new": true,
+    "no-non-null-assertion": true,
+    "no-shadowed-variable": true,
+    "no-string-literal": false,
+    "no-string-throw": true,
+    "no-switch-case-fall-through": true,
+    "no-trailing-whitespace": true,
+    "no-unnecessary-initializer": true,
+    "no-unused-expression": true,
+    "no-use-before-declare": true,
+    "no-var-keyword": true,
+    "object-literal-sort-keys": false,
+    "one-line": [
+      true,
+      "check-open-brace",
+      "check-catch",
+      "check-else",
+      "check-whitespace"
+    ],
+    "prefer-const": true,
+    "quotemark": [
+      true,
+      "single"
+    ],
+    "radix": true,
+    "semicolon": [
+      true,
+      "always"
+    ],
+    "triple-equals": [
+      true,
+      "allow-null-check"
+    ],
+    "typedef-whitespace": [
+      true,
+      {
+        "call-signature": "nospace",
+        "index-signature": "nospace",
+        "parameter": "nospace",
+        "property-declaration": "nospace",
+        "variable-declaration": "nospace"
+      }
+    ],
+    "typeof-compare": true,
+    "unified-signatures": true,
+    "variable-name": true,
+    "whitespace": [
+      true,
+      "check-branch",
+      "check-decl",
+      "check-operator",
+      "check-separator",
+      "check-type"
+    ],
+    "directive-selector": [
+      true,
+      "attribute",
+      "oa",
+      "camelCase"
+    ],
+    "component-selector": [
+      true,
+      "element",
+      "oa",
+      "kebab-case"
+    ],
+    "angular-whitespace": [true, "check-interpolation"],
+    "no-output-on-prefix": true,
+    "use-input-property-decorator": true,
+    "use-output-property-decorator": true,
+    "use-host-property-decorator": true,
+    "no-input-rename": true,
+    "no-output-rename": true,
+    "use-life-cycle-interface": true,
+    "use-pipe-transform-interface": true,
+    "component-class-suffix": true,
+    "directive-class-suffix": true
+  }
+}
index 4029d86ead43a21291132c6d9daebe1cd6e676cc..ab027bb40d979aa66431fd886b08eeea665dff50 100644 (file)
@@ -58,7 +58,18 @@ class Module(MgrModule):
         cherrypy.tools.autenticate = cherrypy.Tool('before_handler',
                                                    Auth.check_auth)
 
+        current_dir = os.path.dirname(os.path.abspath(__file__))
+        fe_dir = os.path.join(current_dir, 'frontend/dist')
+        config = {
+            '/': {
+                "tools.staticdir.on": True,
+                'tools.staticdir.dir': fe_dir,
+                'tools.staticdir.index': "index.html"
+            }
+        }
+
         cherrypy.tree.mount(Module.ApiRoot(self), "/api")
+        cherrypy.tree.mount(Module.StaticRoot(), '/', config=config)
         cherrypy.engine.start()
         self.log.info("Waiting for engine...")
         cherrypy.engine.block()
@@ -88,3 +99,6 @@ class Module(MgrModule):
                                 .format(ctrl.__name__, ctrl._cp_path_))
                 ins = ctrl()
                 setattr(Module.ApiRoot, ctrl._cp_path_, ins)
+
+    class StaticRoot(object):
+        pass