merged with release

pull/1097/head
Rhythm 2022-01-06 11:57:48 +05:30
commit fa743f2919
20 changed files with 1184 additions and 5 deletions

3
.gitignore vendored
View File

@ -41,4 +41,5 @@ tmp
/frontend/cypress/videos/
services.yml
statping.wiki
assets/
assets/
.vscode/settings.json

3
frontend/.gitignore vendored
View File

@ -19,3 +19,6 @@ yarn-error.log*
*.njsproj
*.sln
*.sw?
# Package lock file
package-lock.json

View File

@ -282,6 +282,27 @@ class Api {
await axios.all([all])
}
async downtimes ({ serviceId, start, end, skip, count, subStatus }) {
return axios.get('api/downtimes', {
params: { service_id: serviceId, start, end, skip, count, sub_status: subStatus }
}).then((response) => response.data);
}
async downtime (id) {
return axios.get(`api/downtimes/${id}`).then((response) => response.data);
}
async downtime_create (data) {
return axios.post('/api/downtimes', data).then((response) => response.data);
}
async downtime_update ({ id, data }) {
return axios.patch(`/api/downtimes/${id}`, data).then((response) => response.data);
}
async downtime_delete (id) {
return axios.delete(`/api/downtimes/${id}`).then((response) => response.data);
}
}
const api = new Api()
export default api

View File

@ -85,6 +85,10 @@ A {
.nav-link {
color: $navbar-color;
&.router-link-exact-active {
font-weight: bold;
}
}
.form-control {

View File

@ -0,0 +1,176 @@
<template>
<div class="col-12">
<DowntimesFilterForm
:handle-clear-filters="handleClearFilters"
:params="params"
:handle-filter-search="handleFilterSearch"
:filter-errors="filterErrors"
:handle-filter-change="handleFilterChange"
/>
<div class="card contain-card mb-4">
<div class="card-header">
{{ $t('downtimes') }}
<router-link
v-if="$store.state.admin"
to="/dashboard/create_downtime"
class="btn btn-sm btn-success float-right"
>
<FontAwesomeIcon icon="plus" /> {{ $t('create') }}
</router-link>
</div>
<div class="card-body pt-0">
<div
v-if="isLoading"
class="mt-5"
>
<div class="col-12 text-center">
<FontAwesomeIcon
icon="circle-notch"
size="3x"
spin
/>
</div>
<div class="col-12 text-center mt-3 mb-3">
<span class="text-muted">
Loading Downtimes
</span>
</div>
</div>
<div v-else>
<DowntimesList :get-downtimes="getDowntimes" />
<Pagination
v-if="downtimes.length !== 0"
:get-next-downtimes="getNextDowntimes"
:get-prev-downtimes="getPrevDowntimes"
:skip="params.skip"
:count="params.count"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import DowntimesList from './DowntimesList.vue';
import { mapState } from 'vuex';
import DowntimesFilterForm from '../../forms/DowntimeFilters.vue';
import Pagination from '../Elements/Pagination.vue';
import { removeEmptyParams } from '../../forms/Downtime.vue';
export const initialParams = {
serviceId: '',
start: '',
end: '',
skip: 0,
count: 10,
subStatus: ''
};
export const convertToSec = (val) => {
return (new Date(val).getTime())/1000;
};
export const checkErrors = (params) => {
const { start, end } = params;
const errors = {};
// Converting into millisec
const startSec = convertToSec(start);
const endSec = convertToSec(end) + (60 * 60 * 23 + 59 * 60 + 59);
if (!start && end) {
errors.start = 'Need to enter Start Date';
} else if (start && !end) {
errors.end = 'Need to enter End Date';
} else if ( startSec > endSec ) {
errors.end = 'End Date should be greater than Start Date';
}
return errors;
};
export default {
name: 'DashboardDowntimes',
components: {
DowntimesList,
Pagination,
DowntimesFilterForm
},
data: function () {
return {
isLoading: false,
params: { ...initialParams },
filterErrors: {}
};
},
computed: {
...mapState([ 'downtimes' ]),
},
created: function () {
// Set start date
const startDate = new Date();
startDate.setDate(startDate.getDate() - 30);
startDate.setHours(0,0,0,0);
this.params.start = startDate.toJSON();
// Set end date
const endDate = new Date();
endDate.setHours(0,0,0,0);
this.params.end = endDate.toJSON();
this.getDowntimes(this.params);
},
methods: {
getDowntimes: async function (params = this.params) {
const { start, end } = params;
const errors = checkErrors(this.params);
if (Object.keys(errors).length > 0) {
this.filterErrors = Object.assign({}, errors);
return;
}
let startSec = ''; let endSec = '';
if (start) {
startSec = convertToSec(start);
}
if (end) {
endSec = convertToSec(end) + (60 * 60 * 23 + 59 * 60 + 59); // adding end of time for that particular date.
}
const payload = removeEmptyParams({ ...params, start: startSec, end: endSec });
this.isLoading = true;
await this.$store.dispatch({ type: 'getDowntimes', payload });
this.isLoading = false;
},
getNextDowntimes: function () {
this.params = { ...this.params, skip: this.params.skip + this.params.count };
this.getDowntimes();
},
getPrevDowntimes: function () {
this.params = { ...this.params, skip: this.params.skip - this.params.count };
this.getDowntimes(this.params);
},
handleClearFilters: function () {
this.params = { ...initialParams };
},
handleFilterSearch: function () {
this.params = { ...this.params, skip: 0 };
this.getDowntimes();
},
handleFilterChange: function (e) {
const { name } = e.target;
// reset error according to edit input
delete this.filterErrors[name];
}
}
};
</script>

View File

@ -0,0 +1,172 @@
<template>
<div>
<div
v-if="downtimes.length === 0"
class="alert alert-dark d-block mt-3 mb-0"
>
You currently don't have any downtimes for this services!
</div>
<table
v-else
class="table"
>
<thead>
<tr>
<th scope="col">
{{ $t('name') }}
</th>
<th
scope="col"
class="d-none d-md-table-cell"
>
{{ $t('start_time') }}
</th>
<th
scope="col"
class="d-none d-md-table-cell"
>
{{ $t('end_time') }}
</th>
<th
scope="col"
class="d-none d-md-table-cell"
>
{{ $t('status') }}
</th>
<th
scope="col"
class="d-none d-md-table-cell"
>
{{ $t('failures') }}
</th>
<th
scope="col"
class="d-none d-md-table-cell"
>
{{ $t('actions') }}
</th>
</tr>
<tr
v-for="downtime in downtimes"
:key="downtime.id"
>
<td>
<span>
{{ downtime.service.name }}
</span>
</td>
<td class="d-none d-md-table-cell">
<span
class=""
>
{{ niceDateWithYear(downtime.start) }}
</span>
</td>
<td class="d-none d-md-table-cell">
<span
class=""
>
{{ 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' ]"
>
{{ downtime.sub_status }}
</span>
</td>
<td class="d-none d-md-table-cell">
<span
class=""
>
{{ downtime.failures }}
</span>
</td>
<td class="text-right">
<div class="btn-group">
<button
v-if="$store.state.admin"
:disabled="isLoading"
class="btn btn-sm btn-outline-secondary"
@click.prevent="goto(`/dashboard/edit_downtime/${downtime.id}`)"
>
<FontAwesomeIcon icon="edit" />
</button>
<button
v-if="$store.state.admin"
:disabled="downtimeDeleteId === downtime.id && isLoading"
class="btn btn-sm btn-danger"
@click.prevent="handleDowntimeDelete(downtime)"
>
<FontAwesomeIcon
v-if="!isLoading"
icon="trash"
/>
<FontAwesomeIcon
v-if="downtimeDeleteId === downtime.id && isLoading"
icon="circle-notch"
spin
/>
</button>
</div>
</td>
</tr>
</thead>
</table>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Api from '../../API';
export default {
name: 'DashboardDowntimeList',
props: {
getDowntimes: {
type: Function,
default: function () {}
}
},
data: function () {
return {
isLoading: false,
downtimeDeleteId: null,
};
},
computed: {
...mapState([ 'downtimes' ]),
},
methods: {
goto: function (to) {
this.$router.push(to);
},
setIsLoading ({ id, isLoading }) {
this.downtimeDeleteId = id;
this.isLoading = isLoading;
},
delete: async function (id) {
this.setIsLoading({ id, isLoading: true });
await Api.downtime_delete(id);
this.setIsLoading({ id: null, isLoading: false });
this.getDowntimes();
},
handleDowntimeDelete: async function (downtime) {
const modal = {
visible: true,
title: 'Delete Downtime',
body: `Are you sure you want to delete the downtime for service ${downtime.service.name}?`,
btnColor: 'btn-danger',
btnText: 'Delete Downtime',
func: () => this.delete(downtime.id),
};
this.$store.commit('setModal', modal);
}
}
};
</script>

View File

@ -0,0 +1,60 @@
<template>
<div class="col-12">
<div
v-if="isLoading"
class="row mt-5"
>
<div class="col-12 text-center">
<FontAwesomeIcon
icon="circle-notch"
size="3x"
spin
/>
</div>
<div class="col-12 text-center mt-3 mb-3">
<span class="text-muted">
Loading Downtime
</span>
</div>
</div>
<FormDowntime
v-else
:edit-downtime="editDowntime"
/>
</div>
</template>
<script>
import Api from '../../API';
const FormDowntime = () =>
import(/* webpackChunkName: "dashboard" */ '../../forms/Downtime.vue');
export default {
name: 'EditDowntime',
components: {
FormDowntime
},
data: function () {
return { isLoading: false, editDowntime: null };
},
mounted () {
this.getDowntime();
},
methods: {
getDowntime: async function () {
const id = this.$route.params.id;
if (!id) {
return;
}
this.isLoading = true;
const { output } = await Api.downtime(id);
this.isLoading = false;
this.editDowntime = output;
}
}
};
</script>

View File

@ -14,6 +14,9 @@
<li @click="navopen = !navopen" class="nav-item navbar-item">
<router-link to="/dashboard/services" class="nav-link">{{ $t('services') }}</router-link>
</li>
<li @click="navopen = !navopen" class="nav-item navbar-item">
<router-link to="/dashboard/downtimes" class="nav-link">{{'Downtimes'}}</router-link>
</li>
<li v-if="admin" @click="navopen = !navopen" class="nav-item navbar-item">
<router-link to="/dashboard/users" class="nav-link">{{ $t('users') }}</router-link>
</li>

View File

@ -0,0 +1,59 @@
<template>
<ul class="pagination d-flex justify-content-center">
<li class="page-item">
<button
class="btn btn-outline-secondary page-link"
aria-label="Previous"
:disabled="skip <= 0"
@click="getPrevDowntimes"
>
<span aria-hidden="true">
&laquo;
</span>
</button>
</li>
<li class="page-item">
<button
class="btn btn-outline-secondary page-link"
aria-label="Next"
:disabled="downtimes.length < count"
@click="getNextDowntimes"
>
<span aria-hidden="true">
&raquo;
</span>
</button>
</li>
</ul>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'DasboardDowntimePagination',
props: {
count:{
type: Number,
default: 10,
},
getNextDowntimes: {
type: Function,
default: function () {}
},
getPrevDowntimes: {
type: Function,
default: function () {}
},
skip: {
type: Number,
default: 0,
}
},
computed: {
...mapState([ 'downtimes' ])
},
};
</script>

View File

@ -0,0 +1,325 @@
<template>
<div class="card contain-card mb-4">
<div class="card-header d-flex align-items-center">
<button
class="btn p-0 mr-2"
@click="$router.push('/dashboard/downtimes');"
>
<FontAwesomeIcon icon="arrow-circle-left" />
</button>
<div>{{ $t("downtime_info") }}</div>
</div>
<div class="card-body">
<form>
<div class="form-group row">
<label class="col-sm-4 col-form-label">
{{
$t("service_name")
}}
</label>
<div class="col-sm-8">
<select
v-model="downtime.serviceId"
name="service"
class="form-control"
required
:disabled="$route.params.id"
>
<option
v-for="(service) in services"
:key="service.id"
:value="service.id"
>
{{ service.name }}
</option>
</select>
<small
class="form-text text-muted"
>
Select Servive you want to have downtime for
</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">
{{
$t("downtime_status")
}}
</label>
<div class="col-sm-8">
<select
v-model="downtime.subStatus"
name="service"
class="form-control"
required
>
<option
value="degraded"
>
Degraded
</option>
<option value="down">
Down
</option>
</select>
<small
class="form-text text-muted"
>
Choose status you want to give to the Servive
</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">
{{
$t("downtime_date_range")
}}
</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-6">
<FlatPickr
id="start"
v-model="downtime.start"
type="text"
name="start"
class="form-control form-control-plaintext"
:config="config"
placeholder="Select Start Date"
@on-change="() => handleFormChange({target: {name: 'start'}})"
/>
<small
v-if="errors.start"
class="form-text text-danger"
>
{{ errors.start }}
</small>
</div>
<div class="col-sm-6">
<FlatPickr
id="end"
v-model="downtime.end"
type="text"
name="end"
class="form-control form-control-plaintext"
:config="config"
placeholder="Select End Date"
@on-change="() => handleFormChange({target: {name: 'end'}})"
/>
<small
v-if="errors.end"
class="form-text text-danger"
>
{{ errors.end }}
</small>
</div>
</div>
<small
class="form-text text-muted"
>
Enter the Start and End date for which your service will be down/degraded
</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">
{{
$t("failures")
}}
</label>
<div class="col-sm-8">
<input
v-model.number="downtime.failures"
type="number"
name="check_interval"
class="form-control"
min="0"
required
>
<small
v-if="errors.failures"
class="form-text text-danger"
>
{{ errors.failures }}
</small>
<small
class="form-text text-muted"
>
Select the number of failures you want for your service
</small>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<button
:disabled="isLoading || !isCreateDowntimeBtnEnabled()"
type="button"
class="btn btn-success btn-block"
@click.prevent="saveDowntime"
>
{{ $route.params.id ? $t("downtime_update") : $t("downtime_create") }}
</button>
</div>
</div>
</form>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Api from '../API';
import FlatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
import { convertToSec } from '../components/Dashboard/DashboardDowntimes.vue';
const checkFormErrors = (value, id) => {
const { failures, start, end } = value;
const errors = {};
let endSec = ''; let startSec = '';
// Converting into millisec
if (start) {
startSec = convertToSec(start);
}
if (end) {
endSec = convertToSec(end);
}
// Check for valid positive numbers
if (!(/^\d+$/.test(failures))) {
errors.failures = 'Enter Valid Positve Number without decimal point';
} else if (!start && end) {
errors.start = 'Need to enter Start Date';
} else if ( id && startSec && !endSec ) {
errors.end = 'Need to enter End Date';
} else if ( endSec && startSec > endSec ) {
errors.end = 'End Date should be greater than Start Date';
}
return errors;
};
export const removeEmptyParams = (obj) => {
const updatedObj = {};
for (const [ key , value ] of Object.entries(obj)) {
if (value) {
updatedObj[key] = value;
}
}
return updatedObj;
};
export default {
name: 'FormDowntime',
components: {
FlatPickr,
},
props: {
editDowntime: {
type: Object,
default: null,
}
},
data: function () {
return {
isLoading: false,
errors: {},
downtime: {
serviceId: '',
subStatus: 'degraded',
failures: 20,
start: new Date().toJSON(),
end: null,
},
config: {
altFormat: 'J M, Y, h:iK',
altInput: true,
enableTime: true,
dateFormat: 'Z',
maxDate: new Date().toJSON(),
},
};
},
computed: {
...mapState({
services : function (state) {
const { id } = this.$route.params;
if (!id && state.services.length > 0) {
this.downtime.serviceId = state.services[0].id;
}
return state.services;
}
}),
},
mounted: function () {
if (this.editDowntime) {
const { service_id, sub_status, failures, start, end } = this.editDowntime;
this.downtime = {
start,
end,
failures,
serviceId: service_id,
subStatus: sub_status
};
}
},
methods: {
isCreateDowntimeBtnEnabled: function () {
const { id } = this.$route.params;
const { serviceId, subStatus, failures, start, end } = this.downtime;
return serviceId && subStatus && failures && start && (id ? end : true);
},
saveDowntime: async function () {
const { id } = this.$route.params;
const errors = checkFormErrors(this.downtime, id);
// Check of invalid input.
if (Object.keys(errors).length > 0) {
this.errors = Object.assign({}, errors);
return;
}
const { serviceId, subStatus, ...rest } = removeEmptyParams(this.downtime);
const downtime = {
...rest,
...(!id && { 'service_id': serviceId }),
'sub_status': subStatus,
};
this.isLoading=true;
try {
if (id) {
await Api.downtime_update({ id, data: downtime });
} else {
await Api.downtime_create(downtime);
}
this.isLoading=false;
} catch (error) {
this.isLoading=false;
throw new Error(error.message);
}
this.$router.push('/dashboard/downtimes');
},
handleFormChange: function (e) {
const { name } = e.target;
delete this.errors[name];
}
}
};
</script>

View File

@ -0,0 +1,173 @@
<template>
<div class="card contain-card mb-4">
<div class="card-header">
{{ $t('filters') }}
</div>
<div class="card-body">
<form>
<div class="form-row">
<div class="form-group col-md-2">
<label class="col-form-label">
{{ $t('service') }}
</label>
<select
v-model="params.serviceId"
name="service"
class="form-control"
>
<option value="">
All
</option>
<option
v-for="service in services"
:key="service.id"
:value="service.id"
>
{{ service.name }}
</option>
</select>
</div>
<div class="form-group col-md-5">
<label class="col-form-label">
{{ $t('downtime_date_range') }}
</label>
<div class="form-row">
<div class="col-sm-6">
<FlatPickr
id="start"
v-model="params.start"
type="text"
name="start"
class="form-control form-control-plaintext"
value=""
:config="config"
placeholder="Select Start Date"
@on-change="() => handleFilterChange({target: {name: 'start'}})"
/>
<small
v-if="filterErrors.start"
class="form-text text-danger"
>
{{ filterErrors.start }}
</small>
</div>
<div class="col-sm-6">
<FlatPickr
id="end"
v-model="params.end"
type="text"
name="end"
class="form-control form-control-plaintext"
value=""
:config="config"
placeholder="Select End Date"
@on-change="() => handleFilterChange({target: {name: 'end'}})"
/>
<small
v-if="filterErrors.end"
class="form-text text-danger"
>
{{ filterErrors.end }}
</small>
</div>
</div>
</div>
<div class="form-group col-md-2">
<label class="col-form-label">
{{ $t('status') }}
</label>
<select
v-model="params.subStatus"
name="status"
class="form-control"
>
<option value="">
All
</option>
<option value="degraded">
Degraded
</option>
<option value="down">
Down
</option>
</select>
</div>
<div class="form-group col-md-3">
<label class="col-form-label invisible">
{{ $t('actions') }}
</label>
<div
class="d-flex justify-content-end"
role="group"
>
<button
type="button"
class="btn btn-primary mr-1"
@click.prevent="handleFilterSearch"
>
{{ $t('search') }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
@click.prevent="handleClearFilters"
>
{{ $t('clear') }}
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import FlatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
import { initialParams } from '../components/Dashboard/DashboardDowntimes.vue';
export default {
name: 'DashboardDowntimeFilters',
components: {
FlatPickr
},
props: {
params: {
type: Object,
default: initialParams
},
handleClearFilters: {
type: Function,
default: function () {}
},
handleFilterSearch: {
type: Function,
default: function () {}
},
filterErrors: {
type: Object,
default: null
},
handleFilterChange: {
type: Function,
default: function () {}
}
},
data: function () {
return {
config: {
altFormat: 'J M, Y',
altInput: true,
dateFormat: 'Z',
maxDate: new Date().toJSON(),
},
};
},
computed: {
...mapState([ 'services' ])
},
};
</script>

View File

@ -6,4 +6,4 @@ import Vue from "vue";
library.add(fas, fab)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.component('FontAwesomeIcon', FontAwesomeIcon);

View File

@ -140,6 +140,20 @@ const english = {
notify_all: "Notify All Changes",
service_update: "Update Service",
service_create: "Create Service",
start_time: 'Start Time',
end_time: "End Time",
actions: "Actions",
services: "Services",
downtimes: "Downtimes",
downtime_info: "Downtime Info",
downtime_status: "Downtime Status",
downtime_date_range: "Downtime Date Range",
downtime_create: "Create Downtime",
downtime_update: "Update Downtime",
filters: "Filters",
service: "Service",
clear: "Clear",
search: "Search",
};
export default english;

View File

@ -256,6 +256,9 @@ export default Vue.mixin({
},
addSeconds(date, amount) {
return addSeconds(date, amount)
}
},
niceDateWithYear (val) {
return format(parseISO(val), 'do MMM, yyyy h:mma');
},
}
});

View File

@ -16,6 +16,9 @@ const Checkins = () => import(/* webpackChunkName: "dashboard" */ '@/components/
const Failures = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/Failures')
const NotFound = () => import(/* webpackChunkName: "index" */ '@/pages/NotFound')
const Importer = () => import(/* webpackChunkName: "index" */ '@/components/Dashboard/Importer')
const DashboardDowntimes = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/DashboardDowntimes')
const EditDowntime = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/EditDowntime')
import VueRouter from "vue-router";
import Api from "./API";
@ -137,6 +140,27 @@ const routes = [
requiresAuth: true,
title: 'Statping - Service Failures',
}
},{
path: 'downtimes',
component: DashboardDowntimes,
meta: {
requiresAuth: true,
title: 'Statping - Downtimes',
}
},{
path: 'create_downtime',
component: EditDowntime,
meta: {
requiresAuth: true,
title: 'Statping - Create Downtime',
}
},{
path: 'edit_downtime/:id',
component: EditDowntime,
meta: {
requiresAuth: true,
title: 'Statping - Update Downtime',
}
},{
path: 'messages',
component: DashboardMessages,
@ -228,3 +252,4 @@ router.beforeEach((to, from, next) => {
});
export default router

View File

@ -23,6 +23,7 @@ export default new Vuex.Store({
oauth: {},
token: null,
services: [],
downtimes: [],
service: null,
groups: [],
messages: [],
@ -152,12 +153,19 @@ export default new Vuex.Store({
setModal(state, modal) {
state.modal = modal
},
setDowntimes (state, downtimes) {
state.downtimes = downtimes;
}
},
actions: {
async getAllServices(context) {
const services = await Api.services()
context.commit("setServices", services);
},
async getDowntimes (context, { payload }) {
const { output } = await Api.downtimes(payload);
context.commit('setDowntimes', output ?? []);
},
async loadCore(context) {
const core = await Api.core()
const token = await Api.token()

View File

@ -167,7 +167,9 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
case *downtimes.Downtime:
objName = "downtime"
objId = v.Id
case *DowntimeService:
objName = "downtime_with_service"
objId = v.Id
default:
objName = fmt.Sprintf("%T", v)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/statping/statping/types/services"
"github.com/statping/statping/utils"
"net/http"
"net/url"
"time"
)
@ -21,10 +22,75 @@ func findDowntime(r *http.Request) (*downtimes.Downtime, error) {
return downtime, nil
}
func convertToMap(query url.Values) map[string]string {
vars := make(map[string]string)
if query.Get("start") != "" {
vars["start"] = query.Get("start")
}
if query.Get("end") != "" {
vars["end"] = query.Get("end")
}
if query.Get("sub_status") != "" {
vars["sub_status"] = query.Get("sub_status")
}
if query.Get("service_id") != "" {
vars["service_id"] = query.Get("service_id")
}
if query.Get("type") != "" {
vars["type"] = query.Get("type")
}
if query.Get("skip") != "" {
vars["skip"] = query.Get("skip")
}
if query.Get("count") != "" {
vars["count"] = query.Get("count")
}
return vars
}
type DowntimeService struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Service *services.Service `gorm:"foreignKey:service" json:"service"`
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
SubStatus string `gorm:"column:sub_status" json:"sub_status"`
Failures int `gorm:"column:failures" json:"failures"`
Start *time.Time `gorm:"index;column:start" json:"start"`
End *time.Time `gorm:"column:end" json:"end"`
Type string `gorm:"default:'auto';column:type" json:"type"`
}
func apiAllDowntimes(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
vars := convertToMap(query)
downtime, err := downtimes.FindAll(vars)
var downtimeWithService []DowntimeService
servicesMap := services.All()
if downtime == nil {
sendJsonAction(downtimeWithService, "fetch", w, r)
return
}
for _, dtime := range *downtime {
var downtimeWithServiceVar DowntimeService
downtimeWithServiceVar.Id = dtime.Id
downtimeWithServiceVar.ServiceId = dtime.ServiceId
downtimeWithServiceVar.SubStatus = dtime.SubStatus
downtimeWithServiceVar.Failures = dtime.Failures
downtimeWithServiceVar.Start = dtime.Start
downtimeWithServiceVar.End = dtime.End
downtimeWithServiceVar.Type = dtime.Type
downtimeWithServiceVar.Service = servicesMap[dtime.ServiceId]
downtimeWithService = append(downtimeWithService, downtimeWithServiceVar)
}
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(downtimeWithService, "fetch", w, r)
}
func apiAllDowntimesForServiceHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
serviceId := utils.ToInt(vars["service_id"])
ninetyDaysAgo := time.Now().Add(time.Duration(-90*24) * time.Hour)
downtime, err := downtimes.FindByService(serviceId, ninetyDaysAgo, time.Now())

View File

@ -194,6 +194,7 @@ func Router() *mux.Router {
//r.Handle("/checkin/{api}", http.HandlerFunc(checkinHitHandler))
// API DOWNTIME Routes
api.Handle("/api/downtimes", authenticated(apiAllDowntimes, false)).Methods("GET")
api.Handle("/api/service/{service_id}/downtimes", authenticated(apiAllDowntimesForServiceHandler, false)).Methods("GET")
api.Handle("/api/downtimes", authenticated(apiCreateDowntimeHandler, false)).Methods("POST")
api.Handle("/api/downtimes/{id}", authenticated(apiDowntimeHandler, false)).Methods("GET")

View File

@ -3,6 +3,7 @@ package downtimes
import (
"fmt"
"github.com/statping/statping/database"
"strconv"
"time"
)
@ -66,6 +67,68 @@ func FindDowntime(timeVar time.Time) []Downtime {
}
func ConvertToUnixTime(str string) (time.Time, error) {
i, err := strconv.ParseInt(str, 10, 64)
var t time.Time
if err != nil {
return t, err
}
tm := time.Unix(i, 0)
return tm, nil
}
func FindAll(vars map[string]string) (*[]Downtime, error) {
var downtime []Downtime
var start time.Time
var end time.Time
st, err1 := vars["start"]
en, err2 := vars["end"]
startInt, err := strconv.ParseInt(st, 10, 64)
endInt, err := strconv.ParseInt(en, 10, 64)
if err1 && err2 && (endInt > startInt) {
start, err = ConvertToUnixTime(vars["start"])
if err != nil {
return &downtime, err
}
end, err = ConvertToUnixTime(vars["end"])
if err != nil {
return &downtime, err
}
} else {
ninetyDaysAgo := time.Now().Add(time.Duration(-90*24) * time.Hour)
start = ninetyDaysAgo
end = time.Now()
}
q := db.Where("start BETWEEN ? AND ?", start, end)
if subStatusVar, subStatusErr := vars["sub_status"]; subStatusErr {
q = q.Where("sub_status = ?", subStatusVar)
}
if serviceIdVar, serviceIdErr := vars["service_id"]; serviceIdErr {
q = q.Where("service = ?", serviceIdVar)
}
if typeVar, typeErr := vars["type"]; typeErr {
q = q.Where("type = ?", typeVar)
}
var count int64
if countVar, countErr := vars["count"]; countErr {
count, err = strconv.ParseInt(countVar, 10, 64)
if count > 100 {
count = 100
}
} else {
count = 20
}
var skip int64
if skipVar, err6 := vars["skip"]; err6 {
skip, err = strconv.ParseInt(skipVar, 10, 64)
} else {
skip = 0
}
q = q.Order("start DESC")
q = q.Limit((int)(count)).Offset((int)(skip)).Find(&downtime)
return &downtime, q.Error()
}
func (c *Downtime) Create() error {
q := db.Create(c)
return q.Error()