New web ui
This commit is contained in:
parent
e09d5cb4ec
commit
9c651ae913
105 changed files with 7314 additions and 5514 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,6 +6,7 @@
|
||||||
/traefik
|
/traefik
|
||||||
/traefik.toml
|
/traefik.toml
|
||||||
/static/
|
/static/
|
||||||
|
/webui/.tmp/
|
||||||
.vscode/
|
.vscode/
|
||||||
/site/
|
/site/
|
||||||
*.log
|
*.log
|
||||||
|
|
|
@ -14,9 +14,19 @@ type DashboardHandler struct{}
|
||||||
// AddRoutes add dashboard routes on a router
|
// AddRoutes add dashboard routes on a router
|
||||||
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
||||||
// Expose dashboard
|
// Expose dashboard
|
||||||
router.Methods(http.MethodGet).Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
router.Methods(http.MethodGet).
|
||||||
|
Path("/").
|
||||||
|
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
|
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
|
||||||
})
|
})
|
||||||
router.Methods(http.MethodGet).PathPrefix("/dashboard/").
|
|
||||||
|
router.Methods(http.MethodGet).
|
||||||
|
Path("/dashboard/status").
|
||||||
|
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
http.Redirect(response, request, "/dashboard/", 302)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Methods(http.MethodGet).
|
||||||
|
PathPrefix("/dashboard/").
|
||||||
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))
|
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))
|
||||||
}
|
}
|
||||||
|
|
63
webui/.angular-cli.json
Normal file
63
webui/.angular-cli.json
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"project": {
|
||||||
|
"name": "webui"
|
||||||
|
},
|
||||||
|
"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": "app",
|
||||||
|
"styles": [
|
||||||
|
"styles/app.sass"
|
||||||
|
],
|
||||||
|
"scripts": [
|
||||||
|
"../node_modules/@fortawesome/fontawesome/index.js",
|
||||||
|
"../node_modules/@fortawesome/fontawesome-free-solid/index.js"
|
||||||
|
],
|
||||||
|
"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": "sass",
|
||||||
|
"component": {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["es2015"]
|
|
||||||
}
|
|
|
@ -1,13 +1,13 @@
|
||||||
# http://editorconfig.org
|
# Editor configuration, see http://editorconfig.org
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
1
webui/.gitattributes
vendored
1
webui/.gitattributes
vendored
|
@ -1 +0,0 @@
|
||||||
* text=auto
|
|
48
webui/.gitignore
vendored
48
webui/.gitignore
vendored
|
@ -1,6 +1,44 @@
|
||||||
.tmp/
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
coverage/
|
|
||||||
dist/
|
|
||||||
node_modules/
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/dist-server
|
||||||
|
/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
|
||||||
|
yarn-error.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# e2e
|
||||||
|
/e2e/*.js
|
||||||
|
/e2e/*.map
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"generator-fountain-angular1": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"props": {
|
|
||||||
"resolved": "/Users/micael/Documents/zenika/fountain/generator-fountain-angular1/generators/app/index.js",
|
|
||||||
"namespace": "fountain-angular1:app",
|
|
||||||
"argv": {
|
|
||||||
"remain": [],
|
|
||||||
"cooked": [],
|
|
||||||
"original": []
|
|
||||||
},
|
|
||||||
"framework": "angular1",
|
|
||||||
"modules": "webpack",
|
|
||||||
"css": "scss",
|
|
||||||
"js": "js",
|
|
||||||
"sample": "hello",
|
|
||||||
"router": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,4 +19,4 @@ RUN yarn install
|
||||||
|
|
||||||
COPY . $WEBUI_DIR/
|
COPY . $WEBUI_DIR/
|
||||||
|
|
||||||
EXPOSE 3000 3001 8080
|
EXPOSE 8080
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
const conf = require('./gulp.conf');
|
|
||||||
|
|
||||||
module.exports = function () {
|
|
||||||
return {
|
|
||||||
server: {
|
|
||||||
baseDir: [
|
|
||||||
conf.paths.dist
|
|
||||||
]
|
|
||||||
},
|
|
||||||
open: false
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
const conf = require('./gulp.conf');
|
|
||||||
const proxy = require('http-proxy-middleware');
|
|
||||||
|
|
||||||
const apiProxy = proxy('/api', {
|
|
||||||
target: 'http://localhost:8080',
|
|
||||||
changeOrigin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const healthProxy = proxy('/health', {
|
|
||||||
target: 'http://localhost:8080',
|
|
||||||
changeOrigin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = function () {
|
|
||||||
return {
|
|
||||||
server: {
|
|
||||||
baseDir: [
|
|
||||||
conf.paths.tmp,
|
|
||||||
conf.paths.src
|
|
||||||
],
|
|
||||||
middleware: [
|
|
||||||
apiProxy,
|
|
||||||
healthProxy
|
|
||||||
]
|
|
||||||
},
|
|
||||||
open: false
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,47 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file contains the variables used in other gulp files
|
|
||||||
* which defines tasks
|
|
||||||
* By design, we only put there very generic config values
|
|
||||||
* which are used in several places to keep good readability
|
|
||||||
* of the tasks
|
|
||||||
*/
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const gutil = require('gulp-util');
|
|
||||||
|
|
||||||
exports.ngModule = 'traefik';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main paths of your project handle these with care
|
|
||||||
*/
|
|
||||||
exports.paths = {
|
|
||||||
src: 'src',
|
|
||||||
dist: '../static',
|
|
||||||
tmp: '.tmp',
|
|
||||||
e2e: 'e2e',
|
|
||||||
tasks: 'gulp_tasks'
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.path = {};
|
|
||||||
for (const pathName in exports.paths) {
|
|
||||||
if (exports.paths.hasOwnProperty(pathName)) {
|
|
||||||
exports.path[pathName] = function pathJoin() {
|
|
||||||
const pathValue = exports.paths[pathName];
|
|
||||||
const funcArgs = Array.prototype.slice.call(arguments);
|
|
||||||
const joinArgs = [pathValue].concat(funcArgs);
|
|
||||||
return path.join.apply(this, joinArgs);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common implementation for an error handler of a Gulp plugin
|
|
||||||
*/
|
|
||||||
exports.errorHandler = function (title) {
|
|
||||||
return function (err) {
|
|
||||||
gutil.log(gutil.colors.red(`[${title}]`), err.toString());
|
|
||||||
this.emit('end');
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,55 +0,0 @@
|
||||||
const conf = require('./gulp.conf');
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
|
||||||
const configuration = {
|
|
||||||
basePath: '../',
|
|
||||||
singleRun: false,
|
|
||||||
autoWatch: true,
|
|
||||||
logLevel: 'INFO',
|
|
||||||
junitReporter: {
|
|
||||||
outputDir: 'test-reports'
|
|
||||||
},
|
|
||||||
browsers: [
|
|
||||||
'PhantomJS'
|
|
||||||
],
|
|
||||||
frameworks: [
|
|
||||||
'jasmine'
|
|
||||||
],
|
|
||||||
files: [
|
|
||||||
'node_modules/es6-shim/es6-shim.js',
|
|
||||||
conf.path.src('index.spec.js'),
|
|
||||||
conf.path.src('**/*.html')
|
|
||||||
],
|
|
||||||
preprocessors: {
|
|
||||||
[conf.path.src('index.spec.js')]: [
|
|
||||||
'webpack'
|
|
||||||
],
|
|
||||||
[conf.path.src('**/*.html')]: [
|
|
||||||
'ng-html2js'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
ngHtml2JsPreprocessor: {
|
|
||||||
stripPrefix: `${conf.paths.src}/`
|
|
||||||
},
|
|
||||||
reporters: ['progress', 'coverage'],
|
|
||||||
coverageReporter: {
|
|
||||||
type: 'html',
|
|
||||||
dir: 'coverage/'
|
|
||||||
},
|
|
||||||
webpack: require('./webpack-test.conf'),
|
|
||||||
webpackMiddleware: {
|
|
||||||
noInfo: true
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
require('karma-jasmine'),
|
|
||||||
require('karma-junit-reporter'),
|
|
||||||
require('karma-coverage'),
|
|
||||||
require('karma-phantomjs-launcher'),
|
|
||||||
require('karma-phantomjs-shim'),
|
|
||||||
require('karma-ng-html2js-preprocessor'),
|
|
||||||
require('karma-webpack')
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
config.set(configuration);
|
|
||||||
};
|
|
|
@ -1,55 +0,0 @@
|
||||||
const conf = require('./gulp.conf');
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
|
||||||
const configuration = {
|
|
||||||
basePath: '../',
|
|
||||||
singleRun: true,
|
|
||||||
autoWatch: false,
|
|
||||||
logLevel: 'INFO',
|
|
||||||
junitReporter: {
|
|
||||||
outputDir: 'test-reports'
|
|
||||||
},
|
|
||||||
browsers: [
|
|
||||||
'PhantomJS'
|
|
||||||
],
|
|
||||||
frameworks: [
|
|
||||||
'jasmine'
|
|
||||||
],
|
|
||||||
files: [
|
|
||||||
'node_modules/es6-shim/es6-shim.js',
|
|
||||||
conf.path.src('index.spec.js'),
|
|
||||||
conf.path.src('**/*.html')
|
|
||||||
],
|
|
||||||
preprocessors: {
|
|
||||||
[conf.path.src('index.spec.js')]: [
|
|
||||||
'webpack'
|
|
||||||
],
|
|
||||||
[conf.path.src('**/*.html')]: [
|
|
||||||
'ng-html2js'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
ngHtml2JsPreprocessor: {
|
|
||||||
stripPrefix: `${conf.paths.src}/`
|
|
||||||
},
|
|
||||||
reporters: ['progress', 'coverage'],
|
|
||||||
coverageReporter: {
|
|
||||||
type: 'html',
|
|
||||||
dir: 'coverage/'
|
|
||||||
},
|
|
||||||
webpack: require('./webpack-test.conf'),
|
|
||||||
webpackMiddleware: {
|
|
||||||
noInfo: true
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
require('karma-jasmine'),
|
|
||||||
require('karma-junit-reporter'),
|
|
||||||
require('karma-coverage'),
|
|
||||||
require('karma-phantomjs-launcher'),
|
|
||||||
require('karma-phantomjs-shim'),
|
|
||||||
require('karma-ng-html2js-preprocessor'),
|
|
||||||
require('karma-webpack')
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
config.set(configuration);
|
|
||||||
};
|
|
|
@ -1,75 +0,0 @@
|
||||||
const webpack = require('webpack');
|
|
||||||
const conf = require('./gulp.conf');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const SplitByPathPlugin = require('webpack-split-by-path');
|
|
||||||
const ExtractTextPlugin = require("extract-text-webpack-plugin");
|
|
||||||
const autoprefixer = require('autoprefixer');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
module: {
|
|
||||||
preLoaders: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'eslint'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
loaders: [
|
|
||||||
{
|
|
||||||
test: /.json$/,
|
|
||||||
loaders: [
|
|
||||||
'json'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(css|scss)$/,
|
|
||||||
loaders: ExtractTextPlugin.extract('style', 'css?minimize!sass', 'postcss')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loaders: [
|
|
||||||
'babel-loader',
|
|
||||||
'ng-annotate'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /.html$/,
|
|
||||||
loaders: [
|
|
||||||
'html'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
|
|
||||||
loader: 'url-loader?limit=100000'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
|
||||||
new webpack.NoErrorsPlugin(),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: conf.path.src('index.html'),
|
|
||||||
inject: true
|
|
||||||
}),
|
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
|
||||||
compress: {unused: true, dead_code: true} // eslint-disable-line camelcase
|
|
||||||
}),
|
|
||||||
new SplitByPathPlugin([{
|
|
||||||
name: 'vendor',
|
|
||||||
path: path.join(__dirname, '../node_modules')
|
|
||||||
}]),
|
|
||||||
new ExtractTextPlugin('./index-[contenthash].css')
|
|
||||||
],
|
|
||||||
postcss: () => [autoprefixer],
|
|
||||||
output: {
|
|
||||||
path: path.join(process.cwd(), conf.paths.dist),
|
|
||||||
filename: './[name]-[hash].js'
|
|
||||||
},
|
|
||||||
entry: {
|
|
||||||
app: `./${conf.path.src('index')}`
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,41 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
module: {
|
|
||||||
preLoaders: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'eslint'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
loaders: [
|
|
||||||
{
|
|
||||||
test: /.json$/,
|
|
||||||
loaders: [
|
|
||||||
'json'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loaders: [
|
|
||||||
'ng-annotate'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /.html$/,
|
|
||||||
loaders: [
|
|
||||||
'html'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /(node_modules|.*\.spec\.js)/,
|
|
||||||
loader: 'isparta'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
debug: true,
|
|
||||||
devtool: 'cheap-module-eval-source-map'
|
|
||||||
};
|
|
|
@ -1,61 +0,0 @@
|
||||||
const webpack = require('webpack');
|
|
||||||
const conf = require('./gulp.conf');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const autoprefixer = require('autoprefixer');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
module: {
|
|
||||||
loaders: [
|
|
||||||
{
|
|
||||||
test: /.json$/,
|
|
||||||
loaders: [
|
|
||||||
'json'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(css|scss)$/,
|
|
||||||
loaders: [
|
|
||||||
'style',
|
|
||||||
'css',
|
|
||||||
'sass',
|
|
||||||
'postcss'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loaders: [
|
|
||||||
'ng-annotate'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /.html$/,
|
|
||||||
loaders: [
|
|
||||||
'html'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
|
|
||||||
loader: 'url-loader?limit=100000'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
|
||||||
new webpack.NoErrorsPlugin(),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: conf.path.src('index.html'),
|
|
||||||
inject: true
|
|
||||||
})
|
|
||||||
],
|
|
||||||
postcss: () => [autoprefixer],
|
|
||||||
debug: true,
|
|
||||||
devtool: 'cheap-module-eval-source-map',
|
|
||||||
output: {
|
|
||||||
path: path.join(process.cwd(), conf.paths.tmp),
|
|
||||||
filename: 'index.js'
|
|
||||||
},
|
|
||||||
entry: `./${conf.path.src('index')}`
|
|
||||||
};
|
|
|
@ -1,21 +0,0 @@
|
||||||
const gulp = require('gulp');
|
|
||||||
const browserSync = require('browser-sync');
|
|
||||||
const spa = require('browser-sync-spa');
|
|
||||||
|
|
||||||
const browserSyncConf = require('../conf/browsersync.conf');
|
|
||||||
const browserSyncDistConf = require('../conf/browsersync-dist.conf');
|
|
||||||
|
|
||||||
browserSync.use(spa());
|
|
||||||
|
|
||||||
gulp.task('browsersync', browserSyncServe);
|
|
||||||
gulp.task('browsersync:dist', browserSyncDist);
|
|
||||||
|
|
||||||
function browserSyncServe(done) {
|
|
||||||
browserSync.init(browserSyncConf());
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
function browserSyncDist(done) {
|
|
||||||
browserSync.init(browserSyncDistConf());
|
|
||||||
done();
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const gulp = require('gulp');
|
|
||||||
const karma = require('karma');
|
|
||||||
|
|
||||||
gulp.task('karma:single-run', karmaSingleRun);
|
|
||||||
gulp.task('karma:auto-run', karmaAutoRun);
|
|
||||||
|
|
||||||
function karmaFinishHandler(done) {
|
|
||||||
return failCount => {
|
|
||||||
done(failCount ? new Error(`Failed ${failCount} tests.`) : null);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function karmaSingleRun(done) {
|
|
||||||
const configFile = path.join(process.cwd(), 'conf', 'karma.conf.js');
|
|
||||||
const karmaServer = new karma.Server({configFile}, karmaFinishHandler(done));
|
|
||||||
karmaServer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function karmaAutoRun(done) {
|
|
||||||
const configFile = path.join(process.cwd(), 'conf', 'karma-auto.conf.js');
|
|
||||||
const karmaServer = new karma.Server({configFile}, karmaFinishHandler(done));
|
|
||||||
karmaServer.start();
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const gulp = require('gulp');
|
|
||||||
const del = require('del');
|
|
||||||
const filter = require('gulp-filter');
|
|
||||||
|
|
||||||
const conf = require('../conf/gulp.conf');
|
|
||||||
|
|
||||||
gulp.task('clean', clean);
|
|
||||||
gulp.task('other', other);
|
|
||||||
|
|
||||||
function clean() {
|
|
||||||
return del([conf.paths.tmp]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function other() {
|
|
||||||
const fileFilter = filter(file => file.stat.isFile());
|
|
||||||
|
|
||||||
return gulp.src([
|
|
||||||
path.join(conf.paths.src, '/**/*'),
|
|
||||||
path.join(`!${conf.paths.src}`, '/**/*.{scss,js,html}')
|
|
||||||
])
|
|
||||||
.pipe(fileFilter)
|
|
||||||
.pipe(gulp.dest(conf.paths.dist));
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
/* eslint angular/module-getter:0 */
|
|
||||||
const gulp = require('gulp');
|
|
||||||
const gutil = require('gulp-util');
|
|
||||||
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const webpackConf = require('../conf/webpack.conf');
|
|
||||||
const webpackDistConf = require('../conf/webpack-dist.conf');
|
|
||||||
const browsersync = require('browser-sync');
|
|
||||||
|
|
||||||
gulp.task('webpack:dev', done => {
|
|
||||||
webpackWrapper(false, webpackConf, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('webpack:watch', done => {
|
|
||||||
webpackWrapper(true, webpackConf, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('webpack:dist', done => {
|
|
||||||
webpackWrapper(false, webpackDistConf, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
function webpackWrapper(watch, conf, done) {
|
|
||||||
const webpackBundler = webpack(conf);
|
|
||||||
|
|
||||||
const webpackChangeHandler = (err, stats) => {
|
|
||||||
if (err) {
|
|
||||||
conf.errorHandler('Webpack')(err);
|
|
||||||
}
|
|
||||||
gutil.log(stats.toString({
|
|
||||||
colors: true,
|
|
||||||
chunks: false,
|
|
||||||
hash: false,
|
|
||||||
version: false
|
|
||||||
}));
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
done = null;
|
|
||||||
} else {
|
|
||||||
browsersync.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (watch) {
|
|
||||||
webpackBundler.watch(200, webpackChangeHandler);
|
|
||||||
} else {
|
|
||||||
webpackBundler.run(webpackChangeHandler);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
const gulp = require('gulp');
|
|
||||||
const HubRegistry = require('gulp-hub');
|
|
||||||
const browserSync = require('browser-sync');
|
|
||||||
|
|
||||||
const conf = require('./conf/gulp.conf');
|
|
||||||
|
|
||||||
// Load some files into the registry
|
|
||||||
const hub = new HubRegistry([conf.path.tasks('*.js')]);
|
|
||||||
|
|
||||||
// Tell gulp to use the tasks just loaded
|
|
||||||
gulp.registry(hub);
|
|
||||||
|
|
||||||
gulp.task('build', gulp.series(gulp.parallel('other', 'webpack:dist')));
|
|
||||||
gulp.task('test', gulp.series('karma:single-run'));
|
|
||||||
gulp.task('test:auto', gulp.series('karma:auto-run'));
|
|
||||||
gulp.task('serve', gulp.series('webpack:watch', 'watch', 'browsersync'));
|
|
||||||
gulp.task('serve:dist', gulp.series('default', 'browsersync:dist'));
|
|
||||||
gulp.task('default', gulp.series('clean', 'build'));
|
|
||||||
gulp.task('watch', watch);
|
|
||||||
|
|
||||||
function reloadBrowserSync(cb) {
|
|
||||||
browserSync.reload();
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
|
|
||||||
function watch(done) {
|
|
||||||
gulp.watch(conf.path.src('app/**/*.html'), reloadBrowserSync);
|
|
||||||
done();
|
|
||||||
}
|
|
33
webui/karma.conf.js
Normal file
33
webui/karma.conf.js
Normal file
|
@ -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
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,103 +1,58 @@
|
||||||
{
|
{
|
||||||
"name": "traefik",
|
"name": "traefik",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"homepage": "http://traefik.io",
|
|
||||||
"authors": [
|
"authors": [
|
||||||
"Fernandez Ludovic <lfernandez.dev@gmail.com>",
|
"Fernandez Ludovic <lfernandez.dev@gmail.com>",
|
||||||
"Micaël Mbagira <micael.mbagira@icloud.com>"
|
"Micaël Mbagira <micael.mbagira@icloud.com>",
|
||||||
|
"Jan Kuri <jan@bleenco.com>"
|
||||||
],
|
],
|
||||||
"description": "Front end for Træfik",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve --proxy-config proxy.conf.json",
|
||||||
|
"build": "ng build --prod --no-delete-output-path --output-path ../static/",
|
||||||
|
"test": "ng test",
|
||||||
|
"lint": "ng lint",
|
||||||
|
"e2e": "ng e2e"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular": "^1.4.2",
|
"@angular/animations": "^5.2.0",
|
||||||
"angular-animate": "^1.5.8",
|
"@angular/common": "^5.2.0",
|
||||||
"angular-aria": "^1.5.8",
|
"@angular/compiler": "^5.2.0",
|
||||||
"angular-cookies": "^1.5.8",
|
"@angular/core": "^5.2.0",
|
||||||
"angular-messages": "^1.5.8",
|
"@angular/forms": "^5.2.0",
|
||||||
"angular-nvd3": "^1.0.8",
|
"@angular/http": "^5.2.0",
|
||||||
"angular-resource": "^1.5.8",
|
"@angular/platform-browser": "^5.2.0",
|
||||||
"angular-sanitize": "^1.5.8",
|
"@angular/platform-browser-dynamic": "^5.2.0",
|
||||||
"angular-ui-bootstrap": "^2.0.0",
|
"@angular/router": "^5.2.0",
|
||||||
"angular-ui-router": "^0.3.1",
|
"@fortawesome/fontawesome": "^1.1.5",
|
||||||
"animate.css": "^3.4.0",
|
"@fortawesome/fontawesome-free-solid": "^5.0.10",
|
||||||
"bootstrap": "^3.3.6",
|
"bulma": "^0.6.2",
|
||||||
"http-status-codes": "^1.3.0",
|
"core-js": "^2.4.1",
|
||||||
|
"d3": "^4.13.0",
|
||||||
|
"date-fns": "^1.29.0",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
"moment": "^2.14.1",
|
"rxjs": "^5.5.6",
|
||||||
"nvd3": "^1.8.4"
|
"zone.js": "^0.8.19"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular-mocks": "^1.4.2",
|
"@angular/cli": "~1.7.2",
|
||||||
"autoprefixer": "^6.2.2",
|
"@angular/compiler-cli": "^5.2.0",
|
||||||
"babel-core": "^6.24.1",
|
"@angular/language-service": "^5.2.0",
|
||||||
"babel-loader": "^7.0.0",
|
"@types/jasmine": "~2.8.3",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"@types/jasminewd2": "~2.0.2",
|
||||||
"browser-sync": "^2.9.11",
|
"@types/node": "~6.0.60",
|
||||||
"browser-sync-spa": "^1.0.3",
|
"codelyzer": "^4.0.1",
|
||||||
"css-loader": "^0.23.1",
|
"jasmine-core": "~2.8.0",
|
||||||
"del": "^2.0.2",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"es6-shim": "^0.35.0",
|
"karma": "~2.0.0",
|
||||||
"eslint": "^2.11.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"eslint-config-angular": "^0.5.0",
|
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||||
"eslint-config-xo-space": "^0.12.0",
|
"karma-jasmine": "~1.1.0",
|
||||||
"eslint-loader": "^1.3.0",
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
"eslint-plugin-angular": "^1.3.0",
|
"protractor": "~5.1.2",
|
||||||
"extract-text-webpack-plugin": "^1.0.1",
|
"ts-node": "~4.1.0",
|
||||||
"file-loader": "^0.9.0",
|
"tslint": "~5.9.1",
|
||||||
"gulp": "gulpjs/gulp#4ed9a4a3275559c73a396eff7e1fde3824951ebb",
|
"typescript": "~2.5.3"
|
||||||
"gulp-angular-filesort": "^1.1.1",
|
|
||||||
"gulp-angular-templatecache": "^1.8.0",
|
|
||||||
"gulp-filter": "^4.0.0",
|
|
||||||
"gulp-htmlmin": "^1.3.0",
|
|
||||||
"gulp-hub": "frankwallis/gulp-hub#d461b9c700df9010d0a8694e4af1fb96d9f38bf4",
|
|
||||||
"gulp-insert": "^0.5.0",
|
|
||||||
"gulp-ng-annotate": "^1.1.0",
|
|
||||||
"gulp-sass": "^2.1.1",
|
|
||||||
"gulp-util": "^3.0.7",
|
|
||||||
"html-loader": "^0.4.3",
|
|
||||||
"html-webpack-plugin": "^2.9.0",
|
|
||||||
"http-proxy-middleware": "^0.17.4",
|
|
||||||
"isparta-loader": "^2.0.0",
|
|
||||||
"jasmine": "^2.4.1",
|
|
||||||
"json-loader": "^0.5.4",
|
|
||||||
"karma": "^0.13.14",
|
|
||||||
"karma-angular-filesort": "^1.0.0",
|
|
||||||
"karma-coverage": "^0.5.3",
|
|
||||||
"karma-jasmine": "^0.3.8",
|
|
||||||
"karma-junit-reporter": "^0.4.2",
|
|
||||||
"karma-ng-html2js-preprocessor": "^0.2.0",
|
|
||||||
"karma-phantomjs-launcher": "^1.0.0",
|
|
||||||
"karma-phantomjs-shim": "^1.1.2",
|
|
||||||
"karma-webpack": "^1.7.0",
|
|
||||||
"ng-annotate-loader": "^0.0.10",
|
|
||||||
"node-sass": "^3.4.2",
|
|
||||||
"phantomjs-prebuilt": "^2.1.6",
|
|
||||||
"postcss-loader": "^0.8.0",
|
|
||||||
"sass-loader": "^3.1.2",
|
|
||||||
"style-loader": "^0.13.0",
|
|
||||||
"url-loader": "^0.5.7",
|
|
||||||
"webpack": "2.1.0-beta.15",
|
|
||||||
"webpack-split-by-path": "^0.0.10"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "gulp",
|
|
||||||
"serve": "gulp serve",
|
|
||||||
"serve:dist": "gulp serve:dist",
|
|
||||||
"test": "gulp test",
|
|
||||||
"test:auto": "gulp test:auto"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"globals": {
|
|
||||||
"expect": true
|
|
||||||
},
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"jasmine": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"angular",
|
|
||||||
"xo-space"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
webui/protractor.conf.js
Normal file
28
webui/protractor.conf.js
Normal file
|
@ -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 } }));
|
||||||
|
}
|
||||||
|
};
|
10
webui/proxy.conf.json
Normal file
10
webui/proxy.conf.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"/api": {
|
||||||
|
"target": "http://localhost:8080",
|
||||||
|
"secure": false
|
||||||
|
},
|
||||||
|
"/health": {
|
||||||
|
"target": "http://localhost:8080",
|
||||||
|
"secure": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ make generate-webui # Generate static contents in `traefik/static/` folder.
|
||||||
|
|
||||||
## How to build (only for frontends developer)
|
## How to build (only for frontends developer)
|
||||||
|
|
||||||
- prerequisite: [Node 4+](https://nodejs.org) [yarn](https://yarnpkg.com/)
|
- prerequisite: [Node 6+](https://nodejs.org) [yarn](https://yarnpkg.com/)
|
||||||
|
|
||||||
Note: In case of conflict with the Apache Hadoop Yarn Command Line Interface, use the `yarnpkg`
|
Note: In case of conflict with the Apache Hadoop Yarn Command Line Interface, use the `yarnpkg`
|
||||||
alias.
|
alias.
|
||||||
|
@ -51,29 +51,15 @@ make generate-webui # Generate static contents in `traefik/static/` folder.
|
||||||
|
|
||||||
- Go to the directory `webui`
|
- Go to the directory `webui`
|
||||||
- Edit files in `webui/src`
|
- Edit files in `webui/src`
|
||||||
|
|
||||||
- Run in development mode :
|
- Run in development mode :
|
||||||
- `yarn run serve`
|
- `yarn start`
|
||||||
|
|
||||||
- Træfik API connections are defined in:
|
|
||||||
- `webui/src/app/core/health.resource.js`
|
|
||||||
- `webui/src/app/core/providers.resource.js`
|
|
||||||
|
|
||||||
- The pages contents are in the directory `webui/src/app/sections`.
|
|
||||||
|
|
||||||
|
|
||||||
## Libraries
|
## Libraries
|
||||||
|
|
||||||
- [Node](https://nodejs.org)
|
- [Node](https://nodejs.org)
|
||||||
- [Yarn](https://yarnpkg.com/)
|
- [Yarn](https://yarnpkg.com/)
|
||||||
- [Generator FountainJS](https://github.com/FountainJS/generator-fountain-webapp)
|
|
||||||
- [Webpack](https://github.com/webpack/webpack)
|
- [Webpack](https://github.com/webpack/webpack)
|
||||||
- [AngularJS](https://docs.angularjs.org/api)
|
- [Angular](https://angular.io)
|
||||||
- [UI Router](https://github.com/angular-ui/ui-router)
|
- [Bulma](https://bulma.io)
|
||||||
- [UI Router - Documentation](https://github.com/angular-ui/ui-router/wiki)
|
|
||||||
- [Bootstrap](https://getbootstrap.com)
|
|
||||||
- [Angular Bootstrap](https://angular-ui.github.io/bootstrap)
|
|
||||||
- [D3](https://d3js.org)
|
- [D3](https://d3js.org)
|
||||||
- [D3 - Documentation](https://github.com/mbostock/d3/wiki)
|
- [D3 - Documentation](https://github.com/mbostock/d3/wiki)
|
||||||
- [NVD3](http://nvd3.org)
|
|
||||||
- [Angular nvD3](https://krispo.github.io/angular-nvd3)
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"plugins": ["angular"],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"jasmine": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"angular": true,
|
|
||||||
"module": true,
|
|
||||||
"inject": true
|
|
||||||
}
|
|
||||||
}
|
|
32
webui/src/app/app.component.spec.ts
Normal file
32
webui/src/app/app.component.spec.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent
|
||||||
|
],
|
||||||
|
}).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 'app'`, async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.debugElement.componentInstance;
|
||||||
|
expect(app.title).toEqual('app');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should render title in a h1 tag', async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.debugElement.nativeElement;
|
||||||
|
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||||
|
}));
|
||||||
|
});
|
10
webui/src/app/app.component.ts
Normal file
10
webui/src/app/app.component.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
template: `
|
||||||
|
<app-header></app-header>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class AppComponent { }
|
43
webui/src/app/app.module.ts
Normal file
43
webui/src/app/app.module.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ApiService } from './services/api.service';
|
||||||
|
import { WindowService } from './services/window.service';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { HeaderComponent } from './components/header/header.component';
|
||||||
|
import { ProvidersComponent } from './components/providers/providers.component';
|
||||||
|
import { HealthComponent } from './components/health/health.component';
|
||||||
|
import { LineChartComponent } from './charts/line-chart/line-chart.component';
|
||||||
|
import { BarChartComponent } from './charts/bar-chart/bar-chart.component';
|
||||||
|
import { KeysPipe } from './pipes/keys.pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
HeaderComponent,
|
||||||
|
ProvidersComponent,
|
||||||
|
HealthComponent,
|
||||||
|
LineChartComponent,
|
||||||
|
BarChartComponent,
|
||||||
|
KeysPipe
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
CommonModule,
|
||||||
|
HttpClientModule,
|
||||||
|
FormsModule,
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{ path: '', component: ProvidersComponent, pathMatch: 'full' },
|
||||||
|
{ path: 'status', component: HealthComponent }
|
||||||
|
])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ApiService,
|
||||||
|
WindowService
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
7
webui/src/app/charts/bar-chart/bar-chart.component.html
Normal file
7
webui/src/app/charts/bar-chart/bar-chart.component.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="bar-chart" [class.is-hidden]="loading"></div>
|
||||||
|
<div class="loading-text" [class.is-hidden]="!loading">
|
||||||
|
<span>
|
||||||
|
<span>Loading, please wait...</span>
|
||||||
|
<img src="./assets/images/loader.svg" class="main-loader">
|
||||||
|
</span>
|
||||||
|
</div>
|
25
webui/src/app/charts/bar-chart/bar-chart.component.spec.ts
Normal file
25
webui/src/app/charts/bar-chart/bar-chart.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BarChartComponent } from './bar-chart.component';
|
||||||
|
|
||||||
|
describe('BarChartComponent', () => {
|
||||||
|
let component: BarChartComponent;
|
||||||
|
let fixture: ComponentFixture<BarChartComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ BarChartComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(BarChartComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
114
webui/src/app/charts/bar-chart/bar-chart.component.ts
Normal file
114
webui/src/app/charts/bar-chart/bar-chart.component.ts
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { WindowService } from '../../services/window.service';
|
||||||
|
import {
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
easeLinear,
|
||||||
|
select,
|
||||||
|
axisLeft,
|
||||||
|
axisBottom,
|
||||||
|
scaleBand,
|
||||||
|
scaleLinear
|
||||||
|
} from 'd3';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-bar-chart',
|
||||||
|
templateUrl: './bar-chart.component.html'
|
||||||
|
})
|
||||||
|
export class BarChartComponent implements OnInit, OnChanges {
|
||||||
|
@Input() value: any;
|
||||||
|
|
||||||
|
barChartEl: HTMLElement;
|
||||||
|
svg: any;
|
||||||
|
x: any;
|
||||||
|
y: any;
|
||||||
|
g: any;
|
||||||
|
bars: any;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
margin = { top: 40, right: 40, bottom: 40, left: 40 };
|
||||||
|
loading: boolean;
|
||||||
|
data: any[];
|
||||||
|
|
||||||
|
constructor(public elementRef: ElementRef, public windowService: WindowService) {
|
||||||
|
this.loading = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart');
|
||||||
|
this.setup();
|
||||||
|
setTimeout(() => this.loading = false, 4000);
|
||||||
|
|
||||||
|
this.windowService.resize.subscribe(w => this.draw());
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (!this.value || !this.svg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = this.value;
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(): void {
|
||||||
|
this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right;
|
||||||
|
this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom;
|
||||||
|
|
||||||
|
this.svg = select(this.barChartEl).append('svg')
|
||||||
|
.attr('width', this.width + this.margin.left + this.margin.right)
|
||||||
|
.attr('height', this.height + this.margin.top + this.margin.bottom);
|
||||||
|
|
||||||
|
this.g = this.svg.append('g')
|
||||||
|
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`);
|
||||||
|
|
||||||
|
this.x = scaleBand().padding(0.05);
|
||||||
|
this.y = scaleLinear();
|
||||||
|
|
||||||
|
this.g.append('g')
|
||||||
|
.attr('class', 'axis axis--x');
|
||||||
|
|
||||||
|
this.g.append('g')
|
||||||
|
.attr('class', 'axis axis--y');
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(): void {
|
||||||
|
this.x.domain(this.data.map((d: any) => d.code));
|
||||||
|
this.y.domain([0, max(this.data, (d: any) => d.count)]);
|
||||||
|
|
||||||
|
this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right;
|
||||||
|
this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom;
|
||||||
|
|
||||||
|
this.svg
|
||||||
|
.attr('width', this.width + this.margin.left + this.margin.right)
|
||||||
|
.attr('height', this.height + this.margin.top + this.margin.bottom);
|
||||||
|
|
||||||
|
this.x.rangeRound([0, this.width]);
|
||||||
|
this.y.rangeRound([this.height, 0]);
|
||||||
|
|
||||||
|
this.g.select('.axis--x')
|
||||||
|
.attr('transform', `translate(0, ${this.height})`)
|
||||||
|
.call(axisBottom(this.x));
|
||||||
|
|
||||||
|
this.g.select('.axis--y')
|
||||||
|
.call(axisLeft(this.y).tickSize(-this.width));
|
||||||
|
|
||||||
|
const bars = this.g.selectAll('.bar').data(this.data);
|
||||||
|
|
||||||
|
bars.enter()
|
||||||
|
.append('rect')
|
||||||
|
.attr('class', 'bar')
|
||||||
|
.attr('x', (d: any) => d.code)
|
||||||
|
.attr('y', (d: any) => d.count)
|
||||||
|
.attr('width', this.x.bandwidth())
|
||||||
|
.attr('height', (d: any) => (this.height - this.y(d.count)) < 0 ? 0 : this.height - this.y(d.count));
|
||||||
|
|
||||||
|
bars.attr('x', (d: any) => this.x(d.code))
|
||||||
|
.attr('y', (d: any) => this.y(d.count))
|
||||||
|
.attr('width', this.x.bandwidth())
|
||||||
|
.attr('height', (d: any) => (this.height - this.y(d.count)) < 0 ? 0 : this.height - this.y(d.count));
|
||||||
|
|
||||||
|
bars.exit().remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="line-chart" [class.is-hidden]="loading"></div>
|
||||||
|
<div class="loading-text" [class.is-hidden]="!loading">
|
||||||
|
<span>
|
||||||
|
<span>Loading, please wait...</span>
|
||||||
|
<img src="./assets/images/loader.svg" class="main-loader">
|
||||||
|
</span>
|
||||||
|
</div>
|
162
webui/src/app/charts/line-chart/line-chart.component.ts
Normal file
162
webui/src/app/charts/line-chart/line-chart.component.ts
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { WindowService } from '../../services/window.service';
|
||||||
|
import {
|
||||||
|
range,
|
||||||
|
scaleTime,
|
||||||
|
scaleLinear,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
curveLinear,
|
||||||
|
line,
|
||||||
|
easeLinear,
|
||||||
|
select,
|
||||||
|
axisLeft,
|
||||||
|
axisBottom,
|
||||||
|
timeSecond,
|
||||||
|
timeFormat
|
||||||
|
} from 'd3';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-line-chart',
|
||||||
|
templateUrl: 'line-chart.component.html'
|
||||||
|
})
|
||||||
|
export class LineChartComponent implements OnChanges, OnInit {
|
||||||
|
@Input() value: { count: number, date: string };
|
||||||
|
|
||||||
|
lineChartEl: HTMLElement;
|
||||||
|
svg: any;
|
||||||
|
g: any;
|
||||||
|
line: any;
|
||||||
|
path: any;
|
||||||
|
x: any;
|
||||||
|
y: any;
|
||||||
|
data: number[];
|
||||||
|
now: Date;
|
||||||
|
duration: number;
|
||||||
|
limit: number;
|
||||||
|
options: any;
|
||||||
|
xAxis: any;
|
||||||
|
yAxis: any;
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
margin = { top: 40, right: 40, bottom: 60, left: 60 };
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
constructor(private elementRef: ElementRef, public windowService: WindowService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.lineChartEl = this.elementRef.nativeElement.querySelector('.line-chart');
|
||||||
|
this.limit = 40;
|
||||||
|
this.duration = 3000;
|
||||||
|
this.now = new Date(Date.now() - this.duration);
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
title: '',
|
||||||
|
color: '#3A84C5'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
setTimeout(() => this.loading = false, 4000);
|
||||||
|
this.windowService.resize.subscribe(w => {
|
||||||
|
if (this.svg) {
|
||||||
|
const el = this.lineChartEl.querySelector('svg');
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.width = this.lineChartEl.clientWidth - this.margin.left - this.margin.right;
|
||||||
|
this.height = this.lineChartEl.clientHeight - this.margin.top - this.margin.bottom;
|
||||||
|
|
||||||
|
this.svg = select(this.lineChartEl).append('svg')
|
||||||
|
.attr('width', this.width + this.margin.left + this.margin.right)
|
||||||
|
.attr('height', this.height + this.margin.top + this.margin.bottom)
|
||||||
|
.append('g')
|
||||||
|
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`);
|
||||||
|
|
||||||
|
if (!this.data) {
|
||||||
|
this.data = range(this.limit).map(i => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.x = scaleTime().range([0, this.width]);
|
||||||
|
this.y = scaleLinear().range([this.height, 0]);
|
||||||
|
|
||||||
|
this.x.domain([<any>this.now - (this.limit - 2), <any>this.now - this.duration]);
|
||||||
|
this.y.domain([0, max(this.data, (d: any) => d)]);
|
||||||
|
|
||||||
|
this.line = line()
|
||||||
|
.x((d: any, i: number) => this.x(<any>this.now - (this.limit - 1 - i) * this.duration))
|
||||||
|
.y((d: any) => this.y(d))
|
||||||
|
.curve(curveLinear);
|
||||||
|
|
||||||
|
this.svg.append('defs').append('clipPath')
|
||||||
|
.attr('id', 'clip')
|
||||||
|
.append('rect')
|
||||||
|
.attr('width', this.width)
|
||||||
|
.attr('height', this.height);
|
||||||
|
|
||||||
|
this.xAxis = this.svg.append('g')
|
||||||
|
.attr('class', 'x axis')
|
||||||
|
.attr('transform', `translate(0, ${this.height})`)
|
||||||
|
.call(axisBottom(this.x).tickSize(-this.height).ticks(timeSecond, 5).tickFormat(timeFormat('%H:%M:%S')));
|
||||||
|
|
||||||
|
this.yAxis = this.svg.append('g')
|
||||||
|
.attr('class', 'y axis')
|
||||||
|
.call(axisLeft(this.y).tickSize(-this.width));
|
||||||
|
|
||||||
|
this.path = this.svg.append('g')
|
||||||
|
.attr('clip-path', 'url(#clip)')
|
||||||
|
.append('path')
|
||||||
|
.data([this.data])
|
||||||
|
.attr('class', 'line');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (!this.value || !this.svg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateData(this.value.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData = (value: number) => {
|
||||||
|
this.data.push(value * 1000000);
|
||||||
|
this.now = new Date();
|
||||||
|
|
||||||
|
this.x.domain([<any>this.now - (this.limit - 2) * this.duration, <any>this.now - this.duration]);
|
||||||
|
const minv = min(this.data, (d: any) => d) > 0 ? min(this.data, (d: any) => d) - 4 : 0;
|
||||||
|
const maxv = max(this.data, (d: any) => d) + 4;
|
||||||
|
this.y.domain([minv, maxv]);
|
||||||
|
|
||||||
|
this.xAxis
|
||||||
|
.transition()
|
||||||
|
.duration(this.duration)
|
||||||
|
.ease(easeLinear)
|
||||||
|
.call(axisBottom(this.x).tickSize(-this.height).ticks(timeSecond, 5).tickFormat(timeFormat('%H:%M:%S')))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('text-anchor', 'end')
|
||||||
|
.attr('dx', '-.8em')
|
||||||
|
.attr('dy', '.15em')
|
||||||
|
.attr('transform', 'rotate(-65)');
|
||||||
|
|
||||||
|
this.yAxis
|
||||||
|
.transition()
|
||||||
|
.duration(500)
|
||||||
|
.ease(easeLinear)
|
||||||
|
.call(axisLeft(this.y).tickSize(-this.width));
|
||||||
|
|
||||||
|
this.path
|
||||||
|
.transition()
|
||||||
|
.duration(0)
|
||||||
|
.attr('d', this.line(this.data))
|
||||||
|
.attr('transform', null)
|
||||||
|
.transition()
|
||||||
|
.duration(this.duration)
|
||||||
|
.ease(easeLinear)
|
||||||
|
.attr('transform', `translate(${this.x(<any>this.now - (this.limit - 1) * this.duration)})`);
|
||||||
|
|
||||||
|
this.data.shift();
|
||||||
|
}
|
||||||
|
}
|
29
webui/src/app/components/header/header.component.html
Normal file
29
webui/src/app/components/header/header.component.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-menu">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" routerLink="/">
|
||||||
|
<img src="./assets/images/traefik.logo.svg" alt="Traefik" class="navbar-logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-start">
|
||||||
|
<div class="navbar-menu">
|
||||||
|
<a class="navbar-item" routerLink="/" routerLinkActive="is-active" [routerLinkActiveOptions]="{ exact: true }">
|
||||||
|
Providers
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" routerLink="/status" routerLinkActive="is-active">
|
||||||
|
Health
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end is-hidden-mobile">
|
||||||
|
<a class="navbar-item" [href]="releaseLink" target="_blank">
|
||||||
|
{{ version }} / {{ codename }}
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="https://docs.traefik.io" target="_blank">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
23
webui/src/app/components/header/header.component.ts
Normal file
23
webui/src/app/components/header/header.component.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
templateUrl: 'header.component.html'
|
||||||
|
})
|
||||||
|
export class HeaderComponent implements OnInit {
|
||||||
|
version: string;
|
||||||
|
codename: string;
|
||||||
|
releaseLink: string;
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.apiService.fetchVersion()
|
||||||
|
.subscribe(data => {
|
||||||
|
this.version = data.Version;
|
||||||
|
this.codename = data.Codename;
|
||||||
|
this.releaseLink = 'https://github.com/containous/traefik/tree/' + data.Version;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
104
webui/src/app/components/health/health.component.html
Normal file
104
webui/src/app/components/health/health.component.html
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
|
||||||
|
<div class="column is-12">
|
||||||
|
<div class="content-item">
|
||||||
|
<div class="content-item-data">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="item-data border-right">
|
||||||
|
<span class="data-grey">Total Response Time</span>
|
||||||
|
<span class="data-blue">{{ totalResponseTime }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="item-data border-right">
|
||||||
|
<span class="data-grey">Total Code Count</span>
|
||||||
|
<span class="data-blue">{{ totalCodeCount }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="item-data">
|
||||||
|
<span class="data-grey">Uptime Since <br/>{{ uptimeSince }}</span>
|
||||||
|
<span class="data-blue">{{ uptime }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-item">
|
||||||
|
<div class="content-item-data">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="item-data border-right">
|
||||||
|
<span class="data-grey">Average Response Time</span>
|
||||||
|
<span class="data-blue">{{ averageResponseTime }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="item-data border-right">
|
||||||
|
<span class="data-grey">Code Count</span>
|
||||||
|
<span class="data-blue">{{ codeCount }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="item-data">
|
||||||
|
<span class="data-grey">PID</span>
|
||||||
|
<span class="data-blue">{{ pid }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-6">
|
||||||
|
<div class="content-item">
|
||||||
|
<h2>Average Response Time (µs)</h2>
|
||||||
|
<app-line-chart [value]="chartValue"></app-line-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-6">
|
||||||
|
<div class="content-item">
|
||||||
|
<h2>Total Status Code Count</h2>
|
||||||
|
<app-bar-chart [value]="statusCodeValue"></app-bar-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content" *ngIf="recentErrors">
|
||||||
|
<div class="content-item">
|
||||||
|
<h2>Recent HTTP Errors</h2>
|
||||||
|
<table class="table is-fullwidth">
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>Request</td>
|
||||||
|
<td>Time</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let entry of recentErrors">
|
||||||
|
<td>
|
||||||
|
<span class="tag is-info">{{ entry.status_code }}</span> <span>{{ entry.status }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="tag">{{ entry.method }}</span> <a>{{ entry.host }}{{ entry.path }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span>{{ entry.time }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="!recentErrors?.length">
|
||||||
|
<td colspan="3">
|
||||||
|
<p class="text-muted text-center">No entries</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
57
webui/src/app/components/health/health.component.ts
Normal file
57
webui/src/app/components/health/health.component.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
import 'rxjs/add/observable/timer';
|
||||||
|
import 'rxjs/add/operator/timeInterval';
|
||||||
|
import 'rxjs/add/operator/mergeMap';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import { format, distanceInWordsStrict, subSeconds } from 'date-fns';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-health',
|
||||||
|
templateUrl: 'health.component.html'
|
||||||
|
})
|
||||||
|
export class HealthComponent implements OnInit, OnDestroy {
|
||||||
|
sub: Subscription;
|
||||||
|
recentErrors: any;
|
||||||
|
pid: number;
|
||||||
|
uptime: string;
|
||||||
|
uptimeSince: string;
|
||||||
|
averageResponseTime: string;
|
||||||
|
totalResponseTime: string;
|
||||||
|
codeCount: number;
|
||||||
|
totalCodeCount: number;
|
||||||
|
chartValue: any;
|
||||||
|
statusCodeValue: any;
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.sub = Observable.timer(0, 3000)
|
||||||
|
.timeInterval()
|
||||||
|
.mergeMap(() => this.apiService.fetchHealthStatus())
|
||||||
|
.subscribe(data => {
|
||||||
|
if (data) {
|
||||||
|
this.recentErrors = data.recent_errors;
|
||||||
|
this.chartValue = { count: data.average_response_time_sec, date: data.time };
|
||||||
|
this.statusCodeValue = Object.keys(data.total_status_code_count)
|
||||||
|
.map(key => ({ code: key, count: data.total_status_code_count[key] }));
|
||||||
|
|
||||||
|
this.pid = data.pid;
|
||||||
|
this.uptime = distanceInWordsStrict(subSeconds(new Date(), data.uptime_sec), new Date());
|
||||||
|
this.uptimeSince = format(subSeconds(new Date(), data.uptime_sec), 'MM/DD/YYYY HH:mm:ss');
|
||||||
|
this.totalResponseTime = data.total_response_time;
|
||||||
|
this.averageResponseTime = data.average_response_time;
|
||||||
|
this.codeCount = data.count;
|
||||||
|
this.totalCodeCount = data.total_count;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.sub) {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
566
webui/src/app/components/providers/providers.component.html
Normal file
566
webui/src/app/components/providers/providers.component.html
Normal file
|
@ -0,0 +1,566 @@
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<div class="columns is-multiline" *ngIf="keys?.length">
|
||||||
|
<div class="column is-12">
|
||||||
|
|
||||||
|
<div class="search-container">
|
||||||
|
<span class="icon"><i class="fas fa-search"></i></span>
|
||||||
|
<input type="text" placeholder="Filter by name or id ..." [(ngModel)]="keyword" (ngModelChange)="filter()">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tabs" *ngIf="keys?.length">
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let provider of keys" [class.is-active]="tab === provider" (click)="tab = provider">
|
||||||
|
<a>{{ provider }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="keys?.length">
|
||||||
|
<div class="columns">
|
||||||
|
<!-- Frontends -->
|
||||||
|
<div class="column is-6">
|
||||||
|
<h2 class="subtitle"><span class="tag is-info">{{ providers[tab]?.frontends.length }}</span> Frontends</h2>
|
||||||
|
<div class="message" *ngFor="let p of providers[tab]?.frontends; let i = index;">
|
||||||
|
<div class="message-header">
|
||||||
|
<h2>
|
||||||
|
<div>
|
||||||
|
<i class="icon fas fa-globe"></i>
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-info">{{ p.id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="p.backend">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<a class="tags has-addons" [href]="'#' + p.backend">
|
||||||
|
<span class="tag is-light">Backend</span>
|
||||||
|
<span class="tag is-primary">{{ p.backend }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message-body">
|
||||||
|
<div class="tabs is-fullwidth is-small is-boxed">
|
||||||
|
<ul>
|
||||||
|
<li [class.is-active]="p.section !== 'details'" (click)="p.section = 'main'"><a>Main</a></li>
|
||||||
|
<li [class.is-active]="p.section === 'details'" (click)="p.section = 'details'"><a>Details</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<div *ngIf="p.section !== 'details'">
|
||||||
|
|
||||||
|
<div *ngIf="p.routes && p.routes.length">
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Route Rule</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let route of p.routes; let ri = index;">
|
||||||
|
<td><span class="has-text-grey" title="{{ route.title }}">{{ route.rule }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.entryPoints && p.entryPoints.length">
|
||||||
|
<hr>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-3">
|
||||||
|
<h2>Entry Points</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-9">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags">
|
||||||
|
<span class="tag is-info" *ngFor="let ep of p.entryPoints; let ri = index;">{{ ep }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Details -->
|
||||||
|
<div *ngIf="p.section === 'details'">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-3">
|
||||||
|
<h2>Misc.</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-9">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Priority</span>
|
||||||
|
<span class="tag is-info">{{ p.priority }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Host Header</span>
|
||||||
|
<span class="tag is-info">{{ p.passHostHeader }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control" *ngIf="p.passTLSCert">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">TLS Cert</span>
|
||||||
|
<span class="tag is-info">{{ p.passTLSCert }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.basicAuth && p.basicAuth.length">
|
||||||
|
<hr/>
|
||||||
|
<h2>Basic Authentication</h2>
|
||||||
|
<div class="tags padding-5-10">
|
||||||
|
<span class="tag is-info" *ngFor="let auth of p.basicAuth; let ri = index;">{{ auth }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.errors">
|
||||||
|
<hr/>
|
||||||
|
<h2>Error Pages</h2>
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Backend</td>
|
||||||
|
<td>Query</td>
|
||||||
|
<td>Status</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let key of p.errors | keys">
|
||||||
|
<td><span class="has-text-grey-light">{{ p.errors[key].backend }}</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.errors[key].query }}</span></td>
|
||||||
|
<td>
|
||||||
|
<span class="tag is-light" *ngFor="let state of p.errors[key].status">{{ state }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.whiteList">
|
||||||
|
<hr/>
|
||||||
|
<div class="columns is-gapless is-multiline is-mobile">
|
||||||
|
<div class="column is-half">
|
||||||
|
<h2>Whitelist</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">useXForwardedFor</span>
|
||||||
|
<span class="tag is-info">{{ p.whiteList.useXForwardedFor }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags">
|
||||||
|
<span class="tag is-info" *ngFor="let wlRange of p.whiteList.sourceRange; let ri = index;">{{ wlRange }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.headers">
|
||||||
|
<hr/>
|
||||||
|
<h2>Headers</h2>
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
|
||||||
|
<div class="column is-12" *ngIf="p.headers.customRequestHeaders">
|
||||||
|
<h2>Custom Request Headers</h2>
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let key of p.headers.customRequestHeaders | keys">
|
||||||
|
<td><span class="has-text-grey-light">{{ key }}</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.customRequestHeaders[key] }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" *ngIf="p.headers.customResponseHeaders">
|
||||||
|
<h2>Custom Response Headers</h2>
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let key of p.headers.customResponseHeaders | keys">
|
||||||
|
<td><span class="has-text-grey-light">{{ key }}</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.customResponseHeaders[key] }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12">
|
||||||
|
<h2>Secure</h2>
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngIf="p.headers.browserXssFilter">
|
||||||
|
<td><span class="has-text-grey">Browser XSS Filter</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.browserXssFilter }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.contentSecurityPolicy">
|
||||||
|
<td><span class="has-text-grey">Content Security Policy</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.contentSecurityPolicy }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.contentTypeNoSniff">
|
||||||
|
<td><span class="has-text-grey">Content Type (No sniff)</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.contentTypeNoSniff }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.customFrameOptionsValue">
|
||||||
|
<td><span class="has-text-grey">Custom Frame Options Value</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.customFrameOptionsValue }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.forceSTSHeader">
|
||||||
|
<td><span class="has-text-grey">Force STS Header</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.forceSTSHeader }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.frameDeny">
|
||||||
|
<td><span class="has-text-grey">Frame Deny</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.frameDeny }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.isDevelopment">
|
||||||
|
<td><span class="has-text-grey">Is Development</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.isDevelopment }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.publicKey">
|
||||||
|
<td><span class="has-text-grey">Public Key</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.publicKey }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.referrerPolicy">
|
||||||
|
<td><span class="has-text-grey">Referrer Policy</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.referrerPolicy }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.sslHost">
|
||||||
|
<td><span class="has-text-grey">SSL Host</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.sslHost }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.sslRedirect">
|
||||||
|
<td><span class="has-text-grey">SSL Redirect</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.sslRedirect }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.sslTemporaryRedirect">
|
||||||
|
<td><span class="has-text-grey">SSL Temporary Redirect</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.sslTemporaryRedirect }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.stsIncludeSubdomains">
|
||||||
|
<td><span class="has-text-grey">STS Include Subdomains</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.stsIncludeSubdomains }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.stsPreload">
|
||||||
|
<td><span class="has-text-grey">STS Preload</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.stsPreload }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="p.headers.stsSeconds">
|
||||||
|
<td><span class="has-text-grey">STS Seconds</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.stsSeconds }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" *ngIf="p.headers.allowedHosts">
|
||||||
|
<h2>Allowed Hosts</h2>
|
||||||
|
<div class="tags-list">
|
||||||
|
<span class="tag is-light" *ngFor="let host of p.headers.allowedHosts">{{ host }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" *ngIf="p.headers.sslProxyHeaders">
|
||||||
|
<h2>SSL Proxy Headers</h2>
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let key of p.headers.sslProxyHeaders | keys">
|
||||||
|
<td><span class="has-text-grey-light">{{ key }}</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ p.headers.sslProxyHeaders[key] }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" *ngIf="p.headers.hostsProxyHeaders">
|
||||||
|
<h2>Hosts Proxy Headers</h2>
|
||||||
|
<div class="tags-list">
|
||||||
|
<span class="tag is-light" *ngFor="let h of p.headers.hostsProxyHeaders">{{ h }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Backends -->
|
||||||
|
<div class="column is-6">
|
||||||
|
<h2 class="subtitle"><span class="tag is-primary">{{ providers[tab]?.backends.length }}</span> Backends</h2>
|
||||||
|
<div class="message" *ngFor="let p of providers[tab]?.backends; let i = index;">
|
||||||
|
<div class="message-header">
|
||||||
|
<h2 [id]="p.id">
|
||||||
|
<div>
|
||||||
|
<i class="icon fas fa-server"></i>
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-primary">{{ p.id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
|
||||||
|
<div class="tabs is-fullwidth is-small is-boxed">
|
||||||
|
<ul>
|
||||||
|
<li [class.is-active]="p.section !== 'details'" (click)="p.section = 'main'"><a>Main</a></li>
|
||||||
|
<li [class.is-active]="p.section === 'details'" (click)="p.section = 'details'"><a>Details</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<div *ngIf="p.section !== 'details'">
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Server</td>
|
||||||
|
<td>Weight</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let server of p.servers; let ri = index;">
|
||||||
|
<td><a href="{{ server.url }}" title="{{ server.title }}">{{ server.url }}</a></td>
|
||||||
|
<td><span class="has-text-grey">{{ server.weight }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Details -->
|
||||||
|
<div *ngIf="p.section === 'details'">
|
||||||
|
|
||||||
|
<div *ngIf="p.loadBalancer">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-3">
|
||||||
|
<h2>Load Balancer</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-9">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Method</span>
|
||||||
|
<span class="tag is-info">{{ p.loadBalancer.method }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons" *ngIf="p.loadBalancer.stickiness || p.loadBalancer.sticky">
|
||||||
|
<span class="tag is-light">Stickiness</span>
|
||||||
|
<span class="tag is-info">true</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control" *ngIf="p.loadBalancer.stickiness">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Cookie Name</span>
|
||||||
|
<span class="tag is-info">{{ p.loadBalancer.stickiness.cookieName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.maxConn">
|
||||||
|
<hr/>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-3">
|
||||||
|
<h2>Max Connections</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-9">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Amount</span>
|
||||||
|
<span class="tag is-info">{{ p.maxConn.amount }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Extractor Function</span>
|
||||||
|
<span class="tag is-info">{{ p.maxConn.extractorFunc }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.circuitBreaker">
|
||||||
|
<hr/>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-3">
|
||||||
|
<h2>Circuit Breaker</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-9">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Expression</span>
|
||||||
|
<span class="tag is-info">{{ p.circuitBreaker.expression }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.healthCheck">
|
||||||
|
<hr/>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-3">
|
||||||
|
<h2>Health Check</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-9">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Path</span>
|
||||||
|
<span class="tag is-info">{{ p.healthCheck.path }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control" *ngIf="p.healthCheck.port">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Port</span>
|
||||||
|
<span class="tag is-info">{{ p.healthCheck.port }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control" *ngIf="p.healthCheck.interval">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Interval</span>
|
||||||
|
<span class="tag is-info">{{ p.healthCheck.interval }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control" *ngIf="p.healthCheck.hostname">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Hostname</span>
|
||||||
|
<span class="tag is-info">{{ p.healthCheck.hostname }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.buffering">
|
||||||
|
<hr>
|
||||||
|
<div class="columns list-title">
|
||||||
|
<div class="column is-12">
|
||||||
|
<h2>Buffering</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-item">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-4">
|
||||||
|
<span>Request Body Bytes</span>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Max</span>
|
||||||
|
<span class="tag is-info">{{ p.buffering.maxRequestBodyBytes }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Men</span>
|
||||||
|
<span class="tag is-info">{{ p.buffering.memRequestBodyBytes }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-item">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-4">
|
||||||
|
<span>Response Body Bytes</span>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Max</span>
|
||||||
|
<span class="tag is-info">{{ p.buffering.maxResponseBodyBytes }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-light">Men</span>
|
||||||
|
<span class="tag is-info">{{ p.buffering.memResponseBodyBytes }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-item">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-4">
|
||||||
|
<span>Retry Expression</span>
|
||||||
|
</div>
|
||||||
|
<div class="column is-8">
|
||||||
|
<span class="tag is-info">{{ p.buffering.retryExpression }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns" *ngIf="!keys || !keys.length">
|
||||||
|
<div class="column is-12">
|
||||||
|
<div class="notification">
|
||||||
|
No providers found.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
59
webui/src/app/components/providers/providers.component.ts
Normal file
59
webui/src/app/components/providers/providers.component.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import * as _ from "lodash";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-providers',
|
||||||
|
templateUrl: 'providers.component.html'
|
||||||
|
})
|
||||||
|
export class ProvidersComponent implements OnInit, OnDestroy {
|
||||||
|
sub: Subscription;
|
||||||
|
keys: string[];
|
||||||
|
data: any;
|
||||||
|
previousData: any;
|
||||||
|
providers: any;
|
||||||
|
tab: string;
|
||||||
|
keyword: string;
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.keyword = '';
|
||||||
|
this.sub = Observable.timer(0, 2000)
|
||||||
|
.timeInterval()
|
||||||
|
.mergeMap(() => this.apiService.fetchProviders())
|
||||||
|
.subscribe(data => {
|
||||||
|
if (!_.isEqual(this.previousData, data)) {
|
||||||
|
this.previousData = _.cloneDeep(data);
|
||||||
|
this.data = data;
|
||||||
|
this.providers = data;
|
||||||
|
this.keys = Object.keys(this.providers);
|
||||||
|
this.tab = this.keys[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filter(): void {
|
||||||
|
const keyword = this.keyword.toLowerCase();
|
||||||
|
this.providers = Object.keys(this.data)
|
||||||
|
.filter(value => value !== 'acme' && value !== 'ACME')
|
||||||
|
.reduce((acc, curr) => {
|
||||||
|
return Object.assign(acc, {
|
||||||
|
[curr]: {
|
||||||
|
backends: this.data[curr].backends.filter(d => d.id.toLowerCase().includes(keyword)),
|
||||||
|
frontends: this.data[curr].frontends.filter(d => {
|
||||||
|
return d.id.toLowerCase().includes(keyword) || d.backend.toLowerCase().includes(keyword);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.sub) {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
|
|
||||||
var traefikCoreHealth = 'traefik.core.health';
|
|
||||||
module.exports = traefikCoreHealth;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikCoreHealth, ['ngResource'])
|
|
||||||
.factory('Health', Health);
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function Health($resource) {
|
|
||||||
return $resource('../health');
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
|
|
||||||
var traefikCoreProvider = 'traefik.core.provider';
|
|
||||||
module.exports = traefikCoreProvider;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikCoreProvider, ['ngResource'])
|
|
||||||
.factory('Providers', Providers);
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function Providers($resource, $q) {
|
|
||||||
const resourceProvider = $resource('../api/providers');
|
|
||||||
return {
|
|
||||||
get: function () {
|
|
||||||
return $q((resolve, reject) => {
|
|
||||||
resourceProvider.get()
|
|
||||||
.$promise
|
|
||||||
.then((rawProviders) => {
|
|
||||||
delete rawProviders.acme;
|
|
||||||
delete rawProviders.ACME;
|
|
||||||
|
|
||||||
for (let providerName in rawProviders) {
|
|
||||||
if (rawProviders.hasOwnProperty(providerName)) {
|
|
||||||
if (!providerName.startsWith('$')) {
|
|
||||||
// BackEnds mapping
|
|
||||||
let bckends = rawProviders[providerName].backends || {};
|
|
||||||
rawProviders[providerName].backends = Object.keys(bckends)
|
|
||||||
.map(key => {
|
|
||||||
const goodBackend = bckends[key];
|
|
||||||
goodBackend.backendId = key;
|
|
||||||
return goodBackend;
|
|
||||||
});
|
|
||||||
|
|
||||||
// FrontEnds mapping
|
|
||||||
let frtends = rawProviders[providerName].frontends || {};
|
|
||||||
rawProviders[providerName].frontends = Object.keys(frtends)
|
|
||||||
.map(key => {
|
|
||||||
const goodFrontend = frtends[key];
|
|
||||||
goodFrontend.frontendId = key;
|
|
||||||
return goodFrontend;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve(rawProviders);
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
|
|
||||||
var traefikCoreVersion = 'traefik.core.version';
|
|
||||||
module.exports = traefikCoreVersion;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikCoreVersion, ['ngResource'])
|
|
||||||
.factory('Version', Version);
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function Version($resource) {
|
|
||||||
return $resource('../api/version');
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/**
|
|
||||||
* If you want to override some bootstrap variables, you have to change values here.
|
|
||||||
* The list of variables are listed here bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss
|
|
||||||
*/
|
|
||||||
$navbar-inverse-link-color: #5AADBB;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not remove the comments below. It's the markers used by wiredep to inject
|
|
||||||
* sass dependencies when defined in the bower.json of your dependencies
|
|
||||||
*/
|
|
||||||
// bower:scss
|
|
||||||
// endbower
|
|
||||||
|
|
||||||
.browsehappy {
|
|
||||||
margin: 0.2em 0;
|
|
||||||
background: #ccc;
|
|
||||||
color: #000;
|
|
||||||
padding: 0.2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail {
|
|
||||||
height: 200px;
|
|
||||||
|
|
||||||
img.pull-right {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not remove the comments below. It's the markers used by gulp-inject to inject
|
|
||||||
* all your sass files automatically
|
|
||||||
*/
|
|
||||||
// injector
|
|
||||||
// endinjector
|
|
8
webui/src/app/pipes/keys.pipe.ts
Normal file
8
webui/src/app/pipes/keys.pipe.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { PipeTransform, Pipe } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({ name: 'keys' })
|
||||||
|
export class KeysPipe implements PipeTransform {
|
||||||
|
transform(value, args: string[]): any {
|
||||||
|
return Object.keys(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,238 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var d3 = require('d3'),
|
|
||||||
moment = require('moment'),
|
|
||||||
HttpStatus = require('http-status-codes');
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function HealthController($scope, $interval, $log, Health) {
|
|
||||||
|
|
||||||
var vm = this;
|
|
||||||
|
|
||||||
vm.graph = {
|
|
||||||
averageResponseTime: {},
|
|
||||||
totalStatusCodeCount: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
vm.graph.totalStatusCodeCount.options = {
|
|
||||||
"chart": {
|
|
||||||
type: 'discreteBarChart',
|
|
||||||
tooltip: {
|
|
||||||
contentGenerator: function (e) {
|
|
||||||
var d = e.data;
|
|
||||||
return d.label + " " + d.text;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
height: 200,
|
|
||||||
margin: {
|
|
||||||
top: 20,
|
|
||||||
right: 20,
|
|
||||||
bottom: 40,
|
|
||||||
left: 55
|
|
||||||
},
|
|
||||||
x: function (d) {
|
|
||||||
return d.label;
|
|
||||||
},
|
|
||||||
y: function (d) {
|
|
||||||
return d.value;
|
|
||||||
},
|
|
||||||
showValues: true,
|
|
||||||
valueFormat: function (d) {
|
|
||||||
return d3.format('d')(d);
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
axisLabelDistance: 30,
|
|
||||||
tickFormat: d3.format('d')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"enable": true,
|
|
||||||
"text": "Total Status Code Count",
|
|
||||||
"css": {
|
|
||||||
"textAlign": "center"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
vm.graph.totalStatusCodeCount.data = [
|
|
||||||
{
|
|
||||||
key: "Total Status Code Count",
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
"label": "200",
|
|
||||||
"value": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update Total Status Code Count graph
|
|
||||||
*
|
|
||||||
* @param {Object} totalStatusCodeCount Object from API
|
|
||||||
*/
|
|
||||||
function updateTotalStatusCodeCount(totalStatusCodeCount) {
|
|
||||||
|
|
||||||
// extract values
|
|
||||||
vm.graph.totalStatusCodeCount.data[0].values = [];
|
|
||||||
for (var code in totalStatusCodeCount) {
|
|
||||||
if (totalStatusCodeCount.hasOwnProperty(code)) {
|
|
||||||
var statusCodeText = "";
|
|
||||||
try {
|
|
||||||
statusCodeText = HttpStatus.getStatusText(code);
|
|
||||||
} catch (e) {
|
|
||||||
// HttpStatus.getStatusText throws error on unknown codes
|
|
||||||
statusCodeText = "Unknown status code";
|
|
||||||
}
|
|
||||||
vm.graph.totalStatusCodeCount.data[0].values.push({
|
|
||||||
label: code,
|
|
||||||
value: totalStatusCodeCount[code],
|
|
||||||
text: statusCodeText
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Total Status Code Count graph render
|
|
||||||
if (vm.graph.totalStatusCodeCount.api) {
|
|
||||||
vm.graph.totalStatusCodeCount.api.update();
|
|
||||||
} else {
|
|
||||||
$log.error('fail');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.graph.averageResponseTime.options = {
|
|
||||||
chart: {
|
|
||||||
type: 'lineChart',
|
|
||||||
height: 200,
|
|
||||||
margin: {
|
|
||||||
top: 20,
|
|
||||||
right: 40,
|
|
||||||
bottom: 40,
|
|
||||||
left: 55
|
|
||||||
},
|
|
||||||
x: function (d) {
|
|
||||||
return d.x;
|
|
||||||
},
|
|
||||||
y: function (d) {
|
|
||||||
return d.y;
|
|
||||||
},
|
|
||||||
useInteractiveGuideline: true,
|
|
||||||
xAxis: {
|
|
||||||
tickFormat: function (d) {
|
|
||||||
return d3.time.format('%X')(new Date(d));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
tickFormat: function (d) {
|
|
||||||
return d3.format(',.1f')(d);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
forceY: [0., 1.], // This prevents the chart from showing -1 on Oy when all the input data points
|
|
||||||
// have y = 0. It won't disable the automatic adjustment of the max value.
|
|
||||||
duration: 0 // Bug: Markers will not be drawn if you set this to some other value...
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"enable": true,
|
|
||||||
"text": "Average response time",
|
|
||||||
"css": {
|
|
||||||
"textAlign": "center"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var initialPoint = {
|
|
||||||
x: Date.now() - 3000,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
vm.graph.averageResponseTime.data = [
|
|
||||||
{
|
|
||||||
values: [initialPoint],
|
|
||||||
key: 'Average response time (ms)',
|
|
||||||
type: 'line',
|
|
||||||
color: '#2ca02c'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update average response time graph
|
|
||||||
*
|
|
||||||
* @param {Number} x Coordinate X
|
|
||||||
* @param {Number} y Coordinate Y
|
|
||||||
*/
|
|
||||||
function updateAverageResponseTimeGraph(x, y) {
|
|
||||||
|
|
||||||
// x multiply 1000 by because unix time is in seconds and JS Date are in milliseconds
|
|
||||||
var data = {
|
|
||||||
x: x * 1000,
|
|
||||||
y: y * 1000
|
|
||||||
};
|
|
||||||
vm.graph.averageResponseTime.data[0].values.push(data);
|
|
||||||
// limit graph entries
|
|
||||||
if (vm.graph.averageResponseTime.data[0].values.length > 100) {
|
|
||||||
vm.graph.averageResponseTime.data[0].values.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Average Response Time graph render
|
|
||||||
if (vm.graph.averageResponseTime.api) {
|
|
||||||
vm.graph.averageResponseTime.api.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the timestamp as "x seconds ago", etc.
|
|
||||||
*
|
|
||||||
* @param {String} t Timestamp returned from the API
|
|
||||||
*/
|
|
||||||
function formatTimestamp(t) {
|
|
||||||
return moment(t, "YYYY-MM-DDTHH:mm:ssZ").fromNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all graph's datas
|
|
||||||
*
|
|
||||||
* @param {Object} health Health data from server
|
|
||||||
*/
|
|
||||||
function loadData(health) {
|
|
||||||
// Load datas and update Average Response Time graph render
|
|
||||||
updateAverageResponseTimeGraph(health.unixtime, health.average_response_time_sec);
|
|
||||||
|
|
||||||
// Load datas and update Total Status Code Count graph render
|
|
||||||
updateTotalStatusCodeCount(health.total_status_code_count);
|
|
||||||
|
|
||||||
// Format the timestamps
|
|
||||||
if (health.recent_errors) {
|
|
||||||
angular.forEach(health.recent_errors, function(i) {
|
|
||||||
i.time_formatted = formatTimestamp(i.time);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// set data's view
|
|
||||||
vm.health = health;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action when load datas failed
|
|
||||||
*
|
|
||||||
* @param {Object} error Error state object
|
|
||||||
*/
|
|
||||||
function erroData(error) {
|
|
||||||
vm.health = {};
|
|
||||||
$log.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// first load
|
|
||||||
Health.get(loadData, erroData);
|
|
||||||
|
|
||||||
// Auto refresh data
|
|
||||||
var intervalId = $interval(function () {
|
|
||||||
Health.get(loadData, erroData);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
// Stop auto refresh when page change
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
$interval.cancel(intervalId);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = HealthController;
|
|
|
@ -1,73 +0,0 @@
|
||||||
<div>
|
|
||||||
<h1 class="text-danger">
|
|
||||||
<span class="glyphicon glyphicon-heart" aria-hidden="true"></span> Health
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div>
|
|
||||||
<nvd3 options="healthCtrl.graph.averageResponseTime.options" data="healthCtrl.graph.averageResponseTime.data" api="healthCtrl.graph.averageResponseTime.api"></nvd3>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">
|
|
||||||
<span>Total response time :</span><span class="badge">{{healthCtrl.health.total_response_time}}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">
|
|
||||||
<span>PID :</span><span class="badge">{{healthCtrl.health.pid}}</span>
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item">
|
|
||||||
<span>Uptime :</span><span class="badge">{{healthCtrl.health.uptime}}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div>
|
|
||||||
<nvd3 options="healthCtrl.graph.totalStatusCodeCount.options" data="healthCtrl.graph.totalStatusCodeCount.data" api="healthCtrl.graph.totalStatusCodeCount.api"></nvd3>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">
|
|
||||||
<span>Total count :</span><span class="badge">{{healthCtrl.health.total_count}}</span>
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item">
|
|
||||||
<span>Count :</span><span class="badge">{{healthCtrl.health.count}}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-if="healthCtrl.health.recent_errors">
|
|
||||||
<h3>Recent HTTP Errors</h3>
|
|
||||||
<table class="table table-striped table-bordered">
|
|
||||||
<tr>
|
|
||||||
<td>Status</td>
|
|
||||||
<td>Request</td>
|
|
||||||
<td>Time</td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-repeat="entry in healthCtrl.health.recent_errors"
|
|
||||||
ng-class="{'text-danger': entry.status_code >= 500}">
|
|
||||||
<td>{{ entry.status_code }} — {{ entry.status }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge">{{ entry.method }}</span>
|
|
||||||
|
|
||||||
{{ entry.host }}{{ entry.path }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span title="{{ entry.time }}">
|
|
||||||
{{ entry.time_formatted }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-if="healthCtrl.health.recent_errors.length == 0">
|
|
||||||
<td colspan="3">
|
|
||||||
<p class="text-muted text-center">No entries</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,24 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
var traefikCoreHealth = require('../../core/health.resource');
|
|
||||||
var HealthController = require('./health.controller');
|
|
||||||
|
|
||||||
var traefikSectionHealth = 'traefik.section.health';
|
|
||||||
module.exports = traefikSectionHealth;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikSectionHealth, [traefikCoreHealth])
|
|
||||||
.controller('HealthController', HealthController)
|
|
||||||
.config(config);
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function config($stateProvider) {
|
|
||||||
|
|
||||||
$stateProvider.state('health', {
|
|
||||||
url: '/health',
|
|
||||||
template: require('./health.html'),
|
|
||||||
controller: 'HealthController',
|
|
||||||
controllerAs: 'healthCtrl'
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function backendMonitor() {
|
|
||||||
return {
|
|
||||||
restrict: 'EA',
|
|
||||||
template: require('./backend-monitor.html'),
|
|
||||||
controller: BackendMonitorController,
|
|
||||||
controllerAs: 'backendCtrl',
|
|
||||||
bindToController: true,
|
|
||||||
scope: {
|
|
||||||
backend: '='
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function BackendMonitorController() {
|
|
||||||
// Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = backendMonitor;
|
|
|
@ -1,23 +0,0 @@
|
||||||
<div class="panel panel-success">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<strong><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{backendCtrl.backend.backendId}}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<table class="panel-table__servers table table-striped table-hover">
|
|
||||||
<tr>
|
|
||||||
<td><em>Server</em></td>
|
|
||||||
<td><em>URL</em></td>
|
|
||||||
<td><em>Weight</em></td>
|
|
||||||
</tr>
|
|
||||||
<tr data-ng-repeat="(serverId, server) in backendCtrl.backend.servers">
|
|
||||||
<td>{{serverId}}</td>
|
|
||||||
<td><code><a data-ng-href="{{server.url}}">{{server.url}}</a></code></td>
|
|
||||||
<td>{{server.weight}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="panel-footer" data-ng-show="backendCtrl.backend.loadBalancer || backendCtrl.backend.circuitBreaker">
|
|
||||||
<span data-ng-show="backendCtrl.backend.loadBalancer" class="label label-success">Load Balancer: {{backendCtrl.backend.loadBalancer.method}}</span>
|
|
||||||
<span data-ng-show="backendCtrl.backend.circuitBreaker" class="label label-success">Circuit Breaker: {{backendCtrl.backend.circuitBreaker.expression}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,10 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
var backendMonitor = require('./backend-monitor.directive');
|
|
||||||
|
|
||||||
var traefikBackendMonitor = 'traefik.section.providers.backend-monitor';
|
|
||||||
module.exports = traefikBackendMonitor;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikBackendMonitor, [])
|
|
||||||
.directive('backendMonitor', backendMonitor);
|
|
|
@ -1,20 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function frontendMonitor() {
|
|
||||||
return {
|
|
||||||
restrict: 'EA',
|
|
||||||
template: require('./frontend-monitor.html'),
|
|
||||||
controller: FrontendMonitorController,
|
|
||||||
controllerAs: 'frontendCtrl',
|
|
||||||
bindToController: true,
|
|
||||||
scope: {
|
|
||||||
frontend: '='
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function FrontendMonitorController() {
|
|
||||||
// Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = frontendMonitor;
|
|
|
@ -1,29 +0,0 @@
|
||||||
<div class="panel panel-warning">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<strong><span class="glyphicon glyphicon-globe" aria-hidden="true"></span> {{frontendCtrl.frontend.frontendId}}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<table class="panel-table__routes table table-striped table-hover">
|
|
||||||
<tr>
|
|
||||||
<td><em>Route</em></td>
|
|
||||||
<td><em>Rule</em></td>
|
|
||||||
</tr>
|
|
||||||
<tr data-ng-repeat="(routeId, route) in frontendCtrl.frontend.routes">
|
|
||||||
<td>{{routeId}}</td>
|
|
||||||
<td><code>{{route.rule}}</code></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
|
|
||||||
<span data-ng-repeat="entryPoint in frontendCtrl.frontend.entryPoints">
|
|
||||||
<span class="label label-primary">{{entryPoint}}</span><span data-ng-hide="$last"> </span>
|
|
||||||
</span>
|
|
||||||
<span data-ng-show="frontendCtrl.frontend.redirect" class="label label-success">Redirect to {{frontendCtrl.frontend.redirect}}</span>
|
|
||||||
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">Backend:{{frontendCtrl.frontend.backend}}</span>
|
|
||||||
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">PassHostHeader</span>
|
|
||||||
<span data-ng-repeat="whitelistSourceRange in frontendCtrl.frontend.whitelistSourceRange">
|
|
||||||
<span class="label label-warning">Whitelist {{ whitelistSourceRange }}</span>
|
|
||||||
</span>
|
|
||||||
<span data-ng-show="frontendCtrl.frontend.priority" class="label label-warning">Priority:{{frontendCtrl.frontend.priority}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,10 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
var frontendMonitor = require('./frontend-monitor.directive');
|
|
||||||
|
|
||||||
var traefikFrontendMonitor = 'traefik.section.providers.frontend-monitor';
|
|
||||||
module.exports = traefikFrontendMonitor;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikFrontendMonitor, [])
|
|
||||||
.directive('frontendMonitor', frontendMonitor);
|
|
|
@ -1,33 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function ProvidersController($scope, $interval, $log, Providers) {
|
|
||||||
const vm = this;
|
|
||||||
|
|
||||||
function loadProviders() {
|
|
||||||
Providers
|
|
||||||
.get()
|
|
||||||
.then(providers => {
|
|
||||||
if (!_.isEqual(vm.previousProviders, providers)) {
|
|
||||||
vm.providers = providers;
|
|
||||||
vm.previousProviders = _.cloneDeep(providers);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
vm.providers = {};
|
|
||||||
$log.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadProviders();
|
|
||||||
|
|
||||||
const intervalId = $interval(loadProviders, 2000);
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
$interval.cancel(intervalId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ProvidersController;
|
|
|
@ -1,22 +0,0 @@
|
||||||
<div>
|
|
||||||
<div><input type="text" data-ng-model="providersCtrl.providerFilter" placeholder="Filter" class="form-control"></div>
|
|
||||||
<br>
|
|
||||||
<uib-tabset>
|
|
||||||
<uib-tab data-ng-repeat="(providerId, provider) in providersCtrl.providers" heading="{{providerId}}">
|
|
||||||
|
|
||||||
<div class="row tabset-row__providers">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div data-ng-repeat="frontend in provider.frontends | filter: providersCtrl.providerFilter ">
|
|
||||||
<frontend-monitor data-provider-id="providerId" data-frontend="frontend"></frontend-monitor>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div data-ng-repeat="backend in provider.backends | filter: providersCtrl.providerFilter">
|
|
||||||
<backend-monitor data-provider-id="providerId" data-backend="backend"></backend-monitor>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</uib-tab>
|
|
||||||
</uib-tabset>
|
|
||||||
</div>
|
|
|
@ -1,30 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
var traefikCoreProvider = require('../../core/providers.resource');
|
|
||||||
var ProvidersController = require('./providers.controller');
|
|
||||||
var traefikBackendMonitor = require('./backend-monitor/backend-monitor.module');
|
|
||||||
var traefikFrontendMonitor = require('./frontend-monitor/frontend-monitor.module');
|
|
||||||
|
|
||||||
var traefikSectionProviders = 'traefik.section.providers';
|
|
||||||
module.exports = traefikSectionProviders;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikSectionProviders, [
|
|
||||||
traefikCoreProvider,
|
|
||||||
traefikBackendMonitor,
|
|
||||||
traefikFrontendMonitor
|
|
||||||
])
|
|
||||||
.config(config)
|
|
||||||
.controller('ProvidersController', ProvidersController);
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function config($stateProvider) {
|
|
||||||
|
|
||||||
$stateProvider.state('provider', {
|
|
||||||
url: '/',
|
|
||||||
template: require('./providers.html'),
|
|
||||||
controller: 'ProvidersController',
|
|
||||||
controllerAs: 'providersCtrl'
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
require('nvd3');
|
|
||||||
var ndv3 = require('angular-nvd3');
|
|
||||||
var traefikSectionHealth = require('./health/health.module');
|
|
||||||
var traefikSectionProviders = require('./providers/providers.module');
|
|
||||||
|
|
||||||
var traefikSection = 'traefik.section';
|
|
||||||
module.exports = traefikSection;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikSection, [
|
|
||||||
'ui.router',
|
|
||||||
'ui.bootstrap',
|
|
||||||
ndv3,
|
|
||||||
traefikSectionProviders,
|
|
||||||
traefikSectionHealth
|
|
||||||
])
|
|
||||||
.config(config);
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function config($urlRouterProvider) {
|
|
||||||
$urlRouterProvider.otherwise('/');
|
|
||||||
}
|
|
88
webui/src/app/services/api.service.ts
Normal file
88
webui/src/app/services/api.service.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/observable/empty';
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
|
import 'rxjs/add/operator/catch';
|
||||||
|
import 'rxjs/add/operator/retry';
|
||||||
|
|
||||||
|
export interface ProviderType {
|
||||||
|
[provider: string]: {
|
||||||
|
backends: any;
|
||||||
|
frontends: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ApiService {
|
||||||
|
headers: HttpHeaders;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
this.headers = new HttpHeaders({
|
||||||
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchVersion(): Observable<any> {
|
||||||
|
return this.http.get(`/api/version`, { headers: this.headers })
|
||||||
|
.retry(4)
|
||||||
|
.catch((err: HttpErrorResponse) => {
|
||||||
|
console.error(`[version] returned code ${err.status}, body was: ${err.error}`);
|
||||||
|
return Observable.empty<any>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchHealthStatus(): Observable<any> {
|
||||||
|
return this.http.get(`/health`, { headers: this.headers })
|
||||||
|
.retry(2)
|
||||||
|
.catch((err: HttpErrorResponse) => {
|
||||||
|
console.error(`[health] returned code ${err.status}, body was: ${err.error}`);
|
||||||
|
return Observable.empty<any>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchProviders(): Observable<any> {
|
||||||
|
return this.http.get(`/api/providers`, { headers: this.headers })
|
||||||
|
.retry(2)
|
||||||
|
.catch((err: HttpErrorResponse) => {
|
||||||
|
console.error(`[providers] returned code ${err.status}, body was: ${err.error}`);
|
||||||
|
return Observable.of<any>({});
|
||||||
|
})
|
||||||
|
.map(this.parseProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseProviders(data: any): ProviderType {
|
||||||
|
return Object.keys(data)
|
||||||
|
.filter(value => value !== 'acme' && value !== 'ACME')
|
||||||
|
.reduce((acc, curr) => {
|
||||||
|
acc[curr] = {
|
||||||
|
backends: Object.keys(data[curr].backends || {}).map(key => {
|
||||||
|
data[curr].backends[key].id = key;
|
||||||
|
data[curr].backends[key].servers = Object.keys(data[curr].backends[key].servers || {}).map(server => {
|
||||||
|
return {
|
||||||
|
title: server,
|
||||||
|
url: data[curr].backends[key].servers[server].url,
|
||||||
|
weight: data[curr].backends[key].servers[server].weight
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return data[curr].backends[key];
|
||||||
|
}),
|
||||||
|
frontends: Object.keys(data[curr].frontends || {}).map(key => {
|
||||||
|
data[curr].frontends[key].id = key;
|
||||||
|
data[curr].frontends[key].routes = Object.keys(data[curr].frontends[key].routes || {}).map(route => {
|
||||||
|
return {
|
||||||
|
title: route,
|
||||||
|
rule: data[curr].frontends[key].routes[route].rule
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return data[curr].frontends[key];
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
17
webui/src/app/services/window.service.ts
Normal file
17
webui/src/app/services/window.service.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { EventManager } from '@angular/platform-browser';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WindowService {
|
||||||
|
resize: Subject<any>;
|
||||||
|
|
||||||
|
constructor(private eventManager: EventManager) {
|
||||||
|
this.resize = new Subject();
|
||||||
|
this.eventManager.addGlobalEventListener('window', 'resize', this.onResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
onResize = (event: UIEvent) => {
|
||||||
|
this.resize.next(event.target);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +0,0 @@
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'charterregular';
|
|
||||||
src: url('./assets/fonts/charter_regular-webfont.eot');
|
|
||||||
src: url('./assets/fonts/charter_regular-webfont.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('./assets/fonts/charter_regular-webfont.woff') format('woff');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.traefik-blue {
|
|
||||||
color: #00B1FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.traefik-text {
|
|
||||||
font-family: 'charterregular', Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-body .panel-table__servers,
|
|
||||||
.panel-body .panel-table__routes {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabset-row__providers {
|
|
||||||
margin-top: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function VersionController($scope, Version) {
|
|
||||||
Version.get(function (version) {
|
|
||||||
$scope.version = version;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = VersionController;
|
|
|
@ -1,11 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
var traefikCoreVersion = require('../core/version.resource');
|
|
||||||
var VersionController = require('./version.controller');
|
|
||||||
|
|
||||||
var traefikVersion = 'traefik.version';
|
|
||||||
module.exports = traefikVersion;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(traefikVersion, [traefikCoreVersion])
|
|
||||||
.controller('VersionController', VersionController);
|
|
BIN
webui/src/assets/fonts/OpenSans-Bold.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-Bold.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-BoldItalic.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-ExtraBold.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-ExtraBold.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-ExtraBoldItalic.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-ExtraBoldItalic.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-Italic.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-Italic.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-Light.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-Light.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-LightItalic.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-LightItalic.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-Regular.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-Regular.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-Semibold.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-Semibold.ttf
Executable file
Binary file not shown.
BIN
webui/src/assets/fonts/OpenSans-SemiboldItalic.ttf
Executable file
BIN
webui/src/assets/fonts/OpenSans-SemiboldItalic.ttf
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
30
webui/src/assets/images/loader.svg
Normal file
30
webui/src/assets/images/loader.svg
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<svg version="1.1" id="L5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
|
||||||
|
<circle fill="#0294FF" stroke="none" cx="6" cy="50" r="6">
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="1s"
|
||||||
|
type="translate"
|
||||||
|
values="0 15 ; 0 -15; 0 15"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.1"/>
|
||||||
|
</circle>
|
||||||
|
<circle fill="#0294FF" stroke="none" cx="30" cy="50" r="6">
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="1s"
|
||||||
|
type="translate"
|
||||||
|
values="0 10 ; 0 -10; 0 10"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.2"/>
|
||||||
|
</circle>
|
||||||
|
<circle fill="#0294FF" stroke="none" cx="54" cy="50" r="6">
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="1s"
|
||||||
|
type="translate"
|
||||||
|
values="0 5 ; 0 -5; 0 5"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.3"/>
|
||||||
|
</circle>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 973 B |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
227
webui/src/assets/images/traefik.logo.svg
Normal file
227
webui/src/assets/images/traefik.logo.svg
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
enable-background="new 0 0 595.28 841.89"
|
||||||
|
viewBox="0 0 340 456.33044"
|
||||||
|
height="486.75247"
|
||||||
|
width="362.66666"
|
||||||
|
y="0px"
|
||||||
|
x="0px"
|
||||||
|
id="Calque_1"
|
||||||
|
version="1.1"><metadata
|
||||||
|
id="metadata3280"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs3278" /><path
|
||||||
|
style="fill:#c9781f"
|
||||||
|
id="path3156"
|
||||||
|
d="m 65.412121,155.07154 c 0,0 24.66784,-21.47409 97.677319,-21.47409 66.97804,0 85.80047,15.19398 104.91761,21.47409 l -99.73639,48.73542 z" /><g
|
||||||
|
transform="matrix(0.63926204,0,0,0.63926204,-21.039129,-84.874827)"
|
||||||
|
id="g3158"><path
|
||||||
|
style="fill:#f6d2a2"
|
||||||
|
id="path3160"
|
||||||
|
d="m 118.946,476.458 c 0.707,14.572 15.264,7.83 21.858,3.274 6.259,-4.325 8.089,-0.73 8.638,-9.266 0.36,-5.61 1.007,-11.22 0.688,-16.853 -9.464,-0.858 -19.759,1.396 -27.518,7.033 -3.996,2.905 -11.49,12.174 -3.666,15.812" /><path
|
||||||
|
style="fill:#c6b198"
|
||||||
|
id="path3162"
|
||||||
|
d="m 118.946,476.458 c 2.119,-0.788 4.364,-1.348 5.802,-3.264" /><path
|
||||||
|
style="fill:#37abc8"
|
||||||
|
id="path3164"
|
||||||
|
d="M 152.588,302.861 C 96.804,287.174 138.284,216.207 183.08,245.397 Z" /><path
|
||||||
|
style="fill:#37abc8"
|
||||||
|
id="path3166"
|
||||||
|
d="m 400.436,240.071 c 44.155,-31.014 84.056,38.959 32.74,56.565 z" /><path
|
||||||
|
style="fill:#f6d2a2"
|
||||||
|
id="path3168"
|
||||||
|
d="m 409.934,655.8 c 11.216,6.94 31.716,27.923 14.891,38.098 -16.166,14.802 -25.214,-16.247 -39.403,-20.549 6.111,-8.298 13.856,-15.865 24.512,-17.549 z" /><path
|
||||||
|
style="fill:none"
|
||||||
|
id="path3170"
|
||||||
|
d="m 424.825,693.897 c -2.494,-4.96 -3.332,-10.748 -7.496,-14.746" /><path
|
||||||
|
style="fill:#f6d2a2"
|
||||||
|
id="path3172"
|
||||||
|
d="m 209.561,679.514 c -13.164,2.037 -20.574,13.914 -31.548,19.945 -10.341,6.166 -14.297,-1.974 -15.229,-3.627 -1.621,-0.739 -1.485,0.688 -3.987,-1.831 -9.587,-15.13 9.989,-26.189 20.182,-33.705 14.198,-2.871 23.096,9.438 30.582,19.218 z" /><path
|
||||||
|
style="fill:none"
|
||||||
|
id="path3174"
|
||||||
|
d="m 162.785,695.831 c 0.501,-5.766 5.074,-9.628 7.251,-14.504" /><path
|
||||||
|
style="fill:#077e91"
|
||||||
|
id="path3176"
|
||||||
|
d="m 154.916,283.26 c -7.36,-3.893 -12.759,-9.18 -8.257,-17.693 4.168,-7.88 11.911,-7.025 19.271,-3.132 z" /><path
|
||||||
|
style="fill:#077e91"
|
||||||
|
id="path3178"
|
||||||
|
d="m 421.549,275.859 c 7.36,-3.893 12.759,-9.18 8.257,-17.693 -4.168,-7.881 -11.91,-7.025 -19.271,-3.132 z" /><path
|
||||||
|
style="fill:#f6d2a2"
|
||||||
|
id="path3180"
|
||||||
|
d="m 472.21,474.607 c -0.707,14.572 -15.264,7.83 -21.858,3.274 -6.259,-4.325 -8.089,-0.73 -8.638,-9.265 -0.36,-5.61 -1.007,-11.22 -0.688,-16.853 9.464,-0.858 19.759,1.396 27.518,7.033 3.996,2.904 11.49,12.174 3.666,15.811" /><path
|
||||||
|
style="fill:#c6b198"
|
||||||
|
id="path3182"
|
||||||
|
d="m 472.21,474.607 c -2.119,-0.788 -4.364,-1.348 -5.802,-3.264" /><g
|
||||||
|
id="g3184"><path
|
||||||
|
style="fill:#37abc8"
|
||||||
|
id="path3186"
|
||||||
|
d="m 289.988,210.595 c 55.847,0 108.2,7.987 135.492,61.642 24.496,60.141 15.785,124.993 19.521,188.553 3.208,54.577 10.322,117.629 -14.997,168.205 -26.635,53.21 -93.191,66.595 -148.026,64.634 -43.071,-1.541 -95.101,-15.593 -119.409,-54.944 -28.519,-46.165 -15.017,-114.81 -12.946,-166.179 2.454,-60.849 -16.482,-121.882 3.508,-181.425 20.737,-61.765 76.665,-75.724 136.857,-80.486" /></g><path
|
||||||
|
style="fill:#ffffff"
|
||||||
|
id="path3188"
|
||||||
|
d="m 299.847,285.567 c 10.027,58.288 105.304,42.877 91.619,-15.91 -12.271,-52.716 -94.951,-38.124 -91.619,15.91" /><path
|
||||||
|
style="fill:#ffffff"
|
||||||
|
id="path3190"
|
||||||
|
d="m 185.992,294.994 c 12.996,50.745 94.24,37.753 91.178,-13.149 -3.669,-60.964 -103.603,-49.2 -91.178,13.149" /><path
|
||||||
|
style="fill:#ffffff"
|
||||||
|
id="path3192"
|
||||||
|
d="m 318.343,353.511 c 0.044,7.79 1.843,15.403 0.289,24.148 -1.935,3.656 -5.729,4.043 -9.001,5.52 -4.524,-0.71 -8.328,-3.68 -10.143,-7.912 -1.161,-9.202 0.433,-18.111 0.726,-27.316 z" /><g
|
||||||
|
id="g3194"><ellipse
|
||||||
|
id="ellipse3196"
|
||||||
|
ry="14.86"
|
||||||
|
rx="13.719"
|
||||||
|
cy="286.71799"
|
||||||
|
cx="208.39999" /><ellipse
|
||||||
|
style="fill:#ffffff"
|
||||||
|
id="ellipse3198"
|
||||||
|
ry="3.777"
|
||||||
|
rx="3.234"
|
||||||
|
cy="290.07101"
|
||||||
|
cx="214.64" /></g><g
|
||||||
|
id="g3200"><ellipse
|
||||||
|
id="ellipse3202"
|
||||||
|
ry="14.86"
|
||||||
|
rx="13.491"
|
||||||
|
cy="283.017"
|
||||||
|
cx="323.34799" /><ellipse
|
||||||
|
style="fill:#ffffff"
|
||||||
|
id="ellipse3204"
|
||||||
|
ry="3.777"
|
||||||
|
rx="3.181"
|
||||||
|
cy="286.371"
|
||||||
|
cx="329.48499" /></g><path
|
||||||
|
style="fill:#ffffff"
|
||||||
|
id="path3206"
|
||||||
|
d="m 279.137,354.685 c -5.986,14.507 3.338,43.515 19.579,22.119 -1.161,-9.202 0.433,-18.111 0.726,-27.316 z" /><g
|
||||||
|
id="g3208"><path
|
||||||
|
style="fill:#f6d2a2"
|
||||||
|
id="path3210"
|
||||||
|
d="m 278.185,326.748 c -11.156,0.951 -20.276,14.216 -14.475,24.71 7.682,13.9 24.828,-1.23 35.507,0.188 12.291,0.252 22.361,12.996 32.233,2.304 10.979,-11.892 -4.727,-23.474 -17.002,-28.652 z" /></g></g><g
|
||||||
|
transform="matrix(0.63926204,0,0,0.63926204,-21.039129,-84.874827)"
|
||||||
|
id="g3212"><path
|
||||||
|
style="fill:#ef9325"
|
||||||
|
id="path3214"
|
||||||
|
d="m 135.236,375.349 c 0,0 3.532,38.261 3.29,54.754 -0.242,16.492 15.56,6.079 16.38,32.016 0.82,25.937 -9.572,21.018 -15.047,33.713 -5.475,12.694 -9.196,72.145 -9.196,72.145 0,0 5.618,11.108 23.166,21.11 17.548,10.002 45.765,15.62 70.198,14.513 24.433,-1.107 41.885,-6.341 46.249,-9.236 4.363,-2.894 11.605,-12.05 14.329,-19.131 2.724,-7.081 8.415,-65.402 8.018,-98.775 -0.397,-33.373 -5.911,-64.166 -5.911,-64.166 z" /><path
|
||||||
|
style="fill:#e5e5e5"
|
||||||
|
id="path3216"
|
||||||
|
d="m 290.007,537.841 c -71.952,8.032 -155.439,-14.172 -155.439,-14.172 0,0 1.226,-22.521 6.626,-30.447 74.833,23.185 150.329,15.834 150.329,15.834 1.12,10.514 0.331,18.872 -1.516,28.785 z" /><path
|
||||||
|
style="fill:#e5e5e5"
|
||||||
|
id="path3218"
|
||||||
|
d="m 280.95,582.228 c -71.952,8.032 -150.287,-15.064 -150.287,-15.064 0,0 -0.251,-21.631 2.607,-31.13 74.833,23.185 154.763,17.407 154.763,17.407 -0.673,11.413 -2.278,22.646 -7.083,28.787 z" /></g><g
|
||||||
|
transform="matrix(0.63926204,0,0,0.63926204,-21.039129,-84.874827)"
|
||||||
|
id="g3220"><path
|
||||||
|
style="fill:#ef9325"
|
||||||
|
id="path3222"
|
||||||
|
d="m 452.156,375.349 c 0,0 -0.488,38.261 -0.243,54.754 0.245,16.492 -9.578,6.079 -10.409,32.016 -0.831,25.937 9.692,21.018 15.236,33.713 5.544,12.694 9.312,72.145 9.312,72.145 0,0 -5.689,11.108 -23.457,21.11 -17.768,10.002 -46.34,15.62 -71.08,14.513 -24.74,-1.107 -42.411,-6.341 -46.83,-9.236 -4.419,-2.895 -11.751,-12.05 -14.509,-19.131 -2.758,-7.081 -8.521,-65.402 -8.119,-98.775 0.402,-33.373 5.985,-64.166 5.985,-64.166 z" /><path
|
||||||
|
style="fill:#e5e5e5"
|
||||||
|
id="path3224"
|
||||||
|
d="m 304.706,537.841 c 72.856,8.032 157.392,-14.172 157.392,-14.172 0,0 -1.242,-22.521 -6.709,-30.447 -75.774,23.185 -152.218,15.834 -152.218,15.834 -1.134,10.514 -0.335,18.872 1.535,28.785 z" /><path
|
||||||
|
style="fill:#e5e5e5"
|
||||||
|
id="path3226"
|
||||||
|
d="m 313.876,582.228 c 72.856,8.032 152.175,-15.064 152.175,-15.064 0,0 0.254,-21.631 -2.64,-31.13 -75.774,23.185 -156.707,17.407 -156.707,17.407 0.682,11.413 2.307,22.646 7.172,28.787 z" /></g><g
|
||||||
|
transform="matrix(0.63926204,0,0,0.63926204,-21.039129,-84.874827)"
|
||||||
|
id="g3228"><path
|
||||||
|
style="fill:#d2e261"
|
||||||
|
id="path3230"
|
||||||
|
d="m 481.221,445.525 c -2.264,4.272 -6.745,6.334 -10.009,4.604 l -2.615,-1.385 c -3.264,-1.729 -4.076,-6.595 -1.812,-10.867 l 50.985,-96.234 c 2.263,-4.272 6.745,-6.334 10.009,-4.604 l 2.615,1.385 c 3.264,1.729 4.076,6.595 1.812,10.867 z" /><path
|
||||||
|
id="path3232"
|
||||||
|
d="m 457.143,490.972 c -1.319,2.489 -5.034,3.105 -8.298,1.375 l -2.615,-1.385 c -3.264,-1.729 -4.842,-5.149 -3.523,-7.638 l 23.92,-45.149 c 1.319,-2.489 5.034,-3.105 8.298,-1.375 l 2.615,1.385 c 3.264,1.729 4.842,5.149 3.523,7.638 z" /><path
|
||||||
|
style="fill:#9b9b9b"
|
||||||
|
id="path3234"
|
||||||
|
d="m 478.411,436.54 -2.615,-1.385 c -3.264,-1.729 -6.604,-1.823 -7.459,-0.21 l -1.529,2.886 c 0.855,-1.614 4.194,-1.52 7.459,0.21 l 2.615,1.385 c 3.264,1.729 5.218,4.44 4.363,6.053 l 1.529,-2.886 c 0.855,-1.613 -1.099,-4.323 -4.363,-6.053 z" /></g><ellipse
|
||||||
|
style="fill:#f6d2a2"
|
||||||
|
id="ellipse3236"
|
||||||
|
ry="8.6829996"
|
||||||
|
rx="11.224"
|
||||||
|
cy="462.66101"
|
||||||
|
cx="456.83801"
|
||||||
|
transform="matrix(0.59911638,0.2229746,-0.2229746,0.59911638,100.48325,-168.1724)" /><g
|
||||||
|
transform="matrix(0.63926204,0,0,0.63926204,-21.039129,-84.874827)"
|
||||||
|
id="g3238"><path
|
||||||
|
style="fill:#d2e261"
|
||||||
|
id="path3240"
|
||||||
|
d="m 111.779,447.078 c 2.065,4.372 6.447,6.637 9.787,5.059 l 2.675,-1.264 c 3.34,-1.578 4.374,-6.401 2.309,-10.773 L 80.032,341.629 c -2.065,-4.372 -6.447,-6.637 -9.787,-5.059 l -2.675,1.264 c -3.34,1.578 -4.374,6.401 -2.309,10.773 z" /><path
|
||||||
|
id="path3242"
|
||||||
|
d="m 133.748,493.582 c 1.203,2.547 4.886,3.332 8.227,1.755 l 2.675,-1.264 c 3.34,-1.578 5.073,-4.922 3.869,-7.469 l -21.825,-46.199 c -1.203,-2.547 -4.886,-3.332 -8.227,-1.755 l -2.675,1.264 c -3.34,1.578 -5.073,4.922 -3.869,7.469 z" /><path
|
||||||
|
style="fill:#9b9b9b"
|
||||||
|
id="path3244"
|
||||||
|
d="m 114.998,438.232 2.675,-1.264 c 3.34,-1.578 6.68,-1.519 7.46,0.132 l 1.395,2.954 c -0.78,-1.651 -4.12,-1.71 -7.46,-0.133 l -2.675,1.264 c -3.34,1.578 -5.416,4.196 -4.636,5.847 l -1.395,-2.954 c -0.779,-1.65 1.296,-4.268 4.636,-5.846 z" /></g><ellipse
|
||||||
|
style="fill:#f6d2a2"
|
||||||
|
id="ellipse3246"
|
||||||
|
ry="8.6829996"
|
||||||
|
rx="11.224"
|
||||||
|
cy="463.922"
|
||||||
|
cx="137.56"
|
||||||
|
transform="matrix(-0.59911638,0.2229746,-0.2229746,-0.59911638,252.76813,458.95304)" /><g
|
||||||
|
transform="matrix(0.63926204,0,0,0.63926204,-21.039129,-84.874827)"
|
||||||
|
id="g3248"><g
|
||||||
|
id="g3250"><path
|
||||||
|
style="fill:#960000"
|
||||||
|
id="path3252"
|
||||||
|
d="m 159.132,324.732 c -2.218,4.033 -7.45,5.414 -11.687,3.084 l -4.873,-2.679 c -4.237,-2.33 -5.873,-7.488 -3.656,-11.521 l 50.714,-92.23 c 2.218,-4.033 7.45,-5.414 11.687,-3.084 l 4.873,2.679 c 4.237,2.33 5.33,6.689 3.656,11.521 -17.765,51.258 -50.714,92.23 -50.714,92.23 z" /><path
|
||||||
|
style="fill:#595959"
|
||||||
|
id="path3254"
|
||||||
|
d="m 172.547,272.051 c 15.422,-28.047 25.555,-52.169 23.141,-54.905 l 0.057,-0.103 c -0.066,-0.036 -0.136,-0.06 -0.202,-0.096 -0.008,-0.005 -0.01,-0.021 -0.019,-0.026 l -0.007,0.014 c -23.699,-12.841 -55.583,-0.124 -71.334,28.522 -15.751,28.646 -9.41,62.381 14.128,75.514 l -0.007,0.014 c 0.009,0.005 0.023,-0.002 0.032,0.002 0.066,0.037 0.123,0.083 0.189,0.119 l 0.057,-0.103 c 3.602,0.573 18.543,-20.905 33.965,-48.952 z" /></g><g
|
||||||
|
id="g3256"><path
|
||||||
|
style="fill:#960000"
|
||||||
|
id="path3258"
|
||||||
|
d="m 426.693,324.925 c 2.1,4.095 7.291,5.627 11.593,3.42 l 4.948,-2.538 c 4.302,-2.206 6.087,-7.315 3.987,-11.41 L 399.19,220.742 c -2.1,-4.095 -7.291,-5.627 -11.593,-3.42 l -4.948,2.538 c -4.302,2.206 -5.521,6.533 -3.987,11.41 16.279,51.749 48.031,93.655 48.031,93.655 z" /><path
|
||||||
|
style="fill:#595959"
|
||||||
|
id="path3260"
|
||||||
|
d="m 414.804,271.879 c -14.606,-28.48 -24.039,-52.885 -21.547,-55.55 l -0.054,-0.105 c 0.067,-0.034 0.138,-0.056 0.205,-0.09 0.009,-0.005 0.011,-0.021 0.02,-0.025 l 0.007,0.014 c 24.059,-12.152 55.563,1.48 70.481,30.569 14.918,29.089 7.606,62.627 -16.301,75.075 l 0.007,0.014 c -0.009,0.004 -0.023,-0.003 -0.032,10e-4 -0.067,0.035 -0.125,0.08 -0.192,0.114 l -0.054,-0.105 c -3.618,0.468 -17.934,-21.432 -32.54,-49.912 z" /></g><path
|
||||||
|
style="fill:#353535"
|
||||||
|
id="path3262"
|
||||||
|
d="m 462.36,259.869 c 0,0 -17.746,-44.446 -38.326,-67.945 -20.58,-23.498 -221.937,-31.512 -255.003,0 -33.066,31.512 -45.533,67.945 -45.533,67.945 v -9.844 c 0,0 15.295,-43.268 45.533,-67.945 30.238,-24.677 228.946,-23.378 254.582,0 25.635,23.378 38.747,67.945 38.747,67.945 z" /><ellipse
|
||||||
|
style="fill:#960000"
|
||||||
|
id="ellipse3264"
|
||||||
|
ry="16.975"
|
||||||
|
rx="6.9489999"
|
||||||
|
cy="248.211"
|
||||||
|
cx="462.09201"
|
||||||
|
transform="matrix(-0.8898,0.4563,-0.4563,-0.8898,986.5333,258.1984)" /><ellipse
|
||||||
|
style="fill:#960000"
|
||||||
|
id="ellipse3266"
|
||||||
|
ry="16.975"
|
||||||
|
rx="6.9489999"
|
||||||
|
cy="247.02901"
|
||||||
|
cx="125.962"
|
||||||
|
transform="matrix(0.8763,0.4818,-0.4818,0.8763,134.6121,-30.1261)" /></g><g
|
||||||
|
transform="matrix(0.63926204,0,0,0.63926204,-21.039129,-84.874827)"
|
||||||
|
id="g3268"><path
|
||||||
|
style="opacity:0.6;fill:#ffffff"
|
||||||
|
id="path3270"
|
||||||
|
d="M 386.49,250.492 H 349.285 319.791 274.555 249 198.68 c -33.72,0 -33.604,89.606 -1.945,94.771 l 65.466,-4.21 c 7.915,0 23.719,-30.655 26.684,-33.065 2.965,-2.41 12.729,-2.704 16.575,0 3.846,2.704 12.593,29.976 20.507,29.976 l 65.466,7.298 c 30.579,-14.072 22.87,-94.77 -4.943,-94.77 z" /><path
|
||||||
|
style="opacity:0.5;fill:#ffffff"
|
||||||
|
id="path3272"
|
||||||
|
d="m 248.545,269.019 c -20.685,-0.462 -53.05,-0.274 -70.729,-0.13 -2.964,9.719 -6.269,22.024 -4.63,33.972 16.056,0 50.96,0.078 75.782,0.078 14.946,0 27.512,2.82 39.198,5.875 0.288,-0.368 0.53,-0.639 0.719,-0.792 2.965,-2.41 12.729,-2.704 16.575,0 0.995,0.7 2.323,3.058 3.874,6.149 5.967,1.258 11.943,2.127 18.153,2.205 16.915,0.214 56.557,0.146 82.066,0.074 3.249,-10.857 0.853,-22.495 0.125,-33.432 -24.906,0 -64.487,-0.562 -83.463,-0.562 -26.738,-0.001 -50.083,-12.821 -77.67,-13.437 z" /></g><path
|
||||||
|
id="path3274"
|
||||||
|
d="m 155.89646,123.63135 c -0.52803,-12.35565 23.02878,-13.90011 25.81085,-3.55813 2.77504,10.31833 -24.65122,12.71492 -25.81085,3.55813 -0.92565,-7.31124 0,0 0,0 z" /><g
|
||||||
|
transform="matrix(0.16363642,0,0,0.16363643,2.8667513,26.432443)"
|
||||||
|
id="g4788"><g
|
||||||
|
transform="matrix(4.1803662,0,0,4.1803662,-117.56255,1825.0688)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
id="text3009"><path
|
||||||
|
d="m 119.09119,162.63228 h 4.14844 c 1.68746,0 3.04684,-0.0469 4.07813,-0.14063 1.07808,-0.0937 2.13277,-0.30468 3.16406,-0.63281 1.07808,-0.32812 1.87495,-0.84375 2.39062,-1.54687 0.51558,-0.75 0.77339,-1.71094 0.77344,-2.88282 -5e-5,-1.31249 -0.44536,-2.46093 -1.33594,-3.44531 -0.89067,-0.98437 -2.10942,-1.47655 -3.65625,-1.47656 h -0.42187 l -4.78125,0.21094 h -0.77344 c -2.95316,0 -5.10941,-1.05468 -6.46875,-3.16407 -1.3594,-2.15623 -2.03909,-5.64842 -2.03906,-10.47656 v -38.32031 h 12.9375 c 3.0937,6e-5 4.64058,-1.335877 4.64062,-4.007814 -4e-5,-1.265559 -0.39848,-2.296808 -1.19531,-3.09375 -0.75004,-0.796806 -1.89848,-1.195243 -3.44531,-1.195313 h -12.9375 V 72.069778 c -3e-5,-1.640533 -0.46878,-2.88272 -1.40625,-3.726562 -0.93753,-0.890531 -2.1094,-1.335843 -3.51563,-1.335938 -1.50002,9.5e-5 -2.88283,0.539157 -4.14843,1.617188 -1.21877,1.078217 -1.87502,2.367278 -1.96875,3.867187 l -1.68751,19.96875 h -8.64844 c -1.546881,7e-5 -2.718754,0.398507 -3.515625,1.195313 -0.796878,0.750067 -1.195315,1.734441 -1.195313,2.953125 -2e-6,1.265689 0.398435,2.2735 1.195313,3.023437 0.843745,0.750062 2.039057,1.125062 3.585937,1.125002 h 8.578128 v 40.5 c -1e-5,7.26564 1.59374,12.65626 4.78126,16.17187 3.18747,3.46875 7.47653,5.20313 12.86718,5.20313"
|
||||||
|
style="font-size:144px;font-family:Nunito;-inkscape-font-specification:Nunito;fill:#333333"
|
||||||
|
id="path3014" /><path
|
||||||
|
d="m 154.80994,161.6479 c 1.73436,0 3.23436,-0.51562 4.5,-1.54687 1.31248,-1.03125 1.96873,-2.48437 1.96875,-4.35938 v -36.21093 c -2e-5,-5.48433 1.73435,-9.86714 5.20313,-13.14844 3.51559,-3.28119 8.43746,-4.92182 14.76562,-4.92188 1.49996,6e-5 2.6484,-0.49212 3.44532,-1.476559 0.79682,-1.031187 1.19526,-2.249936 1.19531,-3.65625 -5e-5,-1.499933 -0.44536,-2.812432 -1.33594,-3.9375 -0.84379,-1.12493 -2.01567,-1.687429 -3.51562,-1.6875 -4.96879,7.1e-5 -9.23441,1.476632 -12.79688,4.429687 -3.51565,2.906314 -5.8594,6.539122 -7.03125,10.898442 l 0.0703,-8.437504 c -2e-5,-1.781184 -0.63283,-3.163995 -1.89843,-4.148438 -1.21877,-1.03118 -2.69534,-1.546805 -4.42969,-1.546875 -1.73439,7e-5 -3.23439,0.515695 -4.5,1.546875 -1.26564,0.984443 -1.89845,2.414129 -1.89844,4.289063 v 58.007809 c -1e-5,1.87501 0.60936,3.32813 1.82813,4.35938 1.21873,1.03125 2.69529,1.54687 4.42968,1.54687"
|
||||||
|
style="font-size:144px;font-family:Nunito;-inkscape-font-specification:Nunito;fill:#333333"
|
||||||
|
id="path3016" /><path
|
||||||
|
d="m 326.8058,161.6479 c 1.73435,0 3.23435,-0.53906 4.5,-1.61718 1.2656,-1.125 1.89841,-2.67187 1.89844,-4.64063 v -54.63281 h 11.88281 c 2.99996,6e-5 4.49996,-1.359314 4.5,-4.078127 -4e-5,-1.218683 -0.37504,-2.226495 -1.125,-3.023437 -0.70317,-0.796806 -1.82817,-1.195243 -3.375,-1.195313 h -11.88281 v -5.484375 c -3e-5,-3.796796 0.14059,-6.796793 0.42187,-9 0.3281,-2.249914 0.91404,-3.960849 1.75782,-5.132812 0.89059,-1.21866 1.8984,-1.992097 3.02343,-2.320313 1.12497,-0.374908 2.69528,-0.562408 4.71094,-0.5625 h 5.97656 c 1.40621,9.2e-5 2.48433,-0.468657 3.23438,-1.40625 0.79682,-0.937406 1.19526,-2.062404 1.19531,-3.375 -5e-5,-1.312402 -0.39849,-2.437401 -1.19531,-3.375 -0.75005,-0.984274 -1.80474,-1.476461 -3.16406,-1.476562 h -8.57813 c -6.28128,1.01e-4 -11.20315,1.781349 -14.76562,5.34375 -3.56252,3.515717 -5.34377,9.468836 -5.34375,17.859375 v 8.929687 h -9.21094 c -1.54688,7e-5 -2.69532,0.398507 -3.44531,1.195313 -0.75001,0.796942 -1.12501,1.804754 -1.125,3.023437 -1e-5,1.171939 0.37499,2.156313 1.125,2.953125 0.74999,0.750062 1.89843,1.125062 3.44531,1.125002 h 9.21094 v 54.63281 c -2e-5,1.96876 0.60935,3.51563 1.82812,4.64063 1.26561,1.07812 2.76561,1.61718 4.5,1.61718"
|
||||||
|
style="font-size:144px;font-family:Nunito;-inkscape-font-specification:Nunito;fill:#333333"
|
||||||
|
id="path3020" /><path
|
||||||
|
d="m 371.17299,75.515091 c 2.48435,8.6e-5 4.47654,-0.703038 5.97656,-2.109375 1.49998,-1.453035 2.24998,-3.328033 2.25,-5.625 -2e-5,-2.343654 -0.75002,-4.218652 -2.25,-5.625 -1.50002,-1.453024 -3.46877,-2.179586 -5.90625,-2.179688 -2.48439,1.02e-4 -4.50001,0.726664 -6.04687,2.179688 -1.50001,1.453223 -2.25001,3.328221 -2.25,5.625 -1e-5,2.296967 0.74999,4.171965 2.25,5.625 1.49998,1.406337 3.49217,2.109461 5.97656,2.109375 m -0.0703,86.132809 c 1.73435,0 3.23435,-0.5625 4.5,-1.6875 1.31247,-1.17187 1.96872,-2.74218 1.96875,-4.71093 V 98.085403 c -3e-5,-1.968684 -0.63284,-3.515557 -1.89844,-4.640625 -1.21877,-1.12493 -2.6719,-1.68743 -4.35938,-1.6875 -1.73439,7e-5 -3.25782,0.56257 -4.57031,1.6875 -1.26564,1.125068 -1.89845,2.671941 -1.89844,4.640625 v 57.164067 c -1e-5,2.0625 0.60937,3.65625 1.82813,4.78125 1.21873,1.07812 2.6953,1.61718 4.42969,1.61718"
|
||||||
|
style="font-size:144px;font-family:Nunito;-inkscape-font-specification:Nunito;fill:#333333"
|
||||||
|
id="path3022" /><path
|
||||||
|
d="m 404.92299,161.6479 c 1.73436,0 3.23435,-0.5625 4.5,-1.6875 1.31248,-1.12499 1.96873,-2.6953 1.96875,-4.71093 v -30.65625 l 33.04687,35.15625 c 1.17182,1.3125 2.57807,1.96875 4.21875,1.96875 1.54682,0 2.90619,-0.58594 4.07813,-1.75782 1.21868,-1.17187 1.82806,-2.5078 1.82812,-4.00781 -6e-5,-1.35937 -0.51569,-2.57812 -1.54687,-3.65625 l -27.91406,-29.39062 25.52343,-22.85157 c 1.12494,-1.078059 1.68744,-2.27337 1.6875,-3.585934 -6e-5,-1.453058 -0.586,-2.765557 -1.75781,-3.9375 -1.12506,-1.171805 -2.43756,-1.757742 -3.9375,-1.757813 -1.17193,7.1e-5 -2.27349,0.445383 -3.30469,1.335938 L 411.39174,121.35884 V 67.569778 c -2e-5,-1.968654 -0.63284,-3.515527 -1.89844,-4.640625 -1.21877,-1.1249 -2.67189,-1.687399 -4.35937,-1.6875 -1.73439,1.01e-4 -3.25783,0.5626 -4.57032,1.6875 -1.26563,1.125098 -1.89844,2.671971 -1.89843,4.640625 v 87.679692 c -1e-5,2.0625 0.60936,3.65625 1.82812,4.78125 1.21874,1.07812 2.6953,1.61718 4.42969,1.61718"
|
||||||
|
style="font-size:144px;font-family:Nunito;-inkscape-font-specification:Nunito;fill:#333333"
|
||||||
|
id="path3024" /></g><path
|
||||||
|
style="display:inline;fill:#37abc8;fill-opacity:1;stroke:none"
|
||||||
|
d="m 776.62606,2500.7168 c -33.86547,-2.9196 -59.74328,-13.1348 -78.56521,-31.0137 -19.14975,-18.1909 -27.36664,-41.9069 -24.12384,-69.6286 3.73164,-31.9013 18.89136,-54.4293 45.47029,-67.5719 20.48497,-10.1294 40.92086,-13.9825 74.2015,-13.9904 23.6457,-0.01 43.45939,1.9263 73.67896,7.1839 7.4724,1.3001 13.94181,2.3644 14.37649,2.3653 0.63211,0 1.13342,-2.3782 2.5045,-11.8865 2.48071,-17.2034 2.92533,-23.1396 2.24949,-30.0309 -0.78666,-8.0217 -1.81332,-11.8425 -4.85232,-18.0592 -7.8409,-16.0392 -26.4539,-25.8526 -56.3431,-29.7057 -8.54805,-1.1019 -28.27642,-1.2561 -36.83948,-0.2884 -14.34651,1.622 -22.35237,3.6887 -40.20696,10.3786 -10.44586,3.9141 -10.4936,3.9262 -14.66034,3.6816 -7.16306,-0.4197 -12.05304,-3.6628 -15.18945,-10.0734 -1.29168,-2.6399 -1.5188,-3.7297 -1.51885,-7.2868 0,-5.3399 1.39069,-8.1747 6.23314,-12.7045 10.85909,-10.1583 29.13255,-18.8091 47.57591,-22.5234 11.80945,-2.3782 17.62271,-2.8782 33.18165,-2.8543 16.09952,0.042 26.39396,0.9338 40.7011,3.5942 23.79706,4.4245 41.86649,12.2234 55.49189,23.9501 4.04288,3.4794 10.13915,10.3276 12.75137,14.3237 0.79841,1.2215 1.6209,2.221 1.8277,2.221 0.20693,0 1.52767,-1.4677 2.9352,-3.2611 20.63182,-26.2903 55.38062,-41.1712 96.0916,-41.1503 28.7948,0 55.5598,7.2994 75.5111,20.5528 15.8247,10.5123 28.6803,26.7063 33.8736,42.6702 2.9819,9.1659 3.4352,12.5529 3.4627,25.8661 0.016,8.2838 -0.2116,13.8713 -0.6736,16.4602 -4.6692,26.1661 -15.7909,44.2934 -35.0951,57.2012 -15.53,10.384 -35.0343,16.4601 -60.5435,18.8601 -12.8411,1.2081 -36.32755,1.094 -51.54251,-0.2509 -12.9128,-1.1411 -28.76889,-3.149 -35.53309,-4.4992 -5.6619,-1.1304 -25.0506,-4.3677 -25.21024,-4.2092 -0.0703,0.071 -0.99285,5.8692 -2.04985,12.888 -1.68766,11.2067 -1.92673,14.003 -1.96214,22.9511 -0.0376,9.0977 0.0915,10.6938 1.18915,14.8925 3.52969,13.5013 11.30818,22.5326 25.42034,29.5146 13.88254,6.8684 28.43586,9.8406 50.42569,10.2988 9.73545,0.2048 14.72115,0.062 20.81185,-0.5827 14.7651,-1.5656 21.4933,-3.2833 41.1098,-10.4948 4.8858,-1.7964 9.872,-3.4309 11.0805,-3.6324 2.5898,-0.4318 7.4198,0.4599 10.2348,1.89 5.081,2.5805 9.3499,9.4627 9.3233,15.0301 -0.029,6.5042 -2.2232,10.4438 -8.6626,15.5672 -15.3968,12.2502 -35.0556,19.7627 -58.5228,22.3646 -8.3681,0.928 -27.4738,1.0647 -36.70232,0.2633 -25.98905,-2.2573 -44.03272,-6.7024 -60.52259,-14.9089 -12.86662,-6.4034 -22.68346,-14.3474 -30.01545,-24.2895 -1.80157,-2.443 -3.33187,-4.5027 -3.40069,-4.578 -0.069,-0.075 -1.26635,1.3361 -2.66126,3.1353 -6.16583,7.9536 -15.59468,16.5025 -24.80897,22.4937 -13.30221,8.6492 -32.39892,15.2475 -51.72224,17.8715 -5.76268,0.7822 -24.88234,1.4276 -29.78512,1.005 z m 33.18166,-37.2321 c 15.77633,-3.4964 29.83536,-11.3772 40.04156,-22.4447 9.30988,-10.0952 16.94591,-25.2189 21.14838,-41.8852 2.36889,-9.395 6.30655,-35.5428 5.43369,-36.082 -0.34447,-0.2132 -2.42546,-0.5497 -4.62386,-0.7487 -2.19846,-0.2007 -8.46495,-0.9389 -13.92555,-1.645 -32.43526,-4.195 -52.33389,-5.8249 -63.48931,-5.1995 -21.20826,1.188 -29.85413,3.0395 -42.58748,9.1207 -6.5233,3.1152 -11.02568,6.4959 -14.5881,10.953 -5.11092,6.3951 -8.19582,12.7501 -10.34612,21.3132 -1.5648,6.2312 -1.72448,22.5471 -0.27214,27.804 2.44652,8.8552 5.35078,13.8671 11.88143,20.5034 8.94252,9.0869 20.73103,15.1488 35.35353,18.18 7.47705,1.5501 7.25577,1.535 19.77505,1.3465 9.08402,-0.1379 12.48275,-0.393 16.19892,-1.2157 z M 1016.4745,2340.883 c 13.1943,-1.1642 21.3072,-3.2126 31.2752,-7.8955 8.2903,-3.8952 13.1088,-7.8762 17.6686,-14.5978 6.7131,-9.8962 9.3272,-19.2614 9.2601,-33.1758 -0.048,-9.9175 -0.8885,-14.0431 -4.3122,-21.1631 -7.6497,-15.908 -25.6199,-27.3028 -49.1887,-31.1902 -5.8812,-0.9698 -21.66658,-0.7976 -27.69493,0.3011 -11.5973,2.116 -21.40043,5.9729 -30.04639,11.8207 -15.97364,10.8042 -26.99764,27.0905 -33.13643,48.9538 -2.41127,8.5877 -3.05405,11.884 -5.20388,26.687 l -1.98309,13.6552 2.65642,0.2926 c 8.02308,0.8896 26.11491,3.0802 36.62189,4.4346 6.6102,0.852 14.72275,1.7612 18.02786,2.0208 9.55476,0.75 26.68545,0.6802 36.05555,-0.1463 v 0 z"
|
||||||
|
id="path4507" /></g></svg>
|
After Width: | Height: | Size: 24 KiB |
3
webui/src/environments/environment.prod.ts
Normal file
3
webui/src/environments/environment.prod.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const environment = {
|
||||||
|
production: true
|
||||||
|
};
|
8
webui/src/environments/environment.ts
Normal file
8
webui/src/environments/environment.ts
Normal file
|
@ -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
|
||||||
|
};
|
BIN
webui/src/favicon.ico
Normal file
BIN
webui/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -1,53 +1,13 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html ng-app="traefik">
|
<html class="has-navbar-fixed-top">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Træfik</title>
|
<title>Traefik</title>
|
||||||
<meta name="description" content="">
|
<base href="./">
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/png" href="traefik.icon.png" />
|
<link rel="icon" type="image/x-icon" href="./assets/images/traefik.icon.png">
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
<body>
|
</body>
|
||||||
<!--[if lt IE 10]>
|
|
||||||
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
|
|
||||||
<![endif]-->
|
|
||||||
<div class="container">
|
|
||||||
<header>
|
|
||||||
<nav class="navbar navbar-default">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<a class="navbar-brand traefik-text" ui-sref="provider"><img height="16" src="traefik.icon.png"/></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse">
|
|
||||||
<ul class="nav navbar-nav">
|
|
||||||
<li><a ui-sref="provider" class="active">Providers</a></li>
|
|
||||||
<li><a ui-sref="health">Health</a></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
<li>
|
|
||||||
<a ng-controller="VersionController" href="https://github.com/containous/traefik/tree/{{version.Version}}" target="_blank">
|
|
||||||
<small>{{version.Version}} / {{version.Codename}}</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.traefik.io" target="_blank">Documentation</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://traefik.io" target="_blank"><span class="traefik-blue">traefik.io</span></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="main">
|
|
||||||
<div data-ui-view></div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var angular = require('angular');
|
|
||||||
var ngAnimate = require('angular-animate');
|
|
||||||
var ngCookies = require('angular-cookies');
|
|
||||||
var ngSanitize = require('angular-sanitize');
|
|
||||||
var ngMessages = require('angular-messages');
|
|
||||||
var ngAria = require('angular-aria');
|
|
||||||
var ngResource = require('angular-resource');
|
|
||||||
var uiRouter = require('angular-ui-router');
|
|
||||||
var uiBootstrap = require('angular-ui-bootstrap');
|
|
||||||
var moment = require('moment');
|
|
||||||
var traefikSection = require('./app/sections/sections');
|
|
||||||
var traefikVersion = require('./app/version/version.module');
|
|
||||||
require('./index.scss');
|
|
||||||
require('animate.css/animate.css');
|
|
||||||
require('nvd3/build/nv.d3.css');
|
|
||||||
require('bootstrap/dist/css/bootstrap.css');
|
|
||||||
|
|
||||||
var app = 'traefik';
|
|
||||||
module.exports = app;
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module(app, [
|
|
||||||
ngAnimate,
|
|
||||||
ngCookies,
|
|
||||||
ngSanitize,
|
|
||||||
ngMessages,
|
|
||||||
ngAria,
|
|
||||||
ngResource,
|
|
||||||
uiRouter,
|
|
||||||
uiBootstrap,
|
|
||||||
traefikSection,
|
|
||||||
traefikVersion
|
|
||||||
])
|
|
||||||
.run(runBlock)
|
|
||||||
.constant('moment', moment)
|
|
||||||
.config(config);
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function config($logProvider) {
|
|
||||||
// Enable log
|
|
||||||
$logProvider.debugEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function runBlock($log) {
|
|
||||||
$log.debug('runBlock end');
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
$icon-font-path: "../bower_components/bootstrap-sass/assets/fonts/bootstrap/";
|
|
||||||
@import 'app/index.scss';
|
|
||||||
@import 'app/traefik.scss';
|
|
12
webui/src/main.ts
Normal file
12
webui/src/main.ts
Normal file
|
@ -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));
|
79
webui/src/polyfills.ts
Normal file
79
webui/src/polyfills.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* 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`.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||||
|
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||||
|
*/
|
||||||
|
|
||||||
|
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||||
|
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||||
|
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||||
|
|
||||||
|
/*
|
||||||
|
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||||
|
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||||
|
*/
|
||||||
|
// (window as any).__Zone_enable_cross_context_check = true;
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* Zone JS is required by default for Angular itself.
|
||||||
|
*/
|
||||||
|
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* APPLICATION IMPORTS
|
||||||
|
*/
|
26
webui/src/styles/app.sass
Normal file
26
webui/src/styles/app.sass
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@charset "utf-8"
|
||||||
|
|
||||||
|
@import 'typography'
|
||||||
|
@import 'colors'
|
||||||
|
@import '../../node_modules/bulma/sass/utilities/all'
|
||||||
|
@import '../../node_modules/bulma/sass/base/all'
|
||||||
|
@import '../../node_modules/bulma/sass/grid/columns'
|
||||||
|
@import '../../node_modules/bulma/sass/elements/container'
|
||||||
|
@import '../../node_modules/bulma/sass/elements/tag'
|
||||||
|
@import '../../node_modules/bulma/sass/elements/box'
|
||||||
|
@import '../../node_modules/bulma/sass/elements/form'
|
||||||
|
@import '../../node_modules/bulma/sass/elements/table'
|
||||||
|
@import '../../node_modules/bulma/sass/components/navbar'
|
||||||
|
@import '../../node_modules/bulma/sass/components/tabs'
|
||||||
|
@import '../../node_modules/bulma/sass/elements/notification'
|
||||||
|
@import 'nav'
|
||||||
|
@import 'content'
|
||||||
|
@import 'message'
|
||||||
|
@import 'label'
|
||||||
|
@import 'charts'
|
||||||
|
@import 'helper'
|
||||||
|
|
||||||
|
html
|
||||||
|
font-family: $open-sans
|
||||||
|
height: 100%
|
||||||
|
background: $background
|
54
webui/src/styles/charts.sass
Normal file
54
webui/src/styles/charts.sass
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
.line-chart
|
||||||
|
width: 100%
|
||||||
|
height: 320px
|
||||||
|
background-color: $white
|
||||||
|
|
||||||
|
svg
|
||||||
|
font: 10px sans-serif
|
||||||
|
|
||||||
|
.line
|
||||||
|
fill: none
|
||||||
|
stroke: $blue
|
||||||
|
stroke-width: 3px
|
||||||
|
shape-rendering: geometricPrecision
|
||||||
|
|
||||||
|
.axis line, .axis path
|
||||||
|
stroke: $text
|
||||||
|
opacity: .2
|
||||||
|
shape-rendering: crispEdges
|
||||||
|
fill: none
|
||||||
|
|
||||||
|
.axis path
|
||||||
|
display: none
|
||||||
|
|
||||||
|
.axis text
|
||||||
|
fill: $text
|
||||||
|
|
||||||
|
|
||||||
|
.bar-chart
|
||||||
|
width: 100%
|
||||||
|
height: 320px
|
||||||
|
background-color: $white
|
||||||
|
|
||||||
|
.bar
|
||||||
|
fill: rgba($blue, 0.91)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
fill: lighten($blue, 10)
|
||||||
|
|
||||||
|
.axis text
|
||||||
|
fill: $text
|
||||||
|
font: 10px sans-serif
|
||||||
|
|
||||||
|
.axis line, .axis path
|
||||||
|
fill: none
|
||||||
|
opacity: .2
|
||||||
|
stroke: $text
|
||||||
|
|
||||||
|
.axis--x
|
||||||
|
|
||||||
|
text
|
||||||
|
font: 12px sans-serif
|
||||||
|
|
||||||
|
path
|
||||||
|
display: none
|
34
webui/src/styles/colors.sass
Normal file
34
webui/src/styles/colors.sass
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
$background: #F1F5F7
|
||||||
|
$border: #DFE2E5
|
||||||
|
$border-secondary: #E6EAEE
|
||||||
|
$text: #7F8FA4
|
||||||
|
$text-dark: #3D495C
|
||||||
|
$text-grey: #8A9AAE
|
||||||
|
|
||||||
|
$color: #354052
|
||||||
|
$color-secondary: #7F8FA4
|
||||||
|
$color-disabled: #C2CAD4
|
||||||
|
$color-search: #A1A7AF
|
||||||
|
|
||||||
|
$border: #DFE2E5
|
||||||
|
$divider: #DFE2E5
|
||||||
|
$border-blue: #2EA2F8
|
||||||
|
$border-light: #f1f3f5
|
||||||
|
|
||||||
|
$blue: #0294FF
|
||||||
|
$blue-secondary: #1991EB
|
||||||
|
$blue-background: #2EA1F8
|
||||||
|
$green: #39B54A
|
||||||
|
$green-secondary: #45B854
|
||||||
|
$green-bg: #36AF47
|
||||||
|
$red: #f03e3e
|
||||||
|
$red-secondary: #ED1C24
|
||||||
|
$yellow: #ffd43b
|
||||||
|
$yellow-secondary: #F7981C
|
||||||
|
$grey: #7F8FA4
|
||||||
|
$grey-light: #adb5bd
|
||||||
|
$orange-secondary: #FD9A18
|
||||||
|
$grey-background: #E8EAF1
|
||||||
|
$grey-color: #8D909F
|
||||||
|
|
||||||
|
$turquoise: #01BCD4
|
142
webui/src/styles/content.sass
Normal file
142
webui/src/styles/content.sass
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
.content
|
||||||
|
background: transparent
|
||||||
|
margin: 40px 0
|
||||||
|
|
||||||
|
.subtitle
|
||||||
|
font-size: 15px
|
||||||
|
text-transform: uppercase
|
||||||
|
color: $black
|
||||||
|
font-weight: $weight-bold
|
||||||
|
text-transform: uppercase
|
||||||
|
margin: 10px 0 0 0
|
||||||
|
|
||||||
|
.list-title
|
||||||
|
color: $text-dark
|
||||||
|
weight: $weight-semibold
|
||||||
|
margin: 5px 0 0 0
|
||||||
|
|
||||||
|
.list-item
|
||||||
|
width: 100%
|
||||||
|
display: block
|
||||||
|
align-items: center
|
||||||
|
font-size: 12px
|
||||||
|
padding: 6px 10px
|
||||||
|
border-top: 1px solid $border-light
|
||||||
|
|
||||||
|
.columns
|
||||||
|
|
||||||
|
.column
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
|
||||||
|
.icon
|
||||||
|
width: 22px
|
||||||
|
height: 22px
|
||||||
|
display: block
|
||||||
|
float: left
|
||||||
|
margin-right: 10px
|
||||||
|
|
||||||
|
.content-item
|
||||||
|
background: $white
|
||||||
|
border: 1px solid $border-secondary
|
||||||
|
margin: 10px 0
|
||||||
|
border-radius: 4px
|
||||||
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
|
|
||||||
|
h2
|
||||||
|
color: $text-dark
|
||||||
|
font-size: 14px
|
||||||
|
padding: 20px 20px 0 20px
|
||||||
|
font-weight: $weight-semibold
|
||||||
|
|
||||||
|
.content-item-data
|
||||||
|
padding: 10px 20px
|
||||||
|
|
||||||
|
.item-data
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: space-between
|
||||||
|
padding: 5px 10px
|
||||||
|
|
||||||
|
&.border-right
|
||||||
|
border-right: 1px solid #DFE3E9
|
||||||
|
|
||||||
|
.data-blue
|
||||||
|
color: $blue
|
||||||
|
font-size: 22px
|
||||||
|
font-weight: $weight-semibold
|
||||||
|
|
||||||
|
.data-grey
|
||||||
|
color: $grey
|
||||||
|
font-size: 12px
|
||||||
|
font-weight: $weight-light
|
||||||
|
|
||||||
|
.widget-item
|
||||||
|
min-height: 80px
|
||||||
|
padding: 20px
|
||||||
|
|
||||||
|
h1
|
||||||
|
color: $text-dark
|
||||||
|
font-size: 18px
|
||||||
|
font-weight: $weight-light
|
||||||
|
|
||||||
|
img
|
||||||
|
width: 40px
|
||||||
|
heught: 40px
|
||||||
|
display: block
|
||||||
|
float: left
|
||||||
|
margin-right: 10px
|
||||||
|
|
||||||
|
span
|
||||||
|
font-size: 13px
|
||||||
|
display: block
|
||||||
|
|
||||||
|
&.mtop12
|
||||||
|
margin-top: 12px
|
||||||
|
|
||||||
|
.loading-text
|
||||||
|
height: 320px
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
|
||||||
|
.main-loader
|
||||||
|
width: 70px
|
||||||
|
display: block
|
||||||
|
margin: 15px auto
|
||||||
|
|
||||||
|
.search-container
|
||||||
|
height: 50px
|
||||||
|
background: $white
|
||||||
|
border-radius: 4px
|
||||||
|
color: $black
|
||||||
|
margin: 10px 0
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
position: relative
|
||||||
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
|
border: 1px solid $border-secondary
|
||||||
|
|
||||||
|
.icon
|
||||||
|
position: absolute
|
||||||
|
left: 10px
|
||||||
|
top: 13px
|
||||||
|
|
||||||
|
input
|
||||||
|
font-size: 16px
|
||||||
|
color: $text
|
||||||
|
width: 100%
|
||||||
|
height: 48px
|
||||||
|
padding-left: 50px
|
||||||
|
border: none
|
||||||
|
outline: none
|
||||||
|
font-weight: $weight-light
|
||||||
|
border-radius: 4px
|
||||||
|
|
||||||
|
.notification
|
||||||
|
background: $white
|
||||||
|
border-radius: 4px
|
||||||
|
color: $text
|
||||||
|
font-size: 16px
|
||||||
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
|
border: 1px solid $border-secondary
|
2
webui/src/styles/helper.sass
Normal file
2
webui/src/styles/helper.sass
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.padding-5-10
|
||||||
|
padding: 5px 10px
|
29
webui/src/styles/label.sass
Normal file
29
webui/src/styles/label.sass
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
.label
|
||||||
|
padding: 5px 10px
|
||||||
|
background: $white
|
||||||
|
color: $color
|
||||||
|
font-size: 12px
|
||||||
|
font-family: $weight-semibold
|
||||||
|
width: 100%
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
border: 1px solid $border
|
||||||
|
background: linear-gradient(0deg, #F2F4F7 0%, #FFFFFF 100%)
|
||||||
|
|
||||||
|
&.green
|
||||||
|
background: $green-secondary
|
||||||
|
|
||||||
|
&.red
|
||||||
|
background: $red-secondary
|
||||||
|
|
||||||
|
&.yellow
|
||||||
|
background: $yellow-secondary
|
||||||
|
|
||||||
|
&.blue
|
||||||
|
background: $blue-secondary
|
||||||
|
|
||||||
|
span
|
||||||
|
display: inline-flex
|
||||||
|
float: left
|
||||||
|
align-items: center
|
89
webui/src/styles/message.sass
Normal file
89
webui/src/styles/message.sass
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
.message
|
||||||
|
display: block
|
||||||
|
font-size: 14px
|
||||||
|
margin: 20px 0 30px 0
|
||||||
|
border: 1px solid $border
|
||||||
|
background: $white
|
||||||
|
border-radius: 4px
|
||||||
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
|
|
||||||
|
.message-header
|
||||||
|
color: $color-secondary
|
||||||
|
border-bottom: 1px solid $border-secondary
|
||||||
|
padding: 20px 10px
|
||||||
|
background: #f8f9fa
|
||||||
|
border-top-left-radius: 4px
|
||||||
|
border-top-right-radius: 4px
|
||||||
|
|
||||||
|
h2
|
||||||
|
font-size: 14px
|
||||||
|
weight: $weight-bold
|
||||||
|
display: flex
|
||||||
|
justify-content: space-between
|
||||||
|
|
||||||
|
&.red
|
||||||
|
background: rgba($red-secondary, 0.4)
|
||||||
|
border-bottom: 1px solid $red-secondary
|
||||||
|
color: $red-secondary
|
||||||
|
|
||||||
|
p
|
||||||
|
color: $red-secondary
|
||||||
|
|
||||||
|
&.green
|
||||||
|
background-color: rgba($green-secondary, 0.4)
|
||||||
|
border-bottom: 1px solid $green-secondary
|
||||||
|
color: $green-secondary
|
||||||
|
|
||||||
|
p
|
||||||
|
color: darken($green-secondary, 10) !important
|
||||||
|
|
||||||
|
&.orange
|
||||||
|
background-color: rgba($orange-secondary, 0.4)
|
||||||
|
border-bottom: 1px solid $orange-secondary
|
||||||
|
color: $orange-secondary
|
||||||
|
|
||||||
|
p
|
||||||
|
color: $orange-secondary
|
||||||
|
|
||||||
|
&.blue
|
||||||
|
background-color: rgba($blue-background, 0.4)
|
||||||
|
border-bottom: 1px solid $blue-background
|
||||||
|
color: $blue-background
|
||||||
|
|
||||||
|
p
|
||||||
|
color: $blue-background !important
|
||||||
|
|
||||||
|
img
|
||||||
|
margin-right: 15px
|
||||||
|
|
||||||
|
.message-body
|
||||||
|
|
||||||
|
.field
|
||||||
|
margin: 5px 10px
|
||||||
|
padding-bottom: 10px
|
||||||
|
|
||||||
|
.tags-list
|
||||||
|
margin: 5px 10px
|
||||||
|
|
||||||
|
.control
|
||||||
|
width: 100%
|
||||||
|
margin: 5px 0
|
||||||
|
|
||||||
|
.tags
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.tag
|
||||||
|
width: 50%
|
||||||
|
|
||||||
|
h2
|
||||||
|
margin: 10px 10px 0 10px
|
||||||
|
color: $black
|
||||||
|
|
||||||
|
hr
|
||||||
|
margin: 5px 0
|
||||||
|
|
||||||
|
.message-subheader
|
||||||
|
border-bottom: 1px solid $border-secondary
|
||||||
|
padding: 10px
|
||||||
|
margin-bottom: 5px
|
||||||
|
|
16
webui/src/styles/nav.sass
Normal file
16
webui/src/styles/nav.sass
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.navbar
|
||||||
|
border-bottom: 1px solid $border
|
||||||
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
|
height: 60px
|
||||||
|
|
||||||
|
.navbar-item
|
||||||
|
font-size: 13px
|
||||||
|
text-transform: uppercase
|
||||||
|
font-weight: $weight-semibold
|
||||||
|
|
||||||
|
.navbar-logo
|
||||||
|
width: 40px
|
||||||
|
min-height: 40px
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: transparent
|
14
webui/src/styles/typography.sass
Normal file
14
webui/src/styles/typography.sass
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
=font-face($family, $path, $weight: normal, $style: normal)
|
||||||
|
@font-face
|
||||||
|
font-family: $family
|
||||||
|
src: url('#{$path}.ttf') format('truetype')
|
||||||
|
font-weight: $weight
|
||||||
|
font-style: $style
|
||||||
|
|
||||||
|
+font-face('Open Sans', '/assets/fonts/OpenSans-Light', 300, 'light')
|
||||||
|
+font-face('Open Sans', '/assets/fonts/OpenSans-Regular', 400, 'regular')
|
||||||
|
+font-face('Open Sans', '/assets/fonts/OpenSans-Semibold', 600, 'semibold')
|
||||||
|
+font-face('Open Sans', '/assets/fonts/OpenSans-Bold', 700, 'bold')
|
||||||
|
+font-face('Open Sans', '/assets/fonts/OpenSans-ExtraBold', 800, 'extrabold')
|
||||||
|
|
||||||
|
$open-sans: 'Open Sans', sans-serif
|
20
webui/src/test.ts
Normal file
20
webui/src/test.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||||
|
|
||||||
|
import 'zone.js/dist/zone-testing';
|
||||||
|
import { getTestBed } from '@angular/core/testing';
|
||||||
|
import {
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting
|
||||||
|
} from '@angular/platform-browser-dynamic/testing';
|
||||||
|
|
||||||
|
declare const require: any;
|
||||||
|
|
||||||
|
// 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);
|
13
webui/src/tsconfig.app.json
Normal file
13
webui/src/tsconfig.app.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/app",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"module": "es2015",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"test.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue