mirror of
https://github.com/BluemediaGER/fancy-gatus.git
synced 2024-11-22 22:55:29 +01:00
Initial build
This commit is contained in:
parent
ba484d3d9a
commit
20686e39b6
23
README.md
23
README.md
|
@ -1,3 +1,26 @@
|
||||||
# fancy-gatus
|
# fancy-gatus
|
||||||
|
|
||||||
Fancy-Gatus is an alternative frontend for [Gatus](https://github.com/TwiN/gatus). The goal is to provide a simplified and modern status page based on data from a Gatus instance, which only displays the most important information in a way that is understandable for end users.
|
Fancy-Gatus is an alternative frontend for [Gatus](https://github.com/TwiN/gatus). The goal is to provide a simplified and modern status page based on data from a Gatus instance, which only displays the most important information in a way that is understandable for end users.
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
yarn serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
yarn lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.24.0",
|
||||||
|
"bootstrap-icons-vue": "^0.8.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"vue": "^3.0.0"
|
"vue": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -5,11 +5,11 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title>Fancy Gatus</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
|
|
144
src/App.vue
144
src/App.vue
|
@ -1,21 +1,157 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div class="main">
|
||||||
|
<div v-if="$data.loading" class="loader-wrapper">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
<div v-if="!$data.loading" class="content-wrapper">
|
||||||
|
<Header :title="this.config.title" />
|
||||||
|
<OverallStatus class="overall-status" :failedEndpoints="failedEndpoints" />
|
||||||
|
<EndpointGroup class="endpoint-group" v-for="(value, key) in groups" :key="key" :name="key" :endpoints="value" />
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Loader from '@/components/Loader.vue';
|
||||||
|
import Header from '@/components/Header.vue';
|
||||||
|
import OverallStatus from '@/components/OverallStatus.vue';
|
||||||
|
import EndpointGroup from '@/components/EndpointGroup.vue';
|
||||||
|
import Footer from '@/components/Footer.vue';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
|
Loader,
|
||||||
|
Header,
|
||||||
|
OverallStatus,
|
||||||
|
EndpointGroup,
|
||||||
|
Footer
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
config: null,
|
||||||
|
apiData: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// Group endpoints by their group name
|
||||||
|
groups() {
|
||||||
|
return this.apiData.reduce(function(rv, x) {
|
||||||
|
(rv[x["group"]] = rv[x["group"]] || []).push(x);
|
||||||
|
return rv;
|
||||||
|
}, {});
|
||||||
|
},
|
||||||
|
// Get an array of all failed endpoints
|
||||||
|
failedEndpoints() {
|
||||||
|
return this.apiData.filter(endpoint => {
|
||||||
|
return !endpoint.results[endpoint.results.length - 1].success;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Get frontend config and trigger initial data fetch
|
||||||
|
getConfig() {
|
||||||
|
axios.get('/config.json')
|
||||||
|
.then(response => {
|
||||||
|
this.config = response.data;
|
||||||
|
// Set title if defined in config
|
||||||
|
if (this.config.title) {
|
||||||
|
document.title = this.config.title;
|
||||||
|
}
|
||||||
|
this.getApiData();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getApiData() {
|
||||||
|
// Set base URL for API calls if defined in config
|
||||||
|
if (this.config.gatusBaseUrl && axios.defaults.baseURL !== this.config.gatusBaseUrl) {
|
||||||
|
axios.defaults.baseURL = this.config.gatusBaseUrl;
|
||||||
|
}
|
||||||
|
axios.get('/api/v1/endpoints/statuses')
|
||||||
|
.then(response => {
|
||||||
|
// Remove hidden endpoints if defined in config
|
||||||
|
if (this.config.hiddenEndpoints) {
|
||||||
|
this.apiData = response.data.filter(endpoint => {
|
||||||
|
return !this.config.hiddenEndpoints.includes(endpoint.name);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.apiData = response.data;
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main {
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.loader-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.content-wrapper {
|
||||||
|
min-width: 60%;
|
||||||
|
max-width: 90%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.overall-status {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
.endpoint-group {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
--green: #00d560;
|
||||||
|
--orange: #ff9100;
|
||||||
|
}
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
color: #2c3e50;
|
color: #2F3545;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
.shadow-box {
|
||||||
|
box-shadow: 0 15px 70px rgb(0 0 0 / 10%);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.green {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
.orange {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
</style>
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
61
src/components/Endpoint.vue
Normal file
61
src/components/Endpoint.vue
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div class="endpoint">
|
||||||
|
<BIconCheckCircleFill class="green icon" v-if="isSuccessfull" />
|
||||||
|
<BIconExclamationCircleFill class="orange icon" v-if="!isSuccessfull" />
|
||||||
|
<span>{{ endpoint.name }}</span>
|
||||||
|
<span class="hostname" v-if="hostname">| {{ hostname }}</span>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<EndpointHistory :results="endpoint.results" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { BIconExclamationCircleFill, BIconCheckCircleFill } from 'bootstrap-icons-vue';
|
||||||
|
import EndpointHistory from '@/components/EndpointHistory.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Endpoint',
|
||||||
|
components: {
|
||||||
|
BIconExclamationCircleFill,
|
||||||
|
BIconCheckCircleFill,
|
||||||
|
EndpointHistory
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
endpoint: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isSuccessfull() {
|
||||||
|
return this.endpoint.results[this.endpoint.results.length - 1].success;
|
||||||
|
},
|
||||||
|
hostname() {
|
||||||
|
return this.endpoint.results[0].hostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.endpoint {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.hostname {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: rgb(143, 143, 143);
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
41
src/components/EndpointGroup.vue
Normal file
41
src/components/EndpointGroup.vue
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<div class="endpoint-group">
|
||||||
|
<h3>{{ name }}</h3>
|
||||||
|
<div class="shadow-box list">
|
||||||
|
<Endpoint class="endpoint" v-for="endpoint in endpoints" :key="endpoint.name" :endpoint="endpoint" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Endpoint from '@/components/Endpoint.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EndpointGroup',
|
||||||
|
components: {
|
||||||
|
Endpoint
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
endpoints: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-items: space-between;
|
||||||
|
}
|
||||||
|
.endpoint {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
54
src/components/EndpointHistory.vue
Normal file
54
src/components/EndpointHistory.vue
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<div class="history">
|
||||||
|
<div v-for="(result, index) in preparedResults" :key="index" :title="result.timestamp" :class="{blob: true, green: result.success === true, orange: result.success === false, grey: !result.timestamp}"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'EndpointHistory',
|
||||||
|
props: {
|
||||||
|
results: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
preparedResults() {
|
||||||
|
let tmp = [...this.results];
|
||||||
|
while (tmp.length < 20) {
|
||||||
|
tmp.unshift({});
|
||||||
|
}
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.history {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.blob {
|
||||||
|
width: 7px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 50rem;
|
||||||
|
--hover-scale: 1.5;
|
||||||
|
}
|
||||||
|
.blob:hover {
|
||||||
|
transform: scale(var(--hover-scale));
|
||||||
|
}
|
||||||
|
.green {
|
||||||
|
background-color: var(--green);
|
||||||
|
}
|
||||||
|
.orange {
|
||||||
|
background-color: var(--orange);
|
||||||
|
}
|
||||||
|
.grey {
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
</style>
|
25
src/components/Footer.vue
Normal file
25
src/components/Footer.vue
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<footer>Powered by
|
||||||
|
<a target="_blank" href="https://github.com/TwiN/gatus">Gatus</a>
|
||||||
|
and
|
||||||
|
<a target="_blank" href="https://github.com/BluemediaGER/fancy-gatus">Fancy Gatus</a>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Footer'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
footer {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #111;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
33
src/components/Header.vue
Normal file
33
src/components/Header.vue
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<img src="@/assets/logo.png" alt="Logo" class="logo">
|
||||||
|
<h2>{{ this.title }}</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: 'Infrastructure Status'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
width: 60px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
256
src/components/Loader.vue
Normal file
256
src/components/Loader.vue
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="loader">
|
||||||
|
<svg viewBox="0 0 80 80">
|
||||||
|
<circle id="test" cx="40" cy="40" r="32"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="loader triangle">
|
||||||
|
<svg viewBox="0 0 86 80">
|
||||||
|
<polygon points="43 8 79 72 7 72"></polygon>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="loader">
|
||||||
|
<svg viewBox="0 0 80 80">
|
||||||
|
<rect x="8" y="8" width="64" height="64"></rect>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Loader"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.loader {
|
||||||
|
--path: #2F3545;
|
||||||
|
--dot: #00d560;
|
||||||
|
--duration: 3s;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.loader:before {
|
||||||
|
content: "";
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
background: var(--dot);
|
||||||
|
top: 37px;
|
||||||
|
left: 19px;
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
-webkit-animation: dotRect var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
animation: dotRect var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
}
|
||||||
|
.loader svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.loader svg rect,
|
||||||
|
.loader svg polygon,
|
||||||
|
.loader svg circle {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--path);
|
||||||
|
stroke-width: 10px;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
.loader svg polygon {
|
||||||
|
stroke-dasharray: 145 76 145 76;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
-webkit-animation: pathTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
animation: pathTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
}
|
||||||
|
.loader svg rect {
|
||||||
|
stroke-dasharray: 192 64 192 64;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
-webkit-animation: pathRect 3s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
animation: pathRect 3s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
}
|
||||||
|
.loader svg circle {
|
||||||
|
stroke-dasharray: 150 50 150 50;
|
||||||
|
stroke-dashoffset: 75;
|
||||||
|
-webkit-animation: pathCircle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
animation: pathCircle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
}
|
||||||
|
.loader.triangle {
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
.loader.triangle:before {
|
||||||
|
left: 21px;
|
||||||
|
transform: translate(-10px, -18px);
|
||||||
|
-webkit-animation: dotTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
animation: dotTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes pathTriangle {
|
||||||
|
33% {
|
||||||
|
stroke-dashoffset: 74;
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
stroke-dashoffset: 147;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 221;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pathTriangle {
|
||||||
|
33% {
|
||||||
|
stroke-dashoffset: 74;
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
stroke-dashoffset: 147;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 221;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes dotTriangle {
|
||||||
|
33% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translate(10px, -18px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-10px, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes dotTriangle {
|
||||||
|
33% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translate(10px, -18px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-10px, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes pathRect {
|
||||||
|
25% {
|
||||||
|
stroke-dashoffset: 64;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dashoffset: 128;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stroke-dashoffset: 192;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes pathRect {
|
||||||
|
25% {
|
||||||
|
stroke-dashoffset: 64;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dashoffset: 128;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stroke-dashoffset: 192;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes dotRect {
|
||||||
|
25% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translate(0, -36px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes dotRect {
|
||||||
|
25% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translate(0, -36px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes pathCircle {
|
||||||
|
25% {
|
||||||
|
stroke-dashoffset: 125;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dashoffset: 175;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stroke-dashoffset: 225;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 275;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes pathCircle {
|
||||||
|
25% {
|
||||||
|
stroke-dashoffset: 125;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dashoffset: 175;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stroke-dashoffset: 225;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 275;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loader {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
*:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #F5F9FF;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
body .dribbble {
|
||||||
|
position: fixed;
|
||||||
|
display: block;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
}
|
||||||
|
body .dribbble img {
|
||||||
|
display: block;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
</style>
|
60
src/components/OverallStatus.vue
Normal file
60
src/components/OverallStatus.vue
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<template>
|
||||||
|
<div class="shadow-box">
|
||||||
|
<div class="status-wrapper" v-if="this.failedEndpoints.length === 0">
|
||||||
|
<BIconCheckCircleFill class="icon green" />
|
||||||
|
<span>All services healthy.</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-wrapper" v-else>
|
||||||
|
<BIconExclamationCircleFill class="icon orange" />
|
||||||
|
<span>{{ failedEndpointCountText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { BIconExclamationCircleFill, BIconCheckCircleFill } from 'bootstrap-icons-vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OverallStatus',
|
||||||
|
components: {
|
||||||
|
BIconExclamationCircleFill,
|
||||||
|
BIconCheckCircleFill
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
failedEndpoints: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
failedEndpointCountText() {
|
||||||
|
if (this.failedEndpoints.length === 2) {
|
||||||
|
return 'Two services are experiencing issues.';
|
||||||
|
} else if (this.failedEndpoints.length === 3) {
|
||||||
|
return 'Three services are experiencing issues.';
|
||||||
|
} else if (this.failedEndpoints.length > 3) {
|
||||||
|
return 'Multiple services are experiencing issues.';
|
||||||
|
} else {
|
||||||
|
return 'One service is experiencing issues.';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.status-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
17
yarn.lock
17
yarn.lock
|
@ -1941,6 +1941,13 @@ aws4@^1.8.0:
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||||
|
|
||||||
|
axios@^0.24.0:
|
||||||
|
version "0.24.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
|
||||||
|
integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.14.4"
|
||||||
|
|
||||||
babel-eslint@^10.1.0:
|
babel-eslint@^10.1.0:
|
||||||
version "10.1.0"
|
version "10.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
|
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
|
||||||
|
@ -2114,6 +2121,11 @@ boolbase@^1.0.0, boolbase@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||||
|
|
||||||
|
bootstrap-icons-vue@^0.8.0:
|
||||||
|
version "0.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bootstrap-icons-vue/-/bootstrap-icons-vue-0.8.0.tgz#922dfc368e62c85fe9cadad4f3568ce4c2b7bdba"
|
||||||
|
integrity sha512-bFBpTGNN5RtUpSH7zTz3WkZs2UZA7WXSmkwAKsrA7qddjbIQrvoGsu/yNJiAl14eJgZ+rTqmjqSB+0gRlNZEfA==
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
@ -4026,6 +4038,11 @@ follow-redirects@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd"
|
||||||
integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==
|
integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==
|
||||||
|
|
||||||
|
follow-redirects@^1.14.4:
|
||||||
|
version "1.14.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
|
||||||
|
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
|
||||||
|
|
||||||
for-in@^1.0.2:
|
for-in@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||||
|
|
Loading…
Reference in a new issue