Merge branch 'instances' of github.com:jmontineri/cabot into jmontineri-instances

Conflicts:
	app/cabotapp/views.py
This commit is contained in:
David Buxton 2014-07-31 13:16:08 +01:00
commit 45c2e13653
18 changed files with 1195 additions and 45 deletions

View File

@ -1,5 +1,5 @@
from django.contrib import admin
from .models import UserProfile, Service, Shift, ServiceStatusSnapshot, StatusCheck, StatusCheckResult
from .models import UserProfile, Service, Shift, ServiceStatusSnapshot, StatusCheck, StatusCheckResult, Instance
admin.site.register(UserProfile)
admin.site.register(Shift)
@ -7,3 +7,4 @@ admin.site.register(Service)
admin.site.register(ServiceStatusSnapshot)
admin.site.register(StatusCheck)
admin.site.register(StatusCheckResult)
admin.site.register(Instance)

View File

@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Instance'
db.create_table('cabotapp_instance', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('last_alert_sent', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
('email_alert', self.gf('django.db.models.fields.BooleanField')(default=False)),
('hipchat_alert', self.gf('django.db.models.fields.BooleanField')(default=True)),
('sms_alert', self.gf('django.db.models.fields.BooleanField')(default=False)),
('telephone_alert', self.gf('django.db.models.fields.BooleanField')(default=False)),
('alerts_enabled', self.gf('django.db.models.fields.BooleanField')(default=True)),
('overall_status', self.gf('django.db.models.fields.TextField')(default='PASSING')),
('old_overall_status', self.gf('django.db.models.fields.TextField')(default='PASSING')),
('hackpad_id', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
('name', self.gf('django.db.models.fields.TextField')()),
('address', self.gf('django.db.models.fields.TextField')(blank=True)),
))
db.send_create_signal('cabotapp', ['Instance'])
# Adding M2M table for field users_to_notify on 'Instance'
db.create_table('cabotapp_instance_users_to_notify', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('instance', models.ForeignKey(orm['cabotapp.instance'], null=False)),
('user', models.ForeignKey(orm['auth.user'], null=False))
))
db.create_unique('cabotapp_instance_users_to_notify', ['instance_id', 'user_id'])
# Adding M2M table for field status_checks on 'Instance'
db.create_table('cabotapp_instance_status_checks', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('instance', models.ForeignKey(orm['cabotapp.instance'], null=False)),
('statuscheck', models.ForeignKey(orm['cabotapp.statuscheck'], null=False))
))
db.create_unique('cabotapp_instance_status_checks', ['instance_id', 'statuscheck_id'])
# Adding M2M table for field services on 'Instance'
db.create_table('cabotapp_instance_services', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('instance', models.ForeignKey(orm['cabotapp.instance'], null=False)),
('service', models.ForeignKey(orm['cabotapp.service'], null=False))
))
db.create_unique('cabotapp_instance_services', ['instance_id', 'service_id'])
# Adding M2M table for field instances on 'Service'
db.create_table('cabotapp_service_instances', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('service', models.ForeignKey(orm['cabotapp.service'], null=False)),
('instance', models.ForeignKey(orm['cabotapp.instance'], null=False))
))
db.create_unique('cabotapp_service_instances', ['service_id', 'instance_id'])
def backwards(self, orm):
# Deleting model 'Instance'
db.delete_table('cabotapp_instance')
# Removing M2M table for field users_to_notify on 'Instance'
db.delete_table('cabotapp_instance_users_to_notify')
# Removing M2M table for field status_checks on 'Instance'
db.delete_table('cabotapp_instance_status_checks')
# Removing M2M table for field services on 'Instance'
db.delete_table('cabotapp_instance_services')
# Removing M2M table for field instances on 'Service'
db.delete_table('cabotapp_service_instances')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cabotapp.instance': {
'Meta': {'ordering': "['name']", 'object_name': 'Instance'},
'address': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.Service']", 'symmetrical': 'False', 'blank': 'True'}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.service': {
'Meta': {'ordering': "['name']", 'object_name': 'Service'},
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instances': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.Instance']", 'symmetrical': 'False', 'blank': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'url': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.servicestatussnapshot': {
'Meta': {'object_name': 'ServiceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'num_checks_active': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_failing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_passing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': "orm['cabotapp.Service']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
'cabotapp.shift': {
'Meta': {'object_name': 'Shift'},
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'end': ('django.db.models.fields.DateTimeField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'start': ('django.db.models.fields.DateTimeField', [], {}),
'uid': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'cabotapp.statuscheck': {
'Meta': {'ordering': "['name']", 'object_name': 'StatusCheck'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cached_health': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'calculated_status': ('django.db.models.fields.CharField', [], {'default': "'passing'", 'max_length': '50', 'blank': 'True'}),
'check_type': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'debounce': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'endpoint': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'expected_num_hosts': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'frequency': ('django.db.models.fields.IntegerField', [], {'default': '5'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'importance': ('django.db.models.fields.CharField', [], {'default': "'ERROR'", 'max_length': '30'}),
'last_run': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'max_queued_build_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'metric': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'password': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_cabotapp.statuscheck_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'status_code': ('django.db.models.fields.TextField', [], {'default': '200', 'null': 'True'}),
'text_match': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'timeout': ('django.db.models.fields.IntegerField', [], {'default': '30', 'null': 'True'}),
'username': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'value': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'verify_ssl_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'cabotapp.statuscheckresult': {
'Meta': {'object_name': 'StatusCheckResult'},
'check': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cabotapp.StatusCheck']"}),
'error': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'raw_data': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'succeeded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'time': ('django.db.models.fields.DateTimeField', [], {}),
'time_complete': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'})
},
'cabotapp.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'fallback_alert_user': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hipchat_alias': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mobile_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['cabotapp']

View File

@ -17,6 +17,7 @@ from django.utils import timezone
import json
import re
import time
import os
import requests
from celery.utils.log import get_task_logger
@ -65,7 +66,11 @@ def calculate_debounced_passing(recent_results, debounce=0):
return False
class Service(models.Model):
class CheckGroupMixin(models.Model):
class Meta:
abstract = True
PASSING_STATUS = 'PASSING'
WARNING_STATUS = 'WARNING'
ERROR_STATUS = 'ERROR'
@ -88,10 +93,7 @@ class Service(models.Model):
)
name = models.TextField()
url = models.TextField(
blank=True,
help_text="URL of service."
)
users_to_notify = models.ManyToManyField(
User,
blank=True,
@ -126,8 +128,6 @@ class Service(models.Model):
help_text='Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js'
)
class Meta:
ordering = ['name']
def __unicode__(self):
return self.name
@ -201,18 +201,6 @@ class Service(models.Model):
s['time'] = time.mktime(s['time'].timetuple())
return snapshots
def active_status_checks(self):
return self.status_checks.filter(active=True)
def inactive_status_checks(self):
return self.status_checks.filter(active=False)
def all_passing_checks(self):
return self.active_status_checks().filter(calculated_status=self.CALCULATED_PASSING_STATUS)
def all_failing_checks(self):
return self.active_status_checks().exclude(calculated_status=self.CALCULATED_PASSING_STATUS)
def graphite_status_checks(self):
return self.status_checks.filter(polymorphic_ctype__model='graphitestatuscheck')
@ -231,6 +219,53 @@ class Service(models.Model):
def active_jenkins_status_checks(self):
return self.jenkins_status_checks().filter(active=True)
def active_status_checks(self):
return self.status_checks.filter(active=True)
def inactive_status_checks(self):
return self.status_checks.filter(active=False)
def all_passing_checks(self):
return self.active_status_checks().filter(calculated_status=self.CALCULATED_PASSING_STATUS)
def all_failing_checks(self):
return self.active_status_checks().exclude(calculated_status=self.CALCULATED_PASSING_STATUS)
class Service(CheckGroupMixin):
instances = models.ManyToManyField(
'Instance',
blank=True,
help_text='Instances this service is running on.',
)
url = models.TextField(
blank=True,
help_text="URL of service."
)
class Meta:
ordering = ['name']
class Instance(CheckGroupMixin):
class Meta:
ordering = ['name']
address = models.TextField(
blank=True,
help_text="Address (IP/Hostname) of service."
)
def icmp_status_checks(self):
return self.status_checks.filter(polymorphic_ctype__model='icmpstatuscheck')
def active_icmp_status_checks(self):
return self.icmp_status_checks().filter(active=True)
class ServiceStatusSnapshot(models.Model):
service = models.ForeignKey(Service, related_name='snapshots')
@ -399,6 +434,28 @@ class StatusCheck(PolymorphicModel):
for service in services:
update_service.delay(service.id)
class ICMPStatusCheck(StatusCheck):
class Meta(StatusCheck.Meta):
proxy = True
@property
def check_category(self):
return "ICMP/Ping Check"
def _run(self):
result = StatusCheckResult(check=self)
instances = self.instance_set.all()
target = self.instance_set.get().address
response = os.system("ping -c 1 " + target)
if response == 0:
result.succeeded = True
else:
result.succeeded = False
result.error = "Could not connect, host is most likely down"
return result
class GraphiteStatusCheck(StatusCheck):
@ -532,7 +589,6 @@ class HttpStatusCheck(StatusCheck):
result.succeeded = True
return result
class JenkinsStatusCheck(StatusCheck):
class Meta(StatusCheck.Meta):

View File

@ -5,8 +5,8 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse_lazy
from django.conf import settings
from models import (
StatusCheck, GraphiteStatusCheck, JenkinsStatusCheck, HttpStatusCheck,
StatusCheckResult, UserProfile, Service, Shift, get_duty_officers)
StatusCheck, GraphiteStatusCheck, JenkinsStatusCheck, HttpStatusCheck, ICMPStatusCheck,
StatusCheckResult, UserProfile, Service, Instance, Shift, get_duty_officers)
from tasks import run_status_check as _run_status_check
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
@ -59,7 +59,6 @@ class StatusCheckResultDetailView(LoginRequiredMixin, DetailView):
model = StatusCheckResult
context_object_name = 'result'
class SymmetricalForm(forms.ModelForm):
symmetrical_fields = () # Iterable of 2-tuples (field, model)
@ -90,7 +89,7 @@ base_widgets = {
class StatusCheckForm(SymmetricalForm):
symmetrical_fields = ('service_set',)
symmetrical_fields = ('service_set', 'instance_set')
service_set = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
required=False,
@ -103,6 +102,17 @@ class StatusCheckForm(SymmetricalForm):
)
)
instance_set = forms.ModelMultipleChoiceField(
queryset=Instance.objects.all(),
required=False,
help_text='Link to instance(s).',
widget=forms.SelectMultiple(
attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
},
)
)
class GraphiteStatusCheckForm(StatusCheckForm):
@ -135,6 +145,19 @@ class GraphiteStatusCheckForm(StatusCheckForm):
})
class ICMPStatusCheckForm(StatusCheckForm):
class Meta:
model = ICMPStatusCheck
fields = (
'name',
'frequency',
'importance',
'active',
'debounce',
)
widgets = dict(**base_widgets)
class HttpStatusCheckForm(StatusCheckForm):
class Meta:
@ -195,6 +218,57 @@ class UserProfileForm(forms.ModelForm):
model = UserProfile
exclude = ('user',)
class InstanceForm(SymmetricalForm):
symmetrical_fields = ('service_set',)
service_set = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
required=False,
help_text='Link to service(s).',
widget=forms.SelectMultiple(
attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
},
)
)
class Meta:
model = Instance
template_name = 'instance_form.html'
fields = (
'name',
'address',
'users_to_notify',
'status_checks',
'service_set',
'email_alert',
'hipchat_alert',
'sms_alert',
'telephone_alert',
'alerts_enabled',
)
widgets = {
'name': forms.TextInput(attrs={'style': 'width: 30%;'}),
'address': forms.TextInput(attrs={'style': 'width: 70%;'}),
'status_checks': forms.SelectMultiple(attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'service_set': forms.SelectMultiple(attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'users_to_notify': forms.CheckboxSelectMultiple(),
}
def __init__(self, *args, **kwargs):
ret = super(InstanceForm, self).__init__(*args, **kwargs)
self.fields['users_to_notify'].queryset = User.objects.filter(
is_active=True)
return ret
class ServiceForm(forms.ModelForm):
@ -206,6 +280,7 @@ class ServiceForm(forms.ModelForm):
'url',
'users_to_notify',
'status_checks',
'instances',
'email_alert',
'hipchat_alert',
'sms_alert',
@ -220,6 +295,10 @@ class ServiceForm(forms.ModelForm):
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'instances': forms.SelectMultiple(attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'users_to_notify': forms.CheckboxSelectMultiple(),
'hackpad_id': forms.TextInput(attrs={'style': 'width:30%;'}),
}
@ -293,20 +372,30 @@ class CheckCreateView(LoginRequiredMixin, CreateView):
if metric:
initial['metric'] = metric
service_id = self.request.GET.get('service')
instance_id = self.request.GET.get('instance')
if service_id:
try:
service = Service.objects.get(id=service_id)
initial['service_set'] = [service]
except Service.DoesNotExist:
pass
if instance_id:
try:
instance = Instance.objects.get(id=instance_id)
initial['instance_set'] = [instance]
except Instance.DoesNotExist:
pass
return initial
def get_success_url(self):
if self.request.GET.get('service') != '':
if self.request.GET.get('service'):
return reverse('service', kwargs={'pk': self.request.GET.get('service')})
else:
return reverse('checks')
if self.request.GET.get('instance'):
return reverse('instance', kwargs={'pk': self.request.GET.get('instance')})
return reverse('checks')
class CheckUpdateView(LoginRequiredMixin, UpdateView):
@ -315,8 +404,16 @@ class CheckUpdateView(LoginRequiredMixin, UpdateView):
def get_success_url(self):
return reverse('check', kwargs={'pk': self.object.id})
class ICMPCheckCreateView(CheckCreateView):
model = ICMPStatusCheck
form_class = ICMPStatusCheckForm
class GraphiteCheckCreateView(CheckCreateView):
class ICMPCheckUpdateView(CheckUpdateView):
model = ICMPStatusCheck
form_class = ICMPStatusCheckForm
class ICMPhiteCheckCreateView(CheckCreateView):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
@ -325,6 +422,9 @@ class GraphiteCheckUpdateView(CheckUpdateView):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
class GraphiteCheckCreateView(CheckCreateView):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
class HttpCheckCreateView(CheckCreateView):
model = HttpStatusCheck
@ -397,6 +497,14 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
return profile
class InstanceListView(LoginRequiredMixin, ListView):
model = Instance
context_object_name = 'instances'
def get_queryset(self):
return Instance.objects.all().order_by('name').prefetch_related('status_checks')
class ServiceListView(LoginRequiredMixin, ListView):
model = Service
context_object_name = 'services'
@ -404,6 +512,20 @@ class ServiceListView(LoginRequiredMixin, ListView):
def get_queryset(self):
return Service.objects.all().order_by('name').prefetch_related('status_checks')
class InstanceDetailView(LoginRequiredMixin, DetailView):
model = Instance
context_object_name = 'instance'
def get_context_data(self, **kwargs):
context = super(InstanceDetailView, self).get_context_data(**kwargs)
date_from = date.today() - relativedelta(day=1)
context['report_form'] = StatusCheckReportForm(initial={
'checks': self.object.status_checks.all(),
'service': self.object,
'date_from': date_from,
'date_to': date_from + relativedelta(months=1) - relativedelta(days=1)
})
return context
class ServiceDetailView(LoginRequiredMixin, DetailView):
model = Service
@ -421,6 +543,43 @@ class ServiceDetailView(LoginRequiredMixin, DetailView):
return context
class InstanceCreateView(LoginRequiredMixin, CreateView):
model = Instance
form_class = InstanceForm
def generateDefaultPingCheck(self):
pc = ICMPStatusCheck()
pc.created_by = self.request.user
pc.name = "Default Ping Check"
pc.frequency = 5
pc.importance = Service.ERROR_STATUS
pc.active = True
pc.debounce = 0
pc.save()
pc.instance_set = [Instance.objects.get(pk=self.object.id)]
pc.save()
def get_success_url(self):
#Where else can I run things when an instance gets created?
self.generateDefaultPingCheck()
return reverse('instance', kwargs={'pk': self.object.id})
def get_initial(self):
if self.initial:
initial = self.initial
else:
initial = {}
service_id = self.request.GET.get('service')
if service_id:
try:
service = Service.objects.get(id=service_id)
initial['service_set'] = [service]
except Service.DoesNotExist:
pass
return initial
class ServiceCreateView(LoginRequiredMixin, CreateView):
model = Service
form_class = ServiceForm
@ -428,6 +587,12 @@ class ServiceCreateView(LoginRequiredMixin, CreateView):
def get_success_url(self):
return reverse('service', kwargs={'pk': self.object.id})
class InstanceUpdateView(LoginRequiredMixin, UpdateView):
model = Instance
form_class = InstanceForm
def get_success_url(self):
return reverse('instance', kwargs={'pk': self.object.id})
class ServiceUpdateView(LoginRequiredMixin, UpdateView):
model = Service
@ -443,6 +608,11 @@ class ServiceDeleteView(LoginRequiredMixin, DeleteView):
context_object_name = 'service'
template_name = 'cabotapp/service_confirm_delete.html'
class InstanceDeleteView(LoginRequiredMixin, DeleteView):
model = Instance
success_url = reverse_lazy('instances')
context_object_name = 'instance'
template_name = 'cabotapp/instance_confirm_delete.html'
class ShiftListView(LoginRequiredMixin, ListView):
model = Shift

View File

@ -41,6 +41,9 @@
</div>
<div class="navbar-collapse collapse" id="navbar-main">
<ul class="nav navbar-nav">
<li>
<a href="{% url instances %}"><i class="fa fa-desktop"></i> Instances</a>
</li>
<li>
<a href="{% url services %}"><i class="fa fa-gears"></i> Services</a>
</li>
@ -54,15 +57,21 @@
<li>
<a href="{% url create-service %}"><i class="fa fa-gears"></i> Service</a>
</li>
<li>
<a href="{% url create-instance %}"><i class="fa fa-desktop"></i> Instance</a>
</li>
<li class="divider"></li>
<li>
<a href="{% url create-check %}?service={{ service.id }}" class=""><i class="glyphicon glyphicon-signal" title="Add new metric check"></i> Graphite check</a>
<a href="{% url create-check %}?service={{ service.id }}&instance={{ instance.id }}" class=""><i class="glyphicon glyphicon-signal" title="Add new metric check"></i> Graphite check</a>
</li>
<li>
<a href="{% url create-http-check %}?service={{ service.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-arrow-up"></i> Http check</a>
<a href="{% url create-http-check %}?service={{ service.id }}&instance={{ instance.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-arrow-up"></i> Http check</a>
</li>
<li>
<a href="{% url create-jenkins-check %}?service={{ service.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-ok"></i> Jenkins check</a>
<a href="{% url create-jenkins-check %}?service={{ service.id }}&instance={{ instance.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-ok"></i> Jenkins check</a>
</li>
<li>
<a href="{% url create-icmp-check %}?service={{ service.id }}&instance={{ instance.id }}" class="" title="Add new ICMP check"><i class="glyphicon glyphicon-transfer"></i> ICMP check</a>
</li>
</ul>
</li>

View File

@ -0,0 +1,43 @@
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
{% if not instances %}
<div class="col-xs-11 col-xs-offset-1">
<hr></hr>
No instances configured
</div>
</tr>
{% else %}
<th>Name</th>
<th>Overall</th>
<th>Active checks</th>
<th>Disabled checks</th>
<th></th>
</tr>
</thead>
<tbody>
{% for instance in instances %}
<tr class="{% if instance.alerts_enabled %}enabled{% else %}warning{% endif %}">
<td>
<a href="{% url instance pk=instance.id %}" title="Alerts {% if instance.alerts_enabled %}enabled{% else %}disabled{% endif %}">{{instance.name}} </a>
</td>
<td>
<span class="label label-{% if not instance.alerts_enabled %}warning{% else %}{% if instance.overall_status == instance.PASSING_STATUS %}success{% else %}danger{% endif %}{% endif %}">{% if instance.alerts_enabled %}{{ instance.overall_status|lower|capfirst }}{% else %}Disabled{% endif %}</span>
</td>
<td>
<span class="label label-{% if instance.active_status_checks.all.count > 0 %}{% if instance.overall_status != instance.PASSING_STATUS %}danger{% else %}success{% endif %}{% else %}default{% endif %}">{{ instance.active_status_checks.all.count }}</span>
</td>
<td>
<span class="label label-{% if instance.inactive_status_checks.all.count > 0 %}warning{% else %}default{% endif %}">{{ instance.inactive_status_checks.all.count }}</span>
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% url update-instance pk=instance.id %}" role="button">
<i class="glyphicon glyphicon-edit"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>

View File

@ -0,0 +1,43 @@
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
{% if not services %}
<div class="col-xs-11 col-xs-offset-1">
<hr></hr>
No services configured
</div>
</tr>
{% else %}
<th>Name</th>
<th>Overall</th>
<th>Active checks</th>
<th>Disabled checks</th>
<th></th>
</tr>
</thead>
<tbody>
{% for service in services %}
<tr class="{% if service.alerts_enabled %}enabled{% else %}warning{% endif %}">
<td>
<a href="{% url service pk=service.id %}" title="Alerts {% if service.alerts_enabled %}enabled{% else %}disabled{% endif %}">{{service.name}} </a>
</td>
<td>
<span class="label label-{% if not service.alerts_enabled %}warning{% else %}{% if service.overall_status == service.PASSING_STATUS %}success{% else %}danger{% endif %}{% endif %}">{% if service.alerts_enabled %}{{ service.overall_status|lower|capfirst }}{% else %}Disabled{% endif %}</span>
</td>
<td>
<span class="label label-{% if service.active_status_checks.all.count > 0 %}{% if service.overall_status != service.PASSING_STATUS %}danger{% else %}success{% endif %}{% else %}default{% endif %}">{{ service.active_status_checks.all.count }}</span>
</td>
<td>
<span class="label label-{% if service.inactive_status_checks.all.count > 0 %}warning{% else %}default{% endif %}">{{ service.inactive_status_checks.all.count }}</span>
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% url update-service pk=service.id %}" role="button">
<i class="glyphicon glyphicon-edit"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>

View File

@ -15,6 +15,9 @@
{% if checks_type == "All" or checks_type == "Jenkins" %}
&nbsp;<a href="{% url create-jenkins-check %}?service={{ service.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-ok"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "ICMP" %}
&nbsp;<a href="{% url create-icmp-check %}?service={{ service.id }}" class="" title="Add new ICMP check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-transfer"></i></a>
{% endif %}
</h3>
</div>
</div>
@ -37,6 +40,7 @@
<th>Test description</th>
<th>Importance</th>
<th>Service(s)</th>
<th>Instance(s)</th>
<th></th>
</tr>
</thead>
@ -81,6 +85,18 @@
<span class="label label-warning">No service</span>
{% endif %}
</td>
<td>
{% for instance in check.instance_set.all %}
<a href="{% url instance pk=instance.id %}">{{ instance.name }}</a>
{% if forloop.last %}
{% else %}
/
{% endif %}
{% endfor %}
{% if not check.instance_set.all %}
<span class="label label-warning">No instance</span>
{% endif %}
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url update-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url update-http-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url update-jenkins-check pk=check.id %}{% endif %}">
<i class="glyphicon glyphicon-edit"></i><span class="break"></span>
@ -100,4 +116,4 @@
</table>
{% endif %}
</div>
</div>
</div>

View File

@ -0,0 +1,119 @@
{% load extra %}
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="{% if checks_type == "All" %}fa fa-cog{% else %}glyphicon glyphicon-{% if checks_type == "Http" %}arrow-up{% elif checks_type == "Jenkins" %}ok{% elif checks_type == "ICMP" %}transfer{% else %}signal{% endif %}{% endif %}"></i></h3></div>
<div class="col-xs-8"><h3>{{ checks_type }} checks</h3></div>
<div class="col-xs-3 text-right">
<h3>
&nbsp;{% if checks_type == "All" or checks_type == "Graphite" %}
<a href="{% url create-check %}?instance={{ instance.id }}" class=""><i class="glyphicon glyphicon-plus" title="Add new metric check"></i><i class="glyphicon glyphicon-signal" title="Add new metric check"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "Http" %}
&nbsp;<a href="{% url create-http-check %}?instance={{ instance.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-arrow-up"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "Jenkins" %}
&nbsp;<a href="{% url create-jenkins-check %}?instance={{ instance.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-ok"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "ICMP" %}
&nbsp;<a href="{% url create-icmp-check %}?instance={{ instance.id }}" class="" title="Add new ICMP check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-transfer"></i></a>
{% endif %}
</h3>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12">
{% if not checks %}
<div class="col-xs-11 col-xs-offset-1">No checks configured</div>
{% else %}
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th></th>
{% if checks_type == "All" %}
<th class="text-center">Type</th>
{% endif %}
<th>Test description</th>
<th>Importance</th>
<th>Service(s)</th>
<th>Instance(s)</th>
<th></th>
</tr>
</thead>
<tbody>
{% for check in checks %}
<tr class="{% if check.active %}enabled{% else %}warning{% endif %}">
<td title="{{ check.name }} - alerts {% if check.active %}enabled{% else %}disabled{% endif %}">
<a href="{% url check pk=check.id %}">{{check.name}}</a>
</td>
<td title="{{ check.calculated_status }}">
{% if check.active %}
<span class="label label-{% if check.calculated_status == 'passing' %}success{% else %}danger{% endif %}">
{{ check.calculated_status|capfirst }}
</span>
{% else %}
<span class="label label-warning">Disabled</span>
{% endif %}
</td>
<td title="Last result from {{ check.last_run|timesince }} ago (rightmost is newest)" class="sparktristate" values="{{ check.cached_health }}">
{% if not check.recent_results %}
No results available
{% endif %}
</td>
{% if checks_type == "All" %}
<td class="text-center">
<i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% endif %}"></i>
</td>
{% endif %}
<td title="">
{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{{ check.metric|truncatechars:70 }} {{ check.check_type }} {{ check.value }}{% if check.expected_num_hosts %} (from {{ check.expected_num_hosts }} hosts){% endif %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}Status code {{ check.status_code }} from {{ check.endpoint }}{% if check.text_match %}; match text /{{ check.text_match }}/{% endif %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}Monitor job {{ check.name }}{% if check.max_queued_build_time %}; check no build waiting for >{{ check.max_queued_build_time }} minutes{% endif %}{% endif %}
</td>
<td>{{ check.get_importance_display }}</td>
<td>
{% for service in check.service_set.all %}
<a href="{% url service pk=service.id %}">{{ service.name }}</a>
{% if forloop.last %}
{% else %}
/
{% endif %}
{% endfor %}
{% if not check.service_set.all %}
<span class="label label-warning">No service</span>
{% endif %}
</td>
<td>
{% for instance in check.instance_set.all %}
<a href="{% url instance pk=instance.id %}">{{ instance.name }}</a>
{% if forloop.last %}
{% else %}
/
{% endif %}
{% endfor %}
{% if not check.instance_set.all %}
<span class="label label-warning">No instance</span>
{% endif %}
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url update-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url update-http-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url update-jenkins-check pk=check.id %}{% endif %}">
<i class="glyphicon glyphicon-edit"></i><span class="break"></span>
</a>
<a class="btn btn-xs" href="{% url run-check pk=check.id %}">
<i class="glyphicon glyphicon-refresh"></i><span class="break"></span>
</a>
{% if checks_type == "Jenkins" %}
<a class="btn btn-xs" href="{% jenkins_human_url check.name %}">
<i class="glyphicon glyphicon-link"></i><span class="break"></span>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>

View File

@ -0,0 +1,103 @@
{% load extra %}
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="{% if checks_type == "All" %}fa fa-cog{% else %}glyphicon glyphicon-{% if checks_type == "Http" %}arrow-up{% elif checks_type == "Jenkins" %}ok{% else %}signal{% endif %}{% endif %}"></i></h3></div>
<div class="col-xs-8"><h3>{{ checks_type }} checks</h3></div>
<div class="col-xs-3 text-right">
<h3>
&nbsp;{% if checks_type == "All" or checks_type == "Graphite" %}
<a href="{% url create-check %}?service={{ service.id }}" class=""><i class="glyphicon glyphicon-plus" title="Add new metric check"></i><i class="glyphicon glyphicon-signal" title="Add new metric check"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "Http" %}
&nbsp;<a href="{% url create-http-check %}?service={{ service.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-arrow-up"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "Jenkins" %}
&nbsp;<a href="{% url create-jenkins-check %}?service={{ service.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-ok"></i></a>
{% endif %}
</h3>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12">
{% if not checks %}
<div class="col-xs-11 col-xs-offset-1">No checks configured</div>
{% else %}
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th></th>
{% if checks_type == "All" %}
<th class="text-center">Type</th>
{% endif %}
<th>Test description</th>
<th>Importance</th>
<th>Service(s)</th>
<th></th>
</tr>
</thead>
<tbody>
{% for check in checks %}
<tr class="{% if check.active %}enabled{% else %}warning{% endif %}">
<td title="{{ check.name }} - alerts {% if check.active %}enabled{% else %}disabled{% endif %}">
<a href="{% url check pk=check.id %}">{{check.name}}</a>
</td>
<td title="{{ check.calculated_status }}">
{% if check.active %}
<span class="label label-{% if check.calculated_status == 'passing' %}success{% else %}danger{% endif %}">
{{ check.calculated_status|capfirst }}
</span>
{% else %}
<span class="label label-warning">Disabled</span>
{% endif %}
</td>
<td title="Last result from {{ check.last_run|timesince }} ago (rightmost is newest)" class="sparktristate" values="{{ check.cached_health }}">
{% if not check.recent_results %}
No results available
{% endif %}
</td>
{% if checks_type == "All" %}
<td class="text-center">
<i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% endif %}"></i>
</td>
{% endif %}
<td title="">
{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{{ check.metric|truncatechars:70 }} {{ check.check_type }} {{ check.value }}{% if check.expected_num_hosts %} (from {{ check.expected_num_hosts }} hosts){% endif %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}Status code {{ check.status_code }} from {{ check.endpoint }}{% if check.text_match %}; match text /{{ check.text_match }}/{% endif %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}Monitor job {{ check.name }}{% if check.max_queued_build_time %}; check no build waiting for >{{ check.max_queued_build_time }} minutes{% endif %}{% endif %}
</td>
<td>{{ check.get_importance_display }}</td>
<td>
{% for service in check.service_set.all %}
<a href="{% url service pk=service.id %}">{{ service.name }}</a>
{% if forloop.last %}
{% else %}
/
{% endif %}
{% endfor %}
{% if not check.service_set.all %}
<span class="label label-warning">No service</span>
{% endif %}
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url update-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url update-http-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url update-jenkins-check pk=check.id %}{% endif %}">
<i class="glyphicon glyphicon-edit"></i><span class="break"></span>
</a>
<a class="btn btn-xs" href="{% url run-check pk=check.id %}">
<i class="glyphicon glyphicon-refresh"></i><span class="break"></span>
</a>
{% if checks_type == "Jenkins" %}
<a class="btn btn-xs" href="{% jenkins_human_url check.name %}">
<i class="glyphicon glyphicon-link"></i><span class="break"></span>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>

View File

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% block content %}
<h1>Delete service</h1>
<form action="." method="post">{% csrf_token %}
<button type="submit" class="btn btn-danger">Delete {{ object }}</button>
</form>
{% endblock %}
{% load compress %}
{% block js %}
{{ block.super }}
{% compress js %}
<script type="text/coffeescript">
</script>
{% endcompress %}
{% endblock %}

View File

@ -0,0 +1,225 @@
{% extends 'base.html' %}
{% block title %}{{ block.super }} - {{ instance.name }}{% endblock title %}
{% block content %}
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h2><i class="fa fa-desktop"></i></h2></div>
<div class="col-xs-5"><h2><span class="break"></span>{{ instance.name }}</h2></div>
<div class="col-xs-4 text-right"><h2><span class="label label-{% if instance.overall_status == instance.PASSING_STATUS %}success{% else %}danger{% endif %}">{{ instance.overall_status|lower|capfirst }}</span> <span class="label label-{% if instance.alerts_enabled %}success{% else %}warning{% endif %}">{% if instance.alerts_enabled %}Alerts enabled{%else %}Alerts disabled{% endif %}</span></h2></div>
<div class="col-xs-2 text-right"><h2><a href="{% url update-instance instance.id %}"><i class="glyphicon glyphicon-edit"></i></a></h2></div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6 col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-cog"></i></h3></div>
<div class="col-xs-11"><h3>Configuration</h3></div>
<div class="col-xs-12">
<div class="col-xs-1"><h5><i class="glyphicon glyphicon-home"></i></h5></div>
<div class="col-xs-3"><h5><span class="break"></span>Address</h5></div>
<div class="col-xs-8"><h5>{{ instance.address|urlize|default:"None configured" }}</h5></div>
</div>
<div class="col-xs-12">
<div class="col-xs-1"><h5><i class="glyphicon glyphicon-user"></i></h5></div>
<div class="col-xs-3"><h5><span class="break"></span>Users watching</h5></div>
<div class="col-xs-8"><h5>
{% if not instance.users_to_notify.all %}
No users subscribed
{% else %}
{{ instance.users_to_notify.all|join:", " }}
{% endif %}
</h5></div>
</div>
<div class="col-xs-12">
<div class="col-xs-1"><h5><i class="fa fa-exclamation-triangle"></i></h5></div>
<div class="col-xs-3"><h5><span class="break"></span>Alert types</h5></div>
<div class="col-xs-8">
<h5>
{% if instance.email_alert %}<i class="fa fa-envelope"></i> Email{% endif %}
{% if instance.hipchat_alert %}<i class="fa fa-comment"></i> Hipchat{% endif %}
{% if instance.sms_alert %}<i class="fa fa-mobile"></i> SMS{% endif %}
{% if instance.telephone_alert %}<i class="fa fa-phone"></i> Telephone{% endif %}
</h5>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="col-xs-1"><h3><i class="fa fa-bar-chart-o"></i></h3></div>
<div class="col-xs-11"><h3>Status (24 hours)</h3></div>
<div class="col-xs-12">
<div id="graph" style="height: 150px; margin: 1 0px;"></div>
<div id="timeline"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-gears"></i></h3></div>
<div class="col-xs-8"><h3>Services</h3></div>
</div>
{% include 'cabotapp/_service_list.html' with services=instance.service_set.all %}
</div>
</div>
<hr>
{% include 'cabotapp/_statuscheck_list_instance.html' with checks=instance.graphite_status_checks.all instance=instance checks_type="Graphite" %}
<hr>
{% include 'cabotapp/_statuscheck_list_instance.html' with checks=instance.http_status_checks.all instance=instance checks_type="Http" %}
<hr>
{% include 'cabotapp/_statuscheck_list_instance.html' with checks=instance.jenkins_status_checks.all instance=instance checks_type="Jenkins" %}
<hr>
{% include 'cabotapp/_statuscheck_list_instance.html' with checks=instance.icmp_status_checks.all instance=instance checks_type="ICMP" %}
<hr>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-table"></i></h3></div>
<div class="col-xs-11">
<h3>Status check report</h3>
</div>
<div class="col-xs-12">
<form action="{% url checks-report %}" method="get">
<div class="form-group">
<div class="col-xs-12">
{{ report_form.instance }}
<label class="col-xs-2 control-label">{{ report_form.checks.label_tag }}</label>
<div class="col-xs-10">{{ report_form.checks }}</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12">
<label class="col-xs-2 control-label">{{ report_form.date_from.label_tag }}</label>
<div class="col-xs-10">{{ report_form.date_from }}</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12">
<label class="col-xs-2 control-label">{{ report_form.date_to.label_tag }}</label>
<div class="col-xs-10">{{ report_form.date_to }}</div>
</div>
</div>
<div class="col-xs-12">
<div class="form-group">
<div class="col-xs-6 col-xs-offset-2">
<button type="submit" class="btn btn-primary">Get report</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-exclamation-triangle"></i></h3></div>
<div class="col-xs-11">
<h3>Recovery instructions</h3>
</div>
{% if instance.hackpad_id %}
<div class="col-xs-12">
<script src="{{ instance.hackpad_id }}"></script>
</div>
{% else %}
<div class="col-xs-11 col-xs-offset-1">No hackpad configured</div>
{% endif %}
</div>
</div>
</div>
{% endblock content %}
{% block js %}
{% load compress %}
{% load jsonify %}
{{ block.super }}
<script type="text/javascript">
window.INSTANCE_HISTORY = {{ instance.recent_snapshots|jsonify }}
</script>
<script type="text/javascript" src="{{ STATIC_URL }}arachnys/js/d3.js"></script>
{% compress js %}
<script type="text/javascript" src="{{ STATIC_URL }}arachnys/js/rickshaw.js"></script>
<script type="text/coffeescript">
$(document).ready ->
data = window.INSTANCE_HISTORY
labels = {
num_checks_active: 'blue'
num_checks_failing: '#f00'
}
processedData = formatDataForRickshaw data, labels
drawRickshaw processedData.series, labels, processedData.events
formatDataForRickshaw = (data, labels) ->
series = {}
events = []
for label, color of labels
series[label] = {
color: color
name: label
data: []
}
for slice in data
if slice.did_send_alert
events.push {time: slice.time, message: 'Sent alert'}
for label, color of labels
series[label].data.push
x: slice.time
y: slice[label]
ret = []
for line, val of series
ret.push val
return {
series: ret
events: events
}
drawRickshaw = (data, labels, events = []) ->
rickshawLine = new Rickshaw.Graph
renderer: 'line'
element: document.querySelector('#graph')
series: data
height: 140
rickshawLine.render()
hoverDetail = new Rickshaw.Graph.HoverDetail({graph: rickshawLine})
xAxis = new Rickshaw.Graph.Axis.Time({graph: rickshawLine})
xAxis.render()
yAxis = new Rickshaw.Graph.Axis.Y({graph: rickshawLine})
yAxis.render()
window.annotator = annotator = new Rickshaw.Graph.Annotate({
graph: rickshawLine
element: document.getElementById('timeline')
})
for evt in events
annotator.add evt.time, evt.message
annotator.update()
</script>
<script type="text/javascript">
$(function(){
$(':input.datepicker').datepicker({
dateFormat: 'yy-mm-dd',
buttonImage: '{{ STATIC_URL }}theme/img/calendar.gif',
buttonImageOnly: true,
showOn: 'button'
});
});
</script>
{% endcompress %}
{% endblock js %}

View File

@ -0,0 +1,36 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-xs-12">
<div class="col-xs-10 col-xs-offset-2">
<h2>{% if form.instance.id %}Edit instance{% else %}New instance{% endif %}</h2>
</div>
</div>
</div>
<form class="form-horizontal" action="" method="post" role="form">
{% include "cabotapp/_base_form.html" %}
<div class="col-xs-12">
<div class="form-group">
<div class="col-xs-6 col-xs-offset-2">
<button type="submit" class="btn btn-primary">Submit</button>
<a href="{% url dashboard %}" class="btn">Back to dashboard</a>
</div>
{% if form.instance.id %}
<div class="col-xs-4">
<a class="btn btn-danger" href="{% url delete-service form.instance.id %}">Delete instance</a>
</div>
{% endif %}
</div>
</div>
</form>
{% endblock %}
{% load compress %}
{% block js %}
{{ block.super }}
{% compress js %}
<script type="text/coffeescript">
</script>
{% endcompress %}
{% endblock %}

View File

@ -0,0 +1,63 @@
{% extends 'base.html' %}
{% block content %}
<div class="row sortable ui-sortable">
<div class="col-xs-12" data-original-title="">
<div class="col-xs-1"><h2><i class="fa fa-desktop"></i></h2></div>
<div class="col-xs-10"><h2><span class="break"></span>Instances</h2></div>
<div class="col-xs-1 text-right">
<h2><a href="{% url create-instance %}" title="New instance"><i class="glyphicon glyphicon-plus"></i></a></h2>
</div>
</div>
<div class="col-xs-12">
{% if not instances %}
No instances
{% else %}
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
<th>Name</th>
<th>Overall</th>
<th>Active checks</th>
<th>Disabled checks</th>
<th></th>
</tr>
</thead>
<tbody>
{% for instance in instances %}
<tr class="{% if instance.alerts_enabled %}enabled{% else %}warning{% endif %}">
<td>
<a href="{% url instance pk=instance.id %}" title="Alerts {% if instance.alerts_enabled %}enabled{% else %}disabled{% endif %}">{{instance.name}} </a>
</td>
<td>
<span class="label label-{% if not instance.alerts_enabled %}warning{% else %}{% if instance.overall_status == instance.PASSING_STATUS %}success{% else %}danger{% endif %}{% endif %}">{% if instance.alerts_enabled %}{{ instance.overall_status|lower|capfirst }}{% else %}Disabled{% endif %}</span>
</td>
<td>
<span class="label label-{% if instance.active_status_checks.all.count > 0 %}{% if instance.overall_status != instance.PASSING_STATUS %}danger{% else %}success{% endif %}{% else %}default{% endif %}">{{ instance.active_status_checks.all.count }}</span>
</td>
<td>
<span class="label label-{% if instance.inactive_status_checks.all.count > 0 %}warning{% else %}default{% endif %}">{{ instance.inactive_status_checks.all.count }}</span>
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% url update-instance pk=instance.id %}" role="button">
<i class="glyphicon glyphicon-edit"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
{% endblock content %}
{% block js %}
{% load compress %}
{{ block.super }}
{% compress js %}
<script type="text/coffeescript">
</script>
{% endcompress %}
{% endblock js %}

View File

@ -55,18 +55,29 @@
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-desktop"></i></h3></div>
<div class="col-xs-8"><h3>Instances</h3></div>
<div class="col-xs-3 text-right">
<h3>
<a href="{% url create-instance %}?service={{ service.id }}" class=""><i class="glyphicon glyphicon-plus" title="Add new instance for this service"></i><i class="fa fa-desktop" title=""></i></a>
</h3>
</div>
{% include 'cabotapp/_instance_list.html' with instances=service.instances.all %}
</div>
</div>
<hr>
{% include 'cabotapp/_statuscheck_list_service.html' with checks=service.graphite_status_checks.all service=service checks_type="Graphite" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=service.graphite_status_checks.all service=service checks_type="Graphite" %}
{% include 'cabotapp/_statuscheck_list_service.html' with checks=service.http_status_checks.all service=service checks_type="Http" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=service.http_status_checks.all service=service checks_type="Http" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=service.jenkins_status_checks.all service=service checks_type="Jenkins" %}
{% include 'cabotapp/_statuscheck_list_service.html' with checks=service.jenkins_status_checks.all service=service checks_type="Jenkins" %}
<hr>

View File

@ -6,14 +6,14 @@
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h2><i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% endif %}"></i></h2></div>
<div class="col-xs-1"><h2><i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}transfer{% endif %}"></i></h2></div>
<div class="col-xs-5"><h2>{{ check.name }}</h2></div>
<div class="col-xs-4 text-right"><h2><span class="label label-{% if check.calculated_status == 'passing' %}success{% else %}danger{% endif %}">{{ check.calculated_status|capfirst }}</span></h2></div>
<div class="col-xs-2 text-right"><h2>
{% if check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}
<a href="{% jenkins_human_url check.name %}" class=""><i class="glyphicon glyphicon-link"></i></a>
{% endif %}
<a href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url update-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url update-http-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url update-jenkins-check pk=check.id %}{% endif %}" class=""><i class="glyphicon glyphicon-edit"></i></a> <a href="{% url run-check pk=check.id %}"><i class="glyphicon glyphicon-refresh"></i></a>
<a href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url update-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url update-http-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url update-jenkins-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}{% url update-icmp-check pk=check.id %}{% endif %}" class=""><i class="glyphicon glyphicon-edit"></i></a> <a href="{% url run-check pk=check.id %}"><i class="glyphicon glyphicon-refresh"></i></a>
</h2></div>
</div>
</div>
@ -81,4 +81,4 @@
</script>
{% endcompress %}
{% endblock js %}
{% endblock js %}

View File

@ -21,4 +21,4 @@
$('.sparktristate').sparkline('html', {type: 'tristate'})
</script>
{% endcompress %}
{% endblock js %}
{% endblock js %}

View File

@ -3,10 +3,12 @@ from cabotapp.views import (
run_status_check, graphite_api_data, twiml_callback, checks_run_recently,
GraphiteCheckCreateView, GraphiteCheckUpdateView,
HttpCheckCreateView, HttpCheckUpdateView,
ICMPCheckCreateView, ICMPCheckUpdateView,
JenkinsCheckCreateView, JenkinsCheckUpdateView,
StatusCheckDeleteView, StatusCheckListView, StatusCheckDetailView,
StatusCheckResultDetailView, StatusCheckReportView)
from cabotapp.views import (ServiceListView, ServiceDetailView,
from cabotapp.views import (InstanceListView, InstanceDetailView,
InstanceUpdateView, InstanceCreateView, InstanceDeleteView, ServiceListView, ServiceDetailView,
ServiceUpdateView, ServiceCreateView, ServiceDeleteView,
UserProfileUpdateView, ShiftListView, subscriptions)
from django.contrib import admin
@ -43,6 +45,19 @@ urlpatterns = patterns('',
url(r'^service/(?P<pk>\d+)/',
view=ServiceDetailView.as_view(), name='service'),
url(r'^instances/', view=InstanceListView.as_view(),
name='instances'),
url(r'^instance/create/', view=InstanceCreateView.as_view(),
name='create-instance'),
url(r'^instance/update/(?P<pk>\d+)/',
view=InstanceUpdateView.as_view(
), name='update-instance'),
url(r'^instance/delete/(?P<pk>\d+)/',
view=InstanceDeleteView.as_view(
), name='delete-instance'),
url(r'^instance/(?P<pk>\d+)/',
view=InstanceDetailView.as_view(), name='instance'),
url(r'^checks/$', view=StatusCheckListView.as_view(),
name='checks'),
url(r'^check/run/(?P<pk>\d+)/',
@ -55,6 +70,11 @@ urlpatterns = patterns('',
url(r'^checks/report/$',
view=StatusCheckReportView.as_view(), name='checks-report'),
url(r'^icmpcheck/create/', view=ICMPCheckCreateView.as_view(),
name='create-icmp-check'),
url(r'^icmpcheck/update/(?P<pk>\d+)/',
view=ICMPCheckUpdateView.as_view(
), name='update-icmp-check'),
url(r'^graphitecheck/create/',
view=GraphiteCheckCreateView.as_view(
), name='create-check'),