mirror of https://github.com/statping/statping
design changes, incidents
parent
2ab4fd97f3
commit
3e791c2c74
|
@ -1,3 +1,7 @@
|
|||
# 0.90.23
|
||||
- Added Incident Reporting
|
||||
- Added Cypress tests
|
||||
|
||||
# 0.90.22
|
||||
- Added range input types for integer form fields
|
||||
- Modified Sentry error logging details
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/// <reference types="cypress" />
|
||||
|
||||
import "../support/commands"
|
||||
|
||||
context('Incidents Tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
cy.restoreLocalStorageCache();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.saveLocalStorageCache();
|
||||
});
|
||||
|
||||
it('should Login', () => {
|
||||
cy.visit('/login')
|
||||
cy.get('#username').clear().type('admin')
|
||||
cy.get('#password').clear().type('admin')
|
||||
cy.get('button[type="submit"]').click()
|
||||
|
||||
cy.get('.navbar-brand').should('contain', 'Statping')
|
||||
cy.getCookies()
|
||||
|
||||
cy.getCookies().should('have.length', 1)
|
||||
})
|
||||
|
||||
it('should create new incident', () => {
|
||||
cy.visit('/dashboard')
|
||||
cy.wait(3000)
|
||||
cy.get('.service_block').eq(0).find(".incident").click()
|
||||
cy.get('#title').clear().type('Downtime')
|
||||
cy.get('#description').clear().type('Recently we found an issue with authentication')
|
||||
cy.get('button[type="submit"]').click()
|
||||
})
|
||||
|
||||
})
|
|
@ -26,35 +26,35 @@ context('Services Tests', () => {
|
|||
|
||||
it('should goto services', () => {
|
||||
cy.visit('/dashboard/services')
|
||||
cy.get(':nth-child(1) > .card-body > .table > tbody > tr').should('have.length', 5)
|
||||
cy.get('#services_list > tr').should('have.length', 5)
|
||||
cy.get('.sortable_groups > tr').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('should create new HTTP service', () => {
|
||||
cy.visit('/dashboard/create_service')
|
||||
cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('HTTP Service')
|
||||
cy.get('#name').clear().type('HTTP Service')
|
||||
cy.get('#service_type').select('http')
|
||||
cy.get('#service_url').clear().type('http://localhost:8888')
|
||||
cy.get('#service_response_code').clear().type('200')
|
||||
cy.get('#service_interval').clear().type('30')
|
||||
cy.get(':nth-child(3) > .card-body > :nth-child(2) > .col-sm-8 > .form-control').clear().type('5')
|
||||
cy.get('#service_interval').invoke('val', 30).trigger('change')
|
||||
cy.get('#timeout').invoke('val', 5).trigger('change')
|
||||
cy.get('#permalink').clear().type('http_service')
|
||||
|
||||
cy.get('#notify_after').clear().type('3')
|
||||
cy.get('#notify_after').invoke('val', 3).trigger('change')
|
||||
|
||||
cy.get('button[type="submit"]').click()
|
||||
})
|
||||
|
||||
it('should create new TCP service', () => {
|
||||
cy.visit('/dashboard/create_service')
|
||||
cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('TCP Service')
|
||||
cy.get('#name').clear().type('TCP Service')
|
||||
cy.get('#service_type').select('tcp')
|
||||
cy.get('#service_url').clear().type('localhost')
|
||||
cy.get('#service_port').clear().type('8888')
|
||||
|
||||
cy.get('#service_interval').clear().type('30')
|
||||
cy.get(':nth-child(3) > .card-body > :nth-child(2) > .col-sm-8 > .form-control').clear().type('5')
|
||||
cy.get('#notify_after').clear().type('3')
|
||||
cy.get('#service_interval').invoke('val', 30).trigger('change')
|
||||
cy.get('#timeout').invoke('val', 5).trigger('change')
|
||||
cy.get('#notify_after').invoke('val', 3).trigger('change')
|
||||
|
||||
cy.get('#permalink').clear().type('tcp_service')
|
||||
|
||||
|
@ -63,14 +63,14 @@ context('Services Tests', () => {
|
|||
|
||||
it('should create new UDP service', () => {
|
||||
cy.visit('/dashboard/create_service')
|
||||
cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('UDP Service')
|
||||
cy.get('#name').clear().type('UDP Service')
|
||||
cy.get('#service_type').select('udp')
|
||||
cy.get('#service_url').clear().type('8.8.8.8')
|
||||
cy.get('#service_port').clear().type('53')
|
||||
|
||||
cy.get('#service_interval').clear().type('30')
|
||||
cy.get(':nth-child(3) > .card-body > :nth-child(2) > .col-sm-8 > .form-control').clear().type('5')
|
||||
cy.get('#notify_after').clear().type('3')
|
||||
cy.get('#service_interval').invoke('val', 30).trigger('change')
|
||||
cy.get('#timeout').invoke('val', 5).trigger('change')
|
||||
cy.get('#notify_after').invoke('val', 3).trigger('change')
|
||||
|
||||
cy.get('#permalink').clear().type('udp_service')
|
||||
|
||||
|
@ -79,12 +79,12 @@ context('Services Tests', () => {
|
|||
|
||||
it('should create new ICMP service', () => {
|
||||
cy.visit('/dashboard/create_service')
|
||||
cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('ICMP Service')
|
||||
cy.get('#name').clear().type('ICMP Service')
|
||||
cy.get('#service_type').select('icmp')
|
||||
cy.get('#service_url').clear().type('8.8.8.8')
|
||||
|
||||
cy.get('#service_interval').clear().type('30')
|
||||
cy.get('#notify_after').clear().type('3')
|
||||
cy.get('#service_interval').invoke('val', 30).trigger('change')
|
||||
cy.get('#notify_after').invoke('val', 3).trigger('change')
|
||||
|
||||
cy.get('#permalink').clear().type('icmp_service')
|
||||
|
||||
|
@ -93,7 +93,16 @@ context('Services Tests', () => {
|
|||
|
||||
it('should confirm new services', () => {
|
||||
cy.visit('/dashboard/services')
|
||||
cy.get(':nth-child(1) > .card-body > .table > tbody > tr').should('have.length', 9)
|
||||
cy.get('#services_list > tr').should('have.length', 9)
|
||||
})
|
||||
|
||||
it('should delete new services', () => {
|
||||
cy.visit('/dashboard/services')
|
||||
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
|
||||
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
|
||||
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
|
||||
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
|
||||
cy.get('#services_list > tr').should('have.length', 4)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
|
||||
<base href="/">
|
||||
<title>Statping</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="(service, index) in $store.getters.services" v-bind:key="index">
|
||||
<div v-for="(service, index) in $store.getters.services" class="service_block" v-bind:key="index">
|
||||
<ServiceInfo :service=service />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<draggable tag="tbody" v-model="servicesList" handle=".drag_icon">
|
||||
<draggable id="services_list" tag="tbody" v-model="servicesList" handle=".drag_icon">
|
||||
<tr v-for="(service, index) in $store.getters.servicesInOrder" :key="service.id">
|
||||
<td>
|
||||
<span v-if="$store.state.admin" class="drag_icon d-none d-md-inline">
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
<GroupServiceFailures :service="service"/>
|
||||
|
||||
<IncidentsBlock :service="service"/>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
@ -18,10 +19,12 @@
|
|||
<script>
|
||||
import Api from '../../API';
|
||||
import GroupServiceFailures from './GroupServiceFailures';
|
||||
import IncidentsBlock from './IncidentsBlock';
|
||||
|
||||
export default {
|
||||
name: 'Group',
|
||||
components: {
|
||||
IncidentsBlock,
|
||||
GroupServiceFailures
|
||||
|
||||
},
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<div class="row mt-4">
|
||||
<div v-for="(incident, i) in incidents" class="col-12">
|
||||
<h6><span class="badge badge-secondary">New</span>
|
||||
{{incident.title}}
|
||||
<span class="font-2">{{incident.created_at}}</span>
|
||||
</h6>
|
||||
{{incident.description}}
|
||||
{{incident}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '../../API';
|
||||
|
||||
export default {
|
||||
name: 'IncidentsBlock',
|
||||
props: {
|
||||
service: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
incidents: null,
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.getIncidents()
|
||||
},
|
||||
methods: {
|
||||
async getIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service)
|
||||
},
|
||||
async getIncidentsUpdates() {
|
||||
this.incidents = await Api.incidents_service(this.service)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -1,7 +1,8 @@
|
|||
<template v-if="service">
|
||||
<div class="col-12 card mb-4" style="min-height: 280px;" :class="{'offline-card': !service.online}">
|
||||
<div class="card-body p-3 p-md-1 pt-md-3 pb-md-1">
|
||||
<h4 class="card-title mb-4"><router-link :to="serviceLink(service)">{{service.name}}</router-link>
|
||||
<h4 class="card-title mb-4">
|
||||
<router-link :to="serviceLink(service)">{{service.name}}</router-link>
|
||||
<span class="badge float-right" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
|
||||
{{service.online ? "ONLINE" : "OFFLINE"}}
|
||||
</span>
|
||||
|
@ -44,13 +45,13 @@
|
|||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<button @click.prevent="Tab('incident')" class="btn btn-block btn-outline-secondary" :class="{'text-white btn-secondary': openTab==='incident'}" >Create Incident</button>
|
||||
<button @click.prevent="Tab('incident')" class="btn btn-block btn-outline-secondary incident" :class="{'text-white btn-secondary': openTab==='incident'}" >Incidents</button>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button @click.prevent="Tab('message')" class="btn btn-block btn-outline-secondary" :class="{'text-white btn-secondary': openTab==='message'}">Create Announcement</button>
|
||||
<button @click.prevent="Tab('message')" class="btn btn-block btn-outline-secondary message" :class="{'text-white btn-secondary': openTab==='message'}">Announcements</button>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button @click.prevent="Tab('failures')" class="btn btn-block btn-outline-secondary" :disabled="service.stats.failures === 0" :class="{'text-white btn-secondary': openTab==='failures'}">
|
||||
<button @click.prevent="Tab('failures')" class="btn btn-block btn-outline-secondary failures" :disabled="service.stats.failures === 0" :class="{'text-white btn-secondary': openTab==='failures'}">
|
||||
Failures <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span></button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<div v-for="(incident, i) in incidents" class="card contain-card text-black-50 bg-white mb-4">
|
||||
<div class="card-header">Incident: {{incident.title}}
|
||||
<button v-if="IsAdmin()" @click="deleteIncident(incident)" class="btn btn-sm btn-danger float-right">
|
||||
<button @click="deleteIncident(incident)" class="btn btn-sm btn-danger float-right">
|
||||
<font-awesome-icon icon="times" /> Delete
|
||||
</button></div>
|
||||
<div class="card-body bg-light pt-1">
|
||||
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div v-if="IsAdmin()" class="card contain-card text-black-50 bg-white mb-5">
|
||||
<div class="card contain-card text-black-50 bg-white mb-5">
|
||||
<div class="card-header">Create Incident for {{service.name}}</div>
|
||||
<div class="card-body">
|
||||
<form @submit.prevent="createIncident">
|
||||
|
@ -79,16 +79,16 @@
|
|||
await this.loadIncidents()
|
||||
},
|
||||
methods: {
|
||||
async loadIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service)
|
||||
},
|
||||
async loadIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service)
|
||||
},
|
||||
async createIncident() {
|
||||
await Api.incident_create(this.service, this.incident)
|
||||
await Api.incident_create(this.service, this.incident)
|
||||
await this.loadIncidents()
|
||||
this.incident = {
|
||||
title: "",
|
||||
description: "",
|
||||
service: this.service.id,
|
||||
description: "",
|
||||
service: this.service.id,
|
||||
}
|
||||
},
|
||||
async deleteIncident(incident) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Service Name</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.name" @input="updatePermalink" type="text" name="name" class="form-control" placeholder="Server Name" required spellcheck="false" autocorrect="off">
|
||||
<input v-model="service.name" @input="updatePermalink" id="name" type="text" name="name" class="form-control" placeholder="Server Name" required spellcheck="false" autocorrect="off">
|
||||
<small class="form-text text-muted">Give your service a name you can recognize</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -101,7 +101,7 @@
|
|||
<label class="col-sm-4 col-form-label">Request Timeout</label>
|
||||
<div class="col-sm-8">
|
||||
<span class="slider-info">{{secondsHumanize(service.timeout)}}</span>
|
||||
<input v-model="service.timeout" type="range" name="timeout" class="slider" min="1" max="180">
|
||||
<input v-model="service.timeout" type="range" id="timeout" name="timeout" class="slider" min="1" max="180">
|
||||
<small class="form-text text-muted">If the endpoint does not respond within this time it will be considered to be offline</small>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@ func Samples() error {
|
|||
|
||||
group2 := &Group{
|
||||
Name: "Linked Services",
|
||||
Public: null.NewNullBool(false),
|
||||
Public: null.NewNullBool(true),
|
||||
Order: 1,
|
||||
}
|
||||
if err := group2.Create(); err != nil {
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package incidents
|
||||
|
||||
import (
|
||||
"github.com/statping/statping/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Samples() error {
|
||||
incident1 := &Incident{
|
||||
Title: "Github Downtime",
|
||||
|
@ -10,30 +15,12 @@ func Samples() error {
|
|||
return err
|
||||
}
|
||||
|
||||
i1 := &IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github's page for Statping seems to be sending a 501 error.",
|
||||
Type: "Investigating",
|
||||
incident2 := &Incident{
|
||||
Title: "Recent Downtime",
|
||||
Description: "We've noticed an issue with authentications and we're looking into it now.",
|
||||
ServiceId: 4,
|
||||
}
|
||||
if err := i1.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i2 := &IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Problem is continuing and we are looking at the issues.",
|
||||
Type: "Update",
|
||||
}
|
||||
if err := i2.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i3 := &IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github is now back online and everything is working.",
|
||||
Type: "Resolved",
|
||||
}
|
||||
if err := i3.Create(); err != nil {
|
||||
if err := incident2.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -41,13 +28,56 @@ func Samples() error {
|
|||
}
|
||||
|
||||
func SamplesUpdates() error {
|
||||
u1 := &IncidentUpdate{
|
||||
t := utils.Now()
|
||||
|
||||
i1 := &IncidentUpdate{
|
||||
IncidentId: 1,
|
||||
Message: "Github's page for Statping seems to be sending a 501 error.",
|
||||
Type: "Investigating",
|
||||
CreatedAt: t.Add(-60 * time.Minute),
|
||||
}
|
||||
if err := i1.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i2 := &IncidentUpdate{
|
||||
IncidentId: 1,
|
||||
Message: "Problem is continuing and we are looking at the issues.",
|
||||
Type: "Update",
|
||||
CreatedAt: t.Add(-30 * time.Minute),
|
||||
}
|
||||
if err := i2.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i3 := &IncidentUpdate{
|
||||
IncidentId: 1,
|
||||
Message: "Github is now back online and everything is working.",
|
||||
Type: "Resolved",
|
||||
CreatedAt: t.Add(-5 * time.Minute),
|
||||
}
|
||||
if err := i3.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u1 := &IncidentUpdate{
|
||||
IncidentId: 2,
|
||||
Message: "Github is now back online and everything is working.",
|
||||
Type: "Resolved",
|
||||
CreatedAt: t.Add(-120 * time.Minute),
|
||||
}
|
||||
if err := u1.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u2 := &IncidentUpdate{
|
||||
IncidentId: 2,
|
||||
Message: "Github is now back online and everything is working.",
|
||||
Type: "Resolved",
|
||||
CreatedAt: t.Add(-60 * time.Minute),
|
||||
}
|
||||
if err := u2.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.90.22
|
||||
0.90.23
|
||||
|
|
Loading…
Reference in New Issue