mirror of https://github.com/statping/statping
PCORE-2161: Adding edit option to created incidents in admin page for external status page (#33)
* feat: added edit option for created incident and fixed bugs in dashboard service summary * feat: added loading ui when adding incidents * fix: downtime page log errors * fix: downtime servie name error * fix: minor style changepull/1113/head
parent
b86b998808
commit
64be130929
|
@ -148,6 +148,10 @@ class Api {
|
|||
return axios.post('api/services/' + service_id + '/incidents', data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async incident_edit(incident_id, data) {
|
||||
return axios.patch('api/incidents/' + incident_id, data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async incident_delete(incident) {
|
||||
return axios.delete('api/incidents/' + incident.id).then(response => (response.data))
|
||||
}
|
||||
|
|
|
@ -53,8 +53,8 @@
|
|||
:key="downtime.id"
|
||||
>
|
||||
<td>
|
||||
<span>
|
||||
{{ downtime.service.name }}
|
||||
<span :class="{'text-danger': !downtime.service}">
|
||||
{{ (downtime.service && downtime.service.name) || 'Deleted service' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
|
@ -65,16 +65,14 @@
|
|||
</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
<span>
|
||||
{{ downtime.end ? niceDateWithYear(downtime.end) : 'Ongoing' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<span
|
||||
class="badge text-uppercase"
|
||||
:class="[downtime.sub_status === 'down' ? 'badge-danger' : 'badge-warning' ]"
|
||||
:class="[downtime.sub_status === 'down' ? 'badge-danger' : 'badge-warning']"
|
||||
>
|
||||
{{ downtime.sub_status }}
|
||||
</span>
|
||||
|
@ -87,7 +85,7 @@
|
|||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<div v-if="downtime.service" class="btn-group">
|
||||
<button
|
||||
v-if="$store.state.admin"
|
||||
:disabled="isLoading"
|
||||
|
|
|
@ -3,9 +3,15 @@
|
|||
|
||||
<div v-for="incident in incidents" :key="incident.id" class="card contain-card mb-4">
|
||||
<div class="card-header">Incident: {{incident.title}}
|
||||
<button @click="deleteIncident(incident)" class="btn btn-sm btn-danger float-right">
|
||||
<font-awesome-icon icon="times" />
|
||||
</button>
|
||||
<div v-if="$store.state.admin" class="btn-group float-right">
|
||||
<button @click.prevent="editIncident(incident)" class="btn btn-sm btn-outline-secondary" type="button">
|
||||
<FontAwesomeIcon icon="edit" />
|
||||
</button>
|
||||
<button @click.prevent="deleteIncident(incident)" class="btn btn-sm btn-danger" type="button" :disabled="incidentId === incident.id && isLoading">
|
||||
<FontAwesomeIcon v-if="incidentId === incident.id && isLoading" icon="circle-notch" spin />
|
||||
<FontAwesomeIcon v-else icon="trash" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormIncidentUpdates :incident="incident"/>
|
||||
|
@ -15,9 +21,16 @@
|
|||
|
||||
|
||||
<div class="card contain-card">
|
||||
<div class="card-header">Create Incident</div>
|
||||
<div class="card-header">{{incident.id ? `${$t('update')} ${incident.title}` : $t('incident_create')}}
|
||||
<transition name="slide-fade">
|
||||
<button @click="resetIncident" v-if="incident.id" class="btn btn-sm float-right btn-danger btn-sm">
|
||||
{{ $t('cancel') }}
|
||||
</button>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form @submit.prevent="createIncident">
|
||||
<form @submit.prevent="saveMessage">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Title</label>
|
||||
<div class="col-sm-8">
|
||||
|
@ -34,10 +47,11 @@
|
|||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button @click.prevent="createIncident"
|
||||
:disabled="!incident.title || !incident.description"
|
||||
<button @click.prevent="saveMessage"
|
||||
:disabled="!incident.title || !incident.description || isLoading"
|
||||
type="submit" class="btn btn-block btn-primary">
|
||||
Create Incident
|
||||
{{incident.id ? $t('incident_edit') : $t('incident_create')}}
|
||||
<FontAwesomeIcon v-if="isLoading" icon="circle-notch" spin />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -54,20 +68,22 @@ import Api from "../../API";
|
|||
|
||||
const FormIncidentUpdates = () => import(/* webpackChunkName: "dashboard" */ '@/forms/IncidentUpdates')
|
||||
|
||||
export default {
|
||||
name: 'Incidents',
|
||||
components: {FormIncidentUpdates},
|
||||
data() {
|
||||
return {
|
||||
serviceID: 0,
|
||||
incidents: [],
|
||||
incident: {
|
||||
title: "",
|
||||
description: "",
|
||||
service: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
export default {
|
||||
name: 'Incidents',
|
||||
components: {FormIncidentUpdates},
|
||||
data() {
|
||||
return {
|
||||
serviceID: 0,
|
||||
incidents: [],
|
||||
isLoading: false,
|
||||
incidentId: null,
|
||||
incident: {
|
||||
title: "",
|
||||
description: "",
|
||||
service: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.serviceID = Number(this.$route.params.id);
|
||||
|
@ -80,13 +96,19 @@ const FormIncidentUpdates = () => import(/* webpackChunkName: "dashboard" */ '@/
|
|||
|
||||
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 delete(i) {
|
||||
this.isLoading = true;
|
||||
this.incidentId = i.id;
|
||||
|
||||
this.res = await Api.incident_delete(i)
|
||||
if (this.res.status === "success") {
|
||||
this.incidents = this.incidents.filter(obj => obj.id !== i.id);
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
this.incidentId = null;
|
||||
},
|
||||
|
||||
async deleteIncident(incident) {
|
||||
const modal = {
|
||||
visible: true,
|
||||
|
@ -99,23 +121,62 @@ const FormIncidentUpdates = () => import(/* webpackChunkName: "dashboard" */ '@/
|
|||
this.$store.commit("setModal", modal)
|
||||
},
|
||||
|
||||
async saveMessage() {
|
||||
if (this.incident.id) {
|
||||
this.updateIncident();
|
||||
} else {
|
||||
this.createIncident();
|
||||
}
|
||||
},
|
||||
|
||||
async createIncident() {
|
||||
this.res = await Api.incident_create(this.serviceID, this.incident)
|
||||
if (this.res.status === "success") {
|
||||
this.incidents.push(this.res.output) // this is better in terms of not having to querry the db to get a fresh copy of all updates
|
||||
this.isLoading = true;
|
||||
|
||||
const res = await Api.incident_create(this.serviceID, this.incident)
|
||||
if (res.status === "success") {
|
||||
this.incidents.push(res.output) // this is better in terms of not having to querry the db to get a fresh copy of all updates
|
||||
//await this.loadIncidents()
|
||||
} // TODO: further error checking here... maybe alert user it failed with modal or so
|
||||
|
||||
// reset the form data
|
||||
this.incident = {
|
||||
title: "",
|
||||
description: "",
|
||||
service: this.serviceID,
|
||||
this.resetIncident();
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
||||
async updateIncident() {
|
||||
const {id, title, description} = this.incident;
|
||||
|
||||
this.isLoading = true;
|
||||
const res = await Api.incident_edit(id, {title, description});
|
||||
if (res.status === "success") {
|
||||
this.incidents = this.incidents.map(incident => {
|
||||
if(incident.id === id) {
|
||||
return res.output
|
||||
}
|
||||
|
||||
return incident;
|
||||
});
|
||||
|
||||
this.resetIncident();
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
||||
editIncident(incident) {
|
||||
this.incident = incident;
|
||||
},
|
||||
|
||||
async loadIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.serviceID)
|
||||
},
|
||||
|
||||
resetIncident() {
|
||||
// reset the form data
|
||||
this.incident = {
|
||||
title: "",
|
||||
description: "",
|
||||
service: 0,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ name: "ServiceEvents",
|
|||
return this.$store.getters.serviceMessages(this.service.id)
|
||||
},
|
||||
success_event() {
|
||||
if (this.service.online && this.service.messages.length === 0 && this.service.incidents.length === 0) {
|
||||
if (this.service.online && !this.service.messages && !this.service.incidents) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="dashboard_card card mb-4" :class="{'offline-card': !service.online}">
|
||||
<div class="card-header pb-1">
|
||||
<h6 v-observe-visibility="setVisible">
|
||||
<h6 v-observe-visibility="setVisible" class="d-flex align-items-baseline justify-content-between">
|
||||
<router-link :to="serviceLink(service)" class="no-decoration">{{service.name}}</router-link>
|
||||
<span class="badge float-right text-uppercase" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
|
||||
<span class="badge text-uppercase" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
|
||||
{{service.online ? $t('online') : $t('offline')}}
|
||||
</span>
|
||||
</h6>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<font-awesome-icon icon="calendar-check"/>
|
||||
</button>
|
||||
<button @click="$router.push({path: `/dashboard/service/${service.id}/failures`, params: {id: service.id}})" @mouseleave="unsetHover" @mouseover="setHover($t('failures'))" class="btn btn-sm btn-white failures">
|
||||
<font-awesome-icon icon="exclamation-triangle"/> <span v-if="service.stats.failures !== 0" class="badge badge-danger ml-1">{{service.stats.failures}}</span>
|
||||
<font-awesome-icon icon="exclamation-triangle"/><span v-if="service.stats && service.stats.failures !== 0" class="badge badge-danger ml-1">{{service.stats.failures}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
<div class="col-12 mb-3 pb-2 border-bottom" role="alert">
|
||||
<span class="font-weight-bold text-capitalize" :class="{'text-success': update.type.toLowerCase()==='resolved', 'text-danger': update.type.toLowerCase()==='issue summary', 'text-warning': update.type.toLowerCase()==='update'}">{{update.type}}</span>
|
||||
<span class="text-muted">- {{update.message}}
|
||||
<button v-if="admin" @click="delete_update(update)" type="button" class="close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button v-if="admin" :disabled="isLoading && incidentId" @click="delete_update(update)" type="button" class="close">
|
||||
<FontAwesomeIcon v-if="isLoading && incidentId === update.id" icon="circle-notch" spin size="xs" />
|
||||
<FontAwesomeIcon v-else icon="trash" size="xs" />
|
||||
</button>
|
||||
</span>
|
||||
<span class="d-block small">{{ago(update.created_at)}} ago</span>
|
||||
|
@ -26,11 +27,24 @@
|
|||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
incidentId: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async delete_update(update) {
|
||||
this.res = await Api.incident_update_delete(update)
|
||||
if (this.res.status === "success") {
|
||||
this.onUpdate()
|
||||
this.isLoading = true;
|
||||
this.incidentId = update.id;
|
||||
|
||||
const res = await Api.incident_update_delete(update);
|
||||
|
||||
if (res.status === "success") {
|
||||
this.onUpdate();
|
||||
|
||||
this.isLoading = false;
|
||||
this.incidentId = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="card-body pt-3">
|
||||
|
||||
<div v-if="updates.length===0" class="alert alert-link text-danger">
|
||||
No updates found, create a new Incident Update below.
|
||||
</div>
|
||||
|
@ -23,9 +22,9 @@
|
|||
|
||||
<div class="col-12 col-md-2">
|
||||
<button @click.prevent="createIncidentUpdate"
|
||||
:disabled="!incident_update.message"
|
||||
:disabled="!incident_update.message || isLoading"
|
||||
type="submit" class="btn btn-block btn-primary">
|
||||
Add
|
||||
Add <FontAwesomeIcon v-if="isLoading" icon="circle-notch" spin />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -49,6 +48,7 @@
|
|||
data () {
|
||||
return {
|
||||
updates: [],
|
||||
isLoading: false,
|
||||
incident_update: {
|
||||
incident: this.incident.id,
|
||||
message: "",
|
||||
|
@ -58,15 +58,18 @@
|
|||
},
|
||||
|
||||
async mounted() {
|
||||
await this.loadUpdates()
|
||||
this.loadUpdates()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async createIncidentUpdate() {
|
||||
this.isLoading = true;
|
||||
|
||||
this.res = await Api.incident_update_create(this.incident_update)
|
||||
if (this.res.status === "success") {
|
||||
this.updates.push(this.res.output) // this is better in terms of not having to querry the db to get a fresh copy of all updates
|
||||
this.updates.push(this.res.output); // this is better in terms of not having to querry the db to get a fresh copy of all updates
|
||||
//await this.loadUpdates()
|
||||
this.isLoading = false;
|
||||
} // TODO: further error checking here... maybe alert user it failed with modal or so
|
||||
|
||||
// reset the form data
|
||||
|
@ -79,7 +82,7 @@
|
|||
},
|
||||
|
||||
async loadUpdates() {
|
||||
this.updates = await Api.incident_updates(this.incident)
|
||||
this.updates = await Api.incident_updates(this.incident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const english = {
|
|||
name: "Name",
|
||||
copy: "Copy",
|
||||
close: "Close",
|
||||
cancel: "Cancel",
|
||||
secret: "Secret",
|
||||
regen_api: "Regenerate API Keys",
|
||||
regen_desc:
|
||||
|
@ -119,6 +120,8 @@ const english = {
|
|||
administrator: "Administrator",
|
||||
checkins: "Checkins",
|
||||
incidents: "Incidents",
|
||||
incident_create: 'Create Incident',
|
||||
incident_edit: 'Edit Incident',
|
||||
service_info: "Service Info",
|
||||
service_name: "Service Name",
|
||||
service_description: "Service Description",
|
||||
|
|
Loading…
Reference in New Issue