pull/429/head
hunterlong 2020-02-19 21:28:39 -08:00
parent b8dbad85fe
commit 7762ca40d7
61 changed files with 2608 additions and 1225 deletions

View File

@ -280,7 +280,6 @@ func recordFailure(s *Service, issue string) {
s.CreateFailure(fail)
s.Online = false
s.SuccessNotified = false
s.UpdateNotify = CoreApp.UpdateNotify.Bool
s.DownText = s.DowntimeText()
notifier.OnFailure(s.Service, fail)
}

View File

@ -40,7 +40,7 @@ func OnFailure(s *types.Service, f *types.Failure) {
}
// check if User wants to receive every Status Change
if s.UpdateNotify {
if s.UpdateNotify.Bool {
// send only if User hasn't been already notified about the Downtime
if !s.UserNotified {
s.UserNotified = true
@ -69,7 +69,7 @@ func OnSuccess(s *types.Service) {
}
// check if User wants to receive every Status Change
if s.UpdateNotify && s.UserNotified {
if s.UpdateNotify.Bool && s.UserNotified {
s.UserNotified = false
}

View File

@ -10,12 +10,14 @@ const environment = require('./dev.env');
const webpackConfig = merge(commonConfig, {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devtool: 'inline-cheap-module-source-map',
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: 'js/[name].bundle.js',
chunkFilename: 'js/[name].chunk.js'
chunkFilename: 'js/[name].chunk.js',
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
},
optimization: {
runtimeChunk: 'single',

View File

@ -6,7 +6,8 @@
"serve": "vue-cli-service serve",
"build": "rm -rf dist && cross-env NODE_ENV=production webpack --mode production",
"dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8888 --progress",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"test": "cross-env NODE_ENV=development mochapack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js"
},
"dependencies": {
"@fortawesome/fontawesome-free-solid": "^5.1.0-3",
@ -18,7 +19,7 @@
"axios": "^0.19.1",
"codemirror-colorpicker": "^1.9.66",
"core-js": "^3.4.4",
"moment": "^2.24.0",
"date-fns": "^2.9.0",
"querystring": "^0.2.0",
"vue": "^2.6.10",
"vue-apexcharts": "^1.5.2",
@ -40,6 +41,7 @@
"@babel/preset-env": "~7.8.3",
"@vue/babel-preset-app": "^4.1.2",
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/test-utils": "^1.0.0-beta.31",
"babel-eslint": "~10.0",
"babel-loader": "~8.0",
"compression-webpack-plugin": "~2.0",
@ -55,10 +57,15 @@
"eslint-plugin-promise": "~3.5",
"eslint-plugin-standard": "~3.0",
"eslint-plugin-vue": "~5.1",
"expect": "^25.1.0",
"file-loader": "^5.0.2",
"friendly-errors-webpack-plugin": "~1.7",
"html-webpack-plugin": "^4.0.0-beta.11",
"jsdom": "^16.2.0",
"jsdom-global": "^3.0.2",
"mini-css-extract-plugin": "~0.5",
"mocha": "^7.0.1",
"mochapack": "^1.1.13",
"node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "~5.0",
"sass-loader": "^8.0.2",

View File

@ -10,11 +10,20 @@
<base href="{{BasePath}}">
{{if USE_CDN}}
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statping.com/favicon.ico">
<link rel="stylesheet" href="https://assets.statping.com/css/bootstrap.min.css">
<link rel="stylesheet" href="https://assets.statping.com/css/base.css">
<link rel="stylesheet" href="https://assets.statping.com/font/all.css">
{{else}}
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
{{if USING_ASSETS}}
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="font/all.css">
{{else}}
<% _.each(htmlWebpackPlugin.tags.headTags, function(headTag) { %>
<%= headTag %> <% }) %>
{{end}}
{{end}}
</head>
<body>
<noscript>

226
frontend/src/API.js Normal file
View File

@ -0,0 +1,226 @@
import axios from 'axios'
const qs = require('querystring')
const tokenKey = "statping_user";
class Api {
constructor() {
}
async core() {
return axios.get('/api').then(response => (response.data))
}
async core_save(obj) {
return axios.post('/api/core', obj).then(response => (response.data))
}
async setup_save(data) {
return axios.post('/api/setup', qs.stringify(data)).then(response => (response.data))
}
async services() {
return axios.get('/api/services').then(response => (response.data))
}
async service(id) {
return axios.get('/api/services/' + id).then(response => (response.data))
}
async service_create(data) {
return axios.post('/api/services', data).then(response => (response.data))
}
async service_update(data) {
return axios.post('/api/services/' + data.id, data).then(response => (response.data))
}
async service_hits(id, start, end, group) {
return axios.get('/api/services/' + id + '/data?start=' + start + '&end=' + end + '&group=' + group).then(response => (response.data))
}
async service_heatmap(id, start, end, group) {
return axios.get('/api/services/' + id + '/heatmap').then(response => (response.data))
}
async service_failures(id, start, end, limit = 999, offset = 0) {
return axios.get('/api/services/' + id + '/failures?start=' + start + '&end=' + end + '&limit=' + limit).then(response => (response.data))
}
async service_delete(id) {
return axios.delete('/api/services/' + id).then(response => (response.data))
}
async services_reorder(data) {
return axios.post('/api/reorder/services', data).then(response => (response.data))
}
async groups() {
return axios.get('/api/groups').then(response => (response.data))
}
async groups_reorder(data) {
return axios.post('/api/reorder/groups', data).then(response => (response.data))
}
async group_delete(id) {
return axios.delete('/api/groups/' + id).then(response => (response.data))
}
async group_create(data) {
return axios.post('/api/groups', data).then(response => (response.data))
}
async group_update(data) {
return axios.post('/api/groups/' + data.id, data).then(response => (response.data))
}
async users() {
return axios.get('/api/users').then(response => (response.data))
}
async user_create(data) {
return axios.post('/api/users', data).then(response => (response.data))
}
async user_update(data) {
return axios.post('/api/users/' + data.id, data).then(response => (response.data))
}
async user_delete(id) {
return axios.delete('/api/users/' + id).then(response => (response.data))
}
async messages() {
return axios.get('/api/messages').then(response => (response.data))
}
async message_create(data) {
return axios.post('/api/messages', data).then(response => (response.data))
}
async message_update(data) {
return axios.post('/api/messages/' + data.id, data).then(response => (response.data))
}
async message_delete(id) {
return axios.delete('/api/messages/' + id).then(response => (response.data))
}
async group(id) {
return axios.get('/api/groups/' + id).then(response => (response.data))
}
async notifiers() {
return axios.get('/api/notifiers').then(response => (response.data))
}
async notifier_save(data) {
return axios.post('/api/notifier/' + data.method, data).then(response => (response.data))
}
async notifier_test(data) {
return axios.post('/api/notifier/' + data.method + '/test', data).then(response => (response.data))
}
async integrations() {
return axios.get('/api/integrations').then(response => (response.data))
}
async integration(name) {
return axios.get('/api/integrations/' + name).then(response => (response.data))
}
async integration_save(data) {
return axios.post('/api/integrations/' + data.name, data).then(response => (response.data))
}
async renewApiKeys() {
return axios.get('/api/renew').then(response => (response.data))
}
async cache() {
return axios.get('/api/cache').then(response => (response.data))
}
async clearCache() {
return axios.get('/api/clear_cache').then(response => (response.data))
}
async logs() {
return axios.get('/api/logs').then(response => (response.data))
}
async logs_last() {
return axios.get('/api/logs/last').then(response => (response.data))
}
async theme() {
return axios.get('/api/theme').then(response => (response.data))
}
async theme_generate(create = true) {
if (create) {
return axios.get('/api/theme/create').then(response => (response.data))
} else {
return axios.delete('/api/theme').then(response => (response.data))
}
}
async theme_save(data) {
return axios.post('/api/theme', data).then(response => (response.data))
}
async login(username, password) {
const f = {username: username, password: password}
return axios.post('/api/login', qs.stringify(f))
.then(response => (response.data))
}
async logout() {
await axios.get('/api/logout').then(response => (response.data))
return localStorage.removeItem(tokenKey)
}
saveToken(username, token) {
const user = {username: username, token: token}
localStorage.setItem(tokenKey, JSON.stringify(user));
return user
}
async scss_base() {
return await axios({
url: '/scss/base.scss',
method: 'GET',
responseType: 'blob'
}).then((response) => {
const reader = new window.FileReader();
return reader.readAsText(response.data)
})
}
token() {
const tk = localStorage.getItem(tokenKey)
if (!tk) {
return {};
}
return JSON.parse(tk);
}
authToken() {
let user = JSON.parse(localStorage.getItem(tokenKey));
if (user && user.token) {
return {'Authorization': 'Bearer ' + user.token};
} else {
return {};
}
}
async allActions(...all) {
await axios.all([all])
}
}
const api = new Api()
export default api

View File

@ -6,7 +6,7 @@
</template>
<script>
import Api from './components/API';
import Api from './API';
import Footer from "./components/Index/Footer";
export default {

View File

@ -1,226 +0,0 @@
import axios from 'axios'
const qs = require('querystring')
const tokenKey = "statping_user";
class Api {
constructor() {
}
async core () {
return axios.get('/api').then(response => (response.data))
}
async core_save (obj) {
return axios.post('/api/core', obj).then(response => (response.data))
}
async setup_save (data) {
return axios.post('/api/setup', qs.stringify(data)).then(response => (response.data))
}
async services () {
return axios.get('/api/services').then(response => (response.data))
}
async service (id) {
return axios.get('/api/services/'+id).then(response => (response.data))
}
async service_create (data) {
return axios.post('/api/services', data).then(response => (response.data))
}
async service_update (data) {
return axios.post('/api/services/'+data.id, data).then(response => (response.data))
}
async service_hits (id, start, end, group) {
return axios.get('/api/services/'+id+'/data?start=' + start + '&end=' + end + '&group=' + group).then(response => (response.data))
}
async service_heatmap (id, start, end, group) {
return axios.get('/api/services/'+id+'/heatmap?start=' + start + '&end=' + end + '&group=' + group).then(response => (response.data))
}
async service_failures (id, start, end, limit=999, offset=0) {
return axios.get('/api/services/'+id+'/failures?start=' + start + '&end=' + end + '&limit=' + limit).then(response => (response.data))
}
async service_delete (id) {
return axios.delete('/api/services/'+id).then(response => (response.data))
}
async services_reorder (data) {
return axios.post('/api/reorder/services', data).then(response => (response.data))
}
async groups () {
return axios.get('/api/groups').then(response => (response.data))
}
async groups_reorder (data) {
return axios.post('/api/reorder/groups', data).then(response => (response.data))
}
async group_delete (id) {
return axios.delete('/api/groups/'+id).then(response => (response.data))
}
async group_create (data) {
return axios.post('/api/groups', data).then(response => (response.data))
}
async group_update (data) {
return axios.post('/api/groups/'+data.id, data).then(response => (response.data))
}
async users () {
return axios.get('/api/users').then(response => (response.data))
}
async user_create (data) {
return axios.post('/api/users', data).then(response => (response.data))
}
async user_update (data) {
return axios.post('/api/users/'+data.id, data).then(response => (response.data))
}
async user_delete (id) {
return axios.delete('/api/users/'+id).then(response => (response.data))
}
async messages () {
return axios.get('/api/messages').then(response => (response.data))
}
async message_create (data) {
return axios.post('/api/messages', data).then(response => (response.data))
}
async message_update (data) {
return axios.post('/api/messages/'+data.id, data).then(response => (response.data))
}
async message_delete (id) {
return axios.delete('/api/messages/'+id).then(response => (response.data))
}
async group (id) {
return axios.get('/api/groups/'+id).then(response => (response.data))
}
async notifiers () {
return axios.get('/api/notifiers').then(response => (response.data))
}
async notifier_save (data) {
return axios.post('/api/notifier/'+data.method, data).then(response => (response.data))
}
async notifier_test (data) {
return axios.post('/api/notifier/'+data.method+'/test', data).then(response => (response.data))
}
async integrations () {
return axios.get('/api/integrations').then(response => (response.data))
}
async integration (name) {
return axios.get('/api/integrations/'+name).then(response => (response.data))
}
async integration_save (data) {
return axios.post('/api/integrations/'+data.name, data).then(response => (response.data))
}
async renewApiKeys () {
return axios.get('/api/renew').then(response => (response.data))
}
async cache () {
return axios.get('/api/cache').then(response => (response.data))
}
async clearCache () {
return axios.get('/api/clear_cache').then(response => (response.data))
}
async logs () {
return axios.get('/api/logs').then(response => (response.data))
}
async logs_last () {
return axios.get('/api/logs/last').then(response => (response.data))
}
async theme () {
return axios.get('/api/theme').then(response => (response.data))
}
async theme_generate (create=true) {
if (create) {
return axios.get('/api/theme/create').then(response => (response.data))
} else {
return axios.delete('/api/theme').then(response => (response.data))
}
}
async theme_save (data) {
return axios.post('/api/theme', data).then(response => (response.data))
}
async login (username, password) {
const f = {username: username, password: password}
return axios.post('/api/login', qs.stringify(f))
.then(response => (response.data))
}
async logout () {
await axios.get('/api/logout').then(response => (response.data))
return localStorage.removeItem(tokenKey)
}
saveToken (username, token) {
const user = {username: username, token: token}
localStorage.setItem(tokenKey, JSON.stringify(user));
return user
}
async scss_base () {
return await axios({
url: '/scss/base.scss',
method: 'GET',
responseType: 'blob'
}).then((response) => {
const reader = new window.FileReader();
return reader.readAsText(response.data)
})
}
token () {
const tk = localStorage.getItem(tokenKey)
if (!tk) {
return {};
}
return JSON.parse(tk);
}
authToken () {
let user = JSON.parse(localStorage.getItem(tokenKey));
if (user && user.token) {
return { 'Authorization': 'Bearer ' + user.token };
} else {
return {};
}
}
async allActions (...all) {
await axios.all([all])
}
}
const api = new Api()
export default api

View File

@ -0,0 +1,56 @@
<template>
<div>
<h3>Cache</h3>
<div v-if="!cache && cache.length !== 0" class="alert alert-danger">
There are no cached files
</div>
<table class="table">
<thead>
<tr>
<th scope="col">URL</th>
<th scope="col">Size</th>
<th scope="col">Expiration</th>
</tr>
</thead>
<tbody>
<tr v-for="(cache, index) in cache">
<td>{{cache.url}}</td>
<td>{{cache.size}}</td>
<td>{{expireTime(cache.expiration)}}</td>
</tr>
</tbody>
</table>
<button @click.prevent="clearCache" class="btn btn-danger btn-block">Clear Cache</button>
</div>
</template>
<script>
import Api from "../../API";
export default {
name: 'Cache',
data() {
return {
cache: [],
}
},
async mounted() {
this.cache = await Api.cache()
},
methods: {
expireTime(ex) {
return this.toLocal(ex)
},
async clearCache() {
await Api.clearCache()
this.cache = []
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -23,33 +23,24 @@
</template>
<script>
import ServiceInfo from "./ServiceInfo";
import ServiceInfo from "../Service/ServiceInfo";
export default {
name: 'DashboardIndex',
components: {
ServiceInfo
},
data () {
return {
name: 'DashboardIndex',
components: {
ServiceInfo
},
methods: {
failuresLast24Hours() {
let total = 0;
this.$store.getters.services.map((s) => {
total += s.failures_24_hours
})
return total
},
}
},
computed: {
},
async created() {
},
methods: {
failuresLast24Hours() {
let total = 0;
this.$store.getters.services.map((s) => { total += s.failures_24_hours })
return total
},
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -37,7 +37,7 @@
</template>
<script>
import Api from "../API"
import Api from "../../API"
import FormMessage from "../../forms/Message";
export default {

View File

@ -99,97 +99,97 @@
<script>
import FormGroup from "../../forms/Group";
import Api from "../../components/API";
import Api from "../../API";
import ToggleSwitch from "../../forms/ToggleSwitch";
import draggable from 'vuedraggable'
export default {
name: 'DashboardServices',
components: {
ToggleSwitch,
FormGroup,
draggable
},
data () {
return {
edit: false,
group: {}
}
},
computed: {
servicesList: {
get() {
return this.$store.state.servicesInOrder
name: 'DashboardServices',
components: {
ToggleSwitch,
FormGroup,
draggable
},
async set(value) {
let data = [];
value.forEach((s, k) => {
data.push({service: s.id, order: k+1})
});
await Api.services_reorder(data)
const services = await Api.services()
this.$store.commit('setServices', services)
}
},
groupsList: {
get() {
return this.$store.state.groupsInOrder
data() {
return {
edit: false,
group: {}
}
},
async set(value) {
let data = [];
value.forEach((s, k) => {
data.push({group: s.id, order: k+1})
});
await Api.groups_reorder(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
}
}
},
beforeMount() {
computed: {
servicesList: {
get() {
return this.$store.state.servicesInOrder
},
async set(value) {
let data = [];
value.forEach((s, k) => {
data.push({service: s.id, order: k + 1})
});
await Api.services_reorder(data)
const services = await Api.services()
this.$store.commit('setServices', services)
}
},
groupsList: {
get() {
return this.$store.state.groupsInOrder
},
async set(value) {
let data = [];
value.forEach((s, k) => {
data.push({group: s.id, order: k + 1})
});
await Api.groups_reorder(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
}
}
},
beforeMount() {
},
methods: {
editChange(v) {
this.group = {}
this.edit = v
},
editGroup(g, mode) {
this.group = g
this.edit = !mode
},
reordered_services() {
},
methods: {
editChange(v) {
this.group = {}
this.edit = v
},
editGroup(g, mode) {
this.group = g
this.edit = !mode
},
reordered_services() {
},
saveUpdatedOrder: function (e) {
window.console.log("saving...");
window.console.log(this.myViews.array()); // this.myViews.array is not a function
},
serviceGroup(s) {
let group = this.$store.getters.groupById(s.group_id)
if (group) {
return group.name
},
saveUpdatedOrder: function (e) {
window.console.log("saving...");
window.console.log(this.myViews.array()); // this.myViews.array is not a function
},
serviceGroup(s) {
let group = this.$store.getters.groupById(s.group_id)
if (group) {
return group.name
}
return ""
},
async deleteGroup(g) {
let c = confirm(`Are you sure you want to delete '${g.name}'?`)
if (c) {
await Api.group_delete(g.id)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
}
},
async deleteService(s) {
let c = confirm(`Are you sure you want to delete '${s.name}'?`)
if (c) {
await Api.service_delete(s.id)
const services = await Api.services()
this.$store.commit('setServices', services)
}
}
}
return ""
},
async deleteGroup(g) {
let c = confirm(`Are you sure you want to delete '${g.name}'?`)
if (c) {
await Api.group_delete(g.id)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
}
},
async deleteService(s) {
let c = confirm(`Are you sure you want to delete '${s.name}'?`)
if (c) {
await Api.service_delete(s.id)
const services = await Api.services()
this.$store.commit('setServices', services)
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -32,7 +32,7 @@
</template>
<script>
import Api from "../API"
import Api from "../../API"
import FormUser from "../../forms/User";
export default {

View File

@ -1,16 +1,16 @@
<template>
<div class="col-12">
<div class="card">
<div class="card-body">
<FormService :in_service="service"/>
</div>
</div>
<div class="card">
<div class="card-body">
<FormService :in_service="service"/>
</div>
</div>
</div>
</template>
<script>
import FormGroup from "../../forms/Group";
import Api from "../../components/API";
import Api from "../../API";
import ToggleSwitch from "../../forms/ToggleSwitch";
import draggable from 'vuedraggable'
import FormService from "../../forms/Service";

View File

@ -44,7 +44,7 @@
</template>
<script>
import Api from "../API";
import Api from "../../API";
// require component
import { codemirror } from 'vue-codemirror'
@ -136,7 +136,6 @@
this.error = null
}
this.pending = false
window.console.log(resp)
await this.fetchTheme()
},
changeTab (v) {

View File

@ -41,7 +41,7 @@
</template>
<script>
import Api from "../API"
import Api from "../../API"
export default {
name: 'TopNav',

View File

@ -8,20 +8,7 @@
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online}">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
</h4>
<div class="row stats_area mt-5">
<div class="col-4">
<span class="lg_number">{{service.avg_response}}ms</span>
Average Response
</div>
<div class="col-4">
<span class="lg_number">{{service.online_24_hours}}%</span>
Uptime last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">{{service.online_7_days}}%</span>
Uptime last 7 Days
</div>
</div>
<ServiceTopStats :service="service"/>
</div>
</div>
@ -48,29 +35,26 @@
<script>
import ServiceChart from "./ServiceChart";
import ServiceTopStats from "@/components/Service/ServiceTopStats";
export default {
name: 'ServiceBlock',
components: {ServiceChart},
props: {
service: {
type: Object,
required: true
name: 'ServiceBlock',
components: {ServiceTopStats, ServiceChart},
props: {
service: {
type: Object,
required: true
},
},
},
methods: {
methods: {
smallText(s) {
if (s.online) {
return `Online, last checked ${this.ago(s.last_success)}`
return `Online, last checked ${this.ago(this.parseTime(s.last_success))}`
} else {
return `Offline, last error: ${s.last_failure.issue} ${this.ago(s.last_failure.created_at)}`
return `Offline, last error: ${s.last_failure.issue} ${this.ago(this.parseTime(s.last_failure.created_at))}`
}
},
ago(t1) {
const tm = this.parseTime(t1)
return this.duration(this.$moment().utc(), tm)
}
}
}
}
}
</script>

View File

@ -3,7 +3,7 @@
</template>
<script>
import Api from "../../components/API"
import Api from "../../API"
const axisOptions = {
labels: {
@ -30,108 +30,108 @@
};
export default {
name: 'ServiceChart',
props: {
service: {
type: Object,
required: true
}
},
async created() {
await this.chartHits()
},
data () {
return {
ready: false,
data: [],
chartOptions: {
chart: {
height: 210,
width: "100%",
type: "area",
animations: {
enabled: true,
initialAnimation: {
enabled: true
}
},
selection: {
enabled: false
},
zoom: {
enabled: false
},
toolbar: {
show: false
},
},
grid: {
show: false,
padding: {
top: 0,
right: 0,
bottom: 0,
left: -10,
name: 'ServiceChart',
props: {
service: {
type: Object,
required: true
}
},
xaxis: {
type: "datetime",
...axisOptions
},
yaxis: {
...axisOptions
},
tooltip: {
enabled: false,
marker: {
show: false,
},
x: {
show: false,
}
},
legend: {
show: false,
},
dataLabels: {
enabled: false
},
floating: true,
axisTicks: {
show: false
},
axisBorder: {
show: false
},
fill: {
colors: [this.service.online ? "#48d338" : "#dd3545"],
opacity: 1,
type: 'solid'
},
stroke: {
show: false,
curve: 'smooth',
lineCap: 'butt',
colors: [this.service.online ? "#3aa82d" : "#dd3545"],
},
async created() {
await this.chartHits()
},
data() {
return {
ready: false,
data: [],
chartOptions: {
chart: {
height: 210,
width: "100%",
type: "area",
animations: {
enabled: true,
initialAnimation: {
enabled: true
}
},
selection: {
enabled: false
},
zoom: {
enabled: false
},
toolbar: {
show: false
},
},
grid: {
show: false,
padding: {
top: 0,
right: 0,
bottom: 0,
left: -10,
}
},
xaxis: {
type: "datetime",
...axisOptions
},
yaxis: {
...axisOptions
},
tooltip: {
enabled: false,
marker: {
show: false,
},
x: {
show: false,
}
},
legend: {
show: false,
},
dataLabels: {
enabled: false
},
floating: true,
axisTicks: {
show: false
},
axisBorder: {
show: false
},
fill: {
colors: [this.service.online ? "#48d338" : "#dd3545"],
opacity: 1,
type: 'solid'
},
stroke: {
show: false,
curve: 'smooth',
lineCap: 'butt',
colors: [this.service.online ? "#3aa82d" : "#dd3545"],
}
},
series: [{
data: []
}]
}
},
series: [{
data: []
}]
}
},
},
methods: {
async chartHits() {
const start = this.ago((3600 * 24) * 7)
this.data = await Api.service_hits(this.service.id, start, this.now(), "hour")
const start = this.nowSubtract((3600 * 24) * 7)
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), "hour")
this.series = [{
name: this.service.name,
...this.data
}]
this.ready = true
}
},
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -33,7 +33,7 @@
<script>
import ServiceChart from "./ServiceChart";
import Api from "../API";
import Api from "../../API";
export default {
name: 'ServiceFailures',

View File

@ -0,0 +1,84 @@
<template>
<apexchart v-if="ready" width="100%" height="300" type="heatmap" :options="chartOptions" :series="series"></apexchart>
</template>
<script>
import Api from "../../API"
export default {
name: 'ServiceHeatmap',
props: {
service: {
type: Object,
required: true
}
},
async created() {
await this.chartHeatmap()
},
data() {
return {
ready: false,
data: [],
chartOptions: {
chart: {
height: "100%",
width: "100%",
type: 'heatmap',
toolbar: {
show: false
}
},
dataLabels: {
enabled: false,
},
enableShades: true,
shadeIntensity: 0.5,
colors: ["#d53a3b"],
series: [{data: [{}]}],
yaxis: {
labels: {
formatter: (value) => {
return value
},
},
},
tooltip: {
enabled: true,
x: {
show: false,
},
y: {
formatter: function(val, opts) { return val+" Failures" },
title: {
formatter: (seriesName) => seriesName,
},
},
}
},
series: [{
data: []
}]
}
},
methods: {
async chartHeatmap() {
const start = this.nowSubtract((3600 * 24) * 7)
const data = await Api.service_heatmap(this.service.id, this.toUnix(start), this.toUnix(new Date()), "hour")
let dataArr = []
data.forEach(function(d) {
let date = new Date(d.date);
dataArr.push({name: date.toLocaleString('en-us', { month: 'long' }), data: d.data});
});
this.series = dataArr
this.ready = true
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -16,15 +16,16 @@
</div>
</div>
<span v-for="(failure, index) in failures" v-bind:key="index" class="alert alert-light">
Failed {{duration(current(), failure.created_at)}}<br>
Failed {{ago(parseTime(failure.created_at))}}<br>
{{failure.issue}}
</span>
</div>
</template>
<script>
import ServiceSparkLine from "./ServiceSparkLine";
import Api from "../API";
import Api from "../../API";
export default {
name: 'ServiceInfo',
@ -37,7 +38,7 @@
required: true
}
},
data () {
data() {
return {
set1: [],
set2: [],
@ -47,7 +48,7 @@
failures: null
}
},
async mounted () {
async mounted() {
this.set1 = await this.getHits(24 * 2, "hour")
this.set1_name = this.calc(this.set1)
this.set2 = await this.getHits(24 * 7, "hour")
@ -55,19 +56,19 @@
this.loaded = true
},
methods: {
async getHits (hours, group) {
const start = this.ago(3600 * hours)
async getHits(hours, group) {
const start = this.nowSubtract(3600 * hours)
if (!this.service.online) {
this.failures = await Api.service_failures(this.service.id, this.now()-360, this.now(), 5)
return [ { name: "None", data: [] } ]
this.failures = await Api.service_failures(this.service.id, this.toUnix(start), this.toUnix(this.now()), 5)
return [{name: "None", data: []}]
}
const data = await Api.service_hits(this.service.id, start, this.now(), group)
const data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(this.now()), group)
if (!data) {
return [ { name: "None", data: [] } ]
return [{name: "None", data: []}]
}
return [ { name: "Latency", data: data.data } ]
return [{name: "Latency", data: data.data}]
},
calc (s) {
calc(s) {
let data = s[0].data
if (data.length > 1) {
let total = 0
@ -75,7 +76,7 @@
total += f.y
});
total = total / data.length
return total.toFixed(0) + "ms Average"
return total.toFixed(0) + "ms"
} else {
return "Offline"
}

View File

@ -0,0 +1,32 @@
<template>
<div class="row stats_area mt-5 mb-4">
<div class="col-4">
<span class="lg_number">{{service.avg_response}}ms</span>
Average Response
</div>
<div class="col-4">
<span class="lg_number">{{service.online_24_hours}}%</span>
Uptime last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">{{service.online_7_days}}%</span>
Uptime last 7 Days
</div>
</div>
</template>
<script>
export default {
name: 'ServiceTopStats',
props: {
service: {
type: Object,
required: true
},
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -1,14 +0,0 @@
class Time {
now () {
return new Date();
}
utc () {
return new Date().getUTCDate();
}
utcToLocal (utc) {
let u = new Date().setUTCDate(utc)
return u.toLocaleString()
}
}
const time = new Time()
export default time

View File

@ -1,5 +1,5 @@
<template>
<form @submit="saveCheckin">
<form @submit.prevent="saveCheckin">
<div class="form-group row">
<div class="col-md-3">
<label for="checkin_interval" class="col-form-label">Checkin Name</label>
@ -14,46 +14,45 @@
<input v-model="checkin.grace" type="number" name="grace" class="form-control" id="grace_period" placeholder="10">
</div>
<div class="col-3">
<button @click="saveCheckin" type="submit" id="submit" class="btn btn-success d-block" style="margin-top: 14px;">Save Checkin</button>
<button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-success d-block" style="margin-top: 14px;">Save Checkin</button>
</div>
</div>
</form>
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'Checkin',
props: {
service: {
type: Object,
required: true
}
},
data () {
return {
checkin: {
name: "",
interval: 60,
grace: 60,
service: this.service.id
}
}
},
mounted() {
name: 'Checkin',
props: {
service: {
type: Object,
required: true
}
},
data() {
return {
checkin: {
name: "",
interval: 60,
grace: 60,
service: this.service.id
}
}
},
mounted() {
},
methods: {
async saveCheckin(e) {
e.preventDefault();
const data = {name: this.group.name, public: this.group.public}
await Api.group_create(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
},
},
methods: {
async saveCheckin() {
const data = {name: this.group.name, public: this.group.public}
await Api.group_create(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
},
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -1,5 +1,5 @@
<template>
<form @submit="saveSettings">
<form @submit.prevent="saveSettings">
<div class="form-group">
<label>Project Name</label>
<input v-model="core.name" type="text" class="form-control" placeholder="Great Uptime">
@ -68,20 +68,9 @@
</select>
</div>
<div class="form-group">
<div class="col-12">
<label class="d-none d-sm-block">Send Updates only</label>
<span class="switch">
<input v-model="core.update_notify" @change="core.update_notify = !!core.update_notify" type="checkbox" class="switch" id="switch-update_notify" v-bind:checked="core.update_notify">
<label for="switch-update_notify" class="mt-2 mt-sm-0"></label>
<small class="form-text text-muted">Enabling this will send only notifications when the status of a services changes.</small>
</span>
</div>
</div>
<button @click.prevent="saveSettings" type="submit" class="btn btn-primary btn-block">Save Settings</button>
<button @click="saveSettings" type="submit" class="btn btn-primary btn-block">Save Settings</button>
<div class="form-group row mt-3">
<div class="form-group row mt-5">
<label class="col-sm-3 col-form-label">API Key</label>
<div class="col-sm-9">
<input v-model="core.api_key" @focus="$event.target.select()" type="text" class="form-control select-input" readonly>
@ -102,43 +91,44 @@
</template>
<script>
import Api from '../components/API'
import Api from '../API'
export default {
name: 'CoreSettings',
data () {
return {
core: this.$store.getters.core,
}
},
async mounted () {
name: 'CoreSettings',
data() {
return {
core: this.$store.getters.core,
}
},
async mounted() {
},
methods: {
async saveSettings (e) {
e.preventDefault()
const c = this.core
const coreForm = {name: c.name, description: c.description, domain: c.domain,
timezone: c.timezone, using_cdn: c.using_cdn, footer: c.footer, update_notify: c.update_notify}
await Api.core_save(coreForm)
const core = await Api.core()
this.$store.commit('setCore', core)
this.core = core
},
async renewApiKeys () {
let r = confirm("Are you sure you want to reset the API keys?");
if (r === true) {
await Api.renewApiKeys()
const core = await Api.core()
this.$store.commit('setCore', core)
this.core = core
}
},
selectAll() {
this.$refs.input.select();
},
methods: {
async saveSettings() {
const c = this.core
const coreForm = {
name: c.name, description: c.description, domain: c.domain,
timezone: c.timezone, using_cdn: c.using_cdn, footer: c.footer, update_notify: c.update_notify
}
await Api.core_save(coreForm)
const core = await Api.core()
this.$store.commit('setCore', core)
this.core = core
},
async renewApiKeys() {
let r = confirm("Are you sure you want to reset the API keys?");
if (r === true) {
await Api.renewApiKeys()
const core = await Api.core()
this.$store.commit('setCore', core)
this.core = core
}
},
selectAll() {
this.$refs.input.select();
}
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -38,65 +38,65 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'FormGroup',
props: {
in_group: {
type: Object
},
edit: {
type: Function
}
},
data () {
return {
loading: false,
group: {
name: "",
public: true
name: 'FormGroup',
props: {
in_group: {
type: Object
},
edit: {
type: Function
}
},
data() {
return {
loading: false,
group: {
name: "",
public: true
}
}
},
watch: {
in_group() {
this.group = this.in_group
}
},
methods: {
removeEdit() {
this.group = {}
this.edit(false)
},
async saveGroup(e) {
e.preventDefault();
this.loading = true
if (this.in_group) {
await this.updateGroup()
} else {
await this.createGroup()
}
this.loading = false
},
async createGroup() {
const g = this.group
const data = {name: g.name, public: g.public}
await Api.group_create(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
this.group = {}
},
async updateGroup() {
const g = this.group
const data = {id: g.id, name: g.name, public: g.public}
await Api.group_update(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
this.edit(false)
}
}
}
},
watch: {
in_group() {
this.group = this.in_group
}
},
methods: {
removeEdit() {
this.group = {}
this.edit(false)
},
async saveGroup(e) {
e.preventDefault();
this.loading = true
if (this.in_group) {
await this.updateGroup()
} else {
await this.createGroup()
}
this.loading = false
},
async createGroup() {
const g = this.group
const data = {name: g.name, public: g.public}
await Api.group_create(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
this.group = {}
},
async updateGroup() {
const g = this.group
const data = {id: g.id, name: g.name, public: g.public}
await Api.group_update(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
this.edit(false)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -59,45 +59,50 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'FormIntegration',
props: {
integration: {
type: Object
}
},
data () {
return {
out: {},
services: []
}
},
watch: {
},
methods: {
async addService(s) {
const data = {name: s.name, type: s.type, domain: s.domain, port: s.port, check_interval: s.check_interval, timeout: s.timeout}
const out = await Api.service_create(data)
const services = await Api.services()
this.$store.commit('setServices', services)
s.added = true
name: 'FormIntegration',
props: {
integration: {
type: Object
}
},
async updateIntegration() {
const i = this.integration
const data = {name: i.name, enabled: i.enabled, fields: i.fields}
this.out = data
const out = await Api.integration_save(data)
if (out != null) {
this.services = out
}
const integrations = await Api.integrations()
this.$store.commit('setIntegrations', integrations)
}
data() {
return {
out: {},
services: []
}
},
watch: {},
methods: {
async addService(s) {
const data = {
name: s.name,
type: s.type,
domain: s.domain,
port: s.port,
check_interval: s.check_interval,
timeout: s.timeout
}
const out = await Api.service_create(data)
const services = await Api.services()
this.$store.commit('setServices', services)
s.added = true
},
async updateIntegration() {
const i = this.integration
const data = {name: i.name, enabled: i.enabled, fields: i.fields}
this.out = data
const out = await Api.integration_save(data)
if (out != null) {
this.services = out
}
const integrations = await Api.integrations()
this.$store.commit('setIntegrations', integrations)
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -26,43 +26,43 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'FormLogin',
data () {
return {
username: "",
password: "",
auth: {},
loading: false,
error: false,
disabled: true
}
},
methods: {
checkForm() {
if (!this.username || !this.password) {
this.disabled = true
} else {
this.disabled = false
name: 'FormLogin',
data() {
return {
username: "",
password: "",
auth: {},
loading: false,
error: false,
disabled: true
}
},
async login () {
this.loading = true
this.error = false
const auth = await Api.login(this.username, this.password)
if (auth.error) {
this.error = true
} else if (auth.token) {
this.auth = Api.saveToken(this.username, auth.token)
await this.$store.dispatch('loadAdmin')
this.$router.push('/dashboard')
methods: {
checkForm() {
if (!this.username || !this.password) {
this.disabled = true
} else {
this.disabled = false
}
},
async login() {
this.loading = true
this.error = false
const auth = await Api.login(this.username, this.password)
if (auth.error) {
this.error = true
} else if (auth.token) {
this.auth = Api.saveToken(this.username, auth.token)
await this.$store.dispatch('loadAdmin')
this.$router.push('/dashboard')
}
this.loading = false
}
this.loading = false
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -24,10 +24,10 @@
<div class="form-group row">
<label class="col-sm-4 col-form-label">Message Date Range</label>
<div class="col-sm-4">
<flatPickr v-model="message.start_on" :config="config" type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="0001-01-01T00:00:00Z" required />
<flatPickr v-model="message.start_on" @on-change="startChange" :config="config" type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="0001-01-01T00:00:00Z" required />
</div>
<div class="col-sm-4">
<flatPickr v-model="message.end_on" :config="config" type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="0001-01-01T00:00:00Z" required />
<flatPickr v-model="message.end_on" @on-change="endChange" :config="config" type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="0001-01-01T00:00:00Z" required />
</div>
</div>
@ -90,7 +90,7 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
import flatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
@ -135,6 +135,12 @@
}
},
methods: {
startChange(e) {
window.console.log(e)
},
endChange(e) {
window.console.log(e)
},
removeEdit() {
this.message = {}
this.edit(false)

View File

@ -1,5 +1,5 @@
<template>
<form @submit="saveNotifier">
<form @submit.prevent="saveNotifier">
<div v-if="error" class="alert alert-danger col-12" role="alert">{{error}}</div>
@ -38,13 +38,13 @@
</div>
<div class="col-12 col-sm-4 mb-2 mb-sm-0 mt-2 mt-sm-0">
<button @click="saveNotifier" type="submit" class="btn btn-block text-capitalize" :class="{'btn-primary': !saved, 'btn-success': saved}">
<button @click.prevent="saveNotifier" type="submit" class="btn btn-block text-capitalize" :class="{'btn-primary': !saved, 'btn-success': saved}">
<i class="fa fa-check-circle"></i> {{loading ? "Loading..." : saved ? "Saved" : "Save"}}
</button>
</div>
<div class="col-12 col-sm-12 mt-3">
<button @click="testNotifier" class="btn btn-secondary btn-block text-capitalize col-12 float-right"><i class="fa fa-vial"></i>
<button @click.prevent="testNotifier" class="btn btn-secondary btn-block text-capitalize col-12 float-right"><i class="fa fa-vial"></i>
{{loading ? "Loading..." : "Test Notifier"}}</button>
</div>
@ -57,67 +57,65 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'Notifier',
props: {
notifier: {
type: Object,
required: true
}
},
data () {
return {
loading: false,
error: null,
saved: false,
ok: false,
}
},
mounted() {
name: 'Notifier',
props: {
notifier: {
type: Object,
required: true
}
},
data() {
return {
loading: false,
error: null,
saved: false,
ok: false,
}
},
mounted() {
},
methods: {
async saveNotifier(e) {
e.preventDefault();
this.loading = true
let form = {}
this.notifier.form.forEach((f) => {
form[f.field] = this.notifier[f.field]
});
form.enabled = this.notifier.enabled
form.limits = parseInt(this.notifier.limits)
form.method = this.notifier.method
await Api.notifier_save(form)
const notifiers = await Api.notifiers()
this.$store.commit('setNotifiers', notifiers)
this.saved = true
this.loading = false
setTimeout(() => {
this.saved = false
}, 2000)
},
async testNotifier(e) {
e.preventDefault();
this.ok = false
this.loading = true
let form = {}
this.notifier.form.forEach((f) => {
form[f.field] = this.notifier[f.field]
});
form.enabled = this.notifier.enabled
form.limits = parseInt(this.notifier.limits)
form.method = this.notifier.method
const tested = await Api.notifier_test(form)
if (tested === 'ok') {
this.ok = true
} else {
this.error = tested
}
this.loading = false
},
}
methods: {
async saveNotifier() {
this.loading = true
let form = {}
this.notifier.form.forEach((f) => {
form[f.field] = this.notifier[f.field]
});
form.enabled = this.notifier.enabled
form.limits = parseInt(this.notifier.limits)
form.method = this.notifier.method
await Api.notifier_save(form)
const notifiers = await Api.notifiers()
await this.$store.commit('setNotifiers', notifiers)
this.saved = true
this.loading = false
setTimeout(() => {
this.saved = false
}, 2000)
},
async testNotifier() {
this.ok = false
this.loading = true
let form = {}
this.notifier.form.forEach((f) => {
form[f.field] = this.notifier[f.field]
});
form.enabled = this.notifier.enabled
form.limits = parseInt(this.notifier.limits)
form.method = this.notifier.method
const tested = await Api.notifier_test(form)
if (tested === 'ok') {
this.ok = true
} else {
this.error = tested
}
this.loading = false
},
}
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<form @submit="saveService">
<form @submit.prevent="saveService">
<h4 class="mb-5 text-muted">Basic Information</h4>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Service Name</label>
@ -12,10 +12,10 @@
<label for="service_type" class="col-sm-4 col-form-label">Service Type</label>
<div class="col-sm-8">
<select v-model="service.type" class="form-control" id="service_type" >
<option value="http" >HTTP Service</option>
<option value="tcp" >TCP Service</option>
<option value="udp" >UDP Service</option>
<option value="icmp" >ICMP Ping</option>
<option value="http">HTTP Service</option>
<option value="tcp">TCP Service</option>
<option value="udp">UDP Service</option>
<option value="icmp">ICMP Ping</option>
</select>
<small class="form-text text-muted">Use HTTP if you are checking a website or use TCP if you are checking a server</small>
</div>
@ -111,15 +111,8 @@
<small class="form-text text-muted">Use text for the service URL rather than the service number.</small>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">List Order</label>
<div class="col-sm-8">
<input v-model="service.order" type="number" name="order" class="form-control" min="0" id="order">
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
</div>
</div>
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Verify SSL</label>
<label class="col-sm-4 col-form-label">Verify SSL</label>
<div class="col-8 mt-1">
<span @click="service.verify_ssl = !!service.verify_ssl" class="switch float-left">
<input v-model="service.verify_ssl" type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" v-bind:checked="service.verify_ssl">
@ -128,7 +121,7 @@
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Notifications</label>
<label class="col-sm-4 col-form-label">Notifications</label>
<div class="col-8 mt-1">
<span @click="service.allow_notifications = !!service.allow_notifications" class="switch float-left">
<input v-model="service.allow_notifications" type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" v-bind:checked="service.allow_notifications">
@ -136,8 +129,17 @@
</span>
</div>
</div>
<div v-if="service.allow_notifications" class="form-group row">
<label class="col-sm-4 col-form-label">Notify All Changes</label>
<div class="col-8 mt-1">
<span @click="service.notify_all_changes = !!service.notify_all_changes" class="switch float-left">
<input v-model="service.notify_all_changes" type="checkbox" name="notify_all-option" class="switch" id="notify_all" v-bind:checked="service.notify_all_changes">
<label for="notify_all">Continuously notify when service is failing.</label>
</span>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Visible</label>
<label class="col-sm-4 col-form-label">Visible</label>
<div class="col-8 mt-1">
<span @click="service.public = !!service.public" class="switch float-left">
<input v-model="service.public" type="checkbox" name="public-option" class="switch" id="switch-public" v-bind:checked="service.public">
@ -157,7 +159,7 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'FormService',
@ -181,6 +183,7 @@
order: 1,
verify_ssl: true,
allow_notifications: true,
notify_all_changes: true,
public: true,
},
groups: [],
@ -203,8 +206,7 @@
}
},
methods: {
async saveService(e) {
e.preventDefault()
async saveService() {
let s = this.service
delete s.failures
delete s.created_at

View File

@ -1,7 +1,7 @@
<template>
<div class="container col-md-7 col-sm-12 mt-2 sm-container">
<div class="col-12 col-md-8 offset-md-2 mb-4">
<img class="col-12 mt-5 mt-md-0" src="/public/banner.png">
<img class="col-12 mt-5 mt-md-0" src="/banner.png">
</div>
<div class="col-12">
@ -96,7 +96,7 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
import Index from "../pages/Index";
export default {

View File

@ -5,39 +5,39 @@
<script>
export default {
name: 'ToggleSwitch',
props: {
service: {
type: Object,
required: true
}
},
data () {
return {
icon: "toggle-on",
running: true
}
},
mounted() {
if (this.service.online) {
this.running = true
this.icon = "toggle-on"
} else {
this.running = false
this.icon = "toggle-off"
}
},
methods: {
toggleChecking() {
if (this.running) {
this.icon = "toggle-off"
} else {
this.icon = "toggle-on"
name: 'ToggleSwitch',
props: {
service: {
type: Object,
required: true
}
},
data() {
return {
icon: "toggle-on",
running: true
}
},
mounted() {
if (this.service.online) {
this.running = true
this.icon = "toggle-on"
} else {
this.running = false
this.icon = "toggle-off"
}
},
methods: {
toggleChecking() {
if (this.running) {
this.icon = "toggle-off"
} else {
this.icon = "toggle-on"
}
this.running = !this.running
},
}
this.running = !this.running
},
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -55,7 +55,7 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'FormUser',

View File

@ -12,7 +12,6 @@ import "./icons"
Vue.component('apexchart', VueApexCharts)
Vue.use(VueRouter);
Vue.use(require('vue-moment'));
Vue.config.productionTip = false
new Vue({

View File

@ -1,45 +1,51 @@
import Vue from "vue";
const { zonedTimeToUtc, utcToZonedTime, subSeconds, parse, parseISO, getUnixTime, fromUnixTime, format, differenceInSeconds, formatDistanceToNow, formatDistance } = require('date-fns')
export default Vue.mixin({
methods: {
now() {
return Math.round(new Date().getTime() / 1000)
return new Date()
},
current() {
return new Date()
},
ago(seconds) {
return this.now() - seconds
current() {
return parse(new Date())
},
utc(val) {
return fromUnixTime(this.toUnix(val) + val.getTimezoneOffset() * 60 * 1000)
},
ago(t1) {
return formatDistanceToNow(t1)
},
nowSubtract(seconds) {
return subSeconds(new Date(), seconds)
},
duration(t1, t2) {
const val = (this.toUnix(t1) - this.toUnix(t2))
if (val <= 59) {
return this.$moment.duration(val, 'seconds').get('seconds') + " seconds ago"
}
return this.$moment.duration(val, 'seconds').humanize();
return formatDistance(t1, t2)
},
niceDate(val) {
return this.parseTime(val).format('LLLL')
},
parseTime(val) {
return this.$moment(val, this.$moment.ISO_8601, true)
},
toLocal(val, suf='at') {
return this.parseTime(val).local().format(`dddd, MMM Do \\${suf} h:mma`)
niceDate(val) {
return this.parseTime(val).format('LLLL')
},
parseTime(val) {
return parseISO(val)
},
toLocal(val, suf = 'at') {
const t = this.parseTime(val)
return format(t, `EEEE, MMM do h:mma`)
},
toUnix(val) {
return getUnixTime(val)
},
toUnix(val) {
return this.$moment(val).utc().unix().valueOf()
},
fromUnix(val) {
return this.$moment.unix(val).utc()
return fromUnixTime(val)
},
isBetween(t1, t2) {
return differenceInSeconds(parseISO(t1), parseISO(t2)) > 0
},
hour() {
return 3600
},
day() {
return 3600 * 24
},
isBetween(t1, t2) {
const now = this.$moment(t1).utc().valueOf()
const sub = this.$moment(t2).utc().valueOf()
return (now - sub) > 0
},
hour(){ return 3600 },
day() { return 3600 * 24 },
serviceLink(service) {
if (!service) {
return ""
@ -53,10 +59,10 @@ export default Vue.mixin({
isInt(n) {
return n % 1 === 0;
},
loggedIn() {
const core = this.$store.getters.core
return core.logged_in === true
},
loggedIn() {
const core = this.$store.getters.core
return core.logged_in === true
},
iconName(name) {
switch (name) {
case "fas fa-terminal":

View File

@ -6,7 +6,7 @@
</template>
<script>
import Api from "../components/API"
import Api from "../API"
import TopNav from "../components/Dashboard/TopNav";
export default {

View File

@ -22,7 +22,7 @@
</template>
<script>
import Api from '../components/API';
import Api from '../API';
import Group from '../components/Index/Group';
import Header from '../components/Index/Header';
import MessageBlock from '../components/Index/MessageBlock';
@ -30,36 +30,36 @@ import ServiceBlock from '../components/Service/ServiceBlock';
export default {
name: 'Index',
components: {
ServiceBlock,
MessageBlock,
Group,
Header
},
data () {
return {
logged_in: false
}
},
async created() {
this.logged_in = this.loggedIn()
},
async mounted() {
name: 'Index',
components: {
ServiceBlock,
MessageBlock,
Group,
Header
},
data() {
return {
logged_in: false
}
},
async created() {
this.logged_in = this.loggedIn()
},
async mounted() {
},
methods: {
inRange(message) {
const start = this.isBetween(new Date(), message.start_on)
const end = this.isBetween(message.end_on, new Date())
return start && end
},
clickService(s) {
this.$nextTick(() => {
this.$refs.s.scrollTop = 0;
});
},
methods: {
inRange(message) {
const start = this.isBetween(new Date(), message.start_on)
const end = this.isBetween(message.end_on, new Date())
return start && end
},
clickService(s) {
this.$nextTick(() => {
this.$refs.s.scrollTop = 0;
});
}
}
}
}
</script>

View File

@ -12,7 +12,7 @@
</template>
<script>
import Api from "../components/API";
import Api from "../API";
import FormLogin from '../forms/Login';
export default {

View File

@ -3,61 +3,55 @@
<p v-if="logs.length === 0" class="text-monospace sm">
Loading Logs...
</p>
<p v-for="(log, index) in logs.reverse()" class="text-monospace sm">{{log}}</p>
<p v-for="(log, index) in logs" class="text-monospace sm">{{log}}</p>
</div>
</template>
<script>
import Api from "../components/API";
import Api from "../API";
export default {
name: 'Logs',
components: {
},
data () {
return {
logs: [],
last: "",
t: null
}
},
created() {
if (!this.t) {
this.t = setInterval(() => {
this.lastLog()
}, 650)
}
},
async mounted() {
await this.getLogs()
},
name: 'Logs',
components: {},
data() {
return {
logs: [],
last: "",
t: null
}
},
async created() {
await this.getLogs()
if (!this.t) {
this.t = setInterval(async () => {
await this.lastLog()
}, 650)
}
},
beforeDestroy() {
clearInterval(this.t)
},
methods: {
cleanLog(l) {
const splitLog = l.split(": ")
const last = splitLog.slice(1);
return last.join(": ")
},
async getLogs() {
const logs = await Api.logs()
this.logs = logs.reverse()
this.last = this.cleanLog(this.logs[this.logs.length-1])
},
async lastLog() {
const log = await Api.logs_last()
const cleanLast = this.cleanLog(log)
if (this.last !== cleanLast) {
this.last = cleanLast
this.logs.reverse().push(log)
}
}
}
cleanLog(l) {
const splitLog = l.split(": ")
const last = splitLog.slice(1);
return last.join(": ")
},
async getLogs() {
const logs = await Api.logs()
const l = logs.reverse()
this.last = this.cleanLog(l[l.length - 1])
this.logs = l
},
async lastLog() {
const log = await Api.logs_last()
const cleanLast = this.cleanLog(log)
if (this.last !== cleanLast) {
this.last = cleanLast
this.logs.reverse().push(log)
}
}
}
}
</script>

View File

@ -7,50 +7,37 @@
{{service.online ? "ONLINE" : "OFFLINE"}}
</span>
<h4 class="mt-2"><router-link to="/">{{$store.getters.core.name}}</router-link> - {{service.name}}
<h4 class="mt-2">
<router-link to="/">{{$store.getters.core.name}}</router-link> - {{service.name}}
<span class="badge float-right d-none d-md-block" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
{{service.online ? "ONLINE" : "OFFLINE"}}
</span>
</h4>
<div class="row stats_area mt-5 mb-5">
<div class="col-4">
<span class="lg_number">{{service.online_24_hours}}%</span>
Online last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">31ms</span>
Average Response
</div>
<div class="col-4">
<span class="lg_number">85.70%</span>
Total Uptime
</div>
</div>
<ServiceTopStats :service="service"/>
<div v-for="(message, index) in messages" v-if="messageInRange(message)">
<MessageBlock :message="message"/>
</div>
<div class="row mt-5 mb-4">
<span class="col-6 font-2">
<flatPickr v-model="start_time" type="text" name="start_time" class="form-control form-control-plaintext" id="start_time" value="0001-01-01T00:00:00Z" required />
</span>
<span class="col-6 font-2">
<flatPickr v-model="end_time" type="text" name="end_time" class="form-control form-control-plaintext" id="end_time" value="0001-01-01T00:00:00Z" required />
</span>
</div>
<div v-if="series" class="service-chart-container">
<apexchart width="100%" height="420" type="area" :options="chartOptions" :series="series"></apexchart>
</div>
<div v-if="series" class="service-chart-heatmap">
<apexchart width="100%" height="215" type="heatmap" :options="chartOptions" :series="series"></apexchart>
<div class="service-chart-heatmap mt-3 mb-4">
<ServiceHeatmap :service="service"/>
</div>
<form id="service_date_form" class="col-12 mt-2 mb-3">
<input type="text" class="d-none" name="start" id="service_start" data-input>
<span data-toggle title="toggle" id="start_date" class="text-muted small float-left pointer mt-2">Thu, 09 Jan 2020 to Thu, 16 Jan 2020</span>
<button type="submit" class="btn btn-light btn-sm mt-2">Set Timeframe</button>
<input type="text" class="d-none" name="end" id="service_end" data-input>
<div id="start_container"></div>
<div id="end_container"></div>
</form>
<nav class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs">
<nav v-if="service.failures" class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs">
<a @click="tab='failures'" class="flex-sm-fill text-sm-center nav-link active">Failures</a>
<a @click="tab='incidents'" class="flex-sm-fill text-sm-center nav-link">Incidents</a>
<a @click="tab='checkins'" v-if="$store.getters.token" class="flex-sm-fill text-sm-center nav-link">Checkins</a>
@ -58,7 +45,7 @@
</nav>
<div class="tab-content">
<div v-if="service.failures" class="tab-content">
<div class="tab-pane fade active show">
<ServiceFailures :service="service"/>
</div>
@ -98,10 +85,14 @@
</template>
<script>
import Api from "../components/API"
import Api from "../API"
import MessageBlock from '../components/Index/MessageBlock';
import ServiceFailures from '../components/Service/ServiceFailures';
import Checkin from "../forms/Checkin";
import ServiceHeatmap from "@/components/Service/ServiceHeatmap";
import ServiceTopStats from "@/components/Service/ServiceTopStats";
import flatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
const axisOptions = {
labels: {
@ -128,143 +119,145 @@
};
export default {
name: 'Service',
components: {
ServiceFailures,
MessageBlock,
Checkin
},
data () {
return {
id: null,
tab: "failures",
service: {},
authenticated: false,
ready: false,
data: null,
messages: [],
failures: [],
chartOptions: {
chart: {
height: 500,
width: "100%",
type: "area",
animations: {
enabled: true,
initialAnimation: {
enabled: true
}
},
selection: {
enabled: false
},
zoom: {
enabled: false
},
toolbar: {
show: false
},
},
grid: {
show: true,
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0,
}
},
xaxis: {
type: "datetime",
...axisOptions
},
yaxis: {
...axisOptions
},
tooltip: {
enabled: false,
marker: {
show: false,
},
x: {
show: false,
}
},
legend: {
show: false,
},
dataLabels: {
enabled: false
},
floating: true,
axisTicks: {
show: false
},
axisBorder: {
show: false
},
fill: {
colors: ["#48d338"],
opacity: 1,
type: 'solid'
},
stroke: {
show: true,
curve: 'smooth',
lineCap: 'butt',
colors: ["#3aa82d"],
name: 'Service',
components: {
ServiceTopStats,
ServiceHeatmap,
ServiceFailures,
MessageBlock,
Checkin,
flatPickr
},
data() {
return {
id: null,
tab: "failures",
service: {},
authenticated: false,
ready: false,
data: null,
messages: [],
failures: [],
start_time: "",
end_time: "",
chartOptions: {
chart: {
height: 500,
width: "100%",
type: "area",
animations: {
enabled: true,
initialAnimation: {
enabled: true
}
},
selection: {
enabled: false
},
zoom: {
enabled: false
},
toolbar: {
show: false
},
},
grid: {
show: true,
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0,
}
},
xaxis: {
type: "datetime",
...axisOptions
},
yaxis: {
...axisOptions
},
tooltip: {
enabled: false,
marker: {
show: false,
},
x: {
show: false,
}
},
legend: {
show: false,
},
dataLabels: {
enabled: false
},
floating: true,
axisTicks: {
show: false
},
axisBorder: {
show: false
},
fill: {
colors: ["#48d338"],
opacity: 1,
type: 'solid'
},
stroke: {
show: true,
curve: 'smooth',
lineCap: 'butt',
colors: ["#3aa82d"],
}
},
series: [{
data: []
}],
heatmap_data: [],
config: {
enableTime: true
},
}
},
series: [{
data: []
}],
heatmap_data: []
}
},
},
async mounted() {
const id = this.$attrs.id
const id = this.$attrs.id
this.id = id
let service;
if (this.isInt(id)) {
service = this.$store.getters.serviceById(id)
} else {
service = this.$store.getters.serviceByPermalink(id)
}
let service;
if (this.isInt(id)) {
service = this.$store.getters.serviceById(id)
} else {
service = this.$store.getters.serviceByPermalink(id)
}
this.service = service
this.getService(service)
this.messages = this.$store.getters.serviceMessages(service.id)
},
methods: {
messageInRange(message) {
const start = this.isBetween(new Date(), message.start_on)
const end = this.isBetween(message.end_on, new Date())
return start && end
},
async getService(s) {
await this.chartHits()
await this.heatmapData()
await this.serviceFailures()
},
async serviceFailures() {
this.failures = await Api.service_failures(this.service.id, this.now() - 3600, this.now(), 15)
},
async chartHits() {
this.data = await Api.service_hits(this.service.id, 0, 99999999999, "hour")
this.series = [{
name: this.service.name,
...this.data
}]
this.ready = true
},
async heatmapData() {
this.data = await Api.service_heatmap(this.service.id, 0, 99999999999, "hour")
this.series = [{
name: this.service.name,
...this.data
}]
this.ready = true
}
}
methods: {
messageInRange(message) {
const start = this.isBetween(new Date(), message.start_on)
const end = this.isBetween(message.end_on, new Date())
return start && end
},
async getService(s) {
await this.chartHits()
await this.serviceFailures()
},
async serviceFailures() {
this.failures = await Api.service_failures(this.service.id, this.now() - 3600, this.now(), 15)
},
async chartHits() {
const start = this.nowSubtract((3600 * 24) * 7)
this.start_time = start
this.end_time = new Date()
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), "hour")
this.series = [{
name: this.service.name,
...this.data
}]
this.ready = true
}
}
}
</script>

View File

@ -42,7 +42,7 @@
<div class="row align-content-center">
<img class="rounded text-center" width="300" height="300" :src="qrcode">
</div>
<a class="btn btn-sm btn-primary" href=statping://setup?domain&#61;https://demo.statping.com&amp;api&#61;6b05b48f4b3a1460f3864c31b26cab6a27dbaff9>Open in Statping App</a>
<a class="btn btn-sm btn-primary" :href="qrurl">Open in Statping App</a>
<a href="settings/export" class="btn btn-sm btn-secondary">Export Settings</a>
</div>
</div>
@ -53,32 +53,11 @@
</div>
<div class="tab-pane fade" v-bind:class="{active: liClass('v-pills-style-tab'), show: liClass('v-pills-style-tab')}" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab">
<ThemeEditor :core="core"/>
</div>
<div class="tab-pane fade" v-bind:class="{active: liClass('v-pills-cache-tab'), show: liClass('v-pills-cache-tab')}" id="v-pills-cache" role="tabpanel" aria-labelledby="v-pills-cache-tab">
<h3>Cache</h3>
<table class="table">
<thead>
<tr>
<th scope="col">URL</th>
<th scope="col">Size</th>
<th scope="col">Expiration</th>
</tr>
</thead>
<tbody>
<tr v-for="(cache, index) in cache">
<td>{{cache.url}}</td>
<td>{{cache.size}}</td>
<td>{{expireTime(cache.expiration)}}</td>
</tr>
</tbody>
</table>
<a @click.prevent="clearCache" href="#" class="btn btn-danger btn-block">Clear Cache</a>
<Cache/>
</div>
<div v-for="(notifier, index) in $store.getters.notifiers" v-bind:key="`${notifier.title}_${index}`" class="tab-pane fade" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), show: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" role="tabpanel" v-bind:aria-labelledby="`v-pills-${notifier.method.toLowerCase()}-tab`">
@ -97,54 +76,50 @@
</template>
<script>
import Api from '../components/API';
import Api from '../API';
import CoreSettings from '../forms/CoreSettings';
import FormIntegration from '../forms/Integration';
import Notifier from "../forms/Notifier";
import ThemeEditor from "../components/Dashboard/ThemeEditor";
import Cache from "@/components/Dashboard/Cache";
export default {
name: 'Settings',
components: {
ThemeEditor,
FormIntegration,
Notifier,
CoreSettings
},
data () {
return {
tab: "v-pills-home-tab",
qrcode: "",
core: this.$store.getters.core,
cache: [],
}
},
async mounted () {
name: 'Settings',
components: {
Cache,
ThemeEditor,
FormIntegration,
Notifier,
CoreSettings
},
data() {
return {
tab: "v-pills-home-tab",
qrcode: "",
qrurl: "",
core: this.$store.getters.core
}
},
async mounted() {
this.cache = await Api.cache()
},
async created() {
const qrurl = `statping://setup?domain=${core.domain}&api=${core.api_secret}`
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURI(qrurl)
},
beforeMount() {
const c = this.$store.getters.core
this.qrurl = `statping://setup?domain=${c.domain}&api=${c.api_secret}`
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURI(this.qrurl)
},
beforeMount() {
},
methods: {
changeTab (e) {
this.tab = e.target.id
},
liClass (id) {
return this.tab === id
},
expireTime(ex) {
return this.toLocal(ex)
},
async clearCache () {
await Api.clearCache()
this.cache = await Api.cache()
}
},
methods: {
changeTab(e) {
this.tab = e.target.id
},
liClass(id) {
return this.tab === id
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->

View File

@ -13,7 +13,7 @@ import Service from "./pages/Service";
import VueRouter from "vue-router";
import Setup from "./forms/Setup";
import Api from "./components/API";
import Api from "./API";
const routes = [
{

View File

@ -1,6 +1,6 @@
import Vuex from 'vuex'
import Vue from 'vue'
import Api from "./components/API"
import Api from "./API"
Vue.use(Vuex)
@ -105,7 +105,7 @@ export default new Vuex.Store({
}
},
actions: {
async loadRequired (context) {
async loadRequired(context) {
const core = await Api.core()
context.commit("setCore", core);
const groups = await Api.groups()
@ -125,7 +125,7 @@ export default new Vuex.Store({
// }
window.console.log('finished loading required data')
},
async loadAdmin (context) {
async loadAdmin(context) {
const core = await Api.core()
context.commit("setCore", core);
const groups = await Api.groups()

16
frontend/test/API.spec.js Normal file
View File

@ -0,0 +1,16 @@
import API from "@/API"
import { shallowMount, mount } from '@vue/test-utils'
describe('API Tests', async () => {
await it('should get core info', async () => {
const wrapper = mount(API)
const core = await wrapper.core()
expect(core).toBe(9)
})
});

View File

@ -0,0 +1,24 @@
// import Vuex from 'vuex'
// import Api from '../src/components/API'
// import thisStore from '../src/store'
//
// import { createLocalVue } from '@vue/test-utils'
// const localVue = createLocalVue()
// localVue.use(Vuex)
//
// describe('MyName test', async () => {
// const services = [
// { id: 1, title: 'Apple', order_id: 3 },
// { id: 2, title: 'Orange', order_id: 2},
// { id: 3, title: 'Carrot', order_id: 1}
// ]
//
// const store = new Vuex.Store(thisStore)
//
// await store.dispatch('loadRequired')
//
// console.log(store.getters.services)
//
// expect(store.getters.services).toEqual(services.slice(0, 20))
// })

View File

@ -0,0 +1,32 @@
import { shallowMount, mount } from '@vue/test-utils'
import FormLogin from "../../src/forms/Login.vue"
const wrapper = shallowMount(FormLogin)
describe('Login Form', () => {
it('has a created hook', () => {
expect(typeof FormLogin.methods.checkForm).toBe('function')
})
it('should login', async () => {
expect(wrapper.vm.$data.loading).toBe(false)
expect(wrapper.vm.$data.username).toBe('')
expect(wrapper.vm.$data.password).toBe('')
wrapper.setData({ username: 'admin' })
wrapper.setData({ password: 'admin' })
wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.$data.loading).toBe(true)
done()
})
});

View File

@ -0,0 +1,17 @@
// import { mount } from '@vue/test-utils';
// import Index from '../../src/pages/Index';
//
// const wrapper = mount(Index);
// describe('MyName test', () => {
// it('Displays my name when I write it', () => {
//
// expect(wrapper.vm.$data.logged_in).toBe('My name');
//
// const input = wrapper.find('input');
// input.element.value = 'Stefan';
// input.trigger('input');
//
// expect(wrapper.vm.$data.name).toBe('Stefan');
// })
// });

5
frontend/test/setup.js Normal file
View File

@ -0,0 +1,5 @@
require('jsdom-global')()
global.expect = require('expect')

File diff suppressed because it is too large Load Diff

View File

@ -89,7 +89,6 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
if c.Timezone != app.Timezone {
app.Timezone = c.Timezone
}
app.UpdateNotify = c.UpdateNotify
app.UseCdn = types.NewNullBool(c.UseCdn.Bool)
core.CoreApp, err = core.UpdateCore(app)
returnJson(core.CoreApp, w, r)

View File

@ -25,6 +25,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
"USE_CDN": func() bool {
return core.CoreApp.UseCdn.Bool
},
"USING_ASSETS": func() bool {
return core.CoreApp.UsingAssets()
},
"BasePath": func() string {
return basePath
},

View File

@ -70,9 +70,6 @@ func (u *discord) OnSuccess(s *types.Service) {
if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
var msg interface{}
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)

View File

@ -193,9 +193,6 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) {
func (u *email) OnSuccess(s *types.Service) {
if !s.Online || !s.SuccessNotified {
var msg string
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))

View File

@ -72,9 +72,6 @@ func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) {
func (u *lineNotifier) OnSuccess(s *types.Service) {
if !s.Online || !s.SuccessNotified {
var msg string
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))

View File

@ -99,9 +99,6 @@ func (u *mobilePush) OnSuccess(s *types.Service) {
data := dataJson(s, nil)
if !s.Online || !s.SuccessNotified {
var msgStr string
if s.UpdateNotify {
s.UpdateNotify = false
}
msgStr = s.DownText
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))

View File

@ -92,9 +92,6 @@ func (u *telegram) OnSuccess(s *types.Service) {
if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
var msg interface{}
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)

View File

@ -102,9 +102,6 @@ func (u *twilio) OnSuccess(s *types.Service) {
if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
var msg string
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)

View File

@ -41,7 +41,6 @@ type Core struct {
Setup bool `gorm:"-" json:"setup"`
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
UpdateNotify NullBool `gorm:"column:update_notify;default:true" json:"update_notify,omitempty"`
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
LoggedIn bool `gorm:"-" json:"logged_in"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`

View File

@ -33,7 +33,6 @@ type Service struct {
Port int `gorm:"not null;column:port" json:"port" scope:"user,admin"`
Timeout int `gorm:"default:30;column:timeout" json:"timeout" scope:"user,admin"`
Order int `gorm:"default:0;column:order_id" json:"order_id"`
AllowNotifications NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications" scope:"user,admin"`
VerifySSL NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin"`
Public NullBool `gorm:"default:true;column:public" json:"public"`
GroupId int `gorm:"default:0;column:group_id" json:"group_id"`
@ -53,10 +52,11 @@ type Service struct {
Checkpoint time.Time `gorm:"-" json:"-"`
SleepDuration time.Duration `gorm:"-" json:"-"`
LastResponse string `gorm:"-" json:"-"`
UserNotified bool `gorm:"-" json:"-"` // True if the User was already notified about a Downtime
UpdateNotify bool `gorm:"-" json:"-"` // This Variable is a simple copy of `core.CoreApp.UpdateNotify.Bool`
DownText string `gorm:"-" json:"-"` // Contains the current generated Downtime Text
SuccessNotified bool `gorm:"-" json:"-"` // Is 'true' if the user has already be informed that the Services now again available
AllowNotifications NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications" scope:"user,admin"`
UserNotified bool `gorm:"-" json:"-"` // True if the User was already notified about a Downtime
UpdateNotify NullBool `gorm:"default:true;column:notify_all_changes" json:"notify_all_changes" scope:"user,admin"` // This Variable is a simple copy of `core.CoreApp.UpdateNotify.Bool`
DownText string `gorm:"-" json:"-"` // Contains the current generated Downtime Text
SuccessNotified bool `gorm:"-" json:"-"` // Is 'true' if the user has already be informed that the Services now again available
LastStatusCode int `gorm:"-" json:"status_code"`
LastOnline time.Time `gorm:"-" json:"last_success"`
Failures []FailureInterface `gorm:"-" json:"failures,omitempty" scope:"user,admin"`