mirror of https://github.com/statping/statping
v0.90.65 (#805)
* notifier panic fix * portainer template * remove default host from discord notifier * fix for updating fields * fix for updating fields * fixed notifier panic * fixed notifier panic * test fix * test fix * missing login banner image * dont delete admin if DEMO_MODE * updatess to service on Dashboard * notifier endpoint fixes, timeframe rounding chart data * modal for UI confirmationspull/815/head^2
parent
243b6f019f
commit
5d85c3ce39
|
@ -257,6 +257,7 @@ jobs:
|
||||||
API_SECRET: demopassword123
|
API_SECRET: demopassword123
|
||||||
DISABLE_LOGS: false
|
DISABLE_LOGS: false
|
||||||
ALLOW_REPORTS: true
|
ALLOW_REPORTS: true
|
||||||
|
SAMPLE_DATA: true
|
||||||
COVERALLS: ${{ secrets.COVERALLS }}
|
COVERALLS: ${{ secrets.COVERALLS }}
|
||||||
DISCORD_URL: ${{ secrets.DISCORD_URL }}
|
DISCORD_URL: ${{ secrets.DISCORD_URL }}
|
||||||
EMAIL_HOST: ${{ secrets.EMAIL_HOST }}
|
EMAIL_HOST: ${{ secrets.EMAIL_HOST }}
|
||||||
|
|
|
@ -257,6 +257,7 @@ jobs:
|
||||||
API_SECRET: demopassword123
|
API_SECRET: demopassword123
|
||||||
DISABLE_LOGS: false
|
DISABLE_LOGS: false
|
||||||
ALLOW_REPORTS: true
|
ALLOW_REPORTS: true
|
||||||
|
SAMPLE_DATA: true
|
||||||
COVERALLS: ${{ secrets.COVERALLS }}
|
COVERALLS: ${{ secrets.COVERALLS }}
|
||||||
DISCORD_URL: ${{ secrets.DISCORD_URL }}
|
DISCORD_URL: ${{ secrets.DISCORD_URL }}
|
||||||
EMAIL_HOST: ${{ secrets.EMAIL_HOST }}
|
EMAIL_HOST: ${{ secrets.EMAIL_HOST }}
|
||||||
|
|
|
@ -3,6 +3,7 @@ snap
|
||||||
prime
|
prime
|
||||||
stage
|
stage
|
||||||
parts
|
parts
|
||||||
|
assets_backup
|
||||||
certs
|
certs
|
||||||
releases
|
releases
|
||||||
core/rice-box.go
|
core/rice-box.go
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
# 0.90.65 (09-01-2020)
|
||||||
|
- Fixed issue with dashboard not logging in (notifier panic)
|
||||||
|
- Modified static email templates to github.com/statping/emails
|
||||||
|
- Modified Regenerate API function to keep API_SECRET env
|
||||||
|
- Added DEMO_MODE env variable, if true, 'admin' cannot be deleted
|
||||||
|
- Modified Service sparklines on Dashboard
|
||||||
|
|
||||||
# 0.90.64 (08-18-2020)
|
# 0.90.64 (08-18-2020)
|
||||||
- Modified max-width for container to 1012px, larger UI
|
- Modified max-width for container to 1012px, larger UI
|
||||||
- Added failure sparklines in the Services list view
|
- Added failure sparklines in the Services list view
|
||||||
|
|
|
@ -148,20 +148,20 @@ func InitApp() error {
|
||||||
if _, err := core.Select(); err != nil {
|
if _, err := core.Select(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// init Sentry error monitoring (its useful)
|
||||||
|
utils.SentryInit(core.App.AllowReports.Bool)
|
||||||
// init prometheus metrics
|
// init prometheus metrics
|
||||||
metrics.InitMetrics()
|
metrics.InitMetrics()
|
||||||
|
// connect each notifier, added them into database if needed
|
||||||
|
notifiers.InitNotifiers()
|
||||||
// select all services in database and store services in a mapping of Service pointers
|
// select all services in database and store services in a mapping of Service pointers
|
||||||
if _, err := services.SelectAllServices(true); err != nil {
|
if _, err := services.SelectAllServices(true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// start routines for each service checking process
|
// start routines for each service checking process
|
||||||
services.CheckServices()
|
services.CheckServices()
|
||||||
// connect each notifier, added them into database if needed
|
|
||||||
notifiers.InitNotifiers()
|
|
||||||
// start routine to delete old records (failures, hits)
|
// start routine to delete old records (failures, hits)
|
||||||
go database.Maintenance()
|
go database.Maintenance()
|
||||||
// init Sentry error monitoring (its useful)
|
|
||||||
utils.SentryInit(core.App.AllowReports.Bool)
|
|
||||||
core.App.Setup = true
|
core.App.Setup = true
|
||||||
core.App.Started = utils.Now()
|
core.App.Started = utils.Now()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -70,19 +70,19 @@ func (t *TimeVar) ToValues() ([]*TimeValue, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphData will return all hits or failures
|
// GraphData will return all hits or failures
|
||||||
func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
|
func (b *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
|
||||||
g.db = g.db.MultipleSelects(
|
b.db = b.db.MultipleSelects(
|
||||||
g.db.SelectByTime(g.Group),
|
b.db.SelectByTime(b.Group),
|
||||||
by.String(),
|
by.String(),
|
||||||
).Group("timeframe").Order("timeframe", true)
|
).Group("timeframe").Order("timeframe", true)
|
||||||
|
|
||||||
caller, err := g.ToTimeValue()
|
caller, err := b.ToTimeValue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.FillEmpty {
|
if b.FillEmpty {
|
||||||
return caller.FillMissing(g.Start, g.End)
|
return caller.FillMissing(b.Start, b.End)
|
||||||
}
|
}
|
||||||
return caller.ToValues()
|
return caller.ToValues()
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,8 @@ func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
|
||||||
// ToTimeValue will format the SQL rows into a JSON format for the API.
|
// ToTimeValue will format the SQL rows into a JSON format for the API.
|
||||||
// [{"timestamp": "2006-01-02T15:04:05Z", "amount": 468293}]
|
// [{"timestamp": "2006-01-02T15:04:05Z", "amount": 468293}]
|
||||||
// TODO redo this entire function, use better SQL query to group by time
|
// TODO redo this entire function, use better SQL query to group by time
|
||||||
func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
func (b *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
||||||
rows, err := g.db.Rows()
|
rows, err := b.db.Rows()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -102,8 +102,8 @@ func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
||||||
if err := rows.Scan(&timeframe, &amount); err != nil {
|
if err := rows.Scan(&timeframe, &amount); err != nil {
|
||||||
log.Error(err, timeframe)
|
log.Error(err, timeframe)
|
||||||
}
|
}
|
||||||
trueTime, _ := g.db.ParseTime(timeframe)
|
trueTime, _ := b.db.ParseTime(timeframe)
|
||||||
newTs := types.FixedTime(trueTime, g.Group)
|
newTs := types.FixedTime(trueTime, b.Group)
|
||||||
|
|
||||||
tv := &TimeValue{
|
tv := &TimeValue{
|
||||||
Timeframe: newTs,
|
Timeframe: newTs,
|
||||||
|
@ -111,7 +111,7 @@ func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
||||||
}
|
}
|
||||||
data = append(data, tv)
|
data = append(data, tv)
|
||||||
}
|
}
|
||||||
return &TimeVar{g, data}, nil
|
return &TimeVar{b, data}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) {
|
func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) {
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": 1,
|
||||||
|
"title": "Statping",
|
||||||
|
"restart_policy": "unless-stopped",
|
||||||
|
"description": "Service monitoring with an easy to use status page and mobile app",
|
||||||
|
"logo": "https://assets.statping.com/icon.png",
|
||||||
|
"image": "statping/statping:latest",
|
||||||
|
"platform": "linux",
|
||||||
|
"categories": ["monitoring"],
|
||||||
|
"administrator_only": false,
|
||||||
|
"ports": [
|
||||||
|
"8080:8080/tcp"
|
||||||
|
],
|
||||||
|
"volumes": [
|
||||||
|
{
|
||||||
|
"container": "/app"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -74,7 +74,7 @@ const webpackConfig = merge(commonConfig, {
|
||||||
threshold: 10240,
|
threshold: 10240,
|
||||||
minRatio: 0.8
|
minRatio: 0.8
|
||||||
}),
|
}),
|
||||||
new webpack.HashedModuleIdsPlugin(),
|
// new webpack.HashedModuleIdsPlugin(),
|
||||||
new HtmlPlugin({
|
new HtmlPlugin({
|
||||||
template: 'public/base.gohtml',
|
template: 'public/base.gohtml',
|
||||||
filename: 'base.gohtml',
|
filename: 'base.gohtml',
|
||||||
|
|
|
@ -7,8 +7,8 @@ const tokenKey = "statping_auth";
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.version = "0.90.64";
|
this.version = "0.90.65";
|
||||||
this.commit = "130cc3ede7463ec9af8d62abb84992e2a0ef453c";
|
this.commit = "5bc10fcc8536a08ce7a099a0b4cbceb2dc9fc35b";
|
||||||
}
|
}
|
||||||
|
|
||||||
async oauth() {
|
async oauth() {
|
||||||
|
|
|
@ -85,13 +85,13 @@
|
||||||
.chartmarker {
|
.chartmarker {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
text-align: right;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chartmarker SPAN {
|
.chartmarker SPAN {
|
||||||
font-size: 4pt;
|
font-size: 4pt;
|
||||||
display: block;
|
display: block;
|
||||||
color: #8b8b8b;
|
color: #b1b1b1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.apexcharts-tooltip {
|
.apexcharts-tooltip {
|
||||||
|
|
|
@ -14,6 +14,34 @@ A:HOVER {
|
||||||
color: lighten($text-color, 12%) !important;
|
color: lighten($text-color, 12%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
z-index: 10000;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #00000073;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
z-index: 999999 !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
top: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
color: lighten($text-color, 30%) !important;
|
color: lighten($text-color, 30%) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,13 +81,21 @@
|
||||||
serviceName (service) {
|
serviceName (service) {
|
||||||
return service.name || "Global Message"
|
return service.name || "Global Message"
|
||||||
},
|
},
|
||||||
|
async delete(m) {
|
||||||
|
await Api.message_delete(m.id)
|
||||||
|
const messages = await Api.messages()
|
||||||
|
this.$store.commit('setMessages', messages)
|
||||||
|
},
|
||||||
async deleteMessage(m) {
|
async deleteMessage(m) {
|
||||||
let c = confirm(`Are you sure you want to delete message '${m.title}'?`)
|
const modal = {
|
||||||
if (c) {
|
visible: true,
|
||||||
await Api.message_delete(m.id)
|
title: "Delete Announcement",
|
||||||
const messages = await Api.messages()
|
body: `Are you sure you want to delete Announcement ${m.title}?`,
|
||||||
this.$store.commit('setMessages', messages)
|
btnColor: "btn-danger",
|
||||||
|
btnText: "Delete Announcement",
|
||||||
|
func: () => this.delete(m),
|
||||||
}
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
<div class="card contain-card mb-4">
|
<div class="card contain-card mb-4">
|
||||||
<div class="card-header">{{ $t('top_nav.services') }}
|
<div class="card-header">{{ $t('top_nav.services') }}
|
||||||
<router-link v-if="$store.state.admin" to="/dashboard/create_service" class="btn btn-sm btn-success float-right">
|
<router-link v-if="$store.state.admin" to="/dashboard/create_service" class="btn btn-sm btn-success float-right">
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const Modal = () => import(/* webpackChunkName: "dashboard" */ "@/components/Elements/Modal")
|
||||||
const FormGroup = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Group')
|
const FormGroup = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Group')
|
||||||
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '@/forms/ToggleSwitch')
|
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '@/forms/ToggleSwitch')
|
||||||
const ServicesList = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ServicesList')
|
const ServicesList = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ServicesList')
|
||||||
|
@ -76,6 +78,7 @@
|
||||||
export default {
|
export default {
|
||||||
name: 'DashboardServices',
|
name: 'DashboardServices',
|
||||||
components: {
|
components: {
|
||||||
|
Modal,
|
||||||
ServicesList,
|
ServicesList,
|
||||||
ToggleSwitch,
|
ToggleSwitch,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
|
@ -112,13 +115,24 @@
|
||||||
this.group = g
|
this.group = g
|
||||||
this.edit = !mode
|
this.edit = !mode
|
||||||
},
|
},
|
||||||
|
confirm_delete(service) {
|
||||||
|
|
||||||
|
},
|
||||||
|
async delete(g) {
|
||||||
|
await Api.group_delete(g.id)
|
||||||
|
const groups = await Api.groups()
|
||||||
|
this.$store.commit('setGroups', groups)
|
||||||
|
},
|
||||||
async deleteGroup(g) {
|
async deleteGroup(g) {
|
||||||
let c = confirm(`Are you sure you want to delete '${g.name}'?`)
|
const modal = {
|
||||||
if (c) {
|
visible: true,
|
||||||
await Api.group_delete(g.id)
|
title: "Delete Group",
|
||||||
const groups = await Api.groups()
|
body: `Are you sure you want to delete group ${g.name}? All services attached will be removed from this group.`,
|
||||||
this.$store.commit('setGroups', groups)
|
btnColor: "btn-danger",
|
||||||
}
|
btnText: "Delete Group",
|
||||||
|
func: () => this.delete(g),
|
||||||
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,13 +74,21 @@
|
||||||
this.user = u
|
this.user = u
|
||||||
this.edit = !mode
|
this.edit = !mode
|
||||||
},
|
},
|
||||||
|
async delete(u) {
|
||||||
|
await Api.user_delete(u.id)
|
||||||
|
const users = await Api.users()
|
||||||
|
this.$store.commit('setUsers', users)
|
||||||
|
},
|
||||||
async deleteUser(u) {
|
async deleteUser(u) {
|
||||||
let c = confirm(`Are you sure you want to delete user '${u.username}'?`)
|
const modal = {
|
||||||
if (c) {
|
visible: true,
|
||||||
await Api.user_delete(u.id)
|
title: "Delete User",
|
||||||
const users = await Api.users()
|
body: `Are you sure you want to delete user ${u.username}?`,
|
||||||
this.$store.commit('setUsers', users)
|
btnColor: "btn-danger",
|
||||||
|
btnText: "Delete User",
|
||||||
|
func: () => this.delete(u),
|
||||||
}
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,14 +151,22 @@ export default {
|
||||||
await this.gotoPage(1)
|
await this.gotoPage(1)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async delete() {
|
||||||
|
await Api.service_failures_delete(this.service)
|
||||||
|
this.service = await Api.service(this.service.id)
|
||||||
|
this.total = 0
|
||||||
|
await this.load()
|
||||||
|
},
|
||||||
async deleteFailures() {
|
async deleteFailures() {
|
||||||
const c = confirm('Are you sure you want to delete all failures?')
|
const modal = {
|
||||||
if (c) {
|
visible: true,
|
||||||
await Api.service_failures_delete(this.service)
|
title: "Delete All Failures",
|
||||||
this.service = await Api.service(this.service.id)
|
body: `Are you sure you want to delete all Failures for service ${this.service.title}?`,
|
||||||
this.total = 0
|
btnColor: "btn-danger",
|
||||||
await this.load()
|
btnText: "Delete Failures",
|
||||||
|
func: () => this.delete(),
|
||||||
}
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
},
|
},
|
||||||
async gotoPage(page) {
|
async gotoPage(page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h5 v-if="group.name" class="h5 col-12 mb-3 mt-2 text-dim">
|
<h5 v-if="group.name && group_services" class="h5 col-12 mb-3 mt-2 text-dim">
|
||||||
<font-awesome-icon @click="toggle" :icon="expanded ? 'minus' : 'plus'" class="pointer mr-3"/> {{group.name}}
|
<font-awesome-icon @click="toggle" :icon="expanded ? 'minus' : 'plus'" class="pointer mr-3"/> {{group.name}}
|
||||||
<span class="badge badge-success text-uppercase float-right ml-2">{{services_online.length}} online</span>
|
<span class="badge badge-success text-uppercase float-right ml-2">{{services_online.length}} online</span>
|
||||||
<span v-if="services_online.services_offline > 0" class="badge badge-danger text-uppercase float-right">
|
<span v-if="services_online.services_offline > 0" class="badge badge-danger text-uppercase float-right">
|
||||||
|
|
|
@ -80,15 +80,23 @@ const FormIncidentUpdates = () => import(/* webpackChunkName: "dashboard" */ '@/
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
async delete(i) {
|
||||||
|
this.res = await Api.incident_delete(i)
|
||||||
|
if (this.res.status === "success") {
|
||||||
|
this.incidents = this.incidents.filter(obj => obj.id !== i.id);
|
||||||
|
//await this.loadIncidents()
|
||||||
|
}
|
||||||
|
},
|
||||||
async deleteIncident(incident) {
|
async deleteIncident(incident) {
|
||||||
let c = confirm(`Are you sure you want to delete '${incident.title}'?`)
|
const modal = {
|
||||||
if (c) {
|
visible: true,
|
||||||
this.res = await Api.incident_delete(incident)
|
title: "Delete Incident",
|
||||||
if (this.res.status === "success") {
|
body: `Are you sure you want to delete Incident ${incident.title}?`,
|
||||||
this.incidents = this.incidents.filter(obj => obj.id !== incident.id); // this is better in terms of not having to querry the db to get a fresh copy of all updates
|
btnColor: "btn-danger",
|
||||||
//await this.loadIncidents()
|
btnText: "Delete Incident",
|
||||||
} // TODO: further error checking here... maybe alert user it failed with modal or so
|
func: () => this.delete(incident),
|
||||||
}
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
},
|
},
|
||||||
|
|
||||||
async createIncident() {
|
async createIncident() {
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-5 pr-0">
|
<div class="col-5 pr-0">
|
||||||
<span class="small text-dim"> {{ hoverbtn }}</span>
|
<span class="small text-dim">{{ hoverbtn }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-7 pr-2 pl-0">
|
<div class="col-7 pr-2 pl-0">
|
||||||
|
@ -121,13 +121,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getUptime() {
|
async getUptime() {
|
||||||
const start = this.nowSubtract(3 * 86400)
|
const end = this.endOf("day", this.now())
|
||||||
this.uptime = await Api.service_uptime(this.service.id, this.toUnix(start), this.toUnix(this.now()))
|
const start = this.beginningOf("day", this.nowSubtract(3 * 86400))
|
||||||
|
this.uptime = await Api.service_uptime(this.service.id, this.toUnix(start), this.toUnix(end))
|
||||||
},
|
},
|
||||||
async loadInfo() {
|
async loadInfo() {
|
||||||
this.set1 = await this.getHits(24 * 7, "6h")
|
this.set1 = await this.getHits(86400 * 7, "12h")
|
||||||
this.set1_name = this.calc(this.set1)
|
this.set1_name = this.calc(this.set1)
|
||||||
this.set2 = await this.getHits(24, "1h")
|
this.set2 = await this.getHits(86400, "60m")
|
||||||
this.set2_name = this.calc(this.set2)
|
this.set2_name = this.calc(this.set2)
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
},
|
},
|
||||||
|
@ -145,14 +146,13 @@
|
||||||
});
|
});
|
||||||
total = total / data.length
|
total = total / data.length
|
||||||
},
|
},
|
||||||
async getHits(hours, group) {
|
async getHits(seconds, group) {
|
||||||
const start = this.nowSubtract(3600 * hours)
|
let start = this.nowSubtract(seconds)
|
||||||
const fetched = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(this.now()), group, false)
|
let end = this.endOf("today")
|
||||||
|
const startEnd = this.startEndParams(start, end, group)
|
||||||
|
const fetched = await Api.service_hits(this.service.id, startEnd.start, startEnd.end, group, true)
|
||||||
const data = this.convertToChartData(fetched, 0.001, true)
|
const data = this.convertToChartData(fetched, 0.001, true)
|
||||||
|
|
||||||
return [{name: "Latency", ...data}]
|
return [{name: "Latency", ...data}]
|
||||||
|
|
||||||
},
|
},
|
||||||
calc(s) {
|
calc(s) {
|
||||||
let data = s[0].data
|
let data = s[0].data
|
||||||
|
|
|
@ -57,13 +57,13 @@
|
||||||
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||||
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
||||||
let val = series[seriesIndex][dataPointIndex];
|
let val = series[seriesIndex][dataPointIndex];
|
||||||
return `<div class="chartmarker"><span>Average Response Time: </span><span class="font-3">${this.humanTime(val)}</span><span>${dt}</span></div>`
|
return `<div class="chartmarker"><span class="font-3">Average Response Time: ${this.humanTime(val)}</span><span>${dt}</span></div>`
|
||||||
},
|
},
|
||||||
fixed: {
|
fixed: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
position: 'topRight',
|
position: 'bottomLeft',
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetY: 0,
|
offsetY: -30,
|
||||||
},
|
},
|
||||||
x: {
|
x: {
|
||||||
show: true,
|
show: true,
|
||||||
|
@ -94,7 +94,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -72,12 +72,14 @@
|
||||||
<script>
|
<script>
|
||||||
import Api from "../../API";
|
import Api from "../../API";
|
||||||
import ServiceSparkList from "@/components/Service/ServiceSparkList";
|
import ServiceSparkList from "@/components/Service/ServiceSparkList";
|
||||||
|
import Modal from "@/components/Elements/Modal";
|
||||||
const draggable = () => import(/* webpackChunkName: "dashboard" */ 'vuedraggable')
|
const draggable = () => import(/* webpackChunkName: "dashboard" */ 'vuedraggable')
|
||||||
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '../../forms/ToggleSwitch');
|
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '../../forms/ToggleSwitch');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ServicesList',
|
name: 'ServicesList',
|
||||||
components: {
|
components: {
|
||||||
|
Modal,
|
||||||
ServiceSparkList,
|
ServiceSparkList,
|
||||||
ToggleSwitch,
|
ToggleSwitch,
|
||||||
draggable
|
draggable
|
||||||
|
@ -159,14 +161,25 @@ export default {
|
||||||
await Api.services_reorder(data)
|
await Api.services_reorder(data)
|
||||||
await this.update()
|
await this.update()
|
||||||
},
|
},
|
||||||
|
tester(s) {
|
||||||
|
console.log(s)
|
||||||
|
},
|
||||||
|
async delete(s) {
|
||||||
|
this.loading = true
|
||||||
|
await Api.service_delete(s.id)
|
||||||
|
await this.update()
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
async deleteService(s) {
|
async deleteService(s) {
|
||||||
let c = confirm(`Are you sure you want to delete '${s.name}'?`)
|
const modal = {
|
||||||
if (c) {
|
visible: true,
|
||||||
this.loading = true
|
title: "Delete Service",
|
||||||
await Api.service_delete(s.id)
|
body: `Are you sure you want to delete service ${s.name}? This will also delete all failures, checkins, and incidents for this service.`,
|
||||||
await this.update()
|
btnColor: "btn-danger",
|
||||||
this.loading = false
|
btnText: "Delete Service",
|
||||||
}
|
func: () => this.delete(s),
|
||||||
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
},
|
},
|
||||||
serviceGroup(s) {
|
serviceGroup(s) {
|
||||||
let group = this.$store.getters.groupById(s.group_id)
|
let group = this.$store.getters.groupById(s.group_id)
|
||||||
|
|
|
@ -144,14 +144,22 @@ import('codemirror/mode/css/css.js')
|
||||||
this.pending = false
|
this.pending = false
|
||||||
await this.fetchTheme()
|
await this.fetchTheme()
|
||||||
},
|
},
|
||||||
|
async delete() {
|
||||||
|
this.pending = true
|
||||||
|
const resp = await Api.theme_generate(false)
|
||||||
|
await this.fetchTheme()
|
||||||
|
this.pending = false
|
||||||
|
},
|
||||||
async deleteAssets() {
|
async deleteAssets() {
|
||||||
this.pending = true
|
const modal = {
|
||||||
let c = confirm('Are you sure you want to delete all local assets?')
|
visible: true,
|
||||||
if (c) {
|
title: "Delete Local Assets",
|
||||||
const resp = await Api.theme_generate(false)
|
body: `Are you sure you want to delete all local assets?`,
|
||||||
await this.fetchTheme()
|
btnColor: "btn-danger",
|
||||||
}
|
btnText: "Delete",
|
||||||
this.pending = false
|
func: () => this.delete(),
|
||||||
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
},
|
},
|
||||||
async saveAssets() {
|
async saveAssets() {
|
||||||
this.pending = true
|
this.pending = true
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="modal.visible" class="modal d-block" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{{modal.title}}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>{{modal.body}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button @click.prevent="close" type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button @click.prevent="runFunc" type="button" :class="`btn ${modal.btnColor}`">{{modal.btnText}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Modal",
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modal() {
|
||||||
|
return this.$store.getters.modal
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
runFunc() {
|
||||||
|
this.$store.getters.modal.func()
|
||||||
|
this.close()
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$store.commit("setModal", {visible: false})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -157,7 +157,8 @@ export default {
|
||||||
},
|
},
|
||||||
async loadFailures() {
|
async loadFailures() {
|
||||||
this.loaded = false
|
this.loaded = false
|
||||||
const data = await Api.service_failures_data(this.service.id, this.toUnix(this.parseISO(this.start)), this.toUnix(this.parseISO(this.end)), this.group, true)
|
const startEnd = this.startEndParams(this.parseISO(this.start), this.parseISO(this.end), this.group)
|
||||||
|
const data = await Api.service_failures_data(this.service.id, startEnd.start, startEnd.end, this.group, true)
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
this.data = [{data: this.convertChartData(data)}]
|
this.data = [{data: this.convertChartData(data)}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,6 @@
|
||||||
const Analytics = () => import(/* webpackChunkName: "service" */ './Analytics');
|
const Analytics = () => import(/* webpackChunkName: "service" */ './Analytics');
|
||||||
const ServiceChart = () => import(/* webpackChunkName: "service" */ "./ServiceChart");
|
const ServiceChart = () => import(/* webpackChunkName: "service" */ "./ServiceChart");
|
||||||
const ServiceTopStats = () => import(/* webpackChunkName: "service" */ "@/components/Service/ServiceTopStats");
|
const ServiceTopStats = () => import(/* webpackChunkName: "service" */ "@/components/Service/ServiceTopStats");
|
||||||
const Graphing = () => import(/* webpackChunkName: "service" */ '../../graphing');
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ServiceBlock',
|
name: 'ServiceBlock',
|
||||||
|
|
|
@ -195,17 +195,14 @@
|
||||||
methods: {
|
methods: {
|
||||||
async chartHits(val) {
|
async chartHits(val) {
|
||||||
this.ready = false
|
this.ready = false
|
||||||
const start = val.start_time
|
const end = this.endOf("hour", this.now())
|
||||||
const end = this.toUnix(new Date())
|
const start = this.beginningOf("hour", this.fromUnix(val.start_time))
|
||||||
this.data = await Api.service_hits(this.service.id, start, end, val.interval, false)
|
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(end), val.interval, false)
|
||||||
if (this.data === null && val.interval !== "5m") {
|
this.ping_data = await Api.service_ping(this.service.id, this.toUnix(start), this.toUnix(end), val.interval, false)
|
||||||
await this.chartHits({start_time: val.start_time, interval: "5m"})
|
|
||||||
}
|
|
||||||
this.ping_data = await Api.service_ping(this.service.id, start, end, val.interval, false)
|
|
||||||
|
|
||||||
this.series = [
|
this.series = [
|
||||||
{name: "Latency", ...this.convertToChartData(this.data)},
|
{name: "Latency", ...this.convertToChartData(this.data)},
|
||||||
{name: "Ping", ...this.convertToChartData(this.ping_data)},
|
{name: "Ping", ...this.convertToChartData(this.ping_data)},
|
||||||
]
|
]
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="password" class="col-4 col-form-label">{{$t('password')}}</label>
|
<label for="password" class="col-4 col-form-label">{{$t('password')}}</label>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<input @keyup="checkForm" type="password" v-model="password" autocomplete="current-password" name="password" class="form-control" id="password" placeholder="password123">
|
<input @keyup="checkForm" type="password" v-model="password" autocomplete="current-password" name="password" class="form-control" id="password" placeholder="************">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
|
|
@ -149,11 +149,11 @@
|
||||||
|
|
||||||
<div v-for="(log, i) in notifier.logs.reverse()" class="alert" :class="{'alert-danger': log.error, 'alert-dark': !log.success && !log.error, 'alert-success': log.success && !log.error}">
|
<div v-for="(log, i) in notifier.logs.reverse()" class="alert" :class="{'alert-danger': log.error, 'alert-dark': !log.success && !log.error, 'alert-success': log.success && !log.error}">
|
||||||
<span class="d-block">
|
<span class="d-block">
|
||||||
Service '{{$store.getters.serviceById(log.service).name}}'
|
Service {{log.service}}
|
||||||
{{log.success ? "Success Triggered" : "Failure Triggered"}}
|
{{log.success ? "Success Triggered" : "Failure Triggered"}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="bg-white p-3 small mt-2">
|
<div v-if="log.message !== ''" class="bg-white p-3 small mt-2">
|
||||||
<code>{{log.message}}</code>
|
<code>{{log.message}}</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
const { startOfDay, startOfWeek, endOfMonth, startOfToday, startOfTomorrow, startOfYesterday, endOfYesterday, endOfTomorrow, endOfToday, endOfDay, startOfMonth, lastDayOfMonth, subSeconds, getUnixTime, fromUnixTime, differenceInSeconds, formatDistance, addMonths, addSeconds, isWithinInterval } = require('date-fns')
|
const { startOfDay, startOfHour, startOfWeek, endOfMonth, endOfHour, startOfToday, startOfTomorrow, startOfYesterday, endOfYesterday, endOfTomorrow, endOfToday, endOfDay, startOfMonth, lastDayOfMonth, subSeconds, getUnixTime, fromUnixTime, differenceInSeconds, formatDistance, addMonths, addSeconds, isWithinInterval } = require('date-fns')
|
||||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
|
||||||
import format from 'date-fns/format'
|
import format from 'date-fns/format'
|
||||||
import parseISO from 'date-fns/parseISO'
|
import parseISO from 'date-fns/parseISO'
|
||||||
|
@ -59,6 +59,8 @@ export default Vue.mixin({
|
||||||
},
|
},
|
||||||
endOf(method, val) {
|
endOf(method, val) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
|
case "hour":
|
||||||
|
return endOfHour(val)
|
||||||
case "day":
|
case "day":
|
||||||
return endOfDay(val)
|
return endOfDay(val)
|
||||||
case "today":
|
case "today":
|
||||||
|
@ -70,10 +72,17 @@ export default Vue.mixin({
|
||||||
case "month":
|
case "month":
|
||||||
return endOfMonth(val)
|
return endOfMonth(val)
|
||||||
}
|
}
|
||||||
return roundToNearestMinutes(val)
|
return val
|
||||||
|
},
|
||||||
|
startEndParams(start, end, group) {
|
||||||
|
start = this.beginningOf("hour", start)
|
||||||
|
end = this.endOf("hour", end)
|
||||||
|
return {start: this.toUnix(start), end: this.toUnix(end), group: group}
|
||||||
},
|
},
|
||||||
beginningOf(method, val) {
|
beginningOf(method, val) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
|
case "hour":
|
||||||
|
return startOfHour(val)
|
||||||
case "day":
|
case "day":
|
||||||
return startOfDay(val)
|
return startOfDay(val)
|
||||||
case "today":
|
case "today":
|
||||||
|
@ -83,11 +92,11 @@ export default Vue.mixin({
|
||||||
case "yesterday":
|
case "yesterday":
|
||||||
return startOfYesterday()
|
return startOfYesterday()
|
||||||
case "week":
|
case "week":
|
||||||
return startOfWeek()
|
return startOfWeek(val)
|
||||||
case "month":
|
case "month":
|
||||||
return startOfMonth(val)
|
return startOfMonth(val)
|
||||||
}
|
}
|
||||||
return roundToNearestMinutes(val)
|
return val
|
||||||
},
|
},
|
||||||
isZero(val) {
|
isZero(val) {
|
||||||
return getUnixTime(parseISO(val)) <= 0
|
return getUnixTime(parseISO(val)) <= 0
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container col-md-7 col-sm-12 mt-md-5">
|
<div class="container col-md-7 col-sm-12 mt-md-5">
|
||||||
|
<div v-if="modal" class="modal-backdrop"></div>
|
||||||
|
<Modal/>
|
||||||
<TopNav :admin="admin"/>
|
<TopNav :admin="admin"/>
|
||||||
<router-view :admin="admin"/>
|
<router-view :admin="admin"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Modal from "@/components/Elements/Modal";
|
||||||
const TopNav = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/TopNav')
|
const TopNav = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/TopNav')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
components: {
|
components: {
|
||||||
|
Modal,
|
||||||
TopNav,
|
TopNav,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
@ -20,6 +24,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
modal() {
|
||||||
|
return this.$store.getters.modal.visible
|
||||||
|
},
|
||||||
admin() {
|
admin() {
|
||||||
return this.$store.getters.admin
|
return this.$store.getters.admin
|
||||||
},
|
},
|
||||||
|
|
|
@ -2275,7 +2275,7 @@ OluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center small text-dim" v-pre>
|
<div class="text-center small text-dim" v-pre>
|
||||||
Automatically generated from Statping's Wiki on 2020-08-22 21:27:09.3468 +0000 UTC
|
Automatically generated from Statping's Wiki on 2020-09-02 02:46:04.864615 +0000 UTC
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,10 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="loaded">
|
<Group v-for="group in groups" v-bind:key="group.id" :group=group />
|
||||||
<Group v-for="group in groups" v-bind:key="group.id" :group=group />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 full-col-12">
|
<div class="col-12 full-col-12">
|
||||||
<MessageBlock v-for="message in messages" v-bind:key="message.id" :message="message" />
|
<MessageBlock v-for="message in messages" v-bind:key="message.id" :message="message" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,20 +63,18 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
loading_text() {
|
loading_text() {
|
||||||
if (this.core == null) {
|
if (!this.$store.getters.core.version) {
|
||||||
return "Loading Core"
|
return "Loading Core"
|
||||||
} else if (this.groups == null) {
|
} else if (this.$store.getters.groups.length === 0) {
|
||||||
return "Loading Groups"
|
return "Loading Groups"
|
||||||
} else if (this.services == null) {
|
} else if (this.$store.getters.services.length === 0) {
|
||||||
return "Loading Services"
|
return "Loading Services"
|
||||||
} else if (this.messages == null) {
|
} else if (this.$store.getters.messages == null) {
|
||||||
return "Loading Announcements"
|
return "Loading Announcements"
|
||||||
} else {
|
|
||||||
return "Completed"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loaded() {
|
loaded() {
|
||||||
return this.core !== null && this.groups !== null && this.services !== null
|
return this.$store.getters.core.version && this.$store.getters.services.length !== 0
|
||||||
},
|
},
|
||||||
core() {
|
core() {
|
||||||
return this.$store.getters.core
|
return this.$store.getters.core
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="offset-md-3 offset-lg-4 offset-0 col-lg-4 col-md-6 mt-5">
|
<div class="offset-md-3 offset-lg-4 offset-0 col-lg-4 col-md-6 mt-5">
|
||||||
|
|
||||||
<div class="offset-1 offset-lg-2 col-lg-8 col-10 mb-4 mb-md-3">
|
<div class="offset-1 offset-lg-2 col-lg-8 col-10 mb-4 mb-md-3">
|
||||||
<img alt="Statping Login" class="embed-responsive" src="http://0.0.0.0:8585/banner.png">
|
<img alt="Statping Login" class="embed-responsive" src="banner.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="login_container col-12 p-4">
|
<div class="login_container col-12 p-4">
|
||||||
|
|
|
@ -189,15 +189,23 @@
|
||||||
liClass(id) {
|
liClass(id) {
|
||||||
return this.tab === id
|
return this.tab === id
|
||||||
},
|
},
|
||||||
|
async renew() {
|
||||||
|
await Api.renewApiKeys()
|
||||||
|
const core = await Api.core()
|
||||||
|
this.$store.commit('setCore', core)
|
||||||
|
this.core = core
|
||||||
|
await this.logout()
|
||||||
|
},
|
||||||
async renewApiKeys() {
|
async renewApiKeys() {
|
||||||
let r = confirm("Are you sure you want to reset the API keys? You will be logged out.");
|
const modal = {
|
||||||
if (r === true) {
|
visible: true,
|
||||||
await Api.renewApiKeys()
|
title: "Reset API Key",
|
||||||
const core = await Api.core()
|
body: `Are you sure you want to reset the API keys? You will be logged out.`,
|
||||||
this.$store.commit('setCore', core)
|
btnColor: "btn-danger",
|
||||||
this.core = core
|
btnText: "Reset",
|
||||||
await this.logout()
|
func: () => this.renew(),
|
||||||
}
|
}
|
||||||
|
this.$store.commit("setModal", modal)
|
||||||
},
|
},
|
||||||
async logout () {
|
async logout () {
|
||||||
await Api.logout()
|
await Api.logout()
|
||||||
|
|
|
@ -31,7 +31,15 @@ export default new Vuex.Store({
|
||||||
checkins: [],
|
checkins: [],
|
||||||
admin: false,
|
admin: false,
|
||||||
user: false,
|
user: false,
|
||||||
loggedIn: false
|
loggedIn: false,
|
||||||
|
modal: {
|
||||||
|
visible: false,
|
||||||
|
title: "Modal Header",
|
||||||
|
body: "This is the content for the modal body",
|
||||||
|
btnText: "Save Changes",
|
||||||
|
btnColor: "btn-primary",
|
||||||
|
func: null,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
hasAllData: state => state.hasAllData,
|
hasAllData: state => state.hasAllData,
|
||||||
|
@ -49,6 +57,7 @@ export default new Vuex.Store({
|
||||||
notifiers: state => state.notifiers,
|
notifiers: state => state.notifiers,
|
||||||
checkins: state => state.checkins,
|
checkins: state => state.checkins,
|
||||||
loggedIn: state => state.loggedIn,
|
loggedIn: state => state.loggedIn,
|
||||||
|
modal: state => state.modal,
|
||||||
|
|
||||||
isAdmin: state => state.admin,
|
isAdmin: state => state.admin,
|
||||||
isUser: state => state.user,
|
isUser: state => state.user,
|
||||||
|
@ -140,6 +149,9 @@ export default new Vuex.Store({
|
||||||
setOAuth(state, oauth) {
|
setOAuth(state, oauth) {
|
||||||
state.oauth = oauth
|
state.oauth = oauth
|
||||||
},
|
},
|
||||||
|
setModal(state, modal) {
|
||||||
|
state.modal = modal
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async getAllServices(context) {
|
async getAllServices(context) {
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -30,6 +30,7 @@ require (
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.6.3
|
github.com/spf13/viper v1.6.3
|
||||||
|
github.com/statping/emails v1.0.0
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.5.1
|
||||||
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1
|
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1
|
||||||
github.com/tdewolff/minify/v2 v2.8.0 // indirect
|
github.com/tdewolff/minify/v2 v2.8.0 // indirect
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -568,6 +568,9 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
|
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
|
||||||
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
||||||
|
github.com/statping/emails v1.0.0 h1:90hGweEhr8wIFiy34KCkiFqGJlkug2gAQLVR6oSCFNU=
|
||||||
|
github.com/statping/emails v1.0.0/go.mod h1:xFU85jXaiWQadqHqu/jDrGsAn6WPSk1WgKyTVuFm0TI=
|
||||||
|
github.com/statping/statping v0.90.64/go.mod h1:lbyNPB73IjWtnommV4wSejYfgUT1yLhhqelMjl1ZBb8=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
|
|
@ -31,10 +31,12 @@ func apiIndexHandler(r *http.Request) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
|
func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
newApi := utils.Params.GetString("API_SECRET")
|
||||||
core.App.ApiSecret = utils.NewSHA256Hash()
|
if newApi == "" {
|
||||||
err = core.App.Update()
|
newApi = utils.NewSHA256Hash()
|
||||||
if err != nil {
|
}
|
||||||
|
core.App.ApiSecret = newApi
|
||||||
|
if err := core.App.Update(); err != nil {
|
||||||
sendErrorJson(err, w, r)
|
sendErrorJson(err, w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,14 @@ import (
|
||||||
func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
|
func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var notifs []notifications.Notification
|
var notifs []notifications.Notification
|
||||||
for _, n := range services.AllNotifiers() {
|
for _, n := range services.AllNotifiers() {
|
||||||
no := n.Select()
|
notif := n.Select()
|
||||||
notif, _ := notifications.Find(no.Method)
|
no, err := notifications.Find(notif.Method)
|
||||||
notifs = append(notifs, *no.UpdateFields(notif))
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
sendErrorJson(err, w, r)
|
||||||
|
}
|
||||||
|
notif.UpdateFields(no)
|
||||||
|
notifs = append(notifs, *notif)
|
||||||
}
|
}
|
||||||
sort.Sort(notifications.NotificationOrder(notifs))
|
sort.Sort(notifications.NotificationOrder(notifs))
|
||||||
returnJson(notifs, w, r)
|
returnJson(notifs, w, r)
|
||||||
|
|
|
@ -132,6 +132,7 @@ func Router() *mux.Router {
|
||||||
api.Handle("/api/services/{id}/failures", scoped(apiServiceFailuresHandler)).Methods("GET")
|
api.Handle("/api/services/{id}/failures", scoped(apiServiceFailuresHandler)).Methods("GET")
|
||||||
api.Handle("/api/services/{id}/failures", authenticated(servicesDeleteFailuresHandler, false)).Methods("DELETE")
|
api.Handle("/api/services/{id}/failures", authenticated(servicesDeleteFailuresHandler, false)).Methods("DELETE")
|
||||||
api.Handle("/api/services/{id}/hits", scoped(apiServiceHitsHandler)).Methods("GET")
|
api.Handle("/api/services/{id}/hits", scoped(apiServiceHitsHandler)).Methods("GET")
|
||||||
|
api.Handle("/api/services/{id}/hits", authenticated(apiServiceHitsDeleteHandler, false)).Methods("DELETE")
|
||||||
|
|
||||||
// API SERVICE CHART DATA Routes
|
// API SERVICE CHART DATA Routes
|
||||||
api.Handle("/api/services/{id}/hits_data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
|
api.Handle("/api/services/{id}/hits_data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
|
||||||
|
|
|
@ -237,6 +237,19 @@ func apiServiceTimeDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
returnJson(uptimeData, w, r)
|
returnJson(uptimeData, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiServiceHitsDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
service, err := findService(r)
|
||||||
|
if err != nil {
|
||||||
|
sendErrorJson(err, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := service.AllHits().DeleteAll(); err != nil {
|
||||||
|
sendErrorJson(err, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendJsonAction(service, "delete", w, r)
|
||||||
|
}
|
||||||
|
|
||||||
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
service, err := findService(r)
|
service, err := findService(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -64,7 +64,7 @@ var AmazonSNS = &amazonSNS{¬ifications.Notification{
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Title: "SNS Topic ARN",
|
Title: "SNS Topic ARN",
|
||||||
SmallText: "The ARN of the Topic",
|
SmallText: "The ARN of the Topic",
|
||||||
DbField: "host",
|
DbField: "Host",
|
||||||
Placeholder: "arn:aws:sns:us-west-2:123456789012:YourTopic",
|
Placeholder: "arn:aws:sns:us-west-2:123456789012:YourTopic",
|
||||||
Required: true,
|
Required: true,
|
||||||
}}},
|
}}},
|
||||||
|
|
|
@ -27,16 +27,15 @@ var Discorder = &discord{¬ifications.Notification{
|
||||||
Author: "Hunter Long",
|
Author: "Hunter Long",
|
||||||
AuthorUrl: "https://github.com/hunterlong",
|
AuthorUrl: "https://github.com/hunterlong",
|
||||||
Delay: time.Duration(5 * time.Second),
|
Delay: time.Duration(5 * time.Second),
|
||||||
Host: null.NewNullString("https://discordapp.com/api/webhooks/****/*****"),
|
|
||||||
Icon: "fab fa-discord",
|
Icon: "fab fa-discord",
|
||||||
SuccessData: null.NewNullString(`{"content": "Your service '{{.Service.Name}}' is currently online!"}`),
|
SuccessData: null.NewNullString(`{"content": "Your service '{{.Service.Name}}' is currently back online and was down for {{.Service.Downtime.Human}}."}`),
|
||||||
FailureData: null.NewNullString(`{"content": "Your service '{{.Service.Name}}' is currently failing! Reason: {{.Failure.Issue}}"}`),
|
FailureData: null.NewNullString(`{"content": "Your service '{{.Service.Name}}' is has been failing for {{.Service.Downtime.Human}}! Reason: {{.Failure.Issue}}"}`),
|
||||||
DataType: "json",
|
DataType: "json",
|
||||||
Limits: 60,
|
Limits: 60,
|
||||||
Form: []notifications.NotificationForm{{
|
Form: []notifications.NotificationForm{{
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Title: "discord webhooker URL",
|
Title: "discord webhooker URL",
|
||||||
Placeholder: "Insert your Webhook URL here",
|
Placeholder: "https://discordapp.com/api/webhooks/****/*****",
|
||||||
DbField: "host",
|
DbField: "host",
|
||||||
}}},
|
}}},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
package notifiers
|
package notifiers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-mail/mail"
|
"github.com/go-mail/mail"
|
||||||
|
"github.com/statping/emails"
|
||||||
"github.com/statping/statping/types/core"
|
"github.com/statping/statping/types/core"
|
||||||
"github.com/statping/statping/types/failures"
|
"github.com/statping/statping/types/failures"
|
||||||
"github.com/statping/statping/types/notifications"
|
"github.com/statping/statping/types/notifications"
|
||||||
"github.com/statping/statping/types/notifier"
|
"github.com/statping/statping/types/notifier"
|
||||||
"github.com/statping/statping/types/services"
|
"github.com/statping/statping/types/services"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"html/template"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ notifier.Notifier = (*emailer)(nil)
|
var _ notifier.Notifier = (*emailer)(nil)
|
||||||
|
@ -92,7 +91,7 @@ type emailOutgoing struct {
|
||||||
// OnFailure will trigger failing service
|
// OnFailure will trigger failing service
|
||||||
func (e *emailer) OnFailure(s services.Service, f failures.Failure) (string, error) {
|
func (e *emailer) OnFailure(s services.Service, f failures.Failure) (string, error) {
|
||||||
subject := fmt.Sprintf("Service %s is Offline", s.Name)
|
subject := fmt.Sprintf("Service %s is Offline", s.Name)
|
||||||
tmpl := renderEmail(s, f, emailFailure)
|
tmpl := renderEmail(s, f, emails.Failure)
|
||||||
email := &emailOutgoing{
|
email := &emailOutgoing{
|
||||||
To: e.Var2.String,
|
To: e.Var2.String,
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
|
@ -105,7 +104,7 @@ func (e *emailer) OnFailure(s services.Service, f failures.Failure) (string, err
|
||||||
// OnSuccess will trigger successful service
|
// OnSuccess will trigger successful service
|
||||||
func (e *emailer) OnSuccess(s services.Service) (string, error) {
|
func (e *emailer) OnSuccess(s services.Service) (string, error) {
|
||||||
subject := fmt.Sprintf("Service %s is Back Online", s.Name)
|
subject := fmt.Sprintf("Service %s is Back Online", s.Name)
|
||||||
tmpl := renderEmail(s, failures.Failure{}, emailSuccess)
|
tmpl := renderEmail(s, failures.Failure{}, emails.Success)
|
||||||
email := &emailOutgoing{
|
email := &emailOutgoing{
|
||||||
To: e.Var2.String,
|
To: e.Var2.String,
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
|
@ -116,27 +115,18 @@ func (e *emailer) OnSuccess(s services.Service) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderEmail(s services.Service, f failures.Failure, emailData string) string {
|
func renderEmail(s services.Service, f failures.Failure, emailData string) string {
|
||||||
wr := bytes.NewBuffer(nil)
|
|
||||||
tmpl := template.New("email")
|
|
||||||
tmpl, err := tmpl.Parse(emailData)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln(err)
|
|
||||||
return emailData
|
|
||||||
}
|
|
||||||
|
|
||||||
data := replacer{
|
data := replacer{
|
||||||
Core: *core.App,
|
Core: *core.App,
|
||||||
Service: s,
|
Service: s,
|
||||||
Failure: f,
|
Failure: f,
|
||||||
Custom: nil,
|
Custom: nil,
|
||||||
}
|
}
|
||||||
|
output, err := emails.Parse(emailData, data)
|
||||||
if err = tmpl.ExecuteTemplate(wr, "email", data); err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
return emailData
|
return emailData
|
||||||
}
|
}
|
||||||
|
return output
|
||||||
return wr.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnTest triggers when this notifier has been saved
|
// OnTest triggers when this notifier has been saved
|
||||||
|
|
|
@ -37,6 +37,8 @@ func InitNotifiers() {
|
||||||
Gotify,
|
Gotify,
|
||||||
AmazonSNS,
|
AmazonSNS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
services.UpdateNotifiers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceTemplate(tmpl string, data replacer) string {
|
func ReplaceTemplate(tmpl string, data replacer) string {
|
||||||
|
@ -56,10 +58,11 @@ func ReplaceTemplate(tmpl string, data replacer) string {
|
||||||
|
|
||||||
func Add(notifs ...services.ServiceNotifier) {
|
func Add(notifs ...services.ServiceNotifier) {
|
||||||
for _, n := range notifs {
|
for _, n := range notifs {
|
||||||
services.AddNotifier(n)
|
notif := n.Select()
|
||||||
if err := n.Select().Create(); err != nil {
|
if err := notif.Create(); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
services.AddNotifier(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/statping/statping/types/null"
|
"github.com/statping/statping/types/null"
|
||||||
"github.com/statping/statping/types/services"
|
"github.com/statping/statping/types/services"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -94,10 +93,5 @@ func (s *slack) OnSave() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *slack) Valid(values notifications.Values) error {
|
func (s *slack) Valid(values notifications.Values) error {
|
||||||
regex := `https\:\/\/hooks\.slack\.com/services/[A-Z0-9]{7,11}/[A-Z0-9]{7,11}/[a-zA-Z0-9]{20,22}`
|
|
||||||
r := regexp.MustCompile(regex)
|
|
||||||
if !r.MatchString(values.Host) {
|
|
||||||
return errors.New("slack webhook does not match with expected regex " + regex)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ func TestSlackNotifier(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Load slack", func(t *testing.T) {
|
t.Run("Load slack", func(t *testing.T) {
|
||||||
slacker.Host = null.NewNullString(SLACK_URL)
|
slacker.Host = null.NewNullString(SLACK_URL)
|
||||||
slacker.Delay = time.Duration(100 * time.Millisecond)
|
slacker.Delay = 100 * time.Millisecond
|
||||||
slacker.Limits = 3
|
slacker.Limits = 3
|
||||||
Add(slacker)
|
Add(slacker)
|
||||||
assert.Equal(t, "Hunter Long", slacker.Author)
|
assert.Equal(t, "Hunter Long", slacker.Author)
|
||||||
|
|
|
@ -8,7 +8,7 @@ func (c *Checkin) LastHit() *CheckinHit {
|
||||||
|
|
||||||
func (c *Checkin) Hits() []*CheckinHit {
|
func (c *Checkin) Hits() []*CheckinHit {
|
||||||
var hits []*CheckinHit
|
var hits []*CheckinHit
|
||||||
dbHits.Where("checkin = ?", c.Id).Order("DESC").Find(&hits)
|
dbHits.Where("checkin = ?", c.Id).Order("id DESC").Find(&hits)
|
||||||
c.AllHits = hits
|
c.AllHits = hits
|
||||||
return hits
|
return hits
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,21 +48,21 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) {
|
||||||
p.Set("ADMIN_EMAIL", email)
|
p.Set("ADMIN_EMAIL", email)
|
||||||
|
|
||||||
confg := &DbConfig{
|
confg := &DbConfig{
|
||||||
DbConn: dbConn,
|
DbConn: dbConn,
|
||||||
DbHost: dbHost,
|
DbHost: dbHost,
|
||||||
DbUser: dbUser,
|
DbUser: dbUser,
|
||||||
DbPass: dbPass,
|
DbPass: dbPass,
|
||||||
DbData: dbDatabase,
|
DbData: dbDatabase,
|
||||||
DbPort: int(dbPort),
|
DbPort: int(dbPort),
|
||||||
Project: project,
|
Project: project,
|
||||||
Description: description,
|
Description: description,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
Email: email,
|
Email: email,
|
||||||
Location: utils.Directory,
|
Location: utils.Directory,
|
||||||
Language: language,
|
Language: language,
|
||||||
SendReports: reports,
|
AllowReports: reports,
|
||||||
}
|
}
|
||||||
|
|
||||||
return confg, nil
|
return confg, nil
|
||||||
|
|
|
@ -59,7 +59,7 @@ func LoadConfigs(cfgFile string) (*DbConfig, error) {
|
||||||
if db.Language != "" {
|
if db.Language != "" {
|
||||||
p.Set("LANGUAGE", db.Language)
|
p.Set("LANGUAGE", db.Language)
|
||||||
}
|
}
|
||||||
if db.SendReports {
|
if db.AllowReports {
|
||||||
p.Set("ALLOW_REPORTS", true)
|
p.Set("ALLOW_REPORTS", true)
|
||||||
}
|
}
|
||||||
if db.LetsEncryptEmail != "" {
|
if db.LetsEncryptEmail != "" {
|
||||||
|
@ -88,11 +88,12 @@ func LoadConfigs(cfgFile string) (*DbConfig, error) {
|
||||||
Location: utils.Directory,
|
Location: utils.Directory,
|
||||||
SqlFile: p.GetString("SQL_FILE"),
|
SqlFile: p.GetString("SQL_FILE"),
|
||||||
Language: p.GetString("LANGUAGE"),
|
Language: p.GetString("LANGUAGE"),
|
||||||
SendReports: p.GetBool("ALLOW_REPORTS"),
|
AllowReports: p.GetBool("ALLOW_REPORTS"),
|
||||||
LetsEncryptEnable: p.GetBool("LETSENCRYPT_ENABLE"),
|
LetsEncryptEnable: p.GetBool("LETSENCRYPT_ENABLE"),
|
||||||
LetsEncryptHost: p.GetString("LETSENCRYPT_HOST"),
|
LetsEncryptHost: p.GetString("LETSENCRYPT_HOST"),
|
||||||
LetsEncryptEmail: p.GetString("LETSENCRYPT_EMAIL"),
|
LetsEncryptEmail: p.GetString("LETSENCRYPT_EMAIL"),
|
||||||
ApiSecret: p.GetString("API_SECRET"),
|
ApiSecret: p.GetString("API_SECRET"),
|
||||||
|
SampleData: p.GetBool("SAMPLE_DATA"),
|
||||||
}
|
}
|
||||||
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + cfgFile)
|
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + cfgFile)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ type DbConfig struct {
|
||||||
DbPort int `yaml:"port" json:"-"`
|
DbPort int `yaml:"port" json:"-"`
|
||||||
ApiSecret string `yaml:"api_secret" json:"-"`
|
ApiSecret string `yaml:"api_secret" json:"-"`
|
||||||
Language string `yaml:"language" json:"language"`
|
Language string `yaml:"language" json:"language"`
|
||||||
SendReports bool `yaml:"send_reports" json:"send_reports"`
|
AllowReports bool `yaml:"allow_reports" json:"allow_reports"`
|
||||||
Project string `yaml:"-" json:"-"`
|
Project string `yaml:"-" json:"-"`
|
||||||
Description string `yaml:"-" json:"-"`
|
Description string `yaml:"-" json:"-"`
|
||||||
Domain string `yaml:"-" json:"-"`
|
Domain string `yaml:"-" json:"-"`
|
||||||
|
@ -26,8 +26,28 @@ type DbConfig struct {
|
||||||
SqlFile string `yaml:"sqlfile,omitempty" json:"-"`
|
SqlFile string `yaml:"sqlfile,omitempty" json:"-"`
|
||||||
LetsEncryptHost string `yaml:"letsencrypt_host,omitempty" json:"letsencrypt_host"`
|
LetsEncryptHost string `yaml:"letsencrypt_host,omitempty" json:"letsencrypt_host"`
|
||||||
LetsEncryptEmail string `yaml:"letsencrypt_email,omitempty" json:"letsencrypt_email"`
|
LetsEncryptEmail string `yaml:"letsencrypt_email,omitempty" json:"letsencrypt_email"`
|
||||||
LetsEncryptEnable bool `yaml:"letsencrypt_enable" json:"letsencrypt_enable"`
|
LetsEncryptEnable bool `yaml:"letsencrypt_enable,omitempty" json:"letsencrypt_enable"`
|
||||||
LocalIP string `yaml:"-" json:"-"`
|
LocalIP string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
|
DisableHTTP bool `yaml:"disable_http" json:"disable_http"`
|
||||||
|
DemoMode bool `yaml:"demo_mode" json:"demo_mode"`
|
||||||
|
DisableLogs bool `yaml:"disable_logs" json:"disable_logs"`
|
||||||
|
UseAssets bool `yaml:"use_assets" json:"use_assets"`
|
||||||
|
BasePath string `yaml:"base_path" json:"base_path"`
|
||||||
|
|
||||||
|
AdminUser string `yaml:"admin_user" json:"admin_user"`
|
||||||
|
AdminPassword string `yaml:"admin_password" json:"admin_password"`
|
||||||
|
AdminEmail string `yaml:"admin_email" json:"admin_email"`
|
||||||
|
|
||||||
|
MaxOpenConnections int `yaml:"db_open_connections" json:"db_open_connections"`
|
||||||
|
MaxIdleConnections int `yaml:"db_idle_connections" json:"db_idle_connections"`
|
||||||
|
MaxLifeConnections int `yaml:"db_max_life_connections" json:"db_max_life_connections"`
|
||||||
|
|
||||||
|
SampleData bool `yaml:"sample_data" json:"sample_data"`
|
||||||
|
UseCDN bool `yaml:"use_cdn" json:"use_cdn"`
|
||||||
|
DisableColors bool `yaml:"disable_colors" json:"disable_colors"`
|
||||||
|
|
||||||
|
PostgresSSLMode string `yaml:"postgres_ssl" json:"postgres_ssl"`
|
||||||
|
|
||||||
Db database.Database `yaml:"-" json:"-"`
|
Db database.Database `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (i *Incident) BeforeCreate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Incident) AfterFind() {
|
func (i *Incident) AfterFind() {
|
||||||
db.Model(i).Related(&i.Updates).Order("DESC")
|
db.Model(i).Related(&i.Updates).Order("id DESC")
|
||||||
metrics.Query("incident", "find")
|
metrics.Query("incident", "find")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,15 @@ func (n *Notification) Values() Values {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func All() []*Notification {
|
||||||
|
var n []*Notification
|
||||||
|
q := db.Find(&n)
|
||||||
|
if q.Error() != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
func Find(method string) (*Notification, error) {
|
func Find(method string) (*Notification, error) {
|
||||||
var n Notification
|
var n Notification
|
||||||
q := db.Where("method = ?", method).Find(&n)
|
q := db.Where("method = ?", method).Find(&n)
|
||||||
|
@ -38,6 +47,7 @@ func (n *Notification) Create() error {
|
||||||
var p Notification
|
var p Notification
|
||||||
q := db.Where("method = ?", n.Method).Find(&p)
|
q := db.Where("method = ?", n.Method).Find(&p)
|
||||||
if q.RecordNotFound() {
|
if q.RecordNotFound() {
|
||||||
|
log.Infof("Notifier '%s' was not found, adding into database...\n", n.Method)
|
||||||
if err := db.Create(n).Error(); err != nil {
|
if err := db.Create(n).Error(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -56,6 +66,9 @@ func (n *Notification) Create() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notification) UpdateFields(notif *Notification) *Notification {
|
func (n *Notification) UpdateFields(notif *Notification) *Notification {
|
||||||
|
if notif == nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
n.Id = notif.Id
|
n.Id = notif.Id
|
||||||
n.Limits = notif.Limits
|
n.Limits = notif.Limits
|
||||||
n.Enabled = notif.Enabled
|
n.Enabled = notif.Enabled
|
||||||
|
|
|
@ -38,12 +38,10 @@ type Notification struct {
|
||||||
AuthorUrl string `gorm:"-" json:"author_url"`
|
AuthorUrl string `gorm:"-" json:"author_url"`
|
||||||
Icon string `gorm:"-" json:"icon"`
|
Icon string `gorm:"-" json:"icon"`
|
||||||
Delay time.Duration `gorm:"-" json:"delay,string"`
|
Delay time.Duration `gorm:"-" json:"delay,string"`
|
||||||
Running chan bool `gorm:"-" json:"-"`
|
|
||||||
|
|
||||||
Form []NotificationForm `gorm:"-" json:"form"`
|
Form []NotificationForm `gorm:"-" json:"form"`
|
||||||
LastSent time.Time `gorm:"-" json:"-"`
|
LastSent time.Time `gorm:"-" json:"-"`
|
||||||
LastSentCount int `gorm:"-" json:"-"`
|
LastSentCount int `gorm:"-" json:"-"`
|
||||||
sentCount int `gorm:"-" json:"-"`
|
|
||||||
Logs []*NotificationLog `gorm:"-" json:"logs,omitempty"`
|
Logs []*NotificationLog `gorm:"-" json:"logs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +57,6 @@ func (n *Notification) Logger() *logrus.Logger {
|
||||||
return log.WithField("notifier", n.Method).Logger
|
return log.WithField("notifier", n.Method).Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunFunc func(interface{}) error
|
|
||||||
|
|
||||||
type Values struct {
|
type Values struct {
|
||||||
Host string
|
Host string
|
||||||
Port int64
|
Port int64
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
package null
|
package null
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s NullString) Value() (driver.Value, error) {
|
||||||
|
return s.String, nil
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON for NullInt64
|
// MarshalJSON for NullInt64
|
||||||
func (i NullInt64) MarshalJSON() ([]byte, error) {
|
func (i NullInt64) MarshalJSON() ([]byte, error) {
|
||||||
if !i.Valid {
|
if !i.Valid {
|
||||||
|
@ -32,7 +37,7 @@ func (bb NullBool) MarshalJSON() ([]byte, error) {
|
||||||
// MarshalJSON for NullString
|
// MarshalJSON for NullString
|
||||||
func (s NullString) MarshalJSON() ([]byte, error) {
|
func (s NullString) MarshalJSON() ([]byte, error) {
|
||||||
if !s.Valid {
|
if !s.Valid {
|
||||||
return []byte("null"), nil
|
return json.Marshal(nil)
|
||||||
}
|
}
|
||||||
return json.Marshal(s.String)
|
return json.Marshal(s.String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,13 @@ func AddNotifier(n ServiceNotifier) {
|
||||||
allNotifiers[notif.Method] = n
|
allNotifiers[notif.Method] = n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateNotifiers() {
|
||||||
|
for _, n := range notifications.All() {
|
||||||
|
notifier := allNotifiers[n.Method]
|
||||||
|
notifier.Select().UpdateFields(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func sendSuccess(s *Service) {
|
func sendSuccess(s *Service) {
|
||||||
if !s.AllowNotifications.Bool {
|
if !s.AllowNotifications.Bool {
|
||||||
return
|
return
|
||||||
|
|
|
@ -14,6 +14,15 @@ func (u *User) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) BeforeDelete() error {
|
||||||
|
if utils.Params.GetBool("DEMO_MODE") {
|
||||||
|
if u.Username == "admin" {
|
||||||
|
return errors.New("cannot delete admin in DEMO_MODE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) BeforeCreate() error {
|
func (u *User) BeforeCreate() error {
|
||||||
if err := u.Validate(); err != nil {
|
if err := u.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
var (
|
|
||||||
StartTime = Now()
|
|
||||||
)
|
|
|
@ -192,7 +192,8 @@ func TestHttpRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigLoad(t *testing.T) {
|
func TestConfigLoad(t *testing.T) {
|
||||||
InitLogs()
|
err := InitLogs()
|
||||||
|
require.Nil(t, err)
|
||||||
InitEnvs()
|
InitEnvs()
|
||||||
|
|
||||||
s := Params.GetString
|
s := Params.GetString
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.90.64
|
0.90.65
|
||||||
|
|
Loading…
Reference in New Issue