mirror of
https://github.com/traefik/traefik.git
synced 2026-06-19 07:36:07 +00:00
New web ui
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
/traefik
|
||||
/traefik.toml
|
||||
/static/
|
||||
/webui/.tmp/
|
||||
.vscode/
|
||||
/site/
|
||||
*.log
|
||||
|
||||
+14
-4
@@ -14,9 +14,19 @@ type DashboardHandler struct{}
|
||||
// AddRoutes add dashboard routes on a router
|
||||
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
||||
// Expose dashboard
|
||||
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)
|
||||
})
|
||||
router.Methods(http.MethodGet).PathPrefix("/dashboard/").
|
||||
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)
|
||||
})
|
||||
|
||||
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"})))
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
# http://editorconfig.org
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
* text=auto
|
||||
+43
-5
@@ -1,6 +1,44 @@
|
||||
.tmp/
|
||||
coverage/
|
||||
dist/
|
||||
node_modules/
|
||||
.sass-cache/
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -19,4 +19,4 @@ RUN yarn install
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
+46
-91
@@ -1,103 +1,58 @@
|
||||
{
|
||||
"name": "traefik",
|
||||
"version": "2.0.0",
|
||||
"homepage": "http://traefik.io",
|
||||
"version": "3.0.0",
|
||||
"authors": [
|
||||
"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",
|
||||
"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": {
|
||||
"angular": "^1.4.2",
|
||||
"angular-animate": "^1.5.8",
|
||||
"angular-aria": "^1.5.8",
|
||||
"angular-cookies": "^1.5.8",
|
||||
"angular-messages": "^1.5.8",
|
||||
"angular-nvd3": "^1.0.8",
|
||||
"angular-resource": "^1.5.8",
|
||||
"angular-sanitize": "^1.5.8",
|
||||
"angular-ui-bootstrap": "^2.0.0",
|
||||
"angular-ui-router": "^0.3.1",
|
||||
"animate.css": "^3.4.0",
|
||||
"bootstrap": "^3.3.6",
|
||||
"http-status-codes": "^1.3.0",
|
||||
"@angular/animations": "^5.2.0",
|
||||
"@angular/common": "^5.2.0",
|
||||
"@angular/compiler": "^5.2.0",
|
||||
"@angular/core": "^5.2.0",
|
||||
"@angular/forms": "^5.2.0",
|
||||
"@angular/http": "^5.2.0",
|
||||
"@angular/platform-browser": "^5.2.0",
|
||||
"@angular/platform-browser-dynamic": "^5.2.0",
|
||||
"@angular/router": "^5.2.0",
|
||||
"@fortawesome/fontawesome": "^1.1.5",
|
||||
"@fortawesome/fontawesome-free-solid": "^5.0.10",
|
||||
"bulma": "^0.6.2",
|
||||
"core-js": "^2.4.1",
|
||||
"d3": "^4.13.0",
|
||||
"date-fns": "^1.29.0",
|
||||
"lodash": "^4.17.5",
|
||||
"moment": "^2.14.1",
|
||||
"nvd3": "^1.8.4"
|
||||
"rxjs": "^5.5.6",
|
||||
"zone.js": "^0.8.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "^1.4.2",
|
||||
"autoprefixer": "^6.2.2",
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"browser-sync": "^2.9.11",
|
||||
"browser-sync-spa": "^1.0.3",
|
||||
"css-loader": "^0.23.1",
|
||||
"del": "^2.0.2",
|
||||
"es6-shim": "^0.35.0",
|
||||
"eslint": "^2.11.0",
|
||||
"eslint-config-angular": "^0.5.0",
|
||||
"eslint-config-xo-space": "^0.12.0",
|
||||
"eslint-loader": "^1.3.0",
|
||||
"eslint-plugin-angular": "^1.3.0",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"gulp": "gulpjs/gulp#4ed9a4a3275559c73a396eff7e1fde3824951ebb",
|
||||
"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"
|
||||
]
|
||||
"@angular/cli": "~1.7.2",
|
||||
"@angular/compiler-cli": "^5.2.0",
|
||||
"@angular/language-service": "^5.2.0",
|
||||
"@types/jasmine": "~2.8.3",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "^4.0.1",
|
||||
"jasmine-core": "~2.8.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~2.0.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.1.2",
|
||||
"ts-node": "~4.1.0",
|
||||
"tslint": "~5.9.1",
|
||||
"typescript": "~2.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 } }));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:8080",
|
||||
"secure": false
|
||||
},
|
||||
"/health": {
|
||||
"target": "http://localhost:8080",
|
||||
"secure": false
|
||||
}
|
||||
}
|
||||
+5
-19
@@ -19,7 +19,7 @@ make generate-webui # Generate static contents in `traefik/static/` folder.
|
||||
|
||||
## 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`
|
||||
alias.
|
||||
@@ -51,29 +51,15 @@ make generate-webui # Generate static contents in `traefik/static/` folder.
|
||||
|
||||
- Go to the directory `webui`
|
||||
- Edit files in `webui/src`
|
||||
|
||||
- Run in development mode :
|
||||
- `yarn run serve`
|
||||
|
||||
- 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`.
|
||||
|
||||
- `yarn start`
|
||||
|
||||
## Libraries
|
||||
|
||||
- [Node](https://nodejs.org)
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- [Generator FountainJS](https://github.com/FountainJS/generator-fountain-webapp)
|
||||
- [Webpack](https://github.com/webpack/webpack)
|
||||
- [AngularJS](https://docs.angularjs.org/api)
|
||||
- [UI Router](https://github.com/angular-ui/ui-router)
|
||||
- [UI Router - Documentation](https://github.com/angular-ui/ui-router/wiki)
|
||||
- [Bootstrap](https://getbootstrap.com)
|
||||
- [Angular Bootstrap](https://angular-ui.github.io/bootstrap)
|
||||
- [Angular](https://angular.io)
|
||||
- [Bulma](https://bulma.io)
|
||||
- [D3](https://d3js.org)
|
||||
- [D3 - Documentation](https://github.com/mbostock/d3/wiki)
|
||||
- [NVD3](http://nvd3.org)
|
||||
- [Angular nvD3](https://krispo.github.io/angular-nvd3)
|
||||
- [D3 - Documentation](https://github.com/mbostock/d3/wiki)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"plugins": ["angular"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jasmine": true
|
||||
},
|
||||
"globals": {
|
||||
"angular": true,
|
||||
"module": true,
|
||||
"inject": true
|
||||
}
|
||||
}
|
||||
@@ -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!');
|
||||
}));
|
||||
});
|
||||
@@ -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 { }
|
||||
@@ -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 { }
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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('/');
|
||||
}
|
||||
@@ -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;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -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 |
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
+11
-51
@@ -1,53 +1,13 @@
|
||||
<!doctype html>
|
||||
<html ng-app="traefik">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Træfik</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="icon" type="image/png" href="traefik.icon.png" />
|
||||
</head>
|
||||
|
||||
|
||||
<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 class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Traefik</title>
|
||||
<base href="./">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="./assets/images/traefik.icon.png">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</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';
|
||||
@@ -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));
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
.padding-5-10
|
||||
padding: 5px 10px
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
Reference in New Issue
Block a user