From 1f21d68f1b4af11ff18996ce7b5c4d3fe68933b5 Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Mon, 22 Jan 2018 14:51:58 +0000 Subject: [PATCH] mgr/dashboard_v2: add initial frontend code This contains the angular boilerplate, some initial structure and the navigation component. Signed-off-by: Tiago Melo --- src/pybind/mgr/dashboard_v2/README.rst | 3 + .../dashboard_v2/frontend/.angular-cli.json | 61 + .../mgr/dashboard_v2/frontend/.editorconfig | 13 + .../mgr/dashboard_v2/frontend/.gitignore | 50 + .../mgr/dashboard_v2/frontend/README.md | 52 + .../dashboard_v2/frontend/e2e/app.e2e-spec.ts | 14 + .../mgr/dashboard_v2/frontend/e2e/app.po.ts | 11 + .../frontend/e2e/tsconfig.e2e.json | 14 + .../mgr/dashboard_v2/frontend/karma.conf.js | 33 + .../mgr/dashboard_v2/frontend/package.json | 51 + .../dashboard_v2/frontend/protractor.conf.js | 28 + .../frontend/src/app/app-routing.module.ts | 10 + .../frontend/src/app/app.component.html | 5 + .../frontend/src/app/app.component.scss | 0 .../frontend/src/app/app.component.spec.ts | 30 + .../frontend/src/app/app.component.ts | 10 + .../frontend/src/app/app.module.ts | 26 + .../frontend/src/app/ceph/ceph.module.ts | 10 + .../src/app/core/core-routing.module.ts | 10 + .../frontend/src/app/core/core.module.ts | 15 + .../app/core/navigation/navigation.module.ts | 12 + .../navigation/navigation.component.html | 92 ++ .../navigation/navigation.component.scss | 143 ++ .../navigation/navigation.component.spec.ts | 25 + .../navigation/navigation.component.ts | 15 + .../frontend/src/app/shared/shared.module.ts | 10 + .../dashboard_v2/frontend/src/assets/.gitkeep | 0 ...Ceph_Logo_Standard_RGB_White_120411_fa.png | Bin 0 -> 4801 bytes .../frontend/src/assets/loading.gif | Bin 0 -> 35386 bytes .../frontend/src/assets/logo-mini.png | Bin 0 -> 1811 bytes .../src/assets/notification-icons.png | Bin 0 -> 3397 bytes .../src/environments/environment.prod.ts | 3 + .../frontend/src/environments/environment.ts | 8 + .../mgr/dashboard_v2/frontend/src/favicon.ico | Bin 0 -> 1150 bytes .../mgr/dashboard_v2/frontend/src/index.html | 25 + .../mgr/dashboard_v2/frontend/src/main.ts | 12 + .../frontend/src/openattic-theme.scss | 1323 +++++++++++++++++ .../dashboard_v2/frontend/src/polyfills.ts | 76 + .../mgr/dashboard_v2/frontend/src/styles.scss | 2 + .../mgr/dashboard_v2/frontend/src/test.ts | 32 + .../frontend/src/tsconfig.app.json | 13 + .../frontend/src/tsconfig.spec.json | 20 + .../dashboard_v2/frontend/src/typings.d.ts | 5 + .../mgr/dashboard_v2/frontend/tsconfig.json | 19 + .../mgr/dashboard_v2/frontend/tslint.json | 142 ++ src/pybind/mgr/dashboard_v2/module.py | 14 + 46 files changed, 2437 insertions(+) create mode 100644 src/pybind/mgr/dashboard_v2/frontend/.angular-cli.json create mode 100644 src/pybind/mgr/dashboard_v2/frontend/.editorconfig create mode 100644 src/pybind/mgr/dashboard_v2/frontend/.gitignore create mode 100644 src/pybind/mgr/dashboard_v2/frontend/README.md create mode 100644 src/pybind/mgr/dashboard_v2/frontend/e2e/app.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/e2e/app.po.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/e2e/tsconfig.e2e.json create mode 100644 src/pybind/mgr/dashboard_v2/frontend/karma.conf.js create mode 100644 src/pybind/mgr/dashboard_v2/frontend/package.json create mode 100644 src/pybind/mgr/dashboard_v2/frontend/protractor.conf.js create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.html create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.scss create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/app.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/core/core-routing.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/core/core.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.scss create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/assets/.gitkeep create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/assets/Ceph_Logo_Standard_RGB_White_120411_fa.png create mode 100755 src/pybind/mgr/dashboard_v2/frontend/src/assets/loading.gif create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/assets/logo-mini.png create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/assets/notification-icons.png create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.prod.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/favicon.ico create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/index.html create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/main.ts create mode 100755 src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/polyfills.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/styles.scss create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/test.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.app.json create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.spec.json create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/typings.d.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/tsconfig.json create mode 100644 src/pybind/mgr/dashboard_v2/frontend/tslint.json diff --git a/src/pybind/mgr/dashboard_v2/README.rst b/src/pybind/mgr/dashboard_v2/README.rst index 5c85e8a636f62..5eed5a11a40c9 100644 --- a/src/pybind/mgr/dashboard_v2/README.rst +++ b/src/pybind/mgr/dashboard_v2/README.rst @@ -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 index 0000000000000..9d39081a536f6 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/.angular-cli.json @@ -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 index 0000000000000..6e87a003da89d --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/.editorconfig @@ -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 index 0000000000000..2e55dc6354ddf --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/.gitignore @@ -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 index 0000000000000..518ea551bb5f6 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/README.md @@ -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 index 0000000000000..3e98370242334 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/e2e/app.e2e-spec.ts @@ -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 index 0000000000000..d9761bb4a4e42 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/e2e/app.po.ts @@ -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 index 0000000000000..1d9e5edf09651 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/e2e/tsconfig.e2e.json @@ -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 index 0000000000000..af139fada3637 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/karma.conf.js @@ -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 index 0000000000000..e7eb975aaf20a --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/package.json @@ -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 index 0000000000000..7ee3b5ee863a7 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/protractor.conf.js @@ -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 index 0000000000000..d425c6f56b578 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts @@ -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 index 0000000000000..41d2dd69a621b --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.html @@ -0,0 +1,5 @@ + + +
+ +
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 index 0000000000000..e69de29bb2d1d 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 index 0000000000000..f99d866c4982d --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts @@ -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 index 0000000000000..3d2afc2725ea2 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.ts @@ -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 index 0000000000000..4375175cc36af --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.module.ts @@ -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 index 0000000000000..9cdda334b93ee --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts @@ -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 index 0000000000000..405e5a0ff7d20 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/core-routing.module.ts @@ -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 index 0000000000000..a738ccbe780ea --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/core.module.ts @@ -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 index 0000000000000..bbac8779babe3 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation.module.ts @@ -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 index 0000000000000..a097b052672b4 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html @@ -0,0 +1,92 @@ + 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 index 0000000000000..0f5acfd9d32c0 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.scss @@ -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 index 0000000000000..3857718160244 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts @@ -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; + + 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 index 0000000000000..00bc05b1292bd --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.ts @@ -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 index 0000000000000..fffbe5b239e85 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts @@ -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 index 0000000000000..e69de29bb2d1d 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 index 0000000000000000000000000000000000000000..0f07b83ed9a372066137b3c00e160a318200e568 GIT binary patch literal 4801 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 bOqlRrTA#MgdlfYT00000NkvXXu0mjfipE=h literal 0 HcmV?d00001 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 index 0000000000000..3612073bc31cd --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.prod.ts @@ -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 index 0000000000000..b7f639aecac5c --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/environments/environment.ts @@ -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 index 0000000000000000000000000000000000000000..90e538ba704907a6e8f5919a2664caac5d568f6d GIT binary patch literal 1150 zcmcIiO)G?96g^XKX)@(SO-d7zniPp2pu|W@`N&sRRu-)N01Hhi#w=`YX2B1zu(1;3 z7qC-4GCs;j4GWBWUvsA0(<>XE>CQdp+;i?bZ}R{V`S?D9v)}=7K}ji&uqf?is)~8ssUG-X@$$peQJ7FH1kv%76 zUNrg2`_e1@&dgt_|FnN>`uf(LNv0rm6`~h)pV=Sa#>9xPy607U@rK@{VlBZnqD8|p z+--NYcPuwiXQs1zWq`P~ZN84!_&yD;{5HFn8STVwM)U{HrN2uB|BY%Bv-fANUwPl) z{vtEV#dko?E_W}dGxDyU)qdqX)U{J{`)?jQS1fnsd3@&P#ns)tmd9c(wXUCBW>M?? u3tW=7eOdx_q + + + + Ceph + + + + + + + + + + + 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 index 0000000000000..91ec6da5f0788 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/main.ts @@ -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 index 0000000000000..6810369409cc6 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/openattic-theme.scss @@ -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 index 0000000000000..20d40751a6bc9 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/polyfills.ts @@ -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 index 0000000000000..c10c1ee98b3cd --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/styles.scss @@ -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 index 0000000000000..cd612eeb0e2f2 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/test.ts @@ -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 index 0000000000000..39ba8dbacbbe0 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.app.json @@ -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 index 0000000000000..63d89ff283f6a --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/tsconfig.spec.json @@ -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 index 0000000000000..ef5c7bd620579 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/typings.d.ts @@ -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 index 0000000000000..a6c016bf38ad7 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/tsconfig.json @@ -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 index 0000000000000..97aef5290a4ba --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/tslint.json @@ -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 + } +} diff --git a/src/pybind/mgr/dashboard_v2/module.py b/src/pybind/mgr/dashboard_v2/module.py index 4029d86ead43a..ab027bb40d979 100644 --- a/src/pybind/mgr/dashboard_v2/module.py +++ b/src/pybind/mgr/dashboard_v2/module.py @@ -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 -- 2.39.5