mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 20:49:00 +08:00
🔥 Removing code or files. 删除 hystrix 监控
This commit is contained in:
1 parent
a16150ba59
commit
e3468b5c8d
173 files changed
+11
-40953
No files matched your search
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
};
|
||||
-16341
File diff suppressed because it is too large.
Load diff
@@ -1,133 +0,0 @@
|
||||
{
|
||||
"name": "spring-boot-admin-server-ui",
|
||||
"description": "Spring Boot Admin UI",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"watch": "vue-cli-service build --watch",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.8",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.5.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.5.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.5.0",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.2",
|
||||
"ansi_up": "^3.0.0",
|
||||
"autolinker": "^1.7.1",
|
||||
"axios": "^0.18.0",
|
||||
"bulma": "^0.7.2",
|
||||
"bulma-badge": "^2.0.0",
|
||||
"d3-array": "^1.2.4",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-brush": "^1.0.6",
|
||||
"d3-scale": "^2.1.2",
|
||||
"d3-selection": "^1.3.2",
|
||||
"d3-shape": "^1.2.2",
|
||||
"d3-time": "^1.0.10",
|
||||
"element-ui": "^2.4.10",
|
||||
"event-source-polyfill": "^1.0.3",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.22.2",
|
||||
"moment-shortformat": "^2.1.0",
|
||||
"popper.js": "^1.14.4",
|
||||
"pretty-bytes": "^5.1.0",
|
||||
"resize-observer-polyfill": "^1.5.0",
|
||||
"rxjs": "^6.3.3",
|
||||
"vue": "^2.5.17",
|
||||
"vue-clickaway": "2.2.1",
|
||||
"vue-router": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.1.1",
|
||||
"@vue/cli-plugin-eslint": "^3.1.4",
|
||||
"@vue/cli-plugin-unit-jest": "^3.1.1",
|
||||
"@vue/cli-service": "^3.1.2",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.20",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-plugin-vue": "^5.0.0-0",
|
||||
"html-loader": "^0.5.5",
|
||||
"node-sass": "^4.9.4",
|
||||
"sass-loader": "^7.0.1",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack-bundle-analyzer": "^3.0.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/strongly-recommended"
|
||||
],
|
||||
"rules": {
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"no-console": [
|
||||
"error",
|
||||
{
|
||||
"allow": [
|
||||
"warn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"vue/max-attributes-per-line": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.spec.js"
|
||||
],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"jest": {
|
||||
"setupFiles": [
|
||||
"<rootDir>/src/test/frontend/globals.js"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"vue"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.vue$": "vue-jest",
|
||||
".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
|
||||
"^.+\\.js$": "babel-jest"
|
||||
},
|
||||
"moduleNameMapper": {
|
||||
"^@/(.*)$": "<rootDir>/src/main/frontend/$1"
|
||||
},
|
||||
"snapshotSerializers": [
|
||||
"jest-serializer-vue"
|
||||
],
|
||||
"testMatch": [
|
||||
"**/*.spec.js"
|
||||
],
|
||||
"testURL": "http://example.com"
|
||||
}
|
||||
}
|
||||
@@ -48,22 +48,6 @@
|
||||
<artifactId>spring-boot-admin-starter-server</artifactId>
|
||||
<version>${spring-boot-admin.version}</version>
|
||||
</dependency>
|
||||
<!--hystrix-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
|
||||
</dependency>
|
||||
<!--turbine-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--Redis-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -79,6 +63,11 @@
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
<!--sentinel 依赖-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-sentinel</artifactId>
|
||||
</dependency>
|
||||
<!--web 模块-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*!
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Import the initial variables
|
||||
@import "~bulma/sass/utilities/initial-variables";
|
||||
@import "~bulma/sass/utilities/functions";
|
||||
|
||||
$turquoise: rgb(66, 211, 165);
|
||||
|
||||
// overrides
|
||||
$primary: $turquoise;
|
||||
$primary-invert: $white;
|
||||
|
||||
// Add new color variables to the color map.
|
||||
@import "~bulma/sass/utilities/derived-variables.sass";
|
||||
|
||||
$addColors: ();
|
||||
$colors: map-merge($colors, $addColors);
|
||||
|
||||
$link: $primary;
|
||||
|
||||
$modal-card-head-background-color: $white;
|
||||
$modal-content-width: calc(100vh - 40px);
|
||||
|
||||
$navbar-background-color: $black-ter;
|
||||
$navbar-item-color: $grey;
|
||||
$navbar-item-hover-color: $white;
|
||||
$navbar-item-hover-background-color: $navbar-background-color;
|
||||
$navbar-item-active-color: $white;
|
||||
$navbar-item-active-background-color: $navbar-background-color;
|
||||
|
||||
@import "~bulma/sass/utilities/mixins.sass";
|
||||
|
||||
// for calculations / originally rem
|
||||
$navbar-height-px: 52px;
|
||||
@@ -1,140 +0,0 @@
|
||||
/*!
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@import "utilities";
|
||||
// Import the rest of Bulma
|
||||
@import "~bulma/bulma";
|
||||
@import "~bulma-badge/dist/css/bulma-badge";
|
||||
|
||||
html {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > div {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.is-breakable {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
pre.is-breakable {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.monospaced {
|
||||
font-family: $family-monospace;
|
||||
}
|
||||
|
||||
.is-muted {
|
||||
color: $grey-light;
|
||||
}
|
||||
|
||||
.is-selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button.is-icon {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-link::after {
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
.navbar .badge {
|
||||
&[data-badge]::after {
|
||||
box-shadow: 0 0 0 $badge-border-size $navbar-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
//Loading spinner
|
||||
.section.is-loading,
|
||||
.content.is-loading {
|
||||
&::after {
|
||||
@include loader;
|
||||
height: 5rem;
|
||||
width: 5rem;
|
||||
left: calc(50% - 2.5em);
|
||||
top: calc(50% - 2.5em);
|
||||
border-width: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
p.is-loading {
|
||||
&::before {
|
||||
display: inline-block;
|
||||
right: 0.25em;
|
||||
@include loader;
|
||||
}
|
||||
}
|
||||
|
||||
//hero as card-header
|
||||
.card .hero {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
//Bullets
|
||||
.has-bullet::before {
|
||||
background: currentColor;
|
||||
content: '';
|
||||
width: 0.75em;
|
||||
height: 0.75em;
|
||||
display: inline-block;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
@each $name, $pair in $colors {
|
||||
.has-bullet.has-bullet-#{$name}::before {
|
||||
$color: nth($pair, 1);
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
//D3 Charts
|
||||
.tick {
|
||||
& line {
|
||||
stroke: currentColor;
|
||||
}
|
||||
& text {
|
||||
fill: currentColor;
|
||||
font-size: $size-7;
|
||||
}
|
||||
}
|
||||
|
||||
path.domain {
|
||||
stroke: currentColor;
|
||||
}
|
||||
|
||||
//for login.html
|
||||
.login {
|
||||
background-color: $black-ter;
|
||||
|
||||
&--logo {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.5 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg viewBox="0 0 135.297 120.36666" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>.hex{fill:#42d3a5;} .line{fill:#fff;}</style>
|
||||
</defs>
|
||||
<path d="M133.605291 66.312792l-27.6913077 47.9628009c-2.1826121 3.7799055-6.2171779 6.1091806-10.5824021 6.1091806H39.9489658c-4.3652242 0-8.3984567-2.3292751-10.5824021-6.1091806L1.6765893 66.312792c-2.1826121-3.7799055-2.1826121-8.4384557 0-12.2196945L29.3665637 6.1316299C31.5505091 2.3503911 35.5837416.021116 39.9489658.021116h55.3826154c4.3652242 0 8.39979 2.3292751 10.5824021 6.1105139l27.6913077 47.9614676c2.1826121 3.7812388 2.1826121 8.439789 0 12.2196945"
|
||||
class="hex"/>
|
||||
<path d="M61.53275952 19.96761733c-1.41466463.03066923-2.66193345.92969675-3.14052148 2.26164345l-17.8355541 49.69575758-5.85535361-9.759556c-.6159846-1.02927427-1.72965676-1.6601585-2.92962676-1.6601585H.05149659c.04970976 2.00834979.5878153 4.00699982 1.62695933 5.80645483l.59373516 1.02727432h27.56531085l8.57008574 14.28264293c.62131447 1.0359741 1.73705657 1.66015849 2.9276268 1.66015849.1119972 0 .22197446-.00559986.33397166-.01757956 1.31466713-.1279968 2.43673908-1.00337492 2.88472788-2.24604385l17.25756855-48.0947976 18.95252617 46.4108397c.48531787 1.18667033 1.5956601 2.00194995 2.87292818 2.11124722 1.26926826.11332717 2.5074373-.50440739 3.1874203-1.59376016l15.14962126-24.28039297 8.8435789 11.5637109c.6479838.84530887 1.65345867 1.34176645 2.71473213 1.34176645h18.8235294l1.24996875-2.16594585c.84245894-1.45896352 1.35666609-3.0480238 1.54876128-4.6678833h-19.93350165l-10.8157296-14.14464637c-.68131296-.88930777-1.7675558-1.39116522-2.87692807-1.33786655-1.119972.05332866-2.14294643.6515037-2.7362316 1.60345991L84.56351375 70.56637232 64.76700867 22.0885843c-.5359866-1.3079673-1.82695433-2.15894603-3.2362191-2.12294693z"
|
||||
class="line"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
-93
@@ -1,93 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`application-status should match the snapshot with custom status 1`] = `
|
||||
<div
|
||||
class="application-status"
|
||||
>
|
||||
<font-awesome-icon-stub
|
||||
class="application-status__icon application-status__icon--?"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`application-status should match the snapshot with status DOWN 1`] = `
|
||||
<div
|
||||
class="application-status"
|
||||
>
|
||||
<font-awesome-icon-stub
|
||||
class="application-status__icon application-status__icon--DOWN"
|
||||
icon="times-circle"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`application-status should match the snapshot with status OFFLINE 1`] = `
|
||||
<div
|
||||
class="application-status"
|
||||
>
|
||||
<font-awesome-icon-stub
|
||||
class="application-status__icon application-status__icon--OFFLINE"
|
||||
icon="minus-circle"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`application-status should match the snapshot with status OUT_OF_SERVICE 1`] = `
|
||||
<div
|
||||
class="application-status"
|
||||
>
|
||||
<font-awesome-icon-stub
|
||||
class="application-status__icon application-status__icon--OUT_OF_SERVICE"
|
||||
icon="ban"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`application-status should match the snapshot with status RESTRICTED 1`] = `
|
||||
<div
|
||||
class="application-status"
|
||||
>
|
||||
<font-awesome-icon-stub
|
||||
class="application-status__icon application-status__icon--RESTRICTED"
|
||||
icon="exclamation"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`application-status should match the snapshot with status UNKNOWN 1`] = `
|
||||
<div
|
||||
class="application-status"
|
||||
>
|
||||
<font-awesome-icon-stub
|
||||
class="application-status__icon application-status__icon--UNKNOWN"
|
||||
icon="question-circle"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`application-status should match the snapshot with status UP with Timestamp 1`] = `
|
||||
<div
|
||||
class="application-status"
|
||||
>
|
||||
<font-awesome-icon-stub
|
||||
class="application-status__icon application-status__icon--UP"
|
||||
icon="check"
|
||||
/>
|
||||
|
||||
<small>
|
||||
14m
|
||||
</small>
|
||||
</div>
|
||||
`;
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`time-ago given a short time passed time should match the snapshot 1`] = `14m`;
|
||||
|
||||
exports[`time-ago given a long time passed time should match the snapshot 1`] = `Jul 7`;
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {library} from '@fortawesome/fontawesome-svg-core';
|
||||
import {faGithub} from '@fortawesome/free-brands-svg-icons/faGithub';
|
||||
import {faGitter} from '@fortawesome/free-brands-svg-icons/faGitter';
|
||||
import {faStackOverflow} from '@fortawesome/free-brands-svg-icons/faStackOverflow';
|
||||
import {faTimesCircle as farTimesCircle} from '@fortawesome/free-regular-svg-icons/faTimesCircle';
|
||||
import {faBan} from '@fortawesome/free-solid-svg-icons/faBan';
|
||||
import {faBell} from '@fortawesome/free-solid-svg-icons/faBell';
|
||||
import {faBellSlash} from '@fortawesome/free-solid-svg-icons/faBellSlash';
|
||||
import {faBook} from '@fortawesome/free-solid-svg-icons/faBook';
|
||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
import {faDownload} from '@fortawesome/free-solid-svg-icons/faDownload';
|
||||
import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation';
|
||||
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
|
||||
import {faHeartbeat} from '@fortawesome/free-solid-svg-icons/faHeartbeat';
|
||||
import {faHome} from '@fortawesome/free-solid-svg-icons/faHome';
|
||||
import {faMinusCircle} from '@fortawesome/free-solid-svg-icons/faMinusCircle';
|
||||
import {faPencilAlt} from '@fortawesome/free-solid-svg-icons/faPencilAlt';
|
||||
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons/faQuestionCircle';
|
||||
import {faSignOutAlt} from '@fortawesome/free-solid-svg-icons/faSignOutAlt';
|
||||
import {faStepBackward} from '@fortawesome/free-solid-svg-icons/faStepBackward';
|
||||
import {faStepForward} from '@fortawesome/free-solid-svg-icons/faStepForward';
|
||||
import {faTimesCircle} from '@fortawesome/free-solid-svg-icons/faTimesCircle';
|
||||
import {faTrash} from '@fortawesome/free-solid-svg-icons/faTrash';
|
||||
import {faUserCircle} from '@fortawesome/free-solid-svg-icons/faUserCircle';
|
||||
import {faWrench} from '@fortawesome/free-solid-svg-icons/faWrench';
|
||||
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
|
||||
|
||||
library.add(
|
||||
//solid
|
||||
faTrash,
|
||||
faDownload,
|
||||
faStepForward,
|
||||
faStepBackward,
|
||||
faCheck,
|
||||
faQuestionCircle,
|
||||
faBan,
|
||||
faTimesCircle,
|
||||
faMinusCircle,
|
||||
faExclamation,
|
||||
faBook,
|
||||
faSignOutAlt,
|
||||
faExclamationTriangle,
|
||||
faPencilAlt,
|
||||
faBell,
|
||||
faBellSlash,
|
||||
faUserCircle,
|
||||
faHeartbeat,
|
||||
faHome,
|
||||
faWrench,
|
||||
//regular,
|
||||
farTimesCircle,
|
||||
//brands
|
||||
faGithub,
|
||||
faStackOverflow,
|
||||
faGitter
|
||||
);
|
||||
|
||||
export default FontAwesomeIcon;
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
const components = [];
|
||||
|
||||
/* global require */
|
||||
const context = require.context('.', false, /^(?:(?!.*\.spec\.(js|vue)$).)*\.(js|vue)$/);
|
||||
context.keys().forEach(function (key) {
|
||||
const name = /^(.\/)+(.*)\.(vue|js)$/.exec(key)[2];
|
||||
components.push({name, component: context(key).default})
|
||||
});
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
components.forEach(component => Vue.component(component.name, component.component));
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<button @click="click" class="confirm-button" :class="{ 'is-warning' : confirm }" v-on-clickaway="abort">
|
||||
<slot name="confirm" v-if="confirm">Confirm</slot>
|
||||
<slot v-else />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {directive as onClickaway} from 'vue-clickaway';
|
||||
|
||||
export default {
|
||||
directives: {onClickaway},
|
||||
data: () => ({
|
||||
confirm: false
|
||||
}),
|
||||
methods: {
|
||||
abort() {
|
||||
this.confirm = false;
|
||||
},
|
||||
click(event) {
|
||||
if (this.confirm) {
|
||||
this.$el.style.width = null;
|
||||
this.$emit('click', event);
|
||||
} else {
|
||||
const width = this.$el.getBoundingClientRect().width;
|
||||
this.$el.style.width = `${width}px`;
|
||||
}
|
||||
this.confirm = !this.confirm;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.confirm-button {
|
||||
transition: all $easing 150ms;
|
||||
}
|
||||
</style>
|
||||
@@ -1,50 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-html="formatted" class="formatted" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Autolink} from '@/utils/autolink';
|
||||
import objToString from '@/utils/objToString';
|
||||
|
||||
const autolink = new Autolink({truncate: {
|
||||
length: 50,
|
||||
location: 'smart'
|
||||
}});
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
// eslint-disable-next-line vue/require-prop-type-constructor
|
||||
type: null,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
formatted() {
|
||||
return autolink(objToString(this.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.formatted {
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<button type="button" class="icon-button" :disabled="disabled" v-on="$listeners">
|
||||
<font-awesome-icon :icon="icon" :size="size" :class="iconClass" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: [String, Array],
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
iconClass: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.icon-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
color: inherit;
|
||||
|
||||
&:not([disabled]) {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
& svg {
|
||||
fill: currentcolor;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,87 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="card panel">
|
||||
<header
|
||||
v-if="title || $slots['header']"
|
||||
class="card-header"
|
||||
:class="{'panel__header--sticky': headerSticksBelow}"
|
||||
v-sticks-below="headerSticksBelow"
|
||||
>
|
||||
<p class="card-header-title">
|
||||
<span v-text="title" />
|
||||
<slot name="header" />
|
||||
</p>
|
||||
<div class="panel__close">
|
||||
<sba-icon-button v-if="closeable" :icon="['far', 'times-circle']" @click.stop="close" />
|
||||
</div>
|
||||
</header>
|
||||
<div v-if="$slots['default']" class="card-content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sticksBelow from '@/directives/sticks-below';
|
||||
import SbaIconButton from './sba-icon-button';
|
||||
|
||||
export default {
|
||||
components: {SbaIconButton},
|
||||
directives: {sticksBelow},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
headerSticksBelow: {
|
||||
type: Array,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close(event) {
|
||||
this.$emit('close', event);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.panel {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&__close {
|
||||
margin-right: 0.75em;
|
||||
color: $grey-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
}
|
||||
|
||||
&__header--sticky {
|
||||
position: sticky;
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {mount} from '@vue/test-utils';
|
||||
import moment from 'moment';
|
||||
import sbaStatus from './sba-status';
|
||||
|
||||
moment.now = () => +new Date(1318781879406);
|
||||
|
||||
describe('application-status', () => {
|
||||
|
||||
const testSnapshotForStatus = (status, date) => {
|
||||
const wrapper = mount(sbaStatus, {
|
||||
propsData: {
|
||||
status,
|
||||
date
|
||||
},
|
||||
stubs: {
|
||||
'font-awesome-icon': true
|
||||
}
|
||||
});
|
||||
expect(wrapper.vm.$el).toMatchSnapshot();
|
||||
};
|
||||
|
||||
it('should match the snapshot with status UP with Timestamp', () => {
|
||||
testSnapshotForStatus('UP', 1318781000000);
|
||||
});
|
||||
|
||||
it('should match the snapshot with status RESTRICTED', () => {
|
||||
testSnapshotForStatus('RESTRICTED');
|
||||
});
|
||||
|
||||
it('should match the snapshot with status OUT_OF_SERVICE', () => {
|
||||
testSnapshotForStatus('OUT_OF_SERVICE');
|
||||
});
|
||||
|
||||
it('should match the snapshot with status DOWN', () => {
|
||||
testSnapshotForStatus('DOWN');
|
||||
});
|
||||
|
||||
it('should match the snapshot with status UNKNOWN', () => {
|
||||
testSnapshotForStatus('UNKNOWN');
|
||||
});
|
||||
|
||||
it('should match the snapshot with status OFFLINE', () => {
|
||||
testSnapshotForStatus('OFFLINE');
|
||||
});
|
||||
|
||||
it('should match the snapshot with custom status', () => {
|
||||
testSnapshotForStatus('?');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="application-status">
|
||||
<font-awesome-icon :icon="icon" class="application-status__icon"
|
||||
:class="`application-status__icon--${status}`"
|
||||
/>
|
||||
<small v-if="date">
|
||||
<sba-time-ago :date="date" />
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import sbaTimeAgo from './sba-time-ago';
|
||||
|
||||
const icons = {
|
||||
'UP': 'check',
|
||||
'RESTRICTED': 'exclamation',
|
||||
'OUT_OF_SERVICE': 'ban',
|
||||
'DOWN': 'times-circle',
|
||||
'OFFLINE': 'minus-circle',
|
||||
'UNKNOWN': 'question-circle'
|
||||
};
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {sbaTimeAgo},
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: 'UNKNOWN'
|
||||
},
|
||||
date: {
|
||||
type: [String, Date, Number, moment],
|
||||
default: null
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
return icons[this.status];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.application-status {
|
||||
text-align: center;
|
||||
line-height: 1rem;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__icon {
|
||||
color: gray;
|
||||
margin: 0 auto;
|
||||
|
||||
&--UP {
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&--RESTRICTED {
|
||||
color: $warning;
|
||||
}
|
||||
|
||||
&--OUT_OF_SERVICE,
|
||||
&--DOWN {
|
||||
color: $danger;
|
||||
}
|
||||
|
||||
&--UNKNOWN,
|
||||
&--OFFLINE {
|
||||
color: $grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,42 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="field is-grouped is-grouped-multiline" v-if="hasTags">
|
||||
<div class="control" v-for="(value, key) in tags" :key="key">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag is-dark" v-text="key" />
|
||||
<span class="tag is-light" v-text="value" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
tags: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasTags() {
|
||||
return Object.keys(this.tags).length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment-shortformat';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
date: {
|
||||
type: [String, Date, Number, moment],
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
now: moment(),
|
||||
timer: null,
|
||||
}),
|
||||
computed: {
|
||||
timeAgo() {
|
||||
return moment(this.date).short(true, this.now);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.timer = window.setInterval(() => {
|
||||
this.now = moment();
|
||||
}, 1000);
|
||||
},
|
||||
render() {
|
||||
return this._v(this.timeAgo);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timer) {
|
||||
window.clearInterval(this.timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {shallowMount} from '@vue/test-utils';
|
||||
import moment from 'moment';
|
||||
import sbaTimeAgo from './sba-time-ago.js';
|
||||
|
||||
moment.now = () => +new Date(1318781879406);
|
||||
|
||||
describe('time-ago', () => {
|
||||
|
||||
describe('given a short time passed time', () => {
|
||||
it('should match the snapshot', () => {
|
||||
const wrapper = shallowMount(sbaTimeAgo, {
|
||||
propsData: {
|
||||
date: moment(1318781000000)
|
||||
}
|
||||
});
|
||||
expect(wrapper.vm.$el).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a long time passed time', () => {
|
||||
it('should match the snapshot', () => {
|
||||
const wrapper = shallowMount(sbaTimeAgo, {
|
||||
propsData: {
|
||||
date: moment(1310000000000)
|
||||
}
|
||||
});
|
||||
expect(wrapper.vm.$el).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
const observers = new WeakMap();
|
||||
|
||||
const bind = (el, binding) => {
|
||||
unbind(el);
|
||||
const observer = new ResizeObserver(binding.value);
|
||||
observer.observe(el);
|
||||
observers.set(el, observer);
|
||||
|
||||
};
|
||||
|
||||
const unbind = (el) => {
|
||||
const observer = observers.get(el);
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
observers.delete(el);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
bind,
|
||||
update(el, binding) {
|
||||
if (binding.value === binding.oldValue) {
|
||||
return
|
||||
}
|
||||
bind(el, binding)
|
||||
},
|
||||
unbind
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Popper from 'popper.js';
|
||||
|
||||
const poppers = new WeakMap();
|
||||
|
||||
const bind = (el, binding) => {
|
||||
const reference = typeof binding.value === 'string' ? document.getElementById(binding.value) : binding.value;
|
||||
if (reference) {
|
||||
const popper = new Popper(reference, el);
|
||||
poppers.set(el, popper);
|
||||
}
|
||||
};
|
||||
|
||||
const unbind = (el) => {
|
||||
const popper = poppers.get(el);
|
||||
if (popper) {
|
||||
popper.destroy(el);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
bind,
|
||||
update(el, binding) {
|
||||
if (binding.value === binding.oldValue) {
|
||||
return
|
||||
}
|
||||
bind(el, binding)
|
||||
},
|
||||
unbind
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const bind = (el, binding) => {
|
||||
if (!binding.value) {
|
||||
return;
|
||||
}
|
||||
const top = binding.value.map(v => document.querySelector(v))
|
||||
.filter(v => Boolean(v))
|
||||
.map(v => v.getBoundingClientRect().height)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
el.style.top = `${top}px`;
|
||||
el.style.position = 'sticky';
|
||||
};
|
||||
|
||||
export default {
|
||||
bind,
|
||||
update(el, binding) {
|
||||
if (binding.value === binding.oldValue) {
|
||||
return
|
||||
}
|
||||
bind(el, binding)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<!--
|
||||
~ Copyright 2014-2018 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<base th:href="@{'/'}" href="/">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="format-detection" content="telephone=no,email=no">
|
||||
<meta name="theme-color" content="#42d3a5">
|
||||
|
||||
<!-- include extensions -->
|
||||
<th:block th:each="cssFile : ${cssExtensions}">
|
||||
<link rel="preload" th:href="${cssFile.resourcePath}" as="style">
|
||||
</th:block>
|
||||
<th:block th:each="jsFile : ${jsExtensions}">
|
||||
<link rel="preload" th:href="${jsFile.resourcePath}" as="script">
|
||||
</th:block>
|
||||
<th:block th:each="cssFile : ${cssExtensions}">
|
||||
<link th:href="${cssFile.resourcePath}" rel="stylesheet">
|
||||
</th:block>
|
||||
|
||||
<link rel="shortcut icon" href="assets/img/favicon.png" type="image/png">
|
||||
<title th:text="${uiSettings.title}">服务监控</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script th:inline="javascript">
|
||||
var SBA = {
|
||||
uiSettings: /*[[${uiSettings}]]*/ {},
|
||||
user: /*[[${user}]]*/ null,
|
||||
extensions: [],
|
||||
/*[#th:block th:if="${_csrf}"]*/
|
||||
csrf: {
|
||||
parameterName: /*[[${_csrf.parameterName}]]*/ null,
|
||||
headerName: /*[[${_csrf.headerName}]]*/ null
|
||||
},
|
||||
/*[/th:block]*/
|
||||
use: function (ext) {
|
||||
this.extensions.push(ext);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- include extensions -->
|
||||
<th:block th:each="jsFile : ${jsExtensions}">
|
||||
<script lang="javascript" th:src="${jsFile.resourcePath}"></script>
|
||||
</th:block>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import '@/assets/css/base.scss';
|
||||
import moment from 'moment';
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import components from './components';
|
||||
import Notifications from './notifications';
|
||||
import sbaShell from './shell';
|
||||
import Store from './store';
|
||||
import ViewRegistry from './viewRegistry';
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import views from './views';
|
||||
|
||||
moment.locale(window.navigator.language);
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(components);
|
||||
Vue.use(ElementUI)
|
||||
|
||||
const applicationStore = new Store();
|
||||
const viewRegistry = new ViewRegistry();
|
||||
|
||||
const installables = [
|
||||
Notifications,
|
||||
...views,
|
||||
...global.SBA.extensions
|
||||
];
|
||||
|
||||
installables.forEach(view => view.install({
|
||||
viewRegistry,
|
||||
applicationStore,
|
||||
vue: Vue
|
||||
}));
|
||||
|
||||
new Vue({
|
||||
router: new VueRouter({
|
||||
linkActiveClass: 'is-active',
|
||||
routes: viewRegistry.routes
|
||||
}),
|
||||
el: '#app',
|
||||
render(h) {
|
||||
return h(sbaShell, {
|
||||
props: {
|
||||
views: this.views,
|
||||
applications: this.applications,
|
||||
error: this.error
|
||||
}
|
||||
});
|
||||
},
|
||||
data: {
|
||||
views: viewRegistry.views,
|
||||
applications: applicationStore.applications,
|
||||
error: null
|
||||
},
|
||||
methods: {
|
||||
onError(error) {
|
||||
console.warn('Connection to server failed:', error);
|
||||
this.error = error;
|
||||
},
|
||||
onConnected() {
|
||||
this.error = null;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
applicationStore.addEventListener('connected', this.onConnected);
|
||||
applicationStore.addEventListener('error', this.onError);
|
||||
applicationStore.start();
|
||||
},
|
||||
beforeDestroy() {
|
||||
applicationStore.stop();
|
||||
applicationStore.removeEventListener('connected', this.onConnected);
|
||||
applicationStore.removeEventListener('error', this.onError)
|
||||
}
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
<!--
|
||||
~ Copyright 2014-2018 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<base th:href="@{'/'}" href="/">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="format-detection" content="telephone=no,email=no">
|
||||
<meta name="theme-color" content="#42d3a5">
|
||||
<link rel="shortcut icon" href="assets/img/favicon.png" type="image/png">
|
||||
<title>Spring Boot Admin - Login</title>
|
||||
</head>
|
||||
<body class="login">
|
||||
|
||||
<section class="hero is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="container has-text-centered">
|
||||
<div class="column is-4 is-offset-4">
|
||||
<div class="box">
|
||||
<figure class="image is-128x128 login--logo">
|
||||
<img src="assets/img/icon-spring-boot-admin.svg">
|
||||
</figure>
|
||||
<h1 class="title has-text-primary">PigX 服务状态监控</h1>
|
||||
<form method="post">
|
||||
<input type="hidden"
|
||||
th:if="${_csrf}"
|
||||
th:name="${_csrf.parameterName}"
|
||||
th:value="${_csrf.token}"/>
|
||||
<div class="field">
|
||||
<p class="is-medium has-text-danger" th:unless="${param.error == null}">
|
||||
Invalid username or password
|
||||
</p>
|
||||
<p class="is-medium" th:unless="${param.logout == null}">
|
||||
Logout successful
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input is-medium" type="input" name="username" placeholder="Username"
|
||||
autofocus th:classappend="${param.error != null} ? is-danger">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input is-medium" type="password" name="password"
|
||||
placeholder="Password" th:classappend="${param.error != null} ? is-danger">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input type="submit" class="button is-block is-primary is-medium is-fullwidth"
|
||||
value="Login">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import '@/assets/css/base.scss';
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export default {
|
||||
created() {
|
||||
this.subscribe();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unsubscribe();
|
||||
},
|
||||
methods: {
|
||||
async subscribe() {
|
||||
if (!this.subscription) {
|
||||
this.subscription = await this.createSubscription();
|
||||
}
|
||||
},
|
||||
unsubscribe() {
|
||||
if (this.subscription) {
|
||||
try {
|
||||
!this.subscription.closed && this.subscription.unsubscribe();
|
||||
} finally {
|
||||
this.subscription = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
let granted = false;
|
||||
|
||||
const requestPermissions = () => {
|
||||
if ('Notification' in window) {
|
||||
granted = (window.Notification.permission === 'granted');
|
||||
if (!granted && window.Notification.permission !== 'denied') {
|
||||
window.Notification.requestPermission(permission => granted = (permission === 'granted'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const notify = (title, options) => {
|
||||
if (granted) {
|
||||
const note = new window.Notification(title, options);
|
||||
if (options.url !== null) {
|
||||
note.onclick = () => {
|
||||
window.focus();
|
||||
window.open(options.url, '_self');
|
||||
};
|
||||
}
|
||||
if (options.timeout > 0) {
|
||||
note.onshow = () => setTimeout(() => note.close(), options.timeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
install: ({applicationStore}) => {
|
||||
requestPermissions();
|
||||
|
||||
applicationStore.addEventListener('updated', (newVal, oldVal) => {
|
||||
if (newVal.status !== oldVal.status) {
|
||||
notify(`${newVal.name} is now ${newVal.status}`, {
|
||||
tag: `${newVal.name}-${newVal.status}`,
|
||||
lang: 'en',
|
||||
body: `was ${oldVal.status}.`,
|
||||
icon: newVal.status === 'UP' ? 'assets/img/favicon.png' : 'assets/img/favicon-danger.png',
|
||||
renotify: true,
|
||||
timeout: 10000
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import axios, {redirectOn401} from '@/utils/axios';
|
||||
import waitForPolyfill from '@/utils/eventsource-polyfill';
|
||||
import {concat, from, ignoreElements, Observable} from '@/utils/rxjs';
|
||||
import uri from '@/utils/uri';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import Instance from './instance';
|
||||
|
||||
class Application {
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.axios = axios.create({
|
||||
baseURL: uri`applications/${this.name}/`
|
||||
});
|
||||
this.axios.interceptors.response.use(
|
||||
response => response,
|
||||
redirectOn401()
|
||||
);
|
||||
}
|
||||
|
||||
findInstance(instanceId) {
|
||||
return this.instances.find(instance => instance.id === instanceId);
|
||||
}
|
||||
|
||||
get isUnregisterable() {
|
||||
return this.instances.findIndex(i => i.isUnregisterable) >= 0;
|
||||
}
|
||||
|
||||
async unregister() {
|
||||
return this.axios.delete('')
|
||||
}
|
||||
|
||||
static async list() {
|
||||
return await axios.get('applications', {
|
||||
transformResponse: Application._transformResponse
|
||||
});
|
||||
}
|
||||
|
||||
static getStream() {
|
||||
return concat(
|
||||
from(waitForPolyfill()).pipe(ignoreElements()),
|
||||
Observable.create(observer => {
|
||||
const eventSource = new EventSource('applications');
|
||||
eventSource.onmessage = message => observer.next({
|
||||
...message,
|
||||
data: Application._transformResponse(message.data)
|
||||
});
|
||||
|
||||
eventSource.onerror = err => observer.error(err);
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
static _transformResponse(data) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
const json = JSON.parse(data);
|
||||
if (json instanceof Array) {
|
||||
const applications = json.map(Application._toApplication);
|
||||
return sortBy(applications, [item => item.name]);
|
||||
}
|
||||
return Application._toApplication(json);
|
||||
}
|
||||
|
||||
static _toApplication(application) {
|
||||
const instances = application.instances.map(instance => Object.assign(new Instance(instance.id), instance));
|
||||
return Object.assign(new Application(application.name), {
|
||||
...application,
|
||||
instances: sortBy(instances, [instance => instance.registration.healthUrl])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Application;
|
||||
@@ -1,332 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import axios, {redirectOn401} from '@/utils/axios';
|
||||
import waitForPolyfill from '@/utils/eventsource-polyfill';
|
||||
import logtail from '@/utils/logtail';
|
||||
import {concat, from, ignoreElements, Observable} from '@/utils/rxjs';
|
||||
import uri from '@/utils/uri';
|
||||
import transform from 'lodash/transform';
|
||||
|
||||
const actuatorMimeTypes = [
|
||||
'application/vnd.spring-boot.actuator.v2+json',
|
||||
'application/vnd.spring-boot.actuator.v1+json',
|
||||
'application/json'
|
||||
];
|
||||
|
||||
const isInstanceActuatorRequest = url => url.match(/^instances[/][^/]+[/]actuator([/].*)?$/);
|
||||
|
||||
class Instance {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
this.axios = axios.create({
|
||||
baseURL: uri`instances/${this.id}/`,
|
||||
});
|
||||
this.axios.interceptors.response.use(
|
||||
response => response,
|
||||
redirectOn401(error => !isInstanceActuatorRequest(error.config.url))
|
||||
);
|
||||
}
|
||||
|
||||
hasEndpoint(endpointId) {
|
||||
return this.endpoints.findIndex(endpoint => endpoint.id === endpointId) >= 0;
|
||||
}
|
||||
|
||||
get isUnregisterable() {
|
||||
return this.registration.source === 'http-api';
|
||||
}
|
||||
|
||||
async unregister() {
|
||||
return this.axios.delete('');
|
||||
}
|
||||
|
||||
async fetchInfo() {
|
||||
return this.axios.get(uri`actuator/info`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMetrics() {
|
||||
return this.axios.get(uri`actuator/metrics`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMetric(metric, tags) {
|
||||
const params = tags ? {
|
||||
tag: Object.entries(tags)
|
||||
.filter(([, value]) => typeof value !== 'undefined' && value !== null)
|
||||
.map(([name, value]) => `${name}:${value}`)
|
||||
.join(',')
|
||||
} : {};
|
||||
return this.axios.get(uri`actuator/metrics/${metric}`, {
|
||||
headers: {'Accept': actuatorMimeTypes},
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
async fetchHealth() {
|
||||
return await this.axios.get(uri`actuator/health`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchEnv(name) {
|
||||
return this.axios.get(uri`actuator/env/${name || '' }`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchConfigprops() {
|
||||
return this.axios.get(uri`actuator/configprops`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async hasEnvManagerSupport() {
|
||||
const response = await this.axios.options(uri`actuator/env`);
|
||||
return response.headers['allow'] && response.headers['allow'].includes('POST');
|
||||
}
|
||||
|
||||
async resetEnv() {
|
||||
return this.axios.delete(uri`actuator/env`);
|
||||
}
|
||||
|
||||
async setEnv(name, value) {
|
||||
return this.axios.post(uri`actuator/env`, {name, value}, {
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
});
|
||||
}
|
||||
|
||||
async refreshContext() {
|
||||
return this.axios.post(uri`actuator/refresh`);
|
||||
}
|
||||
|
||||
async fetchLiquibase() {
|
||||
return this.axios.get(uri`actuator/liquibase`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchScheduledTasks() {
|
||||
return this.axios.get(uri`actuator/scheduledtasks`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchCaches() {
|
||||
return this.axios.get(uri`actuator/caches`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async clearCaches() {
|
||||
return this.axios.delete(uri`actuator/caches`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async clearCache(name, cacheManager) {
|
||||
return this.axios.delete(uri`actuator/caches/${name}` , {
|
||||
params: {'cacheManager': cacheManager},
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchFlyway() {
|
||||
return this.axios.get(uri`actuator/flyway`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchLoggers() {
|
||||
return this.axios.get(uri`actuator/loggers`, {
|
||||
headers: {'Accept': actuatorMimeTypes},
|
||||
transformResponse: Instance._toLoggers
|
||||
});
|
||||
}
|
||||
|
||||
async configureLogger(name, level) {
|
||||
return this.axios.post(uri`actuator/loggers/${name}`, {configuredLevel: level}, {
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchHttptrace() {
|
||||
return this.axios.get(uri`actuator/httptrace`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBeans() {
|
||||
return this.axios.get(uri`actuator/beans`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchThreaddump() {
|
||||
return this.axios.get(uri`actuator/threaddump`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchAuditevents({after, type, principal}) {
|
||||
return this.axios.get(uri`actuator/auditevents`, {
|
||||
headers: {'Accept': actuatorMimeTypes},
|
||||
params: {
|
||||
after: after.toISOString(),
|
||||
type: type || undefined,
|
||||
principal: principal || undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchSessionsByUsername(username) {
|
||||
return this.axios.get(uri`actuator/sessions`, {
|
||||
headers: {'Accept': actuatorMimeTypes},
|
||||
params: {
|
||||
username: username || undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchSession(sessionId) {
|
||||
return this.axios.get(uri`actuator/sessions/${sessionId}`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
async deleteSession(sessionId) {
|
||||
return this.axios.delete(uri`actuator/sessions/${sessionId}`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
streamLogfile(interval) {
|
||||
return logtail(opt => this.axios.get(uri`actuator/logfile`, opt), interval);
|
||||
}
|
||||
|
||||
async listMBeans() {
|
||||
return this.axios.get(uri`actuator/jolokia/list`, {
|
||||
headers: {'Accept': 'application/json'},
|
||||
params: {canonicalNaming: false},
|
||||
transformResponse: Instance._toMBeans
|
||||
});
|
||||
}
|
||||
|
||||
async readMBeanAttributes(domain, mBean) {
|
||||
const body = {
|
||||
type: 'read',
|
||||
mbean: `${domain}:${mBean}`,
|
||||
config: {ignoreErrors: true}
|
||||
};
|
||||
return this.axios.post(uri`actuator/jolokia`, body, {
|
||||
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
|
||||
});
|
||||
}
|
||||
|
||||
async writeMBeanAttribute(domain, mBean, attribute, value) {
|
||||
const body = {
|
||||
type: 'write',
|
||||
mbean: `${domain}:${mBean}`,
|
||||
attribute,
|
||||
value
|
||||
};
|
||||
return this.axios.post(uri`actuator/jolokia`, body, {
|
||||
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
|
||||
});
|
||||
}
|
||||
|
||||
async invokeMBeanOperation(domain, mBean, operation, args) {
|
||||
const body = {
|
||||
type: 'exec',
|
||||
mbean: `${domain}:${mBean}`,
|
||||
operation,
|
||||
'arguments': args
|
||||
};
|
||||
return this.axios.post(uri`actuator/jolokia`, body, {
|
||||
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMappings() {
|
||||
return this.axios.get(uri`actuator/mappings`, {
|
||||
headers: {'Accept': actuatorMimeTypes}
|
||||
});
|
||||
}
|
||||
|
||||
static async fetchEvents() {
|
||||
return axios.get('instances/events');
|
||||
}
|
||||
|
||||
static getEventStream() {
|
||||
return concat(
|
||||
from(waitForPolyfill()).pipe(ignoreElements()),
|
||||
Observable.create(observer => {
|
||||
const eventSource = new EventSource('instances/events');
|
||||
eventSource.onmessage = message => observer.next({
|
||||
...message,
|
||||
data: JSON.parse(message.data)
|
||||
});
|
||||
eventSource.onerror = err => observer.error(err);
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
static async get(id) {
|
||||
return axios.get(uri`instances/${id}`, {
|
||||
transformResponse: Instance._toInstance
|
||||
});
|
||||
}
|
||||
|
||||
static _toInstance(data) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
const instance = JSON.parse(data);
|
||||
return Object.assign(new Instance(instance.id), instance);
|
||||
}
|
||||
|
||||
static _toLoggers(data) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
const raw = JSON.parse(data);
|
||||
const loggers = transform(raw.loggers, (result, value, key) => {
|
||||
return result.push({name: key, ...value});
|
||||
}, []);
|
||||
return {levels: raw.levels, loggers};
|
||||
}
|
||||
|
||||
static _toMBeans(data) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
const raw = JSON.parse(data);
|
||||
return Object.entries(raw.value).map(([domain, mBeans]) => ({
|
||||
domain,
|
||||
mBeans: Object.entries(mBeans).map(([descriptor, mBean]) => ({
|
||||
descriptor: descriptor,
|
||||
...mBean
|
||||
}))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export default Instance;
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import axios from '@/utils/axios';
|
||||
import uri from '@/utils/uri';
|
||||
import moment from 'moment';
|
||||
|
||||
class NotificationFilter {
|
||||
constructor({expiry, ...filter}) {
|
||||
Object.assign(this, filter);
|
||||
this.expiry = expiry ? moment(expiry) : null;
|
||||
}
|
||||
|
||||
affects(obj) {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isApplicationFilter) {
|
||||
return this.applicationName === obj.name;
|
||||
}
|
||||
|
||||
if (this.isInstanceFilter) {
|
||||
return this.instanceId === obj.id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get isApplicationFilter() {
|
||||
return this.hasOwnProperty('applicationName');
|
||||
}
|
||||
|
||||
get isInstanceFilter() {
|
||||
return this.hasOwnProperty('instanceId');
|
||||
}
|
||||
|
||||
async delete() {
|
||||
return axios.delete(uri`notifications/filters/${this.id}`);
|
||||
}
|
||||
|
||||
static async isSupported() {
|
||||
try {
|
||||
const response = await axios.options('notifications/filters');
|
||||
return response.headers['allow'] && response.headers['allow'].includes('POST');
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static async getFilters() {
|
||||
return axios.get('notifications/filters', {
|
||||
transformResponse: NotificationFilter._transformResponse
|
||||
});
|
||||
}
|
||||
|
||||
static async addFilter(object, ttl) {
|
||||
const params = {ttl};
|
||||
if (object.hasOwnProperty('name')) {
|
||||
params.applicationName = object.name;
|
||||
} else if (object.hasOwnProperty('id')) {
|
||||
params.instanceId = object.id;
|
||||
}
|
||||
return axios.post('notifications/filters', null, {
|
||||
params,
|
||||
transformResponse: NotificationFilter._transformResponse
|
||||
});
|
||||
}
|
||||
|
||||
static _transformResponse(data) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
const json = JSON.parse(data);
|
||||
if (json instanceof Array) {
|
||||
return json.map(NotificationFilter._toNotificationFilters).filter(f => !f.expired);
|
||||
}
|
||||
return NotificationFilter._toNotificationFilters(json);
|
||||
}
|
||||
|
||||
static _toNotificationFilters(notificationFilter) {
|
||||
return new NotificationFilter(notificationFilter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default NotificationFilter;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<sba-navbar :views="mainViews" :applications="applications" :error="error" />
|
||||
<router-view :views="childViews" :applications="applications" :error="error" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sbaNavbar from './navbar';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
views: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
applications: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
error: {
|
||||
type: Error,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {sbaNavbar},
|
||||
computed: {
|
||||
mainViews() {
|
||||
return this.views.filter(view => !view.parent);
|
||||
},
|
||||
activeMainViewName() {
|
||||
const currentView = this.$route.meta.view;
|
||||
return currentView && (currentView.parent || currentView.name);
|
||||
},
|
||||
childViews() {
|
||||
return this.views.filter(view => view.parent === this.activeMainViewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,132 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<nav id="navigation" class="navbar is-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<router-link class="navbar-item logo" to="/" v-html="brand" />
|
||||
|
||||
<div class="navbar-burger burger" @click.stop="showMenu = !showMenu">
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-menu" :class="{'is-active' : showMenu}">
|
||||
<div class="navbar-end">
|
||||
<router-link class="navbar-item" v-for="view in enabledViews" :to="{name: view.name}" :key="view.name">
|
||||
<component :is="view.handle" :applications="applications" :error="error" />
|
||||
</router-link>
|
||||
|
||||
<div class="navbar-item has-dropdown is-hoverable" v-if="userName">
|
||||
<a class="navbar-link">
|
||||
<font-awesome-icon icon="user-circle" size="lg" /> <span v-text="userName" />
|
||||
</a>
|
||||
<div class="navbar-dropdown">
|
||||
<a class="navbar-item">
|
||||
<form action="logout" method="post">
|
||||
<input v-if="csrfToken" type="hidden" :name="csrfParameterName" :value="csrfToken">
|
||||
<button class="button is-icon" type="submit" value="logout">
|
||||
<font-awesome-icon icon="sign-out-alt" /> Log out
|
||||
</button>
|
||||
</form>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {compareBy} from '@/utils/collections';
|
||||
|
||||
const readCookie = (name) => {
|
||||
const match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'));
|
||||
return (match ? decodeURIComponent(match[3]) : null);
|
||||
};
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
showMenu: false,
|
||||
brand: '<img src="assets/img/icon-spring-boot-admin.svg"><span>PigX 服务状态监控</span>',
|
||||
userName: null,
|
||||
csrfToken: null,
|
||||
csrfParameterName: null
|
||||
}),
|
||||
props: {
|
||||
views: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
applications: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
error: {
|
||||
type: Error,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
enabledViews() {
|
||||
return [...this.views].filter(
|
||||
view => view.handle && (typeof view.isEnabled === 'undefined' || view.isEnabled())
|
||||
).sort(compareBy(v => v.order));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (global.SBA) {
|
||||
if (global.SBA.uiSettings) {
|
||||
this.brand = global.SBA.uiSettings.brand || this.brand;
|
||||
}
|
||||
|
||||
if (global.SBA.user) {
|
||||
this.userName = global.SBA.user.name;
|
||||
}
|
||||
}
|
||||
this.csrfToken = readCookie('XSRF-TOKEN');
|
||||
this.csrfParameterName = (global.SBA && global.SBA.csrf && global.SBA.csrf.parameterName) || '_csrf';
|
||||
},
|
||||
mounted() {
|
||||
document.documentElement.classList.add('has-navbar-fixed-top');
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.documentElement.classList.remove('has-navbar-fixed-top')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.logo {
|
||||
align-self: center;
|
||||
flex-basis: 12em;
|
||||
flex-basis: content;
|
||||
max-height: 2.25em;
|
||||
padding: 0.5em 1em 0.5em 0.5em;
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
|
||||
img {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Application from '@/services/application';
|
||||
import {concat, concatMap, defer, delay, doFirst, map, retryWhen, tap} from '@/utils/rxjs';
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
this.applications = [];
|
||||
this.applications.findInstance = (instanceId) => {
|
||||
for (let application of this.applications) {
|
||||
const instance = application.findInstance(instanceId);
|
||||
if (instance) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
this.applications.findApplicationForInstance = (instanceId) => {
|
||||
return this.applications.find(application => !!application.findInstance(instanceId));
|
||||
};
|
||||
this.applications.indexOfApplication = (name) => {
|
||||
return this.applications.findIndex(application => application.name === name);
|
||||
};
|
||||
|
||||
this._listeners = {
|
||||
added: [],
|
||||
updated: [],
|
||||
removed: []
|
||||
};
|
||||
}
|
||||
|
||||
addEventListener(type, listener) {
|
||||
if (!(type in this._listeners)) {
|
||||
this._listeners[type] = [];
|
||||
}
|
||||
this._listeners[type].push(listener);
|
||||
}
|
||||
|
||||
removeEventListener(type, listener) {
|
||||
if (!(type in this._listeners)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = this._listeners[type].indexOf(listener);
|
||||
if (idx > 0) {
|
||||
this._listeners[type].splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
_dispatchEvent(type, ...args) {
|
||||
if (!(type in this._listeners)) {
|
||||
return;
|
||||
}
|
||||
const target = this;
|
||||
this._listeners[type].forEach(
|
||||
listener => listener.call(target, ...args)
|
||||
)
|
||||
}
|
||||
|
||||
start() {
|
||||
const list = defer(() => Application.list())
|
||||
.pipe(concatMap(message => message.data));
|
||||
const stream = Application.getStream()
|
||||
.pipe(map(message => message.data));
|
||||
this.subscription = concat(list, stream)
|
||||
.pipe(
|
||||
doFirst(() => this._dispatchEvent('connected')),
|
||||
retryWhen(
|
||||
errors => errors.pipe(
|
||||
tap(error => this._dispatchEvent('error', error)),
|
||||
delay(5000)
|
||||
)
|
||||
)
|
||||
).subscribe({
|
||||
next: application => {
|
||||
const idx = this.applications.indexOfApplication(application.name);
|
||||
if (idx >= 0) {
|
||||
const oldApplication = this.applications[idx];
|
||||
if (application.instances.length > 0) {
|
||||
this.applications.splice(idx, 1, application);
|
||||
this._dispatchEvent('updated', application, oldApplication);
|
||||
} else {
|
||||
this.applications.splice(idx, 1);
|
||||
this._dispatchEvent('removed', oldApplication);
|
||||
}
|
||||
} else {
|
||||
this.applications.push(application);
|
||||
this._dispatchEvent('added', application);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.subscription) {
|
||||
try {
|
||||
!this.subscription.closed && this.subscription.unsubscribe();
|
||||
} finally {
|
||||
this.subscription = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import _Autolinker from 'autolinker';
|
||||
|
||||
export const defaults = {
|
||||
urls: {
|
||||
schemeMatches: true,
|
||||
wwwMatches: false,
|
||||
tldMatches: false
|
||||
},
|
||||
email: false,
|
||||
phone: false,
|
||||
mention: false,
|
||||
hashtag: false,
|
||||
|
||||
stripPrefix: false,
|
||||
stripTrailingSlash: false,
|
||||
newWindow: true,
|
||||
|
||||
truncate: {
|
||||
length: 0,
|
||||
location: 'smart'
|
||||
},
|
||||
|
||||
className: ''
|
||||
};
|
||||
const autolinker = new _Autolinker(defaults);
|
||||
|
||||
export default s => autolinker.link(s);
|
||||
export function Autolink(cfg) {
|
||||
this.autolinker = new _Autolinker({...defaults, ...cfg});
|
||||
return s => this.autolinker.link(s)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import autolink, {Autolink} from './autolink'
|
||||
|
||||
describe('autolink should', () => {
|
||||
it('return the input string for normal text', () => {
|
||||
const str = 'This is just a normal text containing no hyperlinks';
|
||||
expect(autolink(str)).toBe(str);
|
||||
});
|
||||
|
||||
it('return string with anchor tag for the hyperlink', () => {
|
||||
const str = 'Please visit http://example.com.';
|
||||
expect(autolink(str)).toBe('Please visit <a href="http://example.com" target="_blank" rel="noopener noreferrer">http://example.com</a>.');
|
||||
});
|
||||
|
||||
it('return string with anchor tag with shortened text for the hyperlink', () => {
|
||||
const str = 'Please visit http://extraordinary.com/very/very/log/hyperlink.';
|
||||
|
||||
const customAutolink = new Autolink({
|
||||
truncate: {
|
||||
length: 30,
|
||||
location: 'smart'
|
||||
},
|
||||
});
|
||||
expect(customAutolink(str)).toBe('Please visit <a href="http://extraordinary.com/very/very/log/hyperlink" target="_blank" rel="noopener noreferrer" title="http://extraordinary.com/very/very/log/hyperlink">extraordinary.com/very…rlink</a>.');
|
||||
});
|
||||
|
||||
it('return string with anchor for hyperlinks in dense json', () => {
|
||||
const str = '{"name":"John Smith","links":[{"rel":"random-link1","href":"https://localhost:8000/api/123/query?action=do_something&age=21","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}';
|
||||
expect(autolink(str)).toBe('{"name":"John Smith","links":[{"rel":"random-link1","href":"<a href="https://localhost:8000/api/123/query?action=do_something&age=21" target="_blank" rel="noopener noreferrer">https://localhost:8000/api/123/query?action=do_something&age=21</a>","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}');
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
if (global.SBA && global.SBA.csrf && global.SBA.csrf.headerName) {
|
||||
axios.defaults.xsrfHeaderName = global.SBA.csrf.headerName;
|
||||
}
|
||||
|
||||
export const redirectOn401 = (predicate = () => true) => error => {
|
||||
if (error.response && error.response.status === 401 && predicate(error)) {
|
||||
window.location.assign(`login?redirectTo=${encodeURIComponent(window.location.href)}`);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
|
||||
};
|
||||
|
||||
const instance = axios.create();
|
||||
instance.interceptors.response.use(response => response, redirectOn401());
|
||||
instance.create = axios.create;
|
||||
|
||||
export default instance;
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {redirectOn401} from './axios';
|
||||
|
||||
describe('redirectOn401', () => {
|
||||
it('should not redirect on 500', async () => {
|
||||
window.location.assign = jest.fn();
|
||||
|
||||
const error = {
|
||||
response: {
|
||||
status: 500
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await redirectOn401()(error);
|
||||
} catch (e) {
|
||||
expect(e).toBe(error);
|
||||
}
|
||||
|
||||
expect(window.location.assign).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should redirect on 401', async () => {
|
||||
window.location.assign = jest.fn();
|
||||
|
||||
const error = {
|
||||
response: {
|
||||
status: 401
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await redirectOn401()(error);
|
||||
} catch (e) {
|
||||
expect(e).toBe(error);
|
||||
}
|
||||
|
||||
expect(window.location.assign).toBeCalledWith('login?redirectTo=http%3A%2F%2Fexample.com%2F');
|
||||
});
|
||||
|
||||
it('should not redirect on 401 for predicate yields false', async () => {
|
||||
window.location.assign = jest.fn();
|
||||
|
||||
const error = {
|
||||
response: {
|
||||
status: 401
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await redirectOn401(() => false)(error);
|
||||
} catch (e) {
|
||||
expect(e).toBe(error);
|
||||
}
|
||||
|
||||
expect(window.location.assign).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
export const compareBy = mapper => (a, b) => {
|
||||
const valA = mapper(a);
|
||||
const valB = mapper(b);
|
||||
return valA > valB ? 1 : valA < valB ? -1 : 0;
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as array from 'd3-array';
|
||||
import * as axis from 'd3-axis';
|
||||
import * as brush from 'd3-brush';
|
||||
import * as scale from 'd3-scale';
|
||||
import * as selection from 'd3-selection';
|
||||
import * as shape from 'd3-shape';
|
||||
import * as time from 'd3-time';
|
||||
|
||||
export default {
|
||||
...array,
|
||||
...axis,
|
||||
...brush,
|
||||
...scale,
|
||||
...selection,
|
||||
...shape,
|
||||
...time
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export default async () => {
|
||||
if (typeof window.EventSource === 'undefined') {
|
||||
return import(/* webpackChunkName: "event-source-polyfill" */ 'event-source-polyfill');
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {concatMap, EMPTY, of, timer} from '@/utils/rxjs';
|
||||
|
||||
export default (getFn, interval, initialSize = 300 * 1024) => {
|
||||
let range = `bytes=-${initialSize}`;
|
||||
let size = 0;
|
||||
|
||||
return timer(0, interval)
|
||||
.pipe(
|
||||
concatMap(() => getFn({headers: {range, 'Accept': 'text/plain, */*'}})),
|
||||
concatMap(response => {
|
||||
const initial = size === 0;
|
||||
const contentLength = response.data.length;
|
||||
if (response.status === 200) {
|
||||
if (!initial) {
|
||||
throw 'Expected 206 - Partial Content on subsequent requests.';
|
||||
}
|
||||
size = contentLength;
|
||||
} else if (response.status === 206) {
|
||||
size = parseInt(response.headers['content-range'].split('/')[1]);
|
||||
} else {
|
||||
throw 'Unexpected response status: ' + response.status;
|
||||
}
|
||||
|
||||
// Reload the last byte to avoid a 416: Range unsatisfiable.
|
||||
// If the response has length = 1 the file hasn't beent changed.
|
||||
// If the response status is 416 the logfile has been truncated.
|
||||
range = `bytes=${size - 1}-`;
|
||||
|
||||
let addendum = null;
|
||||
let skipped = 0;
|
||||
|
||||
if (initial) {
|
||||
if (contentLength >= size) {
|
||||
addendum = response.data;
|
||||
} else {
|
||||
// In case of a partial response find the first line break.
|
||||
addendum = response.data.substring(response.data.indexOf('\n') + 1);
|
||||
skipped = size - addendum.length;
|
||||
}
|
||||
} else if (response.data.length > 1) {
|
||||
// Remove the first byte which has been part of the previos response.
|
||||
addendum = response.data.substring(1);
|
||||
}
|
||||
|
||||
return addendum ? of({
|
||||
totalBytes: size,
|
||||
skipped,
|
||||
addendum
|
||||
}) : EMPTY;
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import isObject from 'lodash/isObject';
|
||||
|
||||
const nonEmptyComplexValue = value => (Array.isArray(value) || isObject(value)) && !isEmpty(value);
|
||||
|
||||
const objToString = (obj, indent = '') => {
|
||||
if (Array.isArray(obj)) {
|
||||
if (isEmpty(obj)) {
|
||||
return indent + '[]';
|
||||
}
|
||||
|
||||
return obj.map(value => {
|
||||
if (nonEmptyComplexValue(value)) {
|
||||
return `${indent}-\n${objToString(value, indent + ' ')}`;
|
||||
}
|
||||
return `${indent}- ${objToString(value, '')}`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
if (isObject(obj)) {
|
||||
if (isEmpty(obj)) {
|
||||
return indent + '{}';
|
||||
}
|
||||
|
||||
return Object.entries(obj)
|
||||
.map(([key, value]) => {
|
||||
if (nonEmptyComplexValue(value)) {
|
||||
return `${indent}${key}:\n${objToString(value, indent + ' ')}`;
|
||||
}
|
||||
return `${indent}${key}: ${objToString(value, '')}`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
if (obj === null) {
|
||||
return indent + 'null';
|
||||
}
|
||||
|
||||
if (typeof obj === 'undefined' || obj === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return indent + obj;
|
||||
};
|
||||
|
||||
export default objToString;
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import objToString from './objToString'
|
||||
|
||||
describe('objToString should', () => {
|
||||
|
||||
it('return the input string for normal text', () => {
|
||||
const obj = {
|
||||
a: 'start',
|
||||
b: 1,
|
||||
c: true,
|
||||
d: [1, 2, [3, 4]],
|
||||
e: {
|
||||
f: '',
|
||||
g: 1,
|
||||
h: null,
|
||||
i: undefined,
|
||||
j: {},
|
||||
k: [1],
|
||||
l: [{a:1, b:'foo'}, {b:2}]
|
||||
}
|
||||
};
|
||||
const str = `a: start
|
||||
b: 1
|
||||
c: true
|
||||
d:
|
||||
- 1
|
||||
- 2
|
||||
-
|
||||
- 3
|
||||
- 4
|
||||
e:
|
||||
f:
|
||||
g: 1
|
||||
h: null
|
||||
i:
|
||||
j: {}
|
||||
k:
|
||||
- 1
|
||||
l:
|
||||
-
|
||||
a: 1
|
||||
b: foo
|
||||
-
|
||||
b: 2`;
|
||||
expect(objToString(obj)).toBe(str);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {defer} from 'rxjs/internal/observable/defer';
|
||||
import {tap} from 'rxjs/internal/operators/tap';
|
||||
|
||||
export {throwError} from 'rxjs/internal/observable/throwError';
|
||||
export {of} from 'rxjs/internal/observable/of';
|
||||
export {defer} from 'rxjs/internal/observable/defer';
|
||||
export {concat} from 'rxjs/internal/observable/concat';
|
||||
export {EMPTY} from 'rxjs/internal/observable/empty';
|
||||
export {from} from 'rxjs/internal/observable/from';
|
||||
export {timer} from 'rxjs/internal/observable/timer';
|
||||
export {Observable} from 'rxjs/internal/Observable';
|
||||
export {Subject} from 'rxjs/internal/Subject';
|
||||
export {animationFrame as animationFrameScheduler} from 'rxjs/internal/scheduler/animationFrame';
|
||||
export {concatMap} from 'rxjs/internal/operators/concatMap';
|
||||
export {delay} from 'rxjs/internal/operators/delay';
|
||||
export {debounceTime} from 'rxjs/internal/operators/debounceTime';
|
||||
export {merge} from 'rxjs/internal/operators/merge';
|
||||
export {map} from 'rxjs/internal/operators/map';
|
||||
export {retryWhen} from 'rxjs/internal/operators/retryWhen';
|
||||
export {tap} from 'rxjs/internal/operators/tap';
|
||||
export {filter} from 'rxjs/internal/operators/filter';
|
||||
export {concatAll} from 'rxjs/internal/operators/concatAll';
|
||||
export {ignoreElements} from 'rxjs/internal/operators/ignoreElements';
|
||||
|
||||
export const doOnSubscribe = cb => source =>
|
||||
defer(() => {
|
||||
cb();
|
||||
return source
|
||||
});
|
||||
|
||||
export const doFirst = cb => source => {
|
||||
let triggered;
|
||||
return defer(() => {
|
||||
triggered = false;
|
||||
return source;
|
||||
}).pipe(
|
||||
tap(v => {
|
||||
if (!triggered) {
|
||||
triggered = true;
|
||||
cb(v);
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const listen = (cb, execDelay = 150) => source => {
|
||||
let handle = null;
|
||||
return source.pipe(
|
||||
doOnSubscribe(() => handle = setTimeout(() => cb('executing'), execDelay)),
|
||||
tap({
|
||||
complete: () => {
|
||||
handle && clearTimeout(handle);
|
||||
cb('completed');
|
||||
},
|
||||
error: (error) => {
|
||||
console.warn('Operation failed:', error);
|
||||
handle && clearTimeout(handle);
|
||||
cb('failed');
|
||||
}
|
||||
})
|
||||
)
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {concat, EMPTY, of, throwError} from '@/utils/rxjs';
|
||||
import {delay, doFirst, doOnSubscribe, listen} from './rxjs';
|
||||
|
||||
describe('doOnSubscribe', () => {
|
||||
it('should call callback when subscribing', done => {
|
||||
const cb = jest.fn();
|
||||
EMPTY.pipe(
|
||||
doOnSubscribe(cb)
|
||||
).subscribe({
|
||||
complete: () => {
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('doFirst', () => {
|
||||
it('should not call callback when empty', done => {
|
||||
const cb = jest.fn();
|
||||
EMPTY.pipe(
|
||||
doFirst(cb)
|
||||
).subscribe({
|
||||
complete: () => {
|
||||
expect(cb).not.toBeCalled();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should call callback on first item', done => {
|
||||
const cb = jest.fn();
|
||||
of(1,2,3).pipe(
|
||||
doFirst(cb)
|
||||
).subscribe({
|
||||
complete: () => {
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('listen', () => {
|
||||
it('should call callback with complete', done => {
|
||||
const cb = jest.fn();
|
||||
EMPTY.pipe(
|
||||
listen(cb)
|
||||
).subscribe({
|
||||
complete: () => {
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
expect(cb).toHaveBeenCalledWith('completed');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should call callback with executing and complete', done => {
|
||||
const cb = jest.fn();
|
||||
of(1).pipe(
|
||||
delay(10),
|
||||
listen(cb, 1)
|
||||
).subscribe({
|
||||
complete: () => {
|
||||
expect(cb).toHaveBeenCalledTimes(2);
|
||||
expect(cb).toHaveBeenCalledWith('executing');
|
||||
expect(cb).toHaveBeenCalledWith('completed');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should call callback with failed', done => {
|
||||
const cb = jest.fn();
|
||||
throwError(new Error('test')).pipe(
|
||||
listen(cb)
|
||||
).subscribe({
|
||||
error: () => {
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
expect(cb).toHaveBeenCalledWith('failed');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should call callback with executing and failed', done => {
|
||||
const cb = jest.fn();
|
||||
|
||||
concat(
|
||||
of(1).pipe(delay(10)),
|
||||
throwError(new Error('test'))
|
||||
).pipe(
|
||||
listen(cb, 1)
|
||||
).subscribe({
|
||||
error: () => {
|
||||
expect(cb).toHaveBeenCalledTimes(2);
|
||||
expect(cb).toHaveBeenCalledWith('executing');
|
||||
expect(cb).toHaveBeenCalledWith('failed');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export default (fullName, targetLength) => {
|
||||
if (!fullName || fullName.length < targetLength) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
const tokens = fullName.split('.');
|
||||
let shortened = tokens.pop();
|
||||
while (tokens.length > 0) {
|
||||
const next = tokens.pop();
|
||||
if (next.length + 1 + shortened.length < targetLength) {
|
||||
shortened = next + '.' + shortened;
|
||||
} else {
|
||||
shortened = next[0] + '.' + shortened;
|
||||
}
|
||||
}
|
||||
return shortened;
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import shortenClassname from './shortenClassname';
|
||||
|
||||
describe('shortenClassname', () => {
|
||||
|
||||
it('should shorten when too long', () => {
|
||||
expect(shortenClassname('de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration', 40)).toBe('d.c.b.a.s.config.AdminServerAutoConfiguration')
|
||||
});
|
||||
|
||||
it('should not shorten when string is small enough', () => {
|
||||
expect(shortenClassname('de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration', 300)).toBe('de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration')
|
||||
});
|
||||
|
||||
it('should not shorten when no package is present', () => {
|
||||
expect(shortenClassname('AdminServerAutoConfiguration', 1)).toBe('AdminServerAutoConfiguration')
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export default (strings, ...values) => {
|
||||
let result = strings[0];
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
result += encodeURIComponent(values[i]) + strings[i + 1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import uri from './uri';
|
||||
|
||||
describe('uri', () => {
|
||||
|
||||
it('should escape uris properly', () => {
|
||||
expect(uri`http://app/${'foo/bar'}?q=${'???'}`).toBe('http://app/foo%2Fbar?q=%3F%3F%3F');
|
||||
expect(uri`http://app/${'foo/bar'}?q=1`).toBe('http://app/foo%2Fbar?q=1');
|
||||
expect(uri`http://app/${'foo/bar'}`).toBe('http://app/foo%2Fbar');
|
||||
expect(uri`http://app/foo`).toBe('http://app/foo');
|
||||
expect(uri``).toBe('');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const createTextVNode = label => {
|
||||
return {
|
||||
render() {
|
||||
return this._v(label)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default class ViewRegistry {
|
||||
constructor() {
|
||||
this._views = [];
|
||||
this._redirects = [];
|
||||
}
|
||||
|
||||
get views() {
|
||||
return this._views;
|
||||
}
|
||||
|
||||
get routes() {
|
||||
return [
|
||||
...this._toRoutes(this._views, v => !v.parent),
|
||||
...this._redirects
|
||||
]
|
||||
}
|
||||
|
||||
addView(...views) {
|
||||
views.forEach(view => this._addView(view));
|
||||
}
|
||||
|
||||
addRedirect(path, redirectToView) {
|
||||
this._redirects.push({path, redirect: {name: redirectToView}});
|
||||
}
|
||||
|
||||
_addView(view) {
|
||||
if (view.label && !view.handle) {
|
||||
view.handle = createTextVNode(view.label);
|
||||
}
|
||||
this._views.push(view);
|
||||
}
|
||||
|
||||
_toRoutes(views, filter) {
|
||||
return views.filter(filter).map(
|
||||
p => {
|
||||
const children = this._toRoutes(views, v => v.parent === p.name);
|
||||
return ({
|
||||
path: p.path,
|
||||
name: children.length === 0 ? p.name : undefined,
|
||||
component: p.component,
|
||||
props: p.props,
|
||||
meta: {view: p},
|
||||
children
|
||||
});
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="applications-list">
|
||||
<div class="application-list__item card" :class="{'is-active': selected === application.name}"
|
||||
v-for="application in applications" :key="application.name" :id=" application.name"
|
||||
v-on-clickaway="(event) => deselect(event, application.name)"
|
||||
>
|
||||
<header class="hero application-list__item__header"
|
||||
:class="getHeaderClass(application)"
|
||||
@click.stop="select(application.name)"
|
||||
>
|
||||
<template v-if="selected !== application.name">
|
||||
<sba-status :status="application.status"
|
||||
:date="application.statusTimestamp"
|
||||
class="application-list__item__header__status"
|
||||
/>
|
||||
<p class="application-list__item__header__name">
|
||||
<span v-text="application.name" /><br>
|
||||
<span class="is-muted">
|
||||
<a v-if="application.instances.length === 1"
|
||||
v-text="application.instances[0].registration.serviceUrl || application.instances[0].registration.healthUrl"
|
||||
:href="application.instances[0].registration.serviceUrl || application.instances[0].registration.healthUrl"
|
||||
/>
|
||||
<span v-else
|
||||
v-text="`${application.instances.length} instances`"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
<p class="application-list__item__header__version" v-text="application.buildVersion" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1 class="title is-size-5 application-list__item__header__name" v-text="application.name" />
|
||||
</template>
|
||||
<div class="application-list__item__header__actions">
|
||||
<sba-icon-button :id="`nf-settings-${application.name}`"
|
||||
v-if="hasNotificationFiltersSupport"
|
||||
@click.stop="toggleNotificationFilterSettingsFor(application)"
|
||||
:icon="hasActiveFilter(application) ? 'bell-slash' : 'bell'"
|
||||
/>
|
||||
<sba-icon-button icon="trash"
|
||||
v-if="application.isUnregisterable"
|
||||
@click.stop="unregister(application)"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="card-content" v-if="selected === application.name">
|
||||
<table class="table is-hoverable is-selectable is-fullwidth application__instances">
|
||||
<tbody>
|
||||
<tr v-for="instance in application.instances" :key="instance.id" @click.stop="showDetails(instance)">
|
||||
<td class="instance__status">
|
||||
<sba-status :status="instance.statusInfo.status" :date="instance.statusTimestamp" />
|
||||
</td>
|
||||
<td class="is-narrow">
|
||||
<a v-text="instance.registration.serviceUrl || instance.registration.healthUrl"
|
||||
:href="instance.registration.serviceUrl || instance.registration.healthUrl"
|
||||
@click.stop
|
||||
/><br>
|
||||
<span class="is-muted" v-text="instance.id" />
|
||||
</td>
|
||||
<td>
|
||||
<sba-tags :tags="instance.tags" />
|
||||
</td>
|
||||
<td>
|
||||
<span v-text="instance.buildVersion" />
|
||||
</td>
|
||||
<td class="instance__actions">
|
||||
<sba-icon-button :id="`nf-settings-${instance.id}`"
|
||||
v-if="hasNotificationFiltersSupport"
|
||||
@click.stop="toggleNotificationFilterSettingsFor(instance)"
|
||||
:icon="hasActiveFilter(instance) ? 'bell-slash' : 'bell'"
|
||||
/>
|
||||
<sba-icon-button icon="trash"
|
||||
v-if="instance.isUnregisterable"
|
||||
@click.stop="unregister(instance)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<notification-filter-settings v-if="showNotificationFilterSettingsObject"
|
||||
v-popper="`nf-settings-${showNotificationFilterSettingsObject.id || showNotificationFilterSettingsObject.name}`"
|
||||
:notification-filters="notificationFilters"
|
||||
:object="showNotificationFilterSettingsObject"
|
||||
@filter-added="handleFilterChange"
|
||||
@filter-deleted="handleFilterChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popper from '@/directives/popper';
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import NotificationFilter from '@/services/notification-filter';
|
||||
import {concatMap, merge, Subject, timer} from '@/utils/rxjs';
|
||||
import {directive as onClickaway} from 'vue-clickaway';
|
||||
import NotificationFilterSettings from './notification-filter-settings';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
applications: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
directives: {onClickaway, Popper},
|
||||
mixins: [subscribing],
|
||||
components: {NotificationFilterSettings},
|
||||
data: () => ({
|
||||
errors: [],
|
||||
hasNotificationFiltersSupport: false,
|
||||
notificationFilters: [],
|
||||
showNotificationFilterSettingsObject: null
|
||||
}),
|
||||
computed: {},
|
||||
methods: {
|
||||
select(name) {
|
||||
this.$router.replace({name: 'applications', params: {selected: name}});
|
||||
},
|
||||
deselect(event, expectedSelected) {
|
||||
this.toggleNotificationFilterSettingsFor(null);
|
||||
if (event && event.target instanceof HTMLAnchorElement) {
|
||||
return;
|
||||
}
|
||||
if (this.selected === expectedSelected || !expectedSelected) {
|
||||
this.$router.replace({name: 'applications'});
|
||||
}
|
||||
},
|
||||
showDetails(instance) {
|
||||
this.$router.push({name: 'instances/details', params: {instanceId: instance.id}});
|
||||
},
|
||||
async scrollIntoView(id, behavior) {
|
||||
if (id) {
|
||||
await this.$nextTick();
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - 100;
|
||||
window.scroll({top, left: window.scrollX, behavior: behavior || 'smooth'});
|
||||
}
|
||||
}
|
||||
},
|
||||
getHeaderClass(application) {
|
||||
if (this.selected !== application.name) {
|
||||
return 'is-selectable';
|
||||
}
|
||||
if (application.status === 'UP') {
|
||||
return 'is-primary';
|
||||
}
|
||||
if (application.status === 'RESTRICTED') {
|
||||
return 'is-warning';
|
||||
}
|
||||
if (application.status === 'DOWN') {
|
||||
return 'is-danger';
|
||||
}
|
||||
if (application.status === 'OUT_OF_SERVICE') {
|
||||
return 'is-danger';
|
||||
}
|
||||
if (application.status === 'OFFLINE') {
|
||||
return 'is-light';
|
||||
}
|
||||
return 'is-light';
|
||||
},
|
||||
async unregister(item) {
|
||||
try {
|
||||
item.unregister();
|
||||
} catch (e) {
|
||||
this.errors.push(e);
|
||||
}
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
vm.notificationFilterSubject = new Subject();
|
||||
return timer(0, 60000)
|
||||
.pipe(
|
||||
merge(vm.notificationFilterSubject),
|
||||
concatMap(this.fetchNotificationFilters),
|
||||
)
|
||||
.subscribe({
|
||||
next: data => {
|
||||
vm.notificationFilters = data;
|
||||
},
|
||||
error: error => {
|
||||
console.warn('Fetching notification filters failed:', error);
|
||||
vm.errors.push(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
async fetchNotificationFilters() {
|
||||
if (this.hasNotificationFiltersSupport) {
|
||||
const response = await NotificationFilter.getFilters();
|
||||
return response.data;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
handleFilterChange(event) {
|
||||
this.toggleNotificationFilterSettingsFor(null);
|
||||
this.notificationFilterSubject.next(event);
|
||||
},
|
||||
toggleNotificationFilterSettingsFor(obj) {
|
||||
this.showNotificationFilterSettingsObject = obj ? obj : null;
|
||||
},
|
||||
hasActiveFilter(object) {
|
||||
return this.notificationFilters.findIndex(f => f.affects(object)) >= 0;
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.scrollIntoView(this.selected, 'instant');
|
||||
this.hasNotificationFiltersSupport = await NotificationFilter.isSupported();
|
||||
this.fetchNotificationFilters();
|
||||
},
|
||||
watch: {
|
||||
selected(newVal) {
|
||||
this.scrollIntoView(newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.application-list__item {
|
||||
transition: all $easing $speed;
|
||||
|
||||
&.is-active {
|
||||
margin: 0.75rem -0.75rem;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
*:not(.is-active) > &:hover {
|
||||
background-color: $white-bis;
|
||||
}
|
||||
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
&__status {
|
||||
width: $gap;
|
||||
}
|
||||
|
||||
&__name,
|
||||
&__version {
|
||||
flex-grow: 1;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
&__name.title {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
justify-self: end;
|
||||
opacity: 0;
|
||||
transition: all $easing $speed;
|
||||
will-change: opacity;
|
||||
margin-right: ($gap / 2);
|
||||
display: flex;
|
||||
|
||||
*:hover > &,
|
||||
*.is-active & {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
& > * {
|
||||
width: ($gap / 2);
|
||||
height: ($gap / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.application__instances td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.instance {
|
||||
&__status {
|
||||
width: $gap;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
text-align: right;
|
||||
opacity: 0;
|
||||
transition: all $easing $speed;
|
||||
will-change: opacity;
|
||||
margin-right: $gap;
|
||||
|
||||
*:hover > & {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
& > * {
|
||||
width: ($gap / 2);
|
||||
height: ($gap / 2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,60 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<span class="has-text-warning" v-if="error">
|
||||
<font-awesome-icon icon="exclamation-triangle" />
|
||||
</span>
|
||||
<span :class="{ 'badge is-badge-danger' : downCount > 0 }" :data-badge="downCount">
|
||||
应用
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
applications: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
error: {
|
||||
type: Error,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
downCount() {
|
||||
return this.applications.reduce((current, next) => {
|
||||
return current + (next.instances.filter(instance => instance.statusInfo.status !== 'UP').length);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
downCount(newVal, oldVal) {
|
||||
if ((newVal === 0) !== (oldVal === 0)) {
|
||||
this.updateFavicon(newVal === 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateFavicon(up) {
|
||||
document.querySelector('link[rel*="icon"]').href = up ? 'assets/img/favicon.png' : 'assets/img/favicon-danger.png';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,130 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div v-if="error" class="message is-warning">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-warning" icon="exclamation-triangle" />
|
||||
Server connection failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level applications-stats">
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">应用</p>
|
||||
<p class="title" v-text="applicationsCount">1</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">实例</p>
|
||||
<p class="title" v-text="instancesCount">1</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div v-if="downCount === 0">
|
||||
<p class="heading">状态</p>
|
||||
<p class="title has-text-success">all up</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="heading">下线</p>
|
||||
<p class="title has-text-danger" v-text="downCount" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="application-group" v-for="group in statusGroups" :key="group.status">
|
||||
<p class="heading" v-text="group.status" />
|
||||
<applications-list :applications="group.applications" :selected="selected" />
|
||||
</div>
|
||||
<div v-if="statusGroups.length === 0">
|
||||
<p class="is-muted">No applications registered.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import transform from 'lodash/transform';
|
||||
import applicationsList from './applications-list';
|
||||
import handle from './handle';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
applications: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
error: {
|
||||
type: Error,
|
||||
default: null
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {applicationsList},
|
||||
computed: {
|
||||
statusGroups() {
|
||||
const byStatus = groupBy(this.applications, application => application.status);
|
||||
const list = transform(byStatus, (result, value, key) => {
|
||||
result.push({status: key, applications: sortBy(value, [application => application.name])})
|
||||
}, []);
|
||||
return sortBy(list, [item => item.status]);
|
||||
},
|
||||
applicationsCount() {
|
||||
return this.applications.length;
|
||||
},
|
||||
instancesCount() {
|
||||
return this.applications.reduce((current, next) => current + next.instances.length, 0);
|
||||
},
|
||||
downCount() {
|
||||
return this.applications.reduce((current, next) => {
|
||||
return current + (next.instances.filter(instance => instance.statusInfo.status !== 'UP').length);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
path: '/applications/:selected?',
|
||||
props: true,
|
||||
name: 'applications',
|
||||
label: '应用',
|
||||
handle,
|
||||
order: 0,
|
||||
component: this
|
||||
});
|
||||
viewRegistry.addRedirect('/', 'applications');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.application-group {
|
||||
margin: $gap 0;
|
||||
}
|
||||
</style>
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="box">
|
||||
<template v-if="!activeFilter">
|
||||
<div class="field">
|
||||
<p class="control has-inline-text">
|
||||
Suppress notifications on <code v-text="object.id || object.name" /> for
|
||||
<span class="select">
|
||||
<select v-model="ttl" @click.stop>
|
||||
<option v-for="option in ttlOptions"
|
||||
:key="`ttl-instance-${option.value}`"
|
||||
:value="option.value" v-text="option.label"
|
||||
/>
|
||||
</select>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<button class="button is-warning" :class="{'is-loading' : actionState === 'executing'}"
|
||||
@click.stop="addFilter"
|
||||
><font-awesome-icon icon="bell-slash" /> Suppress
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="field">
|
||||
<p class="control has-inline-text">
|
||||
Notifications on <code v-text="object.id || object.name" /> are suppressed for
|
||||
<strong v-text="activeFilter.expiry ? activeFilter.expiry.locale('en').fromNow(true) : 'ever' " />.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<button class="button" :class="{'is-loading' : actionState === 'executing'}" @click.stop="deleteActiveFilter">
|
||||
<font-awesome-icon icon="bell" /> Unsuppress
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import NotificationFilter from '@/services/notification-filter';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
notificationFilters: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
ttl: 5 * 60 * 1000,
|
||||
ttlOptions: [
|
||||
{label: '5 minutes', value: 5 * 60 * 1000},
|
||||
{label: '15 minutes', value: 15 * 60 * 1000},
|
||||
{label: '30 minutes', value: 30 * 60 * 1000},
|
||||
{label: '1 hour', value: 60 * 60 * 1000},
|
||||
{label: '3 hours', value: 3 * 60 * 60 * 1000},
|
||||
{label: '8 hours', value: 8 * 60 * 60 * 1000},
|
||||
{label: '24 hours', value: 24 * 60 * 60 * 1000},
|
||||
{label: 'ever', value: -1}
|
||||
],
|
||||
actionState: null
|
||||
}),
|
||||
computed: {
|
||||
activeFilter() {
|
||||
return this.notificationFilters.find(f => f.affects(this.object));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async addFilter() {
|
||||
this.actionState = 'executing';
|
||||
try {
|
||||
const response = await NotificationFilter.addFilter(this.object, this.ttl);
|
||||
this.actionState = 'completed';
|
||||
this.$emit('filter-added', response.data)
|
||||
} catch (error) {
|
||||
console.warn('Adding notification filter failed:', error);
|
||||
}
|
||||
},
|
||||
async deleteActiveFilter() {
|
||||
this.actionState = 'executing';
|
||||
try {
|
||||
await this.activeFilter.delete();
|
||||
this.actionState = 'completed';
|
||||
this.$emit('filter-deleted', this.activeFilter.id);
|
||||
} catch (error) {
|
||||
this.actionState = 'failed';
|
||||
console.warn('Deleting notification filter failed:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.control.has-inline-text {
|
||||
line-height: 2.25em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const views = [];
|
||||
|
||||
/* global require */
|
||||
const context = require.context('.', true, /^\.\/.+\/index\.(js|vue)$/);
|
||||
context.keys().forEach(function (key) {
|
||||
const defaultExport = context(key).default;
|
||||
if (defaultExport && defaultExport.install) {
|
||||
views.push(defaultExport)
|
||||
}
|
||||
});
|
||||
|
||||
export default views;
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<table class="auditevents table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>Event</th>
|
||||
<th>Principal</th>
|
||||
<th>Remote address</th>
|
||||
<th>Session Id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="event in events">
|
||||
<tr class="is-selectable"
|
||||
:class="{ 'auditevents__event--is-detailed' : showDetails[event.key] }"
|
||||
@click="showDetails[event.key] ? $delete(showDetails, event.key) : $set(showDetails, event.key, true)"
|
||||
:key="event.key"
|
||||
>
|
||||
<td v-text="event.timestamp.format('L HH:mm:ss.SSS')" />
|
||||
<td>
|
||||
<span v-text="event.type" class="tag"
|
||||
:class="{ 'is-success' : event.isSuccess(), 'is-danger' : event.isFailure() }"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="hasSessionEndpoint && event.principal">
|
||||
<router-link v-text="event.principal"
|
||||
:to="{ name: 'instances/sessions', params: { 'instanceId' : instance.id }, query: { username : event.principal} }"
|
||||
/>
|
||||
</td>
|
||||
<td v-else v-text="event.principal" />
|
||||
<td v-text="event.remoteAddress" />
|
||||
<td v-if="hasSessionEndpoint && event.sessionId">
|
||||
<router-link v-text="event.sessionId"
|
||||
:to="{ name: 'instances/sessions', params: { 'instanceId' : instance.id }, query: { sessionId : event.sessionId } }"
|
||||
/>
|
||||
</td>
|
||||
<td v-else v-text="event.sessionId" />
|
||||
</tr>
|
||||
<tr :key="`${event.key}-detail`" v-if="showDetails[event.key]">
|
||||
<td colspan="5">
|
||||
<pre class="auditevents__event-detail" v-text="toJson(event.data)" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="events.length === 0">
|
||||
<td class="is-muted" colspan="5">
|
||||
<p v-if="isLoading" class="is-loading">Loading Audit Events...</p>
|
||||
<p v-else>No Audit Events found.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
events: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
showDetails: {}
|
||||
}),
|
||||
computed: {
|
||||
hasSessionEndpoint() {
|
||||
return this.instance.hasEndpoint('sessions');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prettyBytes,
|
||||
toJson(obj) {
|
||||
return JSON.stringify(obj, null, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.auditevents {
|
||||
table-layout: fixed;
|
||||
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&__event--is-detailed td {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
&__event-detail {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,214 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section">
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
type="search"
|
||||
placeholder="Principal"
|
||||
v-model.trim="filter.principal"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
list="auditevent-type"
|
||||
class="input"
|
||||
type="search"
|
||||
placeholder="Type"
|
||||
v-model="filter.type"
|
||||
>
|
||||
<datalist id="auditevent-type">
|
||||
<option value="AUTHENTICATION_FAILURE" />
|
||||
<option value="AUTHENTICATION_SUCCESS" />
|
||||
<option value="AUTHENTICATION_SWITCH" />
|
||||
<option value="AUTHORIZATION_FAILURE" />
|
||||
</datalist>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
type="datetime-local"
|
||||
placeholder="Date"
|
||||
:value="formatDate(filter.after)"
|
||||
@input="filter.after = parseDate($event.target.value)"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon
|
||||
class="has-text-danger"
|
||||
icon="exclamation-triangle"
|
||||
/>
|
||||
Fetching audit events failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="isOldAuditevents"
|
||||
class="message is-warning"
|
||||
>
|
||||
<div class="message-body">
|
||||
Audit Log is not supported for Spring Boot 1.x applications.
|
||||
</div>
|
||||
</div>
|
||||
<auditevents-list
|
||||
:instance="instance"
|
||||
:events="events"
|
||||
:is-loading="isLoading"
|
||||
/>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, debounceTime, merge, Subject, tap, timer} from '@/utils/rxjs';
|
||||
import AuditeventsList from '@/views/instances/auditevents/auditevents-list';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import moment from 'moment';
|
||||
|
||||
class Auditevent {
|
||||
constructor({timestamp, ...event}) {
|
||||
Object.assign(this, event);
|
||||
this.timestamp = moment(timestamp);
|
||||
}
|
||||
|
||||
get key() {
|
||||
return `${this.timestamp}-${this.type}-${this.principal}`;
|
||||
}
|
||||
|
||||
get remoteAddress() {
|
||||
return this.data && this.data.details && this.data.details.remoteAddress || null;
|
||||
}
|
||||
|
||||
get sessionId() {
|
||||
return this.data && this.data.details && this.data.details.sessionId || null;
|
||||
}
|
||||
|
||||
isSuccess() {
|
||||
return this.type.toLowerCase().includes('success');
|
||||
}
|
||||
|
||||
isFailure() {
|
||||
return this.type.toLowerCase().includes('failure');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
components: {AuditeventsList},
|
||||
data: () => ({
|
||||
isLoading: false,
|
||||
error: null,
|
||||
events: [],
|
||||
filter: {
|
||||
after: moment().startOf('day'),
|
||||
type: null,
|
||||
principal: null
|
||||
},
|
||||
isOldAuditevents: false
|
||||
}),
|
||||
watch: {
|
||||
filter: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.filterChanged.next();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate(value) {
|
||||
return value.format(moment.HTML5_FMT.DATETIME_LOCAL);
|
||||
},
|
||||
parseDate(value) {
|
||||
return moment(value, moment.HTML5_FMT.DATETIME_LOCAL, true);
|
||||
},
|
||||
async fetchAuditevents() {
|
||||
this.isLoading = true;
|
||||
const response = await this.instance.fetchAuditevents(this.filter);
|
||||
const converted = response.data.events.map(event => new Auditevent(event));
|
||||
converted.reverse();
|
||||
this.isLoading = false;
|
||||
return converted;
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
vm.filterChanged = new Subject();
|
||||
vm.error = null;
|
||||
return timer(0, 5000)
|
||||
.pipe(
|
||||
merge(vm.filterChanged.pipe(
|
||||
debounceTime(250),
|
||||
tap({
|
||||
next: () => vm.events = []
|
||||
})
|
||||
)),
|
||||
concatMap(this.fetchAuditevents)
|
||||
)
|
||||
.subscribe({
|
||||
next: events => {
|
||||
vm.addEvents(events);
|
||||
},
|
||||
error: error => {
|
||||
console.warn('Fetching audit events failed:', error);
|
||||
if (error.response.headers['content-type'].includes('application/vnd.spring-boot.actuator.v2')) {
|
||||
vm.error = error;
|
||||
} else {
|
||||
vm.isOldAuditevents = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
addEvents(events) {
|
||||
this.events = uniqBy(this.events ? events.concat(this.events) : events, event => event.key);
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/auditevents',
|
||||
parent: 'instances',
|
||||
path: 'auditevents',
|
||||
component: this,
|
||||
label: 'Audit Log',
|
||||
group: 'Security',
|
||||
order: 600,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('auditevents')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,141 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<table class="beans table is-fullwidth is-hoverable">
|
||||
<tbody>
|
||||
<template v-for="bean in beans">
|
||||
<tr
|
||||
class="is-selectable"
|
||||
:key="bean.name"
|
||||
@click="showDetails[bean.name] ? $delete(showDetails, bean.name) : $set(showDetails, bean.name, true)"
|
||||
>
|
||||
<td class="is-breakable">
|
||||
<span
|
||||
v-text="bean.shortName"
|
||||
:title="bean.name"
|
||||
/><br>
|
||||
<small
|
||||
class="is-muted"
|
||||
v-text="bean.shortType"
|
||||
:title="bean.type"
|
||||
/>
|
||||
</td>
|
||||
<td><span
|
||||
v-text="bean.scope"
|
||||
class="tag"
|
||||
/></td>
|
||||
</tr>
|
||||
<tr
|
||||
:key="`${bean.name}-detail`"
|
||||
v-if="showDetails[bean.name]"
|
||||
>
|
||||
<td
|
||||
colspan="2"
|
||||
class="has-background-white-ter"
|
||||
>
|
||||
<table class="table is-narrow is-fullwidth beans__bean-detail">
|
||||
<tbody>
|
||||
<tr v-if="bean.name !== bean.shortName">
|
||||
<th>
|
||||
<small>Name</small>
|
||||
</th>
|
||||
<td
|
||||
class="is-breakable"
|
||||
v-text="bean.name"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(alias, idx) in bean.aliases"
|
||||
:key="alias"
|
||||
>
|
||||
<th
|
||||
v-if="idx === 0"
|
||||
:rowspan="bean.aliases.length"
|
||||
>
|
||||
<small>Aliases</small>
|
||||
</th>
|
||||
<td
|
||||
class="is-breakable"
|
||||
v-text="alias"
|
||||
/>
|
||||
</tr>
|
||||
<tr v-if="bean.type !== bean.shortType">
|
||||
<th>
|
||||
<small>Type</small>
|
||||
</th>
|
||||
<td
|
||||
class="is-breakable"
|
||||
v-text="bean.type"
|
||||
/>
|
||||
</tr>
|
||||
<tr v-if="bean.resource">
|
||||
<th>
|
||||
<small>Resource</small>
|
||||
</th>
|
||||
<td
|
||||
class="is-breakable"
|
||||
v-text="bean.resource"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(dependency, idx) in bean.dependencies"
|
||||
:key="dependency"
|
||||
>
|
||||
<th
|
||||
v-if="idx === 0"
|
||||
:rowspan="bean.dependencies.length"
|
||||
>
|
||||
<small>Dependencies</small>
|
||||
</th>
|
||||
<td
|
||||
class="is-breakable"
|
||||
v-text="dependency"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
beans: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
showDetails: {}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.beans {
|
||||
table.beans__bean-detail {
|
||||
tbody tr {
|
||||
pointer-events: none;
|
||||
background-color: $white-ter;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,150 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{isLoading: isLoading}">
|
||||
<div class="container">
|
||||
<div
|
||||
v-if="error"
|
||||
class="message is-danger"
|
||||
>
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon
|
||||
class="has-text-danger"
|
||||
icon="exclamation-triangle"
|
||||
/>
|
||||
Fetching beans failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
type="search"
|
||||
placeholder="filter"
|
||||
v-model="filter"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<template v-for="context in filteredContexts">
|
||||
<h3 class="title" v-text="context.name" :key="context.name" />
|
||||
<beans-list :beans="context.beans" :key="`${context.name}-beans`" />
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import {compareBy} from '@/utils/collections';
|
||||
import shortenClassname from '@/utils/shortenClassname';
|
||||
import BeansList from '@/views/instances/beans/beans-list';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
class Bean {
|
||||
constructor(name, bean) {
|
||||
Object.assign(this, bean);
|
||||
this.name = name;
|
||||
this.shortName = shortenClassname(this.name, 80);
|
||||
this.shortType = shortenClassname(this.type, 80);
|
||||
}
|
||||
}
|
||||
|
||||
const flattenBeans = beans => {
|
||||
return Object.keys(beans)
|
||||
.map((key) => {
|
||||
return new Bean(key, beans[key]);
|
||||
});
|
||||
};
|
||||
|
||||
const flattenContexts = beanData => {
|
||||
if (isEmpty(beanData.contexts)) {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(beanData.contexts)
|
||||
.map((key) => ({
|
||||
beans: flattenBeans(beanData.contexts[key].beans),
|
||||
name: key,
|
||||
parent: beanData.contexts[key].parentId
|
||||
}));
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {BeansList},
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
isLoading: false,
|
||||
error: null,
|
||||
contexts: [],
|
||||
filter: '',
|
||||
}),
|
||||
computed: {
|
||||
filteredContexts() {
|
||||
const filterFn = this.getFilterFn();
|
||||
return this.contexts.map(ctx => ({
|
||||
...ctx,
|
||||
beans: ctx.beans.filter(filterFn).sort(compareBy(bean => bean.name))
|
||||
}));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFilterFn() {
|
||||
if (!this.filter || this.filter === '') {
|
||||
return () => true;
|
||||
}
|
||||
const regex = new RegExp(this.filter, 'i');
|
||||
return bean => (bean.name.match(regex) || bean.aliases.some(alias => alias.match(regex)));
|
||||
},
|
||||
async fetchBeans() {
|
||||
this.error = null;
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const res = await this.instance.fetchBeans();
|
||||
this.contexts = flattenContexts(res.data);
|
||||
} catch (error) {
|
||||
console.warn('Fetching beans failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchBeans();
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/beans',
|
||||
parent: 'instances',
|
||||
path: 'beans',
|
||||
group: 'Insights',
|
||||
component: this,
|
||||
label: 'Beans',
|
||||
order: 110,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('beans')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<table class="caches table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Cache Manager</th>
|
||||
<th class="is-narrow">
|
||||
<sba-confirm-button
|
||||
class="button"
|
||||
:class="{'is-loading' : clearingAll === 'executing', 'is-info' : clearingAll === 'completed', 'is-danger' : clearingAll === 'failed'}"
|
||||
:disabled="clearingAll !== null"
|
||||
@click="clearCaches"
|
||||
>
|
||||
<span v-if="clearingAll === 'completed'">Cleared</span>
|
||||
<span v-else-if="clearingAll === 'failed'">Failed</span>
|
||||
<span v-else><font-awesome-icon icon="trash" /> Clear</span>
|
||||
</sba-confirm-button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="cache in caches"
|
||||
:key="cache.key"
|
||||
>
|
||||
<td>
|
||||
<span
|
||||
class="is-breakable"
|
||||
v-text="cache.name"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="is-breakable"
|
||||
v-text="cache.cacheManager"
|
||||
/>
|
||||
</td>
|
||||
<td class="is-narrow">
|
||||
<button class="button"
|
||||
:class="{ 'is-loading' : clearing[cache.name] === 'executing', 'is-info' : clearing[cache.name] === 'completed', 'is-danger' : clearing[cache.name] === 'failed' }"
|
||||
:disabled="cache.name in clearing" @click="clearCache(cache.name)"
|
||||
>
|
||||
<span v-if="clearing[cache.name] === 'completed'">Cleared</span>
|
||||
<span v-else-if="clearing[cache.name] === 'failed'">Failed</span>
|
||||
<span v-else><font-awesome-icon icon="trash" /> Clear</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="caches.length === 0">
|
||||
<td class="is-muted" colspan="3 ">
|
||||
<p v-if="isLoading" class="is-loading">Loading Caches...</p>
|
||||
<p v-else>No caches found.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, from, listen, of, tap} from '@/utils/rxjs';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'CachesList',
|
||||
props: {
|
||||
caches: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
clearing: {},
|
||||
clearingAll: null,
|
||||
}),
|
||||
methods: {
|
||||
clearCaches() {
|
||||
const vm = this;
|
||||
from(vm.instance.clearCaches())
|
||||
.pipe(listen(status => vm.clearingAll = status))
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
setTimeout(() => vm.clearingAll = null, 2500);
|
||||
return vm.$emit('cleared', '*');
|
||||
}
|
||||
});
|
||||
},
|
||||
clearCache(cache) {
|
||||
const vm = this;
|
||||
vm._clearCache(cache)
|
||||
.pipe(listen(status => vm.$set(vm.clearing, cache, status)))
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
setTimeout(() => vm.$delete(vm.clearing, cache), 2500);
|
||||
return vm.$emit('cleared', cache);
|
||||
},
|
||||
});
|
||||
},
|
||||
_clearCache(cache) {
|
||||
const vm = this;
|
||||
return of(cache)
|
||||
.pipe(
|
||||
concatMap(async cache => {
|
||||
await vm.instance.clearCache(cache);
|
||||
return cache;
|
||||
}),
|
||||
tap({
|
||||
error: error => {
|
||||
console.warn(`Clearing cache ${cache} failed:`, error);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.caches {
|
||||
td, th {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,138 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section">
|
||||
<div
|
||||
class="container"
|
||||
>
|
||||
<div
|
||||
v-if="error"
|
||||
class="message is-danger"
|
||||
>
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon
|
||||
class="has-text-danger"
|
||||
icon="exclamation-triangle"
|
||||
/>
|
||||
Fetching caches failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
type="search"
|
||||
placeholder="name filter"
|
||||
v-model="filter"
|
||||
>
|
||||
</p>
|
||||
<p class="control">
|
||||
<span class="button is-static">
|
||||
<span v-text="filteredCaches.length" />
|
||||
/
|
||||
<span v-text="caches.length" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<caches-list :instance="instance" :caches="filteredCaches" :is-loading="isLoading" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import CachesList from '@/views/instances/caches/caches-list';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
const flattenCaches = cacheData => {
|
||||
if (isEmpty(cacheData.cacheManagers)) {
|
||||
return [];
|
||||
}
|
||||
const mappend = Object.entries(cacheData.cacheManagers).flatMap(
|
||||
([cacheManagerName, v]) => Object.keys(v.caches)
|
||||
.map(cacheName => ({cacheManager: cacheManagerName, name: cacheName, key: `${cacheManagerName}:${cacheName}`}))
|
||||
);
|
||||
return mappend.sort((c1, c2) => c1.key.localeCompare(c2.key));
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {CachesList},
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
isLoading: false,
|
||||
error: null,
|
||||
caches: [],
|
||||
filter: '',
|
||||
}),
|
||||
computed: {
|
||||
filteredCaches() {
|
||||
let filterFn = this.getFilterFn();
|
||||
return filterFn ? this.caches.filter(filterFn) : this.caches;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchCaches() {
|
||||
this.error = null;
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const res = await this.instance.fetchCaches();
|
||||
this.caches = flattenCaches(res.data);
|
||||
} catch (error) {
|
||||
console.warn('Fetching caches failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
getFilterFn() {
|
||||
let filterFn = null;
|
||||
|
||||
if (this.filter) {
|
||||
const normalizedFilter = this.filter.toLowerCase();
|
||||
filterFn = cache => cache.name.toLowerCase().includes(normalizedFilter);
|
||||
}
|
||||
|
||||
return filterFn;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchCaches();
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/caches',
|
||||
parent: 'instances',
|
||||
path: 'caches',
|
||||
group: 'Data',
|
||||
component: this,
|
||||
label: 'Caches',
|
||||
order: 970,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('caches')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{ 'is-loading' : !hasLoaded }">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching configuration properties failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field has-addons" v-if="configprops">
|
||||
<p class="control is-expanded">
|
||||
<input class="input" type="search" placeholder="name / value filter" v-model="filter">
|
||||
</p>
|
||||
</div>
|
||||
<sba-panel :header-sticks-below="['#navigation']"
|
||||
v-for="bean in configurationPropertiesBeans"
|
||||
:key="bean.name"
|
||||
:title=" bean.name"
|
||||
>
|
||||
<table class="table is-fullwidth"
|
||||
v-if="Object.keys(bean.properties).length > 0"
|
||||
>
|
||||
<tr v-for="(value, name) in bean.properties" :key="`${bean.name}-${name}`">
|
||||
<td v-text="name" />
|
||||
<td class="is-breakable" v-text="value" />
|
||||
</tr>
|
||||
</table>
|
||||
<p class="is-muted" v-else>No properties set</p>
|
||||
</sba-panel>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import mapKeys from 'lodash/mapKeys';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
|
||||
const filterProperty = (needle) => (value, name) => {
|
||||
return name.toString().toLowerCase().includes(needle) || value.toString().toLowerCase().includes(needle);
|
||||
};
|
||||
const filterProperties = (needle, properties) => pickBy(properties, filterProperty(needle));
|
||||
const filterConfigurationProperties = (needle) => (propertySource) => {
|
||||
if (!propertySource || !propertySource.properties) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...propertySource,
|
||||
properties: filterProperties(needle, propertySource.properties)
|
||||
};
|
||||
};
|
||||
|
||||
function flattenBean(obj, prefix = '') {
|
||||
if (Object(obj) !== obj) {
|
||||
return {[prefix]: obj};
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return {[prefix]: []};
|
||||
} else {
|
||||
return obj.map(
|
||||
(value, idx) => flattenBean(value, `${prefix}[${idx}]`)
|
||||
).reduce((c, n) => ({...c, ...n}), {});
|
||||
}
|
||||
} else {
|
||||
if (isEmpty(obj)) {
|
||||
return {[prefix]: {}};
|
||||
} else {
|
||||
return Object.entries(obj).map(
|
||||
([name, value]) => flattenBean(value, prefix ? `${prefix}.${name}` : name)
|
||||
).reduce((c, n) => ({...c, ...n}), {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flattenConfigurationPropertiesBeans = (configprops) => {
|
||||
const propertySources = [];
|
||||
const contextNames = Object.keys(configprops.contexts);
|
||||
|
||||
for (const contextName of contextNames) {
|
||||
const context = configprops.contexts[contextName];
|
||||
const beanNames = Object.keys(context.beans);
|
||||
|
||||
for (const beanName of beanNames) {
|
||||
const bean = context.beans[beanName];
|
||||
const properties = mapKeys(flattenBean(bean.properties), (value, key) => `${bean.prefix}.${key}`);
|
||||
propertySources.push({
|
||||
name: contextNames.length > 1 ? `${contextName}: ${beanName}` : beanName,
|
||||
properties
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return propertySources;
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
configprops: null,
|
||||
filter: null
|
||||
}),
|
||||
computed: {
|
||||
configurationPropertiesBeans() {
|
||||
if (!this.configprops) {
|
||||
return [];
|
||||
}
|
||||
const configurationProperties = flattenConfigurationPropertiesBeans(this.configprops);
|
||||
if (!this.filter) {
|
||||
return configurationProperties;
|
||||
}
|
||||
return configurationProperties
|
||||
.map(filterConfigurationProperties(this.filter.toLowerCase()))
|
||||
.filter(ps => ps && Object.keys(ps.properties).length > 0);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchConfigprops();
|
||||
},
|
||||
methods: {
|
||||
async fetchConfigprops() {
|
||||
this.error = null;
|
||||
try {
|
||||
const configprops = await this.instance.fetchConfigprops();
|
||||
this.configprops = configprops.data;
|
||||
} catch (error) {
|
||||
console.warn('Fetching configuration properties failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/configprops',
|
||||
parent: 'instances',
|
||||
path: 'configprops',
|
||||
component: this,
|
||||
label: 'Configuration Properties',
|
||||
group: 'Insights',
|
||||
order: 110,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('configprops')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,139 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="cache-chart">
|
||||
<svg class="cache-chart__svg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d3 from '@/utils/d3';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data: () => ({}),
|
||||
methods: {
|
||||
drawChart(_data) {
|
||||
const vm = this;
|
||||
const data = _data.length === 1 ? _data.concat([{..._data[0], timestamp: _data[0].timestamp + 1}]) : _data;
|
||||
|
||||
///setup x and y scale
|
||||
const extent = d3.extent(data, d => d.timestamp);
|
||||
const x = d3.scaleTime()
|
||||
.range([0, vm.width])
|
||||
.domain(extent);
|
||||
|
||||
const y = d3.scaleLinear()
|
||||
.range([vm.height, 0])
|
||||
.domain([0, d3.max(data, d => d.total) * 1.05]);
|
||||
|
||||
//draw areas
|
||||
const miss = vm.areas.selectAll('.cache-chart__area--miss')
|
||||
.data([data]);
|
||||
miss.enter().append('path')
|
||||
.merge(miss)
|
||||
.attr('class', 'cache-chart__area--miss')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(d => y(d.hit))
|
||||
.y1(d => y(d.total)));
|
||||
miss.exit().remove();
|
||||
|
||||
const hit = vm.areas.selectAll('.cache-chart__area--hit')
|
||||
.data([data]);
|
||||
hit.enter().append('path')
|
||||
.merge(hit)
|
||||
.attr('class', 'cache-chart__area--hit')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(y(0))
|
||||
.y1(d => y(d.hit)));
|
||||
hit.exit().remove();
|
||||
|
||||
//draw axis
|
||||
vm.xAxis.call(d3.axisBottom(x)
|
||||
.ticks(5)
|
||||
.tickFormat(d => moment(d).format('HH:mm:ss'))
|
||||
);
|
||||
|
||||
vm.yAxis.call(d3.axisLeft(y)
|
||||
.ticks(5)
|
||||
.tickFormat(d => d <= 1000 ? d : (d / 1000).toFixed(1) + 'K')
|
||||
);
|
||||
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const margin = {
|
||||
top: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
left: 50,
|
||||
};
|
||||
|
||||
this.width = this.$el.getBoundingClientRect().width - margin.left - margin.right;
|
||||
this.height = this.$el.getBoundingClientRect().height - margin.top - margin.bottom;
|
||||
|
||||
this.chartLayer = d3.select(this.$el.querySelector('.cache-chart__svg'))
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
this.xAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'cache-chart__axis-x')
|
||||
.attr('transform', `translate(0,${this.height})`);
|
||||
|
||||
this.yAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'cache-chart__axis-y')
|
||||
.attr('stroke', null);
|
||||
|
||||
this.areas = this.chartLayer.append('g');
|
||||
|
||||
this.drawChart(this.data);
|
||||
},
|
||||
watch: {
|
||||
data: 'drawChart'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.cache-chart {
|
||||
&__svg {
|
||||
height: 159px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__area {
|
||||
&--miss {
|
||||
fill: $warning;
|
||||
opacity: 0.8;
|
||||
}
|
||||
&--hit {
|
||||
fill: $info;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="datasource-chart">
|
||||
<svg class="datasource-chart__svg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d3 from '@/utils/d3';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
drawChart(_data) {
|
||||
const vm = this;
|
||||
const data = _data.length === 1 ? _data.concat([{..._data[0], timestamp: _data[0].timestamp + 1}]) : _data;
|
||||
|
||||
///setup x and y scale
|
||||
const extent = d3.extent(data, d => d.timestamp);
|
||||
const x = d3.scaleTime()
|
||||
.range([0, vm.width])
|
||||
.domain(extent);
|
||||
|
||||
const y = d3.scaleLinear()
|
||||
.range([vm.height, 0])
|
||||
.domain([0, d3.max(data, d => d.active) * 1.05]);
|
||||
|
||||
//draw max
|
||||
const max = vm.areas.selectAll('.datasource-chart__line--max')
|
||||
.data([data]);
|
||||
max.enter().append('path')
|
||||
.merge(max)
|
||||
.attr('class', 'datasource-chart__line--max')
|
||||
.attr('d', d3.line()
|
||||
.x(d => x(d.timestamp))
|
||||
.y(d => y(d.max)));
|
||||
max.exit().remove();
|
||||
|
||||
//draw areas
|
||||
const active = vm.areas.selectAll('.datasource-chart__area--active')
|
||||
.data([data]);
|
||||
active.enter().append('path')
|
||||
.merge(active)
|
||||
.attr('class', 'datasource-chart__area--active')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(y(0))
|
||||
.y1(d => y(d.active)));
|
||||
active.exit().remove();
|
||||
|
||||
//draw axis
|
||||
vm.xAxis.call(d3.axisBottom(x)
|
||||
.ticks(5)
|
||||
.tickFormat(d => moment(d).format('HH:mm:ss'))
|
||||
);
|
||||
|
||||
vm.yAxis.call(d3.axisLeft(y)
|
||||
.ticks(5)
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const margin = {
|
||||
top: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
left: 50,
|
||||
};
|
||||
|
||||
this.width = this.$el.getBoundingClientRect().width - margin.left - margin.right;
|
||||
this.height = this.$el.getBoundingClientRect().height - margin.top - margin.bottom;
|
||||
|
||||
this.chartLayer = d3.select(this.$el.querySelector('.datasource-chart__svg'))
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
this.xAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'datasource-chart__axis-x')
|
||||
.attr('transform', `translate(0,${this.height})`);
|
||||
|
||||
this.yAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'datasource-chart__axis-y')
|
||||
.attr('stroke', null);
|
||||
|
||||
this.areas = this.chartLayer.append('g');
|
||||
|
||||
this.drawChart(this.data);
|
||||
},
|
||||
watch: {
|
||||
data: 'drawChart'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.datasource-chart {
|
||||
&__svg {
|
||||
height: 159px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__area {
|
||||
&--active {
|
||||
fill: $info;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&__line--max {
|
||||
stroke: $grey;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel :title="`Cache: ${cacheName}`" v-if="hasLoaded">
|
||||
<div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching cache metrics failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level cache-current" v-if="current">
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-info">Hits</p>
|
||||
<p v-text="current.hit" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-warning">Misses</p>
|
||||
<p v-text="current.miss" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Hit ratio</p>
|
||||
<p v-text="ratio" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="current.size" class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Size</p>
|
||||
<p v-text="current.size" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<cache-chart v-if="chartData.length > 0" :data="chartData" />
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import moment from 'moment';
|
||||
import cacheChart from './cache-chart';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
},
|
||||
cacheName: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {cacheChart},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
current: null,
|
||||
disableSize: false,
|
||||
chartData: [],
|
||||
}),
|
||||
computed: {
|
||||
ratio() {
|
||||
if (this.current.total > 0) {
|
||||
return (this.current.hit / this.current.total * 100).toFixed(2) + '%';
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchMetrics() {
|
||||
const responseHit = this.instance.fetchMetric('cache.gets', {name: this.cacheName, result: 'hit'});
|
||||
const responseMiss = this.instance.fetchMetric('cache.gets', {name: this.cacheName, result: 'miss'});
|
||||
let size = undefined;
|
||||
if (!this.disableSize) {
|
||||
const responsSize = this.instance.fetchMetric('cache.size', {name: this.cacheName});
|
||||
try {
|
||||
size = (await responsSize).data.measurements[0].value;
|
||||
} catch (error) {
|
||||
this.disableSize = true;
|
||||
console.warn('Fetching cache size failed - error is ignored', error)
|
||||
}
|
||||
}
|
||||
const hit = (await responseHit).data.measurements[0].value;
|
||||
const miss = (await responseMiss).data.measurements[0].value;
|
||||
return {
|
||||
hit,
|
||||
miss,
|
||||
total: hit + miss,
|
||||
size
|
||||
};
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(vm.fetchMetrics))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
vm.hasLoaded = true;
|
||||
vm.current = data;
|
||||
vm.chartData.push({...data, timestamp: moment().valueOf()});
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn(`Fetching cache ${vm.cacheName} metrics failed:`, error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.datasource-current {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,65 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<details-cache v-for="cache in caches" :key="cache"
|
||||
:instance="instance" :cache-name="cache"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import uniq from 'lodash/uniq';
|
||||
import detailsCache from './details-cache';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {detailsCache},
|
||||
data: () => ({
|
||||
caches: [],
|
||||
}),
|
||||
methods: {
|
||||
async fetchCaches() {
|
||||
const response = await this.instance.fetchMetric('cache.gets');
|
||||
return uniq(response.data.availableTags.filter(tag => tag.tag === 'name')[0].values);
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(this.fetchCaches))
|
||||
.subscribe({
|
||||
next: names => {
|
||||
vm.caches = names
|
||||
},
|
||||
error: error => {
|
||||
console.warn('Fetching caches failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel :title="`Datasource: ${dataSource}`" v-if="hasLoaded">
|
||||
<div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching datasource metrics failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level datasource-current" v-if="current">
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-info">Active connections</p>
|
||||
<p v-text="current.active" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Min connections</p>
|
||||
<p v-text="current.min" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Max connections</p>
|
||||
<p v-if="current.max >= 0" v-text="current.max" />
|
||||
<p v-else>unlimited</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<datasource-chart v-if="chartData.length > 0" :data="chartData" />
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import moment from 'moment';
|
||||
import datasourceChart from './datasource-chart';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
},
|
||||
dataSource: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {datasourceChart},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
current: null,
|
||||
chartData: [],
|
||||
}),
|
||||
methods: {
|
||||
async fetchMetrics() {
|
||||
const responseActive = this.instance.fetchMetric('data.source.active.connections', {name: this.dataSource});
|
||||
const responseMin = this.instance.fetchMetric('data.source.min.connections', {name: this.dataSource});
|
||||
const responseMax = this.instance.fetchMetric('data.source.max.connections', {name: this.dataSource});
|
||||
|
||||
return {
|
||||
active: (await responseActive).data.measurements[0].value,
|
||||
min: (await responseMin).data.measurements[0].value,
|
||||
max: (await responseMax).data.measurements[0].value
|
||||
};
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(vm.fetchMetrics))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
vm.hasLoaded = true;
|
||||
vm.current = data;
|
||||
vm.chartData.push({...data, timestamp: moment().valueOf()});
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn(`Fetching datasource ${vm.dataSource} metrics failed:`, error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.datasource-current {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<details-datasource v-for="dataSource in dataSources" :key="dataSource"
|
||||
:instance="instance" :data-source="dataSource"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import detailsDatasource from './details-datasource';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {detailsDatasource},
|
||||
data: () => ({
|
||||
dataSources: [],
|
||||
}),
|
||||
methods: {
|
||||
async fetchDataSources() {
|
||||
const response = await this.instance.fetchMetric('data.source.active.connections');
|
||||
return response.data.availableTags.filter(tag => tag.tag === 'name')[0].values;
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(this.fetchDataSources))
|
||||
.subscribe({
|
||||
next: names => {
|
||||
vm.dataSources = names
|
||||
},
|
||||
error: error => {
|
||||
console.warn('Fetching datasources failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,107 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel title="Garbage Collection Pauses" v-if="hasLoaded">
|
||||
<div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching GC metrics failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level" v-if="current">
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Count</p>
|
||||
<p v-text="current.count" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Total time spent</p>
|
||||
<p v-text="`${current.total_time.asSeconds().toFixed(4)}s`" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Max time spent</p>
|
||||
<p v-text="`${current.max.asSeconds().toFixed(4)}s`" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import moment from 'moment';
|
||||
import {toMillis} from '../metrics/metric';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
current: null,
|
||||
}),
|
||||
methods: {
|
||||
async fetchMetrics() {
|
||||
const response = await this.instance.fetchMetric('jvm.gc.pause');
|
||||
const measurements = response.data.measurements.reduce(
|
||||
(current, measurement) => ({
|
||||
...current,
|
||||
[measurement.statistic.toLowerCase()]: measurement.value
|
||||
}),
|
||||
{}
|
||||
);
|
||||
return {
|
||||
...measurements,
|
||||
total_time: moment.duration(toMillis(measurements.total_time, response.baseUnit)),
|
||||
max: moment.duration(toMillis(measurements.max, response.baseUnit)),
|
||||
};
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(this.fetchMetrics))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
vm.hasLoaded = true;
|
||||
vm.current = data;
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn('Fetching GC metrics failed:', error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,72 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel title="Health">
|
||||
<div>
|
||||
<div v-if="error" class="message is-warning">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-warning" icon="exclamation-triangle" />
|
||||
Fetching live health status failed. This is the last known information.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<health-details name="Instance" :health="health" />
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import healthDetails from './health-details';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {healthDetails},
|
||||
data: () => ({
|
||||
error: null,
|
||||
liveHealth: null,
|
||||
}),
|
||||
created() {
|
||||
this.fetchHealth();
|
||||
},
|
||||
computed: {
|
||||
health() {
|
||||
return this.liveHealth || this.instance.statusInfo;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchHealth() {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await this.instance.fetchHealth();
|
||||
this.liveHealth = res.data;
|
||||
} catch (error) {
|
||||
console.warn('Fetching live health failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,95 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel title="Info">
|
||||
<div>
|
||||
<div v-if="error" class="message is-warning">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-warning" icon="exclamation-triangle" />
|
||||
Fetching live info failed. This is the last known information.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="content info">
|
||||
<table class="table" v-if="!isEmptyInfo">
|
||||
<tr v-for="(value, key) in info" :key="key">
|
||||
<td class="info__key" v-text="key" />
|
||||
<td>
|
||||
<sba-formatted-obj :value="value" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p v-else class="is-muted">No info provided.</p>
|
||||
</div>
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
error: null,
|
||||
liveInfo: null
|
||||
}),
|
||||
computed: {
|
||||
info() {
|
||||
return this.liveInfo || this.instance.info
|
||||
},
|
||||
isEmptyInfo() {
|
||||
return Object.keys(this.info).length <= 0;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchInfo();
|
||||
},
|
||||
methods: {
|
||||
async fetchInfo() {
|
||||
if (this.instance.hasEndpoint('info')) {
|
||||
try {
|
||||
this.error = null;
|
||||
const res = await this.instance.fetchInfo();
|
||||
this.liveInfo = res.data;
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
console.warn('Fetching info failed:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.info {
|
||||
overflow: auto;
|
||||
|
||||
&__key {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel :title="`Memory: ${name}`" v-if="hasLoaded">
|
||||
<div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching memory metrics failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level memory-current" v-if="current">
|
||||
<div class="level-item has-text-centered" v-if="current.metaspace">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-primary">Metaspace</p>
|
||||
<p v-text="prettyBytes(current.metaspace)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-info">Used</p>
|
||||
<p v-text="prettyBytes(current.used)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-warning">Size</p>
|
||||
<p v-text="prettyBytes(current.committed)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered" v-if="current.max >= 0">
|
||||
<div>
|
||||
<p class="heading">Max</p>
|
||||
<p v-text="prettyBytes(current.max)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mem-chart v-if="chartData.length > 0" :data="chartData" />
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import moment from 'moment';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
import memChart from './mem-chart';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {memChart},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
current: null,
|
||||
chartData: [],
|
||||
}),
|
||||
computed: {
|
||||
name() {
|
||||
switch (this.type) {
|
||||
case 'heap':
|
||||
return 'Heap';
|
||||
case 'nonheap':
|
||||
return 'Non heap';
|
||||
default:
|
||||
return this.type;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prettyBytes,
|
||||
async fetchMetrics() {
|
||||
const responseMax = this.instance.fetchMetric('jvm.memory.max', {area: this.type});
|
||||
const responseUsed = this.instance.fetchMetric('jvm.memory.used', {area: this.type});
|
||||
const responeMetaspace = this.type === 'nonheap'
|
||||
? this.instance.fetchMetric('jvm.memory.used', {area: this.type, id: 'Metaspace'})
|
||||
: null;
|
||||
const responseCommitted = this.instance.fetchMetric('jvm.memory.committed', {area: this.type});
|
||||
return {
|
||||
max: (await responseMax).data.measurements[0].value,
|
||||
used: (await responseUsed).data.measurements[0].value,
|
||||
metaspace: responeMetaspace ? (await responeMetaspace).data.measurements[0].value : null,
|
||||
committed: (await responseCommitted).data.measurements[0].value
|
||||
};
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(this.fetchMetrics))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
vm.hasLoaded = true;
|
||||
vm.current = data;
|
||||
vm.chartData.push({...data, timestamp: moment().valueOf()});
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn('Fetching memory metrics failed:', error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.memory-current {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel title="Metadata">
|
||||
<div class="content metadata" v-if="metadata">
|
||||
<table class="table" v-if="!isEmptyMetadata">
|
||||
<tr v-for="(value, key) in metadata" :key="key">
|
||||
<td class="metadata__key" v-text="key" />
|
||||
<td>
|
||||
<sba-formatted-obj :value="value" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p v-else class="is-muted">No metadata provided.</p>
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
metadata() {
|
||||
return this.instance.registration.metadata;
|
||||
},
|
||||
isEmptyMetadata() {
|
||||
return Object.keys(this.metadata).length <= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.metadata {
|
||||
overflow: auto;
|
||||
|
||||
&__key {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel title="Process" v-if="hasLoaded">
|
||||
<div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching process metrics failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level">
|
||||
<div class="level-item has-text-centered" v-if="pid">
|
||||
<div>
|
||||
<p class="heading">PID</p>
|
||||
<p v-text="pid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered" v-if="uptime">
|
||||
<div>
|
||||
<p class="heading">Uptime</p>
|
||||
<p>
|
||||
<process-uptime :value="toMillis(uptime.value, uptime.baseUnit)" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered" v-if="processCpuLoad">
|
||||
<div>
|
||||
<p class="heading">Process CPU Usage</p>
|
||||
<p v-text="processCpuLoad.toFixed(2)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered" v-if="systemCpuLoad">
|
||||
<div>
|
||||
<p class="heading">System CPU Usage</p>
|
||||
<p v-text="systemCpuLoad.toFixed(2)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered" v-if="systemCpuCount">
|
||||
<div>
|
||||
<p class="heading">CPUs</p>
|
||||
<p v-text="systemCpuCount" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import {toMillis} from '../metrics/metric';
|
||||
import processUptime from './process-uptime';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {processUptime},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
pid: null,
|
||||
uptime: {value: null, baseUnit: null},
|
||||
systemCpuLoad: null,
|
||||
processCpuLoad: null,
|
||||
systemCpuCount: null
|
||||
}),
|
||||
created() {
|
||||
this.fetchPid();
|
||||
this.fetchUptime();
|
||||
this.fetchCpuCount();
|
||||
},
|
||||
methods: {
|
||||
toMillis,
|
||||
async fetchUptime() {
|
||||
try {
|
||||
const response = await this.fetchMetric('process.uptime');
|
||||
this.uptime = {value: response.measurements[0].value, baseUnit: response.baseUnit};
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
console.warn('Fetching Uptime failed:', error);
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
},
|
||||
async fetchPid() {
|
||||
if (this.instance.hasEndpoint('env')) {
|
||||
try {
|
||||
const response = await this.instance.fetchEnv('PID');
|
||||
this.pid = response.data.property.value;
|
||||
} catch (error) {
|
||||
console.warn('Fetching PID failed:', error);
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
}
|
||||
},
|
||||
async fetchCpuCount() {
|
||||
try {
|
||||
this.systemCpuCount = (await this.fetchMetric('system.cpu.count')).measurements[0].value;
|
||||
} catch (error) {
|
||||
console.warn('Fetching Cpu Count failed:', error);
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(this.fetchCpuLoadMetrics))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
vm.processCpuLoad = data.processCpuLoad;
|
||||
vm.systemCpuLoad = data.systemCpuLoad;
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn('Fetching CPU Usage metrics failed:', error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
},
|
||||
async fetchCpuLoadMetrics() {
|
||||
const fetchProcessCpuLoad = this.fetchMetric('process.cpu.usage');
|
||||
const fetchSystemCpuLoad = this.fetchMetric('system.cpu.usage');
|
||||
let processCpuLoad;
|
||||
let systemCpuLoad;
|
||||
try {
|
||||
processCpuLoad = (await fetchProcessCpuLoad).measurements[0].value
|
||||
} catch (error) {
|
||||
console.warn('Fetching Process CPU Load failed:', error);
|
||||
}
|
||||
try {
|
||||
systemCpuLoad = (await fetchSystemCpuLoad).measurements[0].value
|
||||
} catch (error) {
|
||||
console.warn('Fetching Sytem CPU Load failed:', error);
|
||||
}
|
||||
return {
|
||||
processCpuLoad,
|
||||
systemCpuLoad
|
||||
};
|
||||
},
|
||||
async fetchMetric(name) {
|
||||
const response = await this.instance.fetchMetric(name);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<sba-panel title="Threads" v-if="hasLoaded">
|
||||
<div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching threads metrics failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level threads-current" v-if="current">
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-warning">Live</p>
|
||||
<p v-text="current.live" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading has-bullet has-bullet-info">Daemon</p>
|
||||
<p v-text="current.daemon" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Peak Live</p>
|
||||
<p v-text="current.peak" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<threads-chart v-if="chartData.length > 0" :data="chartData" />
|
||||
</div>
|
||||
</sba-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import moment from 'moment';
|
||||
import threadsChart from './threads-chart';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {threadsChart},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
current: null,
|
||||
chartData: [],
|
||||
}),
|
||||
methods: {
|
||||
async fetchMetrics() {
|
||||
const responseLive = this.instance.fetchMetric('jvm.threads.live');
|
||||
const responsePeak = this.instance.fetchMetric('jvm.threads.peak');
|
||||
const responseDaemon = this.instance.fetchMetric('jvm.threads.daemon');
|
||||
|
||||
return {
|
||||
live: (await responseLive).data.measurements[0].value,
|
||||
peak: (await responsePeak).data.measurements[0].value,
|
||||
daemon: (await responseDaemon).data.measurements[0].value
|
||||
};
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
return timer(0, 2500)
|
||||
.pipe(concatMap(this.fetchMetrics))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
vm.hasLoaded = true;
|
||||
vm.current = data;
|
||||
vm.chartData.push({...data, timestamp: moment().valueOf()});
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn('Fetching threads metrics failed:', error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.threads-current {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<table class="health-details table is-fullwidth">
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
<span v-text="name" />
|
||||
<span class="health-details__status" :class="`health-details__status--${health.status}`"
|
||||
v-text="health.status"
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr v-if="details && details.length > 0">
|
||||
<td class="health-details__nested" colspan="2">
|
||||
<table class="health-details table is-fullwidth">
|
||||
<tr class="health-details__detail" v-for="detail in details" :key="detail.name">
|
||||
<td v-text="detail.name" />
|
||||
<td v-if="name === 'diskSpace'" v-text="prettyBytes(detail.value)" />
|
||||
<td class="is-breakable" v-else v-text="detail.value" />
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for="child in childHealth" :key="child.name">
|
||||
<td class="health-details__nested" colspan="2">
|
||||
<health-details :name="child.name" :health="child.value" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
const isChildHealth = (value) => {
|
||||
return value !== null && typeof value === 'object' && value.hasOwnProperty('status');
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'HealthDetails',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
health: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prettyBytes
|
||||
},
|
||||
computed: {
|
||||
details() {
|
||||
if (this.health.details) {
|
||||
return Object.entries(this.health.details)
|
||||
.filter(([, value]) => !isChildHealth(value))
|
||||
.map(([name, value]) => ({name, value}));
|
||||
}
|
||||
return [];
|
||||
},
|
||||
childHealth() {
|
||||
if (this.health.details) {
|
||||
return Object.entries(this.health.details)
|
||||
.filter(([, value]) => isChildHealth(value))
|
||||
.map(([name, value]) => ({name, value}));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
td.health-details__nested {
|
||||
padding: 0 0 0 0.75em;
|
||||
}
|
||||
|
||||
td.health-details__nested {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.health-details__nested .table {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.health-details__status {
|
||||
float: right;
|
||||
|
||||
&--UP {
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&--RESTRICTED {
|
||||
color: $warning;
|
||||
}
|
||||
|
||||
&--OUT_OF_SERVICE,
|
||||
&--DOWN {
|
||||
color: $danger;
|
||||
}
|
||||
|
||||
&--UNKNOWN,
|
||||
&--OFFLINE {
|
||||
color: $grey;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,198 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section">
|
||||
<div class="details-header">
|
||||
<h1 class="title" v-if="instance" v-text="instance.registration.name" />
|
||||
<h2 class="subtitle is-5" v-if="instance">Id: <span v-text="instance.id" /></h2>
|
||||
<sba-tags v-if="instance" :tags="instance.tags" />
|
||||
<div class="details-header__urls">
|
||||
<a class="button is-small " :href="instance.registration.serviceUrl">
|
||||
<font-awesome-icon icon="home" />
|
||||
<span v-text="instance.registration.serviceUrl" />
|
||||
</a>
|
||||
<a class="button is-small " :href="instance.registration.managementUrl">
|
||||
<font-awesome-icon icon="wrench" />
|
||||
<span v-text="instance.registration.managementUrl" />
|
||||
</a>
|
||||
<a class="button is-small " :href="instance.registration.healthUrl">
|
||||
<font-awesome-icon icon="heartbeat" />
|
||||
<span v-text="instance.registration.healthUrl" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching metrics index failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-desktop">
|
||||
<div class="column is-half-desktop">
|
||||
<details-info v-if="hasInfo" :instance="instance" />
|
||||
<details-metadata v-if="hasMetadata" :instance="instance" />
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<details-health :instance="instance" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-desktop">
|
||||
<div class="column is-half-desktop">
|
||||
<details-process v-if="hasProcess" :instance="instance" />
|
||||
<details-gc v-if="hasGc" :instance="instance" />
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<details-threads v-if="hasThreads" :instance="instance" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-desktop">
|
||||
<div class="column is-half-desktop">
|
||||
<details-memory v-if="hasMemory" :instance="instance" type="heap" />
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<details-memory v-if="hasMemory" :instance="instance" type="nonheap" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-desktop">
|
||||
<div class="column is-half-desktop">
|
||||
<details-datasources v-if="hasDatasources" :instance="instance" />
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<details-caches v-if="hasCaches" :instance="instance" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Instance from '@/services/instance';
|
||||
import detailsCaches from './details-caches';
|
||||
import detailsDatasources from './details-datasources';
|
||||
import detailsGc from './details-gc';
|
||||
import detailsHealth from './details-health';
|
||||
import detailsInfo from './details-info';
|
||||
import detailsMemory from './details-memory';
|
||||
import detailsMetadata from './details-metadata';
|
||||
import detailsProcess from './details-process';
|
||||
import detailsThreads from './details-threads';
|
||||
|
||||
export default {
|
||||
/* eslint-disable vue/no-unused-components */
|
||||
components: {
|
||||
detailsHealth,
|
||||
detailsInfo,
|
||||
detailsProcess,
|
||||
detailsThreads,
|
||||
detailsDatasources,
|
||||
detailsMemory,
|
||||
detailsGc,
|
||||
detailsCaches,
|
||||
detailsMetadata
|
||||
},
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
metrics: []
|
||||
}),
|
||||
computed: {
|
||||
hasCaches() {
|
||||
return this.hasMetric('cache.gets');
|
||||
},
|
||||
hasDatasources() {
|
||||
return this.hasMetric('data.source.active.connections');
|
||||
},
|
||||
hasGc() {
|
||||
return this.hasMetric('jvm.gc.pause');
|
||||
},
|
||||
hasInfo() {
|
||||
return this.instance.hasEndpoint('info');
|
||||
},
|
||||
hasMemory() {
|
||||
return this.hasMetric('jvm.memory.max');
|
||||
},
|
||||
hasProcess() {
|
||||
return this.hasMetric('process.uptime');
|
||||
},
|
||||
hasThreads() {
|
||||
return this.hasMetric('jvm.threads.live');
|
||||
},
|
||||
hasMetadata() {
|
||||
return this.instance.registration && this.instance.registration.metadata;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchMetricIndex();
|
||||
},
|
||||
methods: {
|
||||
hasMetric(metric) {
|
||||
return this.metrics && this.metrics.includes(metric);
|
||||
},
|
||||
async fetchMetricIndex() {
|
||||
if (this.instance.hasEndpoint('metrics')) {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await this.instance.fetchMetrics();
|
||||
this.metrics = res.data.names;
|
||||
} catch (error) {
|
||||
console.warn('Fetching metric index failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/details',
|
||||
parent: 'instances',
|
||||
path: 'details',
|
||||
component: this,
|
||||
label: 'Details',
|
||||
group: 'Insights',
|
||||
order: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.details-header {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__urls {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,170 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="mem-chart">
|
||||
<svg class="mem-chart__svg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d3 from '@/utils/d3';
|
||||
import moment from 'moment';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data: () => ({}),
|
||||
methods: {
|
||||
drawChart(_data) {
|
||||
const vm = this;
|
||||
const data = _data.length === 1 ? _data.concat([{..._data[0], timestamp: _data[0].timestamp + 1}]) : _data;
|
||||
|
||||
///setup x and y scale
|
||||
const extent = d3.extent(data, d => d.timestamp);
|
||||
const x = d3.scaleTime()
|
||||
.range([0, vm.width])
|
||||
.domain(extent);
|
||||
|
||||
const y = d3.scaleLinear()
|
||||
.range([vm.height, 0])
|
||||
.domain([0, d3.max(data, d => d.committed) * 1.05]);
|
||||
|
||||
//draw max
|
||||
const max = vm.areas.selectAll('.mem-chart__line--max')
|
||||
.data([data]);
|
||||
max.enter().append('path')
|
||||
.merge(max)
|
||||
.attr('class', 'mem-chart__line--max')
|
||||
.attr('d', d3.line()
|
||||
.x(d => x(d.timestamp))
|
||||
.y(d => y(d.max)));
|
||||
max.exit().remove();
|
||||
|
||||
//draw areas
|
||||
const committed = vm.areas.selectAll('.mem-chart__area--committed')
|
||||
.data([data]);
|
||||
committed.enter().append('path')
|
||||
.merge(committed)
|
||||
.attr('class', 'mem-chart__area--committed')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(d => y(d.used))
|
||||
.y1(d => y(d.committed)));
|
||||
committed.exit().remove();
|
||||
|
||||
const used = vm.areas.selectAll('.mem-chart__area--used')
|
||||
.data([data]);
|
||||
used.enter().append('path')
|
||||
.merge(used)
|
||||
.attr('class', 'mem-chart__area--used')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(d => y(d.metaspace || 0))
|
||||
.y1(d => y(d.used)));
|
||||
used.exit().remove();
|
||||
|
||||
const metaspace = vm.areas.selectAll('.mem-chart__area--metaspace')
|
||||
.data([data]);
|
||||
metaspace.enter().append('path')
|
||||
.merge(metaspace)
|
||||
.attr('class', 'mem-chart__area--metaspace')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(y(0))
|
||||
.y1(d => y(d.metaspace)));
|
||||
metaspace.exit().remove();
|
||||
|
||||
//draw axis
|
||||
vm.xAxis.call(d3.axisBottom(x)
|
||||
.ticks(5)
|
||||
.tickFormat(d => moment(d).format('HH:mm:ss'))
|
||||
);
|
||||
|
||||
vm.yAxis.call(d3.axisLeft(y)
|
||||
.ticks(5)
|
||||
.tickFormat(prettyBytes)
|
||||
);
|
||||
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const margin = {
|
||||
top: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
left: 50,
|
||||
};
|
||||
|
||||
this.width = this.$el.getBoundingClientRect().width - margin.left - margin.right;
|
||||
this.height = this.$el.getBoundingClientRect().height - margin.top - margin.bottom;
|
||||
|
||||
this.chartLayer = d3.select(this.$el.querySelector('.mem-chart__svg'))
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
this.xAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'mem-chart__axis-x')
|
||||
.attr('transform', `translate(0,${this.height})`);
|
||||
|
||||
this.yAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'mem-chart__axis-y')
|
||||
.attr('stroke', null);
|
||||
|
||||
this.areas = this.chartLayer.append('g');
|
||||
|
||||
this.drawChart(this.data);
|
||||
},
|
||||
watch: {
|
||||
data: 'drawChart'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.mem-chart {
|
||||
&__svg {
|
||||
height: 159px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__area {
|
||||
&--committed {
|
||||
fill: $warning;
|
||||
opacity: 0.8;
|
||||
}
|
||||
&--used {
|
||||
fill: $info;
|
||||
opacity: 0.8;
|
||||
}
|
||||
&--metaspace {
|
||||
fill: $primary;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&__line--max {
|
||||
stroke: $grey;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import {timer} from '@/utils/rxjs';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
mixins: [subscribing],
|
||||
data: () => ({
|
||||
startTs: null,
|
||||
offset: null
|
||||
}),
|
||||
render() {
|
||||
return this._v(this.clock);
|
||||
},
|
||||
computed: {
|
||||
clock() {
|
||||
if (!this.value) {
|
||||
return null;
|
||||
}
|
||||
const duration = moment.duration(this.value + this.offset);
|
||||
return `${Math.floor(duration.asDays())}d ${duration.hours()}h ${duration.minutes()}m ${duration.seconds()}s`;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: 'subscribe'
|
||||
},
|
||||
methods: {
|
||||
createSubscription() {
|
||||
if (this.value) {
|
||||
const vm = this;
|
||||
vm.startTs = moment();
|
||||
vm.offset = 0;
|
||||
return timer(0, 1000).subscribe({
|
||||
next: () => {
|
||||
vm.offset = moment().valueOf() - vm.startTs.valueOf();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="threads-chart">
|
||||
<svg class="threads-chart__svg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d3 from '@/utils/d3';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
drawChart(_data) {
|
||||
const vm = this;
|
||||
const data = _data.length === 1 ? _data.concat([{..._data[0], timestamp: _data[0].timestamp + 1}]) : _data;
|
||||
|
||||
///setup x and y scale
|
||||
const extent = d3.extent(data, d => d.timestamp);
|
||||
const x = d3.scaleTime()
|
||||
.range([0, vm.width])
|
||||
.domain(extent);
|
||||
|
||||
const y = d3.scaleLinear()
|
||||
.range([vm.height, 0])
|
||||
.domain([0, d3.max(data, d => d.live) * 1.05]);
|
||||
|
||||
//draw max
|
||||
const live = vm.areas.selectAll('.threads-chart__area--live')
|
||||
.data([data]);
|
||||
live.enter().append('path')
|
||||
.merge(live)
|
||||
.attr('class', 'threads-chart__area--live')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(d => y(d.daemon))
|
||||
.y1(d => y(d.live)));
|
||||
live.exit().remove();
|
||||
|
||||
//draw areas
|
||||
const daemon = vm.areas.selectAll('.threads-chart__area--daemon')
|
||||
.data([data]);
|
||||
daemon.enter().append('path')
|
||||
.merge(daemon)
|
||||
.attr('class', 'threads-chart__area--daemon')
|
||||
.attr('d', d3.area()
|
||||
.x(d => x(d.timestamp))
|
||||
.y0(y(0))
|
||||
.y1(d => y(d.daemon)));
|
||||
daemon.exit().remove();
|
||||
|
||||
//draw axis
|
||||
vm.xAxis.call(d3.axisBottom(x)
|
||||
.ticks(5)
|
||||
.tickFormat(d => moment(d).format('HH:mm:ss'))
|
||||
);
|
||||
|
||||
vm.yAxis.call(d3.axisLeft(y)
|
||||
.ticks(5)
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const margin = {
|
||||
top: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
left: 50,
|
||||
};
|
||||
|
||||
this.width = this.$el.getBoundingClientRect().width - margin.left - margin.right;
|
||||
this.height = this.$el.getBoundingClientRect().height - margin.top - margin.bottom;
|
||||
|
||||
this.chartLayer = d3.select(this.$el.querySelector('.threads-chart__svg'))
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
this.xAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'threads-chart__axis-x')
|
||||
.attr('transform', `translate(0,${this.height})`);
|
||||
|
||||
this.yAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'threads-chart__axis-y')
|
||||
.attr('stroke', null);
|
||||
|
||||
this.areas = this.chartLayer.append('g');
|
||||
|
||||
this.drawChart(this.data);
|
||||
},
|
||||
watch: {
|
||||
data: 'drawChart'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.threads-chart {
|
||||
&__svg {
|
||||
height: 159px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__area {
|
||||
&--live {
|
||||
fill: $warning;
|
||||
opacity: 0.8;
|
||||
}
|
||||
&--daemon {
|
||||
fill: $info;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
-260
@@ -1,260 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="box">
|
||||
<h1 class="is-size-5">Environment Manager</h1>
|
||||
<datalist id="allPropertyNames">
|
||||
<option v-for="name in allPropertyNames" :key="name" v-text="name" />
|
||||
</datalist>
|
||||
<div class="field is-horizontal" v-for="(prop, index) in managedProperties" :key="`managed-${index}`">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Property name" list="allPropertyNames"
|
||||
v-model="prop.name" @input="handlePropertyNameChange(prop, index)"
|
||||
>
|
||||
</div>
|
||||
<p class="help is-danger" v-text="prop.validation" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control has-icons-right" :class="{'is-loading' : prop.status === 'executing'}">
|
||||
<input class="input" type="text" placeholder="Value" v-model="prop.input"
|
||||
@input="prop.status = null"
|
||||
>
|
||||
<span class="icon is-right has-text-success" v-if="prop.status === 'completed'">
|
||||
<font-awesome-icon icon="check" />
|
||||
</span>
|
||||
<span class="icon is-right has-text-warning" v-else-if="prop.status === 'failed'">
|
||||
<font-awesome-icon icon="exclamation-triangle" />
|
||||
</span>
|
||||
<span class="icon is-right" v-else-if="prop.input !== prop.value">
|
||||
<font-awesome-icon icon="pencil-alt" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-body" v-if="instance.hasEndpoint('refresh')">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<sba-confirm-button class="button is-light"
|
||||
:class="{'is-loading' : refreshStatus === 'executing', 'is-danger' : refreshStatus === 'failed', 'is-info' : refreshStatus === 'completed'}"
|
||||
:disabled="refreshStatus === 'executing'"
|
||||
@click="refreshContext"
|
||||
>
|
||||
<span v-if="refreshStatus === 'completed'">Context refreshed</span>
|
||||
<span v-else-if="refreshStatus === 'failed'">Failed</span>
|
||||
<span v-else>Refresh Context</span>
|
||||
</sba-confirm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<button class="button is-light"
|
||||
:class="{'is-loading' : resetStatus === 'executing', 'is-danger' : resetStatus === 'failed', 'is-success' : resetStatus === 'completed'}"
|
||||
:disabled="!hasManagedProperty || resetStatus === 'executing'"
|
||||
@click="resetEnvironment"
|
||||
>
|
||||
<span v-if="resetStatus === 'completed'">Resetted</span>
|
||||
<span v-else-if="resetStatus === 'failed'">Failed</span>
|
||||
<span v-else>Reset</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-primary"
|
||||
:class="{'is-loading' : updateStatus === 'executing', 'is-danger' : updateStatus === 'failed', 'is-success' : updateStatus === 'completed'}"
|
||||
:disabled="hasErrorProperty || !hasChangedProperty || updateStatus === 'executing'"
|
||||
@click="updateEnvironment"
|
||||
>
|
||||
<span v-if="updateStatus === 'completed'">Updated</span>
|
||||
<span v-else-if="updateStatus === 'failed'">Failed</span>
|
||||
<span v-else>Update</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, filter, from, listen} from '@/utils/rxjs';
|
||||
import debounce from 'lodash/debounce';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
},
|
||||
propertySources: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
error: null,
|
||||
refreshStatus: null,
|
||||
resetStatus: null,
|
||||
updateStatus: null,
|
||||
managedProperties: [{
|
||||
name: null,
|
||||
input: null,
|
||||
value: null,
|
||||
status: null,
|
||||
validation: null
|
||||
}]
|
||||
}),
|
||||
computed: {
|
||||
allPropertyNames() {
|
||||
return uniq(this.propertySources.map(ps => Object.keys(ps.properties))
|
||||
.reduce((result, names) => result.concat(names))
|
||||
.sort());
|
||||
},
|
||||
managerPropertySource() {
|
||||
return this.propertySources.find(ps => ps.name === 'manager') || {name: 'manager', properties: {}};
|
||||
},
|
||||
hasManagedProperty() {
|
||||
return this.managedProperties.findIndex(property => !!property.name) >= 0;
|
||||
},
|
||||
hasChangedProperty() {
|
||||
return this.managedProperties.findIndex(property => property.input !== property.value) >= 0;
|
||||
},
|
||||
hasErrorProperty() {
|
||||
return this.managedProperties.findIndex(property => property.validation !== null) >= 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlePropertyNameChange: debounce(function (prop, idx) {
|
||||
if (prop.name && idx === this.managedProperties.length - 1) {
|
||||
this.managedProperties.push({
|
||||
name: null,
|
||||
input: null,
|
||||
value: null,
|
||||
status: null,
|
||||
validation: null
|
||||
});
|
||||
}
|
||||
}, 250),
|
||||
refreshContext() {
|
||||
const vm = this;
|
||||
from(vm.instance.refreshContext())
|
||||
.pipe(listen(status => vm.refreshStatus = status))
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
setTimeout(() => vm.refreshStatus = null, 2500);
|
||||
return vm.$emit('reset');
|
||||
},
|
||||
error: () => vm.$emit('reset')
|
||||
});
|
||||
},
|
||||
updateEnvironment() {
|
||||
const vm = this;
|
||||
from(vm.managedProperties)
|
||||
.pipe(
|
||||
filter(property => !!property.name && property.input !== property.value),
|
||||
listen(status => vm.updateStatus = status),
|
||||
concatMap(
|
||||
property => from(vm.instance.setEnv(property.name, property.input))
|
||||
.pipe(listen(status => property.status = status))
|
||||
)
|
||||
)
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
setTimeout(() => vm.updateStatus = null, 2500);
|
||||
return vm.$emit('update');
|
||||
},
|
||||
error: () => vm.$emit('update')
|
||||
});
|
||||
},
|
||||
resetEnvironment() {
|
||||
const vm = this;
|
||||
from(vm.instance.resetEnv())
|
||||
.pipe(listen(status => vm.resetStatus = status))
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
vm.managedProperties = [{
|
||||
name: null,
|
||||
input: null,
|
||||
value: null,
|
||||
status: null,
|
||||
validation: null
|
||||
}];
|
||||
setTimeout(() => vm.resetStatus = null, 2500);
|
||||
return vm.$emit('refresh');
|
||||
},
|
||||
error: () => vm.$emit('refresh')
|
||||
});
|
||||
},
|
||||
updateManagedProperties(manager) {
|
||||
Object.entries(manager.properties).forEach(([name, property]) => {
|
||||
const managedProperty = this.managedProperties.find(property => property.name === name);
|
||||
if (managedProperty) {
|
||||
managedProperty.value = property.value
|
||||
} else {
|
||||
const idx = this.managedProperties.length - 1;
|
||||
this.managedProperties.splice(idx, 0, {
|
||||
name,
|
||||
input: property.value,
|
||||
value: property.value,
|
||||
status: null,
|
||||
validation: null
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
managerPropertySource: {
|
||||
handler: 'updateManagedProperties',
|
||||
immediate: true
|
||||
},
|
||||
managedProperties: {
|
||||
deep: true,
|
||||
handler() {
|
||||
const counts = this.managedProperties.reduce(
|
||||
(acc, v) => {
|
||||
if (v.name) {
|
||||
acc[v.name] = (acc[v.name] || 0) + 1;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
this.managedProperties.forEach(property => {
|
||||
if (!property.name) {
|
||||
if (property.input) {
|
||||
property.validation = 'Property name is required';
|
||||
}
|
||||
return;
|
||||
}
|
||||
const count = counts[property.name] || 0;
|
||||
if (count > 1) {
|
||||
property.validation = 'Property name must be unique';
|
||||
return;
|
||||
}
|
||||
property.validation = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,151 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{ 'is-loading' : !hasLoaded }">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching environment failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-multiline" v-if="env && env.activeProfiles.length > 0">
|
||||
<div class="control" v-for="profile in env.activeProfiles" :key="profile">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag is-medium is-primary">Profile</span>
|
||||
<span class="tag is-medium" v-text="profile" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<sba-env-manager v-if="env && hasEnvManagerSupport"
|
||||
:instance="instance" :property-sources="env.propertySources"
|
||||
@refresh="fetchEnv()" @update="fetchEnv" @reset="fetchEnv()"
|
||||
/>
|
||||
<div class="field has-addons" v-if="env">
|
||||
<p class="control is-expanded">
|
||||
<input class="input" type="search" placeholder="name / value filter" v-model="filter">
|
||||
</p>
|
||||
</div>
|
||||
<sba-panel :header-sticks-below="['#navigation']"
|
||||
v-for="propertySource in propertySources" :key="propertySource.name"
|
||||
:title="propertySource.name"
|
||||
>
|
||||
<table class="table is-fullwidth"
|
||||
v-if="Object.keys(propertySource.properties).length > 0"
|
||||
>
|
||||
<tr v-for="(value, name) in propertySource.properties" :key="`${propertySource.name}-${name}`">
|
||||
<td>
|
||||
<span v-text="name" /><br>
|
||||
<small class="is-muted" v-if="value.origin" v-text="value.origin" />
|
||||
</td>
|
||||
<td class="is-breakable" v-text="value.value" />
|
||||
</tr>
|
||||
</table>
|
||||
<p class="is-muted" v-else>No properties set</p>
|
||||
</sba-panel>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import sbaEnvManager from './env-manager';
|
||||
|
||||
const filterProperty = (needle) => (property, name) => {
|
||||
return name.toString().toLowerCase().includes(needle) || property.value.toString().toLowerCase().includes(needle);
|
||||
};
|
||||
const filterProperties = (needle, properties) => pickBy(properties, filterProperty(needle));
|
||||
const filterPropertySource = (needle) => (propertySource) => {
|
||||
if (!propertySource || !propertySource.properties) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...propertySource,
|
||||
properties: filterProperties(needle, propertySource.properties)
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {sbaEnvManager},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
env: null,
|
||||
filter: null,
|
||||
hasEnvManagerSupport: false
|
||||
}),
|
||||
computed: {
|
||||
propertySources() {
|
||||
if (!this.env) {
|
||||
return [];
|
||||
}
|
||||
if (!this.filter) {
|
||||
return this.env.propertySources;
|
||||
}
|
||||
return this.env.propertySources
|
||||
.map(filterPropertySource(this.filter.toLowerCase()))
|
||||
.filter(ps => ps && Object.keys(ps.properties).length > 0);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchEnv();
|
||||
this.determineEnvManagerSupport();
|
||||
},
|
||||
methods: {
|
||||
async fetchEnv() {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await this.instance.fetchEnv();
|
||||
this.env = res.data;
|
||||
} catch (error) {
|
||||
console.warn('Fetching environment failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
},
|
||||
async determineEnvManagerSupport() {
|
||||
try {
|
||||
this.hasEnvManagerSupport = await this.instance.hasEnvManagerSupport();
|
||||
} catch (error) {
|
||||
console.warn('Determine env manager support failed:', error);
|
||||
this.hasEnvManagerSupport = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/env',
|
||||
parent: 'instances',
|
||||
path: 'env',
|
||||
component: this,
|
||||
label: 'Environment',
|
||||
group: 'Insights',
|
||||
order: 100,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('env')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,140 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{ 'is-loading' : !hasLoaded }">
|
||||
<template v-if="hasLoaded">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching Flyway reports failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(context, ctxName) in contexts">
|
||||
<h3 class="title" v-text="ctxName" :key="ctxName" />
|
||||
<sba-panel v-for="(report, name) in context.flywayBeans" :key="`${ctxName}-${name}`" :title="name"
|
||||
:header-sticks-below="['#navigation']"
|
||||
class="migration"
|
||||
>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Checksum</th>
|
||||
<th>Version</th>
|
||||
<th>Description</th>
|
||||
<th>Script</th>
|
||||
<th>State</th>
|
||||
<th>Installed by</th>
|
||||
<th>Installed on</th>
|
||||
<th>Installed rank</th>
|
||||
<th>Execution Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="migration in report.migrations" :key="migration.checksum">
|
||||
<td v-text="migration.type" />
|
||||
<td v-text="migration.checksum" />
|
||||
<td v-text="migration.version" />
|
||||
<td v-text="migration.description" />
|
||||
<td v-text="migration.script" />
|
||||
<td><span v-text="migration.state" class="tag"
|
||||
:class="stateClass(migration.state)"
|
||||
/></td>
|
||||
<td v-text="migration.installedBy" />
|
||||
<td v-text="migration.installedOn" />
|
||||
<td v-text="migration.installedRank" />
|
||||
<td v-text="`${migration.executionTime}ms`" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</sba-panel>
|
||||
</template>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
contexts: null
|
||||
}),
|
||||
computed: {},
|
||||
created() {
|
||||
this.fetchFlyway();
|
||||
},
|
||||
methods: {
|
||||
async fetchFlyway() {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await this.instance.fetchFlyway();
|
||||
this.contexts = res.data.contexts;
|
||||
} catch (error) {
|
||||
console.warn('Fetching flyway reports failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
},
|
||||
stateClass(state) {
|
||||
switch (state) {
|
||||
case 'BASELINE' :
|
||||
case 'MISSING_SUCCESS' :
|
||||
case 'SUCCESS' :
|
||||
case 'OUT_OF_ORDER' :
|
||||
case 'FUTURE_SUCCESS' :
|
||||
return 'is-success';
|
||||
case 'PENDING':
|
||||
case 'ABOVE_TARGET':
|
||||
case 'PREINIT':
|
||||
case 'BELOW_BASELINE':
|
||||
case 'IGNORED':
|
||||
return 'is-warning';
|
||||
case 'MISSING_FAILED':
|
||||
case 'FAILED':
|
||||
case 'FUTURE_FAILED':
|
||||
return 'is-danger';
|
||||
default:
|
||||
return 'is-light';
|
||||
}
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/flyway',
|
||||
parent: 'instances',
|
||||
path: 'flyway',
|
||||
component: this,
|
||||
label: 'Flyway',
|
||||
group: 'Data',
|
||||
order: 900,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('flyway')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,72 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section heapdump">
|
||||
<div>
|
||||
<div class="message is-warning">
|
||||
<div class="message-body">
|
||||
A heap dump may contain <strong>sensitive data</strong>. Please handle with care.
|
||||
</div>
|
||||
</div>
|
||||
<div class="message is-warning">
|
||||
<div class="message-body">
|
||||
Dumping the heap may be expensive in terms of cpu and disk space.
|
||||
</div>
|
||||
</div>
|
||||
<a class="button is-primary" :href="`instances/${instance.id}/actuator/heapdump`" target="_blank">
|
||||
<font-awesome-icon icon="download" /> Download Heap Dump
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/heapdump',
|
||||
parent: 'instances',
|
||||
path: 'heapdump',
|
||||
component: this,
|
||||
label: 'Heap Dump',
|
||||
group: 'JVM',
|
||||
order: 800,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('heapdump')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.heapdump {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,249 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{ 'is-loading' : !hasLoaded}">
|
||||
<template v-if="hasLoaded">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching traces failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="traces">
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded">
|
||||
<input class="input" type="search" placeholder="path filter" v-model="filter">
|
||||
</p>
|
||||
<p class="control">
|
||||
<span class="button is-static">
|
||||
<span v-text="filteredTraces.length" />
|
||||
/
|
||||
<span v-text="traces.length" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="showSuccess">
|
||||
success
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="showClientErrors">
|
||||
client errors
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="showServerErrors">
|
||||
server errors
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-narrow" v-if="actuatorPath">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="excludeActuator">
|
||||
exclude <span v-text="actuatorPath" />/**
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<sba-traces-chart :traces="filteredTraces" @selected="(d) => selection = d" />
|
||||
<sba-traces-list :traces="selectedTraces" />
|
||||
</template>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import {concatMap, timer} from '@/utils/rxjs';
|
||||
import moment from 'moment';
|
||||
import sbaTracesChart from './traces-chart';
|
||||
import sbaTracesList from './traces-list';
|
||||
|
||||
const addToFilter = (oldFilter, addedFilter) =>
|
||||
!oldFilter
|
||||
? addedFilter
|
||||
: (val, key) => oldFilter(val, key) && addedFilter(val, key);
|
||||
|
||||
class Trace {
|
||||
constructor({timestamp, ...trace}) {
|
||||
Object.assign(this, trace);
|
||||
this.timestamp = moment(timestamp);
|
||||
}
|
||||
|
||||
get key() {
|
||||
return `${this.timestamp}-${this.request.method}-${this.request.uri}`;
|
||||
}
|
||||
|
||||
get contentLength() {
|
||||
const contentLength = this.response.headers['Content-Length'] && this.response.headers['Content-Length'][0];
|
||||
if (contentLength && /^\d+$/.test(contentLength)) {
|
||||
return parseInt(contentLength);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get contentType() {
|
||||
const contentType = this.response.headers['Content-Type'] && this.response.headers['Content-Type'][0];
|
||||
if (contentType) {
|
||||
const idx = contentType.indexOf(';');
|
||||
return idx >= 0 ? contentType.substring(0, idx) : contentType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
compareTo(other) {
|
||||
return this.timestamp - other.timestamp;
|
||||
}
|
||||
|
||||
isSuccess() {
|
||||
return this.response.status <= 399
|
||||
}
|
||||
|
||||
isClientError() {
|
||||
return this.response.status >= 400 && this.response.status <= 499
|
||||
}
|
||||
|
||||
isServerError() {
|
||||
return this.response.status >= 500 && this.response.status <= 599
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {sbaTracesList, sbaTracesChart},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
traces: null,
|
||||
filter: null,
|
||||
excludeActuator: true,
|
||||
showSuccess: true,
|
||||
showClientErrors: true,
|
||||
showServerErrors: true,
|
||||
selection: null
|
||||
}),
|
||||
computed: {
|
||||
actuatorPath() {
|
||||
if (this.instance.registration.managementUrl.includes(this.instance.registration.serviceUrl)) {
|
||||
const appendix = this.instance.registration.managementUrl.substring(this.instance.registration.serviceUrl.length);
|
||||
if (appendix.length > 0) {
|
||||
return appendix.startsWith('/') ? appendix : `/${appendix}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
filteredTraces() {
|
||||
const filterFn = this.getFilterFn();
|
||||
return filterFn ? this.traces.filter(filterFn) : this.traces;
|
||||
},
|
||||
selectedTraces() {
|
||||
if (this.selection === null) {
|
||||
return this.filteredTraces;
|
||||
}
|
||||
const [start, end] = this.selection;
|
||||
return this.filteredTraces.filter(trace => !trace.timestamp.isBefore(start) && !trace.timestamp.isAfter(end));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchHttptrace() {
|
||||
const response = await this.instance.fetchHttptrace();
|
||||
const traces = response.data.traces.map(trace => new Trace(trace)).filter(
|
||||
trace => trace.timestamp.isAfter(this.lastTimestamp)
|
||||
);
|
||||
traces.sort((a, b) => -1 * a.compareTo(b));
|
||||
if (traces.length > 0) {
|
||||
this.lastTimestamp = traces[0].timestamp;
|
||||
}
|
||||
return traces;
|
||||
},
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
vm.lastTimestamp = moment(0);
|
||||
vm.error = null;
|
||||
return timer(0, 5000)
|
||||
.pipe(concatMap(vm.fetchHttptrace))
|
||||
.subscribe({
|
||||
next: traces => {
|
||||
vm.hasLoaded = true;
|
||||
vm.traces = vm.traces ? traces.concat(vm.traces) : traces;
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn('Fetching traces failed:', error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
},
|
||||
getFilterFn() {
|
||||
let filterFn = null;
|
||||
if (this.actuatorPath !== null && this.excludeActuator) {
|
||||
filterFn = addToFilter(filterFn, (trace) => !trace.request.uri.includes(this.actuatorPath));
|
||||
}
|
||||
if (this.filter) {
|
||||
const normalizedFilter = this.filter.toLowerCase();
|
||||
filterFn = addToFilter(filterFn, (trace) => trace.request.uri.toLowerCase().includes(normalizedFilter));
|
||||
}
|
||||
if (!this.showSuccess) {
|
||||
filterFn = addToFilter(filterFn, (trace) => !trace.isSuccess());
|
||||
}
|
||||
if (!this.showClientErrors) {
|
||||
filterFn = addToFilter(filterFn, (trace) => !trace.isClientError());
|
||||
}
|
||||
if (!this.showServerErrors) {
|
||||
filterFn = addToFilter(filterFn, (trace) => !trace.isServerError());
|
||||
}
|
||||
return filterFn;
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/httptrace',
|
||||
parent: 'instances',
|
||||
path: 'httptrace',
|
||||
component: this,
|
||||
label: 'Http Traces',
|
||||
group: 'Web',
|
||||
order: 500,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('httptrace')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-364
@@ -1,364 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="trace-chart">
|
||||
<div class="trace-chart__tooltip"
|
||||
v-if="tooltipSelection "
|
||||
:class="`trace-chart__tooltip--${this.x(tooltipSelection[0]) > this.width / 2 ? 'left' : 'right'}`"
|
||||
>
|
||||
<table class="is-narrow is-size-7">
|
||||
<tr>
|
||||
<th>total requests</th>
|
||||
<td v-text="tooltipContent.totalCount" />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>successful</th>
|
||||
<td v-text="tooltipContent.totalSuccess" />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>status 4xx</th>
|
||||
<td v-text="tooltipContent.totalClientErrors" />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>status 5xx</th>
|
||||
<td v-text="tooltipContent.totalServerErrors" />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>max duration</th>
|
||||
<td v-text="`${tooltipContent.maxTime}ms`" />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ø duration</th>
|
||||
<td v-text="`${tooltipContent.avgTime}ms`" />
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<svg class="trace-chart__svg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//see https://github.com/d3/d3/issues/2733#issuecomment-190743489
|
||||
import d3 from '@/utils/d3';
|
||||
import {event as d3Event} from 'd3-selection';
|
||||
import moment from 'moment';
|
||||
|
||||
const interval = 1000;
|
||||
export default {
|
||||
props: {
|
||||
traces: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
brushSelection: null,
|
||||
hovered: null
|
||||
}),
|
||||
computed: {
|
||||
chartData() {
|
||||
if (this.traces.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
const chartData = [];
|
||||
const now = moment().valueOf();
|
||||
let idx = this.traces.length - 1;
|
||||
const oldest = this.traces[this.traces.length - 1].timestamp.valueOf();
|
||||
|
||||
for (let time = Math.floor(oldest.valueOf() / interval) * interval; time < now; time += interval) {
|
||||
const bucket = {
|
||||
timeStart: time,
|
||||
timeEnd: time + interval,
|
||||
totalCount: 0,
|
||||
totalSuccess: 0,
|
||||
totalClientErrors: 0,
|
||||
totalServerErrors: 0,
|
||||
totalTime: 0,
|
||||
maxTime: 0
|
||||
};
|
||||
|
||||
while (idx >= 0 && this.traces[idx].timestamp.valueOf() < time + interval) {
|
||||
const trace = this.traces[idx];
|
||||
bucket.totalCount++;
|
||||
if (trace.isSuccess()) {
|
||||
bucket.totalSuccess++;
|
||||
} else if (trace.isClientError()) {
|
||||
bucket.totalClientErrors++;
|
||||
} else if (trace.isServerError()) {
|
||||
bucket.totalServerErrors++;
|
||||
}
|
||||
if (trace.timeTaken) {
|
||||
bucket.totalTime += trace.timeTaken;
|
||||
bucket.maxTime = Math.max(bucket.maxTime, trace.timeTaken);
|
||||
}
|
||||
idx--;
|
||||
}
|
||||
chartData.push(bucket);
|
||||
}
|
||||
|
||||
return chartData;
|
||||
},
|
||||
tooltipSelection() {
|
||||
return this.brushSelection ? this.brushSelection : this.hovered ? [this.hovered, this.hovered + interval] : null;
|
||||
},
|
||||
tooltipContent() {
|
||||
const selection = this.tooltipSelection;
|
||||
const totals = this.chartData.filter(
|
||||
bucket => bucket.timeStart.valueOf() >= selection[0] && bucket.timeStart.valueOf() < selection[1]
|
||||
).reduce(
|
||||
(current, next) => ({
|
||||
totalCount: current.totalCount + next.totalCount,
|
||||
totalSuccess: current.totalSuccess + next.totalSuccess,
|
||||
totalClientErrors: current.totalClientErrors + next.totalClientErrors,
|
||||
totalServerErrors: current.totalServerErrors + next.totalServerErrors,
|
||||
totalTime: current.totalTime + next.totalTime,
|
||||
maxTime: Math.max(current.maxTime, next.maxTime)
|
||||
}), {
|
||||
totalCount: 0,
|
||||
totalSuccess: 0,
|
||||
totalClientErrors: 0,
|
||||
totalServerErrors: 0,
|
||||
totalTime: 0,
|
||||
maxTime: 0
|
||||
});
|
||||
return {
|
||||
...totals,
|
||||
avgTime: totals.totalCount > 0 ? Math.floor(totals.totalTime / totals.totalCount) : 0
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
drawChart(data) {
|
||||
const vm = this;
|
||||
|
||||
///setup x and y scale
|
||||
const x = d3.scaleTime()
|
||||
.range([0, vm.width])
|
||||
.domain(d3.extent(data, d => d.timeStart));
|
||||
this.x = x;
|
||||
|
||||
const y = d3.scaleLinear()
|
||||
.range([vm.height, 0])
|
||||
.domain([0, d3.max(data, d => d.totalCount)]);
|
||||
|
||||
//draw areas
|
||||
const area = d3.area()
|
||||
.x(d => x(d.data.timeStart))
|
||||
.y0(d => y(d[0]))
|
||||
.y1(d => y(d[1]));
|
||||
|
||||
const stack = d3.stack()
|
||||
.keys(['totalSuccess', 'totalClientErrors', 'totalServerErrors']);
|
||||
|
||||
const d = vm.areas.selectAll('.trace-chart__area')
|
||||
.data(stack(data));
|
||||
|
||||
d.enter().append('path')
|
||||
.merge(d)
|
||||
.attr('class', d => `trace-chart__area trace-chart__area--${d.key}`)
|
||||
.attr('d', area);
|
||||
|
||||
d.exit().remove();
|
||||
|
||||
//draw axis
|
||||
vm.xAxis.call(d3.axisBottom(x)
|
||||
.ticks(10)
|
||||
.tickFormat(d => moment(d).format('HH:mm:ss'))
|
||||
);
|
||||
|
||||
vm.yAxis.call(d3.axisRight(y)
|
||||
.ticks(Math.min(5, d3.max(data, d => d.totalCount)))
|
||||
.tickSize(this.width)
|
||||
).call(
|
||||
axis => axis.selectAll('.tick text')
|
||||
.attr('x', -2)
|
||||
.attr('dy', 2)
|
||||
.attr('text-anchor', 'end')
|
||||
);
|
||||
|
||||
//draw brush selection
|
||||
const brush = d3.brushX()
|
||||
.extent([[0, 0], [vm.width, vm.height]])
|
||||
.on('start', () => {
|
||||
if (d3Event.selection) {
|
||||
vm.isBrushing = true;
|
||||
vm.hovered = null;
|
||||
}
|
||||
})
|
||||
.on('brush', function () {
|
||||
if (d3Event.sourceEvent === null || d3Event.sourceEvent.type === 'brush') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (d3Event.selection) {
|
||||
const floor = Math.floor(x.invert(d3Event.selection[0]) / interval) * interval;
|
||||
const ceil = Math.ceil(x.invert(d3Event.selection[1]) / interval) * interval;
|
||||
d3.select(this).call(d3Event.target.move, [floor, ceil].map(x));
|
||||
vm.brushSelection = [floor, ceil];
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
vm.isBrushing = false;
|
||||
if (!d3Event.selection) {
|
||||
vm.brushSelection = null;
|
||||
}
|
||||
});
|
||||
|
||||
vm.brushGroup.call(brush)
|
||||
.on('mousemove', () => {
|
||||
if (vm.isBrushing) {
|
||||
return;
|
||||
}
|
||||
const mouseX = d3.mouse(vm.brushGroup.select('.overlay').node())[0];
|
||||
vm.hovered = Math.floor(x.invert(mouseX) / interval) * interval;
|
||||
}).on('mouseout', () => {
|
||||
vm.hovered = null;
|
||||
});
|
||||
|
||||
brush.move(vm.brushGroup, vm.brushSelection ? vm.brushSelection.map(x) : null);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const margin = {
|
||||
top: 20,
|
||||
right: 20,
|
||||
bottom: 30,
|
||||
left: 20,
|
||||
};
|
||||
|
||||
this.width = this.$el.getBoundingClientRect().width - margin.left - margin.right;
|
||||
this.height = this.$el.getBoundingClientRect().height - margin.top - margin.bottom;
|
||||
|
||||
this.chartLayer = d3.select(this.$el.querySelector('.trace-chart__svg'))
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
this.xAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'trace-chart__axis-x')
|
||||
.attr('transform', `translate(0,${this.height})`);
|
||||
|
||||
this.yAxis = this.chartLayer.append('g')
|
||||
.attr('class', 'trace-chart__axis-y')
|
||||
.attr('stroke', null);
|
||||
|
||||
this.areas = this.chartLayer.append('g');
|
||||
|
||||
this.hover = this.chartLayer.append('path')
|
||||
.attr('class', 'trace-chart__hover')
|
||||
.attr('opacity', 0);
|
||||
|
||||
this.brushGroup = this.chartLayer.append('g')
|
||||
.attr('class', 'trace-chart__brush');
|
||||
|
||||
this.drawChart(this.chartData);
|
||||
},
|
||||
watch: {
|
||||
chartData: 'drawChart',
|
||||
hovered(newVal) {
|
||||
if (newVal) {
|
||||
this.hover.attr('opacity', 1)
|
||||
.attr('d', `M${this.x(newVal)},${this.height} ${this.x(newVal)},0`);
|
||||
} else {
|
||||
this.hover.attr('opacity', 0);
|
||||
}
|
||||
},
|
||||
brushSelection(newVal) {
|
||||
this.$emit('selected', newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.trace-chart {
|
||||
&__svg {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__hover {
|
||||
stroke: $grey-light;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
&__tooltip {
|
||||
position: absolute;
|
||||
background: $black;
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
border-radius: $radius-large;
|
||||
padding: 0.825em;
|
||||
width: 200px;
|
||||
|
||||
& table th,
|
||||
& table td {
|
||||
border: none;
|
||||
color: $grey-light;
|
||||
padding: 0.25em 0.75em;
|
||||
}
|
||||
|
||||
& table td {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&--left {
|
||||
left: 5px;
|
||||
}
|
||||
&--right {
|
||||
right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
& .selection {
|
||||
stroke: none;
|
||||
fill: rgba(0, 0, 0, 0.2);
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
&__axis-y {
|
||||
& .domain {
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.tick:not(:first-of-type) {
|
||||
& line {
|
||||
stroke-dasharray: 2, 2;
|
||||
stroke: $grey-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__area {
|
||||
&--totalSuccess {
|
||||
fill: $success;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&--totalClientErrors {
|
||||
fill: $warning;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&--totalServerErrors {
|
||||
fill: $danger;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,140 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<table class="httptraces table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="httptraces__trace-timestamp">Timestamp</th>
|
||||
<th class="httptraces__trace-method">Method</th>
|
||||
<th class="httptraces__trace-uri">Path</th>
|
||||
<th class="httptraces__trace-status">Status</th>
|
||||
<th class="httptraces__trace-contentType">Content-Type</th>
|
||||
<th class="httptraces__trace-contentLength">Length</th>
|
||||
<th class="httptraces__trace-timeTaken">Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="trace in traces">
|
||||
<tr class="is-selectable"
|
||||
:class="{ 'httptraces__trace---is-detailed' : showDetails[trace.key] }"
|
||||
@click="showDetails[trace.key] ? $delete(showDetails, trace.key) : $set(showDetails, trace.key, true)"
|
||||
:key="trace.key"
|
||||
>
|
||||
<td class="httptraces__trace-timestamp" v-text="trace.timestamp.format('L HH:mm:ss.SSS')" />
|
||||
<td class="httptraces__trace-method" v-text="trace.request.method" />
|
||||
<td class="httptraces__trace-uri" v-text="trace.request.uri" />
|
||||
<td class="httptraces__trace-status">
|
||||
<span v-text="trace.response.status" class="tag"
|
||||
:class="{ 'is-success' : trace.isSuccess(), 'is-warning' : trace.isClientError(), 'is-danger' : trace.isServerError() }"
|
||||
/>
|
||||
</td>
|
||||
<td class="httptraces__trace-contentType" v-text="trace.contentType" />
|
||||
<td class="httptraces__trace-contentLength"
|
||||
v-text="trace.contentLength ? prettyBytes(trace.contentLength) : ''"
|
||||
/>
|
||||
<td class="httptraces__trace-timeTaken"
|
||||
v-text="trace.timeTaken !== null && typeof trace.timeTaken !== 'undefined' ? `${trace.timeTaken} ms` : ''"
|
||||
/>
|
||||
</tr>
|
||||
<tr :key="`${trace.key}-detail`" v-if="showDetails[trace.key]">
|
||||
<td colspan="7">
|
||||
<pre class="httptraces__trace-detail" v-text="toJson(trace)" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="traces.length === 0">
|
||||
<td class="is-muted" colspan="7">No traces found.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
traces: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
showDetails: {}
|
||||
}),
|
||||
methods: {
|
||||
prettyBytes,
|
||||
toJson(obj) {
|
||||
return JSON.stringify(obj, null, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.httptraces {
|
||||
table-layout: fixed;
|
||||
|
||||
td {
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
&__trace {
|
||||
&--is-detailed td {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
&-timestamp {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
&-method {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
&-uri {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&-status {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
&-contentType {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
&-contentLength {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
&-timeTaken {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
&-detail {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{ 'is-loading' : !hasLoaded }">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching JMX Beans failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column" v-if="selectedDomain">
|
||||
<h1 class="heading">MBeans</h1>
|
||||
<div class="m-bean card" :class="{'is-active': mBean === selectedMBean}"
|
||||
v-for="mBean in selectedDomain.mBeans" :key="mBean.descriptor.raw" :id="mBean.descriptor.raw"
|
||||
v-on-clickaway="() => mBean === selectedMBean && select(selectedDomain)"
|
||||
>
|
||||
|
||||
<header class="m-bean--header hero"
|
||||
:class="{'is-primary': mBean === selectedMBean, 'is-selectable' : mBean !== selectedMBean }"
|
||||
@click="select(selectedDomain, mBean)"
|
||||
>
|
||||
<div class="level is-clipped">
|
||||
<div class="level-left">
|
||||
<div class="level-item is-narrow"
|
||||
v-for="attribute in mBean.descriptor.attributes"
|
||||
:key="`mBean-desc-${attribute.name}`"
|
||||
>
|
||||
<div class="is-clipped" :title="`${attribute.name} ${attribute.value}`">
|
||||
<p class="heading" v-text="attribute.name" />
|
||||
<p class="title is-size-6" v-text="attribute.value" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<sba-icon-button v-if="mBean === selectedMBean" :icon="['far', 'times-circle']"
|
||||
class="m-bean--header--close has-text-white"
|
||||
@click.stop="select(selectedDomain)"
|
||||
/>
|
||||
<div class="hero-foot tabs is-boxed" v-if="mBean === selectedMBean">
|
||||
<ul>
|
||||
<li v-if="mBean.attr" :class="{'is-active' : selected.view === 'attributes' }">
|
||||
<a @click.stop="select(selectedDomain, selectedMBean, 'attributes')">Attributes</a>
|
||||
</li>
|
||||
<li v-if="mBean.op" :class="{'is-active' : selected.view === 'operations' }">
|
||||
<a @click.stop="select(selectedDomain, selectedMBean, 'operations')">Operations</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="card-content" v-if="mBean === selectedMBean">
|
||||
<m-bean-attributes v-if="selected.view === 'attributes'" :instance="instance"
|
||||
:domain="selectedDomain.domain" :m-bean="mBean"
|
||||
/>
|
||||
<m-bean-operations v-if="selected.view === 'operations'" :instance="instance"
|
||||
:domain="selectedDomain.domain" :m-bean="mBean"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<nav class="menu" v-sticks-below="['#navigation']">
|
||||
<p class="menu-label">domains</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a class="" v-for="domain in domains" :key="domain.domain"
|
||||
:class="{'is-active' : domain === selectedDomain}"
|
||||
v-text="domain.domain" @click="select(domain)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sticksBelow from '@/directives/sticks-below';
|
||||
import Instance from '@/services/instance';
|
||||
import fromPairs from 'lodash/fromPairs';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import {directive as onClickaway} from 'vue-clickaway';
|
||||
import mBeanAttributes from './m-bean-attributes';
|
||||
import mBeanOperations from './m-bean-operations';
|
||||
|
||||
const getOperationName = (name, descriptor) => {
|
||||
const params = descriptor.args.map(arg => arg.type).join(',');
|
||||
return `${name}(${params})`;
|
||||
};
|
||||
|
||||
class MBeanDescriptor {
|
||||
constructor(raw) {
|
||||
Object.assign(this, MBeanDescriptor.parse(raw));
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
static parse(raw) {
|
||||
const attributes = raw.split(',')
|
||||
.map(attribute => attribute.split('='))
|
||||
.map(([name, value]) => ({name, value}));
|
||||
const displayName = attributes.map(({value}) => value).join(' ').trim();
|
||||
return {attributes, displayName}
|
||||
}
|
||||
}
|
||||
|
||||
export class MBean {
|
||||
constructor({descriptor, op, ...mBean}) {
|
||||
Object.assign(this, mBean);
|
||||
this.descriptor = new MBeanDescriptor(descriptor);
|
||||
const flattenedOps = Object.entries(op || {}).flatMap(([name, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(v => [name, v]);
|
||||
} else {
|
||||
return [[name, value]];
|
||||
}
|
||||
}).map(([name, operation]) => [getOperationName(name, operation), operation]);
|
||||
this.op = flattenedOps.length > 0 ? fromPairs(flattenedOps) : null;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {mBeanOperations, mBeanAttributes},
|
||||
directives: {onClickaway, sticksBelow},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
domains: [],
|
||||
selected: {
|
||||
domain: null,
|
||||
mBean: null,
|
||||
view: null
|
||||
}
|
||||
}),
|
||||
computed: {
|
||||
selectedDomain() {
|
||||
return this.domains.find(d => d.domain === this.selected.domain)
|
||||
},
|
||||
selectedMBean() {
|
||||
return this.selectedDomain && this.selectedDomain.mBeans.find(b => b.descriptor.raw === this.selected.mBean)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchMBeans();
|
||||
},
|
||||
watch: {
|
||||
'$route': {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (!isEmpty(this.$route.query)) {
|
||||
this.selected = this.$route.query;
|
||||
} else if (this.domains.length > 0) {
|
||||
this.select(this.domains[0]);
|
||||
}
|
||||
}
|
||||
},
|
||||
selected() {
|
||||
if (!isEqual(this.selected, this.$route.query)) {
|
||||
this.$router.replace({
|
||||
name: 'instances/jolokia',
|
||||
query: this.selected
|
||||
});
|
||||
}
|
||||
},
|
||||
async selectedMBean(newVal) {
|
||||
if (newVal) {
|
||||
await this.$nextTick();
|
||||
const el = document.getElementById(newVal.descriptor.raw);
|
||||
if (el) {
|
||||
const scrollingEl = document.scrollingElement;
|
||||
const navigation = document.querySelector('#navigation');
|
||||
const navbarOffset = (navigation ? navigation.getBoundingClientRect().bottom : 120) + 10;
|
||||
const top = scrollingEl.scrollTop + el.getBoundingClientRect().top - navbarOffset;
|
||||
if (scrollingEl.scrollTo) {
|
||||
scrollingEl.scrollTo({top, behavior: 'smooth'})
|
||||
} else {
|
||||
scrollingEl.scrollTop = top;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchMBeans() {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await this.instance.listMBeans();
|
||||
const domains = sortBy(res.data, [d => d.domain]);
|
||||
this.domains = domains.map(domain => ({
|
||||
...domain,
|
||||
mBeans: sortBy(domain.mBeans.map(mBean => new MBean(mBean)), [b => b.descriptor.displayName])
|
||||
}));
|
||||
if (!this.selectedDomain && this.domains.length > 0) {
|
||||
this.select(this.domains[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Fetching MBeans failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
},
|
||||
select(domain, mBean, view) {
|
||||
this.selected = {
|
||||
domain: domain && domain.domain,
|
||||
mBean: mBean && mBean.descriptor.raw,
|
||||
view: view || (mBean ? (mBean.attr ? 'attributes' : (mBean.op ? 'operations' : null)) : null)
|
||||
};
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/jolokia',
|
||||
parent: 'instances',
|
||||
path: 'jolokia',
|
||||
component: this,
|
||||
label: 'JMX',
|
||||
group: 'JVM',
|
||||
order: 350,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('jolokia')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.m-bean {
|
||||
transition: all $easing $speed;
|
||||
|
||||
&.is-active {
|
||||
margin: 0.75rem -0.75rem;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
&.is-active .m-bean--header {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&:not(.is-active) .m-bean--header:hover {
|
||||
background-color: $white-bis;
|
||||
}
|
||||
|
||||
&--header {
|
||||
& .level .level-left {
|
||||
width: 100%;
|
||||
|
||||
& .level-item {
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
|
||||
& p {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--close {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
<span v-text="name" />
|
||||
<small class="is-muted has-text-weight-normal" v-text="descriptor.type" />
|
||||
</label>
|
||||
<div class="field-body">
|
||||
<div class="field is-expanded">
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<button class="button" :disabled="!descriptor.rw" @click="edit">
|
||||
<font-awesome-icon icon="pencil-alt" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="control is-expanded has-icons-right">
|
||||
<template v-if="!editing">
|
||||
<input v-if="!hasComplexValue" class="input" type="text" readonly :value="value" @dblclick="edit">
|
||||
<textarea v-else class="input m-bean-attribute--text" readonly v-text="jsonValue" />
|
||||
<span class="icon is-right has-text-warning" v-if="error">
|
||||
<font-awesome-icon icon="exclamation-triangle" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<input v-if="!hasComplexValue" class="input" type="text" ref="input"
|
||||
v-model="input" @keyup.esc="cancel" @keyup.enter="save"
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help" v-text="descriptor.desc" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control" v-if="editing">
|
||||
<button class="button is-light is-small" @click="cancel">Cancel</button>
|
||||
<button class="button is-primary is-small" :class="{'is-loading' : saving}" @click="save"
|
||||
:disabled="value === input"
|
||||
>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
descriptor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
// eslint-disable-next-line vue/require-prop-type-constructor
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
onSaveValue: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
input: null,
|
||||
editing: false,
|
||||
saving: false,
|
||||
error: null
|
||||
}),
|
||||
computed: {
|
||||
hasComplexValue() {
|
||||
return this.value !== null && typeof this.value === 'object';
|
||||
},
|
||||
jsonValue() {
|
||||
return JSON.stringify(this.value, null, 4);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async edit() {
|
||||
if (this.descriptor.rw && !this.hasComplexValue) {
|
||||
this.input = this.value;
|
||||
this.editing = true;
|
||||
await this.$nextTick();
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
this.editing = false;
|
||||
},
|
||||
async save() {
|
||||
this.saving = true;
|
||||
try {
|
||||
await this.onSaveValue(this.input);
|
||||
} catch (error) {
|
||||
console.warn(`Error saving attribute ${this.name}`, error);
|
||||
this.error = error;
|
||||
} finally {
|
||||
this.saving = false;
|
||||
this.editing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.m-bean-attribute--text {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
</style>
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching attributes failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<m-bean-attribute v-for="(attribute, name) in mBean.attr" :key="`attr-${name}`"
|
||||
:name="name" :descriptor="attribute" :value="attributeValues && attributeValues[name]"
|
||||
:on-save-value="value => writeAttribute(name, value)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import {MBean} from './index';
|
||||
import mBeanAttribute from './m-bean-attribute';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
domain: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mBean: {
|
||||
type: MBean,
|
||||
required: true
|
||||
},
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {mBeanAttribute},
|
||||
data: () => ({
|
||||
attributeValues: null,
|
||||
error: null
|
||||
}),
|
||||
computed: {},
|
||||
methods: {
|
||||
async readAttributes() {
|
||||
try {
|
||||
const response = await this.instance.readMBeanAttributes(this.domain, this.mBean.descriptor.raw);
|
||||
this.attributeValues = response.data.value;
|
||||
} catch (error) {
|
||||
console.warn('Fetching MBean attributes failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
},
|
||||
async writeAttribute(attribute, value) {
|
||||
try {
|
||||
await this.instance.writeMBeanAttribute(this.domain, this.mBean.descriptor.raw, attribute, value);
|
||||
} finally {
|
||||
await this.readAttributes();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.readAttributes();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="modal is-active">
|
||||
<div class="modal-background" @click="abort" />
|
||||
<div class="modal-content">
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title" v-text="name" />
|
||||
</header>
|
||||
|
||||
<template v-if="state === 'input-args'">
|
||||
<section class="modal-card-body" @keyup.ctrl.enter="invoke(args)">
|
||||
<div class="field" v-for="(arg, idx) in descriptor.args" :key="arg.name">
|
||||
<label class="label">
|
||||
<span v-text="arg.name" />
|
||||
<small class="is-muted has-text-weight-normal" v-text="arg.type" />
|
||||
</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" v-model="args[idx]">
|
||||
</div>
|
||||
<p class="help" v-text="arg.desc" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<button class="button is-primary" @click="invoke(args)">Execute</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<template v-else-if="state === 'executing'">
|
||||
<section class="modal-card-body">
|
||||
<section class="section is-loading">
|
||||
<p>Executing...</p>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="state === 'completed'">
|
||||
<section class="modal-card-body">
|
||||
<div class="message is-success">
|
||||
<div class="message-body">
|
||||
<strong>Execution successful.</strong>
|
||||
</div>
|
||||
</div>
|
||||
<pre v-if="descriptor.ret !== 'void'" v-text="prettyPrintedResult" />
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<button class="button is-light" @click="abort"> Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<template v-else-if="state === 'failed'">
|
||||
<section class="modal-card-body">
|
||||
<div class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger"
|
||||
icon="exclamation-triangle"
|
||||
/>
|
||||
Execution failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<pre v-if="error.stacktrace"
|
||||
v-text="error.stacktrace"
|
||||
/>
|
||||
<pre v-if="error.response && error.response.data"
|
||||
v-text="error.response.data"
|
||||
/>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<button class="button is-light" @click="abort"> Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
descriptor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
// eslint-disable-next-line vue/require-prop-type-constructor
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
onClose: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
onExecute: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
state: null,
|
||||
error: null,
|
||||
args: null,
|
||||
result: null
|
||||
}),
|
||||
computed: {
|
||||
prettyPrintedResult() {
|
||||
if (this.result && typeof this.result === 'string') {
|
||||
try {
|
||||
const o = JSON.parse(this.result);
|
||||
return JSON.stringify(o, undefined, 4);
|
||||
} catch (e) {
|
||||
return this.result;
|
||||
}
|
||||
} else if (typeof result === 'object') {
|
||||
return JSON.stringify(this.result, undefined, 4);
|
||||
}
|
||||
return this.result;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
abort() {
|
||||
this.onClose();
|
||||
},
|
||||
invoke(args) {
|
||||
this.state = (args || this.descriptor.args.length === 0) ? 'prepared' : 'input-args';
|
||||
this.args = args || new Array(this.descriptor.args.length);
|
||||
this.error = null;
|
||||
this.result = null;
|
||||
|
||||
if (this.state === 'prepared') {
|
||||
this.execute()
|
||||
}
|
||||
},
|
||||
async execute() {
|
||||
this.state = 'executing';
|
||||
try {
|
||||
const result = await this.onExecute(this.args);
|
||||
if (result.data.status < 400) {
|
||||
this.result = result.data.value;
|
||||
this.state = 'completed';
|
||||
} else {
|
||||
const error = new Error(`Execution failed: ${result.data.error}`);
|
||||
error.stacktrace = result.data.stacktrace;
|
||||
this.state = 'failed';
|
||||
this.error = error;
|
||||
console.warn('Invocation failed', error);
|
||||
}
|
||||
} catch (error) {
|
||||
this.state = 'failed';
|
||||
this.error = error;
|
||||
console.warn('Invocation failed', error);
|
||||
}
|
||||
},
|
||||
keyHandler(event) {
|
||||
if (event.keyCode === 27) {
|
||||
this.abort()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.invoke();
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keyup', this.keyHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keyup', this.keyHandler)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-light is-fullwidth" @click="$emit('click', $event)">
|
||||
<small class="is-light is-muted" v-text="descriptor.ret" /> <span v-text="name" />
|
||||
</button>
|
||||
<p class="help" v-text="descriptor.desc" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
descriptor: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<mBeanOperation v-for="(operation, name) in mBean.op" :key="`op-${name}`"
|
||||
:name="name" :descriptor="operation" @click="invoke(name, operation)"
|
||||
/>
|
||||
<m-bean-operation-invocation v-if="invocation" :name="invocation.name" :descriptor="invocation.descriptor"
|
||||
:on-execute="execute" :on-close="closeInvocation"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import {MBean} from './index';
|
||||
import mBeanOperation from './m-bean-operation';
|
||||
import mBeanOperationInvocation from './m-bean-operation-invocation';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
domain: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mBean: {
|
||||
type: MBean,
|
||||
required: true
|
||||
},
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {mBeanOperation, mBeanOperationInvocation},
|
||||
data: () => ({
|
||||
invocation: null
|
||||
}),
|
||||
computed: {},
|
||||
methods: {
|
||||
closeInvocation() {
|
||||
this.invocation = null
|
||||
},
|
||||
invoke(name, descriptor) {
|
||||
this.invocation = {name, descriptor};
|
||||
},
|
||||
execute(args) {
|
||||
return this.instance.invokeMBeanOperation(this.domain, this.mBean.descriptor.raw, this.invocation.name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,161 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{ 'is-loading' : !hasLoaded }">
|
||||
<template v-if="hasLoaded">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching Liquibase migrations failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(context, ctxName) in contexts">
|
||||
<h3 class="title" v-text="ctxName" :key="ctxName" />
|
||||
<template v-for="(report, name) in context.liquibaseBeans">
|
||||
<sba-panel :key="`${ctxName}-${name}`" :title="`name`" class="change-set"
|
||||
:header-sticks-below="['#navigation']"
|
||||
>
|
||||
<table class="table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Execution</th>
|
||||
<th>Description</th>
|
||||
<th>Tag</th>
|
||||
<th>Contexts</th>
|
||||
<th>Labels</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="changeSet in report.changeSets">
|
||||
<tr :key="`${ctxName}-${name}-${changeSet.id}`" class="is-selectable"
|
||||
@click="showDetails[changeSet.checksum] ? $delete(showDetails, changeSet.checksum) : $set(showDetails, changeSet.checksum, true)"
|
||||
>
|
||||
<td v-text="changeSet.id" />
|
||||
<td>
|
||||
<span v-text="changeSet.execType" class="tag" :class="execClass(execType)" />
|
||||
</td>
|
||||
<td v-text="changeSet.description" />
|
||||
<td v-text="changeSet.tag" />
|
||||
<td v-text="changeSet.contexts.join(', ')" />
|
||||
<td>
|
||||
<span v-for="label in changeSet.labels" :key="`${ctxName}-${name}-${changeSet.id}-${label}`"
|
||||
class="tag is-info" v-text="label"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="showDetails[changeSet.checksum]" :key="`${ctxName}-${name}-${changeSet.id}-details`">
|
||||
<td colspan="6">
|
||||
<table class="table is-fullwidth">
|
||||
<tr>
|
||||
<th>Changelog</th>
|
||||
<td colspan="3" v-text="changeSet.changeLog" />
|
||||
<th>Author</th>
|
||||
<td v-text="changeSet.author" />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Checksum</th>
|
||||
<td v-text="changeSet.checksum" />
|
||||
<th>Comments</th>
|
||||
<td colspan="3" v-text="changeSet.comments" />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Execution Order</th>
|
||||
<td v-text="changeSet.orderExecuted" />
|
||||
<th>ExecutionDate</th>
|
||||
<td v-text="changeSet.dateExecuted" />
|
||||
<th>DeploymentId</th>
|
||||
<td v-text="changeSet.deploymentId" />
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</sba-panel>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
contexts: null,
|
||||
showDetails: {}
|
||||
}),
|
||||
computed: {},
|
||||
created() {
|
||||
this.fetchLiquibase();
|
||||
},
|
||||
methods: {
|
||||
async fetchLiquibase() {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await this.instance.fetchLiquibase();
|
||||
this.contexts = res.data.contexts;
|
||||
} catch (error) {
|
||||
console.warn('Fetching Liquibase changeSets failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
},
|
||||
execClass(execType) {
|
||||
switch (execType) {
|
||||
case 'EXECUTED':
|
||||
return 'is-success';
|
||||
case 'FAILED':
|
||||
return 'is-danger';
|
||||
case 'SKIPPED':
|
||||
return 'is-light';
|
||||
case 'RERAN':
|
||||
case 'MARK_RAN':
|
||||
return 'is-warning';
|
||||
default:
|
||||
return 'is-info';
|
||||
}
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/liquibase',
|
||||
parent: 'instances',
|
||||
path: 'liquibase',
|
||||
component: this,
|
||||
label: 'Liquibase',
|
||||
group: 'Data',
|
||||
order: 900,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('liquibase')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>å
|
||||
@@ -1,176 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="section logfile-view" :class="{ 'is-loading' : !hasLoaded }">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching logfile failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="logfile-view-actions" v-if="hasLoaded">
|
||||
<div class="logfile-view-actions__navigation">
|
||||
<sba-icon-button :disabled="atTop" @click="scrollToTop" icon="step-backward" size="lg"
|
||||
icon-class="rotated"
|
||||
/>
|
||||
<sba-icon-button :disabled="atBottom" @click="scrollToBottom" icon="step-forward" size="lg"
|
||||
icon-class="rotated"
|
||||
/>
|
||||
</div>
|
||||
<a class="button" :href="`instances/${instance.id}/actuator/logfile`" target="_blank">
|
||||
<font-awesome-icon icon="download" /> Download
|
||||
</a>
|
||||
</div>
|
||||
<p v-if="skippedBytes" v-text="`skipped ${prettyBytes(skippedBytes)}`" />
|
||||
<!-- log will be appended here -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import subscribing from '@/mixins/subscribing';
|
||||
import Instance from '@/services/instance';
|
||||
import autolink from '@/utils/autolink';
|
||||
import {animationFrameScheduler, concatAll, concatMap, map, of, tap} from '@/utils/rxjs';
|
||||
import AnsiUp from 'ansi_up';
|
||||
import chunk from 'lodash/chunk';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [subscribing],
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
atBottom: true,
|
||||
atTop: false,
|
||||
skippedBytes: null
|
||||
}),
|
||||
created() {
|
||||
this.ansiUp = new AnsiUp();
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('scroll', this.onScroll);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
},
|
||||
methods: {
|
||||
prettyBytes,
|
||||
createSubscription() {
|
||||
const vm = this;
|
||||
vm.error = null;
|
||||
return this.instance.streamLogfile(1000)
|
||||
.pipe(
|
||||
tap(part => vm.skippedBytes = vm.skippedBytes || part.skipped),
|
||||
concatMap(part => chunk(part.addendum.split(/\r?\n/), 250)),
|
||||
map(lines => of(lines, animationFrameScheduler)),
|
||||
concatAll()
|
||||
)
|
||||
.subscribe({
|
||||
next: lines => {
|
||||
vm.hasLoaded = true;
|
||||
lines.forEach(line => {
|
||||
const child = document.createElement('pre');
|
||||
child.innerHTML = autolink(this.ansiUp.ansi_to_html(line));
|
||||
vm.$el.appendChild(child);
|
||||
});
|
||||
|
||||
if (vm.atBottom) {
|
||||
vm.scrollToBottom();
|
||||
}
|
||||
},
|
||||
error: error => {
|
||||
vm.hasLoaded = true;
|
||||
console.warn('Fetching logfile failed:', error);
|
||||
vm.error = error;
|
||||
}
|
||||
});
|
||||
},
|
||||
onScroll() {
|
||||
const scrollingEl = document.scrollingElement;
|
||||
const visibleHeight = document.documentElement.clientHeight;
|
||||
this.atBottom = visibleHeight === scrollingEl.scrollHeight - scrollingEl.scrollTop;
|
||||
this.atTop = scrollingEl.scrollTop <= 0;
|
||||
},
|
||||
scrollToTop() {
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
},
|
||||
scrollToBottom() {
|
||||
document.scrollingElement.scrollTop = document.scrollingElement.scrollHeight;
|
||||
}
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/logfile',
|
||||
parent: 'instances',
|
||||
path: 'logfile',
|
||||
component: this,
|
||||
label: 'Logfile',
|
||||
group: 'Logging',
|
||||
order: 200,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('logfile')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/assets/css/utilities";
|
||||
|
||||
.logfile-view {
|
||||
padding: 1.5em;
|
||||
|
||||
pre {
|
||||
word-break: break-all;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background: $grey-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
&-actions {
|
||||
top: (($gap / 2) + $navbar-height-px);
|
||||
right: ($gap /2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: sticky;
|
||||
float: right;
|
||||
|
||||
&__navigation {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rotated {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
||||
@@ -1,219 +0,0 @@
|
||||
<!--
|
||||
- Copyright 2014-2018 the original author or authors.
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="section" :class="{ 'is-loading' : !hasLoaded }">
|
||||
<template v-if="hasLoaded">
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-body">
|
||||
<strong>
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
Fetching loggers failed.
|
||||
</strong>
|
||||
<p v-text="error.message" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="loggerConfig">
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded">
|
||||
<input class="input" type="search" placeholder="name filter" v-model="filter">
|
||||
</p>
|
||||
<p class="control">
|
||||
<span class="button is-static">
|
||||
<span v-text="filteredLoggers.length" />
|
||||
/
|
||||
<span v-text="loggerConfig.loggers.length" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="showClassLoggersOnly">
|
||||
class only
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="showConfiguredLoggersOnly">
|
||||
configured
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<table v-if="loggerConfig" class="table is-hoverable is-fullwidth">
|
||||
<tbody>
|
||||
<tr v-for="logger in limitedLoggers" :key="logger.name">
|
||||
<td>
|
||||
<span class="is-breakable" v-text="logger.name" />
|
||||
<span class="has-text-danger" v-if="logger.name in failed">
|
||||
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle" />
|
||||
<span v-text="`Configuring ${failed[logger.name]} failed.`" />
|
||||
</span>
|
||||
<sba-logger-control class="is-pulled-right"
|
||||
:level-options="levels"
|
||||
:effective-level="logger.effectiveLevel"
|
||||
:configured-level="logger.configuredLevel"
|
||||
:is-loading="loading[logger.name]"
|
||||
:has-failed="failed[logger.name]"
|
||||
:allow-reset="logger.name !== 'ROOT'"
|
||||
@input="level => configureLogger(logger, level)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="limitedLoggers.length === 0">
|
||||
<td class="is-muted" colspan="5">No loggers found.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Instance from '@/services/instance';
|
||||
import sbaLoggerControl from './logger-control';
|
||||
|
||||
const isClassName = name => /\.[A-Z]/.test(name);
|
||||
|
||||
const addToFilter = (oldFilter, addedFilter) =>
|
||||
!oldFilter
|
||||
? addedFilter
|
||||
: (val, key) => oldFilter(val, key) && addedFilter(val, key);
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {sbaLoggerControl},
|
||||
props: {
|
||||
instance: {
|
||||
type: Instance,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
hasLoaded: false,
|
||||
error: null,
|
||||
loggerConfig: null,
|
||||
filter: '',
|
||||
showClassLoggersOnly: false,
|
||||
showConfiguredLoggersOnly: false,
|
||||
visibleLimit: 25,
|
||||
loading: {},
|
||||
failed: {}
|
||||
}),
|
||||
computed: {
|
||||
levels() {
|
||||
return this.loggerConfig.levels;
|
||||
},
|
||||
limitedLoggers() {
|
||||
if (this.visibleLimit > 0) {
|
||||
return this.filteredLoggers.slice(0, this.visibleLimit);
|
||||
} else {
|
||||
return this.filteredLoggers;
|
||||
}
|
||||
},
|
||||
filteredLoggers() {
|
||||
let filterFn = this.getFilterFn();
|
||||
return filterFn ? this.loggerConfig.loggers.filter(filterFn) : this.loggerConfig.loggers;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async configureLogger(logger, level) {
|
||||
const vm = this;
|
||||
const setLoadingHandle = setTimeout(() => vm.$set(vm.loading, logger.name, level), 150);
|
||||
try {
|
||||
await this.instance.configureLogger(logger.name, level);
|
||||
vm.$delete(vm.failed, logger.name, level)
|
||||
} catch (error) {
|
||||
console.warn(`Configuring logger '${logger.name}' failed:`, error);
|
||||
vm.$set(vm.failed, logger.name, level);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.fetchLoggers();
|
||||
} finally {
|
||||
vm.$delete(vm.loading, logger.name);
|
||||
clearTimeout(setLoadingHandle);
|
||||
}
|
||||
},
|
||||
async fetchLoggers() {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await this.instance.fetchLoggers();
|
||||
this.loggerConfig = res.data;
|
||||
} catch (error) {
|
||||
console.warn('Fetching loggers failed:', error);
|
||||
this.error = error;
|
||||
}
|
||||
this.hasLoaded = true;
|
||||
},
|
||||
onScroll() {
|
||||
if (this.loggerConfig && this.$el.getBoundingClientRect().bottom - 400 <= window.innerHeight && this.visibleLimit < this.filteredLoggers.length) {
|
||||
this.visibleLimit += 25;
|
||||
}
|
||||
},
|
||||
getFilterFn() {
|
||||
let filterFn = null;
|
||||
|
||||
if (this.showClassLoggersOnly) {
|
||||
filterFn = addToFilter(filterFn, logger => isClassName(logger.name));
|
||||
}
|
||||
|
||||
if (this.showConfiguredLoggersOnly) {
|
||||
filterFn = addToFilter(filterFn, logger => !!logger.configuredLevel);
|
||||
}
|
||||
|
||||
if (this.filter) {
|
||||
const normalizedFilter = this.filter.toLowerCase();
|
||||
filterFn = addToFilter(filterFn, logger => logger.name.toLowerCase().includes(normalizedFilter));
|
||||
}
|
||||
|
||||
return filterFn;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchLoggers();
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('scroll', this.onScroll);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
},
|
||||
install({viewRegistry}) {
|
||||
viewRegistry.addView({
|
||||
name: 'instances/loggers',
|
||||
parent: 'instances',
|
||||
path: 'loggers',
|
||||
component: this,
|
||||
label: 'Loggers',
|
||||
group: 'Logging',
|
||||
order: 300,
|
||||
isEnabled: ({instance}) => instance.hasEndpoint('loggers')
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
Loaded 100 of 173 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user