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 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(UserProfile)
admin.site.register(Shift) admin.site.register(Shift)
@ -7,3 +7,4 @@ admin.site.register(Service)
admin.site.register(ServiceStatusSnapshot) admin.site.register(ServiceStatusSnapshot)
admin.site.register(StatusCheck) admin.site.register(StatusCheck)
admin.site.register(StatusCheckResult) 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 json
import re import re
import time import time
import os
import requests import requests
from celery.utils.log import get_task_logger from celery.utils.log import get_task_logger
@ -65,7 +66,11 @@ def calculate_debounced_passing(recent_results, debounce=0):
return False return False
class Service(models.Model): class CheckGroupMixin(models.Model):
class Meta:
abstract = True
PASSING_STATUS = 'PASSING' PASSING_STATUS = 'PASSING'
WARNING_STATUS = 'WARNING' WARNING_STATUS = 'WARNING'
ERROR_STATUS = 'ERROR' ERROR_STATUS = 'ERROR'
@ -88,10 +93,7 @@ class Service(models.Model):
) )
name = models.TextField() name = models.TextField()
url = models.TextField(
blank=True,
help_text="URL of service."
)
users_to_notify = models.ManyToManyField( users_to_notify = models.ManyToManyField(
User, User,
blank=True, 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' 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): def __unicode__(self):
return self.name return self.name
@ -201,18 +201,6 @@ class Service(models.Model):
s['time'] = time.mktime(s['time'].timetuple()) s['time'] = time.mktime(s['time'].timetuple())
return snapshots 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): def graphite_status_checks(self):
return self.status_checks.filter(polymorphic_ctype__model='graphitestatuscheck') return self.status_checks.filter(polymorphic_ctype__model='graphitestatuscheck')
@ -231,6 +219,53 @@ class Service(models.Model):
def active_jenkins_status_checks(self): def active_jenkins_status_checks(self):
return self.jenkins_status_checks().filter(active=True) 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): class ServiceStatusSnapshot(models.Model):
service = models.ForeignKey(Service, related_name='snapshots') service = models.ForeignKey(Service, related_name='snapshots')
@ -399,6 +434,28 @@ class StatusCheck(PolymorphicModel):
for service in services: for service in services:
update_service.delay(service.id) 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): class GraphiteStatusCheck(StatusCheck):
@ -532,7 +589,6 @@ class HttpStatusCheck(StatusCheck):
result.succeeded = True result.succeeded = True
return result return result
class JenkinsStatusCheck(StatusCheck): class JenkinsStatusCheck(StatusCheck):
class Meta(StatusCheck.Meta): 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.core.urlresolvers import reverse_lazy
from django.conf import settings from django.conf import settings
from models import ( from models import (
StatusCheck, GraphiteStatusCheck, JenkinsStatusCheck, HttpStatusCheck, StatusCheck, GraphiteStatusCheck, JenkinsStatusCheck, HttpStatusCheck, ICMPStatusCheck,
StatusCheckResult, UserProfile, Service, Shift, get_duty_officers) StatusCheckResult, UserProfile, Service, Instance, Shift, get_duty_officers)
from tasks import run_status_check as _run_status_check from tasks import run_status_check as _run_status_check
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -59,7 +59,6 @@ class StatusCheckResultDetailView(LoginRequiredMixin, DetailView):
model = StatusCheckResult model = StatusCheckResult
context_object_name = 'result' context_object_name = 'result'
class SymmetricalForm(forms.ModelForm): class SymmetricalForm(forms.ModelForm):
symmetrical_fields = () # Iterable of 2-tuples (field, model) symmetrical_fields = () # Iterable of 2-tuples (field, model)
@ -90,7 +89,7 @@ base_widgets = {
class StatusCheckForm(SymmetricalForm): class StatusCheckForm(SymmetricalForm):
symmetrical_fields = ('service_set',) symmetrical_fields = ('service_set', 'instance_set')
service_set = forms.ModelMultipleChoiceField( service_set = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(), queryset=Service.objects.all(),
required=False, 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): 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 HttpStatusCheckForm(StatusCheckForm):
class Meta: class Meta:
@ -195,6 +218,57 @@ class UserProfileForm(forms.ModelForm):
model = UserProfile model = UserProfile
exclude = ('user',) 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): class ServiceForm(forms.ModelForm):
@ -206,6 +280,7 @@ class ServiceForm(forms.ModelForm):
'url', 'url',
'users_to_notify', 'users_to_notify',
'status_checks', 'status_checks',
'instances',
'email_alert', 'email_alert',
'hipchat_alert', 'hipchat_alert',
'sms_alert', 'sms_alert',
@ -220,6 +295,10 @@ class ServiceForm(forms.ModelForm):
'data-rel': 'chosen', 'data-rel': 'chosen',
'style': 'width: 70%', 'style': 'width: 70%',
}), }),
'instances': forms.SelectMultiple(attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'users_to_notify': forms.CheckboxSelectMultiple(), 'users_to_notify': forms.CheckboxSelectMultiple(),
'hackpad_id': forms.TextInput(attrs={'style': 'width:30%;'}), 'hackpad_id': forms.TextInput(attrs={'style': 'width:30%;'}),
} }
@ -293,20 +372,30 @@ class CheckCreateView(LoginRequiredMixin, CreateView):
if metric: if metric:
initial['metric'] = metric initial['metric'] = metric
service_id = self.request.GET.get('service') service_id = self.request.GET.get('service')
instance_id = self.request.GET.get('instance')
if service_id: if service_id:
try: try:
service = Service.objects.get(id=service_id) service = Service.objects.get(id=service_id)
initial['service_set'] = [service] initial['service_set'] = [service]
except Service.DoesNotExist: except Service.DoesNotExist:
pass pass
if instance_id:
try:
instance = Instance.objects.get(id=instance_id)
initial['instance_set'] = [instance]
except Instance.DoesNotExist:
pass
return initial return initial
def get_success_url(self): 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')}) return reverse('service', kwargs={'pk': self.request.GET.get('service')})
else: if self.request.GET.get('instance'):
return reverse('checks') return reverse('instance', kwargs={'pk': self.request.GET.get('instance')})
return reverse('checks')
class CheckUpdateView(LoginRequiredMixin, UpdateView): class CheckUpdateView(LoginRequiredMixin, UpdateView):
@ -315,8 +404,16 @@ class CheckUpdateView(LoginRequiredMixin, UpdateView):
def get_success_url(self): def get_success_url(self):
return reverse('check', kwargs={'pk': self.object.id}) 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 model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm form_class = GraphiteStatusCheckForm
@ -325,6 +422,9 @@ class GraphiteCheckUpdateView(CheckUpdateView):
model = GraphiteStatusCheck model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm form_class = GraphiteStatusCheckForm
class GraphiteCheckCreateView(CheckCreateView):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
class HttpCheckCreateView(CheckCreateView): class HttpCheckCreateView(CheckCreateView):
model = HttpStatusCheck model = HttpStatusCheck
@ -397,6 +497,14 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
return profile 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): class ServiceListView(LoginRequiredMixin, ListView):
model = Service model = Service
context_object_name = 'services' context_object_name = 'services'
@ -404,6 +512,20 @@ class ServiceListView(LoginRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
return Service.objects.all().order_by('name').prefetch_related('status_checks') 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): class ServiceDetailView(LoginRequiredMixin, DetailView):
model = Service model = Service
@ -421,6 +543,43 @@ class ServiceDetailView(LoginRequiredMixin, DetailView):
return context 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): class ServiceCreateView(LoginRequiredMixin, CreateView):
model = Service model = Service
form_class = ServiceForm form_class = ServiceForm
@ -428,6 +587,12 @@ class ServiceCreateView(LoginRequiredMixin, CreateView):
def get_success_url(self): def get_success_url(self):
return reverse('service', kwargs={'pk': self.object.id}) 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): class ServiceUpdateView(LoginRequiredMixin, UpdateView):
model = Service model = Service
@ -443,6 +608,11 @@ class ServiceDeleteView(LoginRequiredMixin, DeleteView):
context_object_name = 'service' context_object_name = 'service'
template_name = 'cabotapp/service_confirm_delete.html' 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): class ShiftListView(LoginRequiredMixin, ListView):
model = Shift model = Shift

View File

@ -41,6 +41,9 @@
</div> </div>
<div class="navbar-collapse collapse" id="navbar-main"> <div class="navbar-collapse collapse" id="navbar-main">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li>
<a href="{% url instances %}"><i class="fa fa-desktop"></i> Instances</a>
</li>
<li> <li>
<a href="{% url services %}"><i class="fa fa-gears"></i> Services</a> <a href="{% url services %}"><i class="fa fa-gears"></i> Services</a>
</li> </li>
@ -54,15 +57,21 @@
<li> <li>
<a href="{% url create-service %}"><i class="fa fa-gears"></i> Service</a> <a href="{% url create-service %}"><i class="fa fa-gears"></i> Service</a>
</li> </li>
<li>
<a href="{% url create-instance %}"><i class="fa fa-desktop"></i> Instance</a>
</li>
<li class="divider"></li> <li class="divider"></li>
<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>
<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>
<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> </li>
</ul> </ul>
</li> </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" %} {% 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> &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 %} {% 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> </h3>
</div> </div>
</div> </div>
@ -37,6 +40,7 @@
<th>Test description</th> <th>Test description</th>
<th>Importance</th> <th>Importance</th>
<th>Service(s)</th> <th>Service(s)</th>
<th>Instance(s)</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -81,6 +85,18 @@
<span class="label label-warning">No service</span> <span class="label label-warning">No service</span>
{% endif %} {% endif %}
</td> </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"> <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 %}"> <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> <i class="glyphicon glyphicon-edit"></i><span class="break"></span>
@ -100,4 +116,4 @@
</table> </table>
{% endif %} {% endif %}
</div> </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>
</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> <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> <hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=service.http_status_checks.all service=service checks_type="Http" %} {% include 'cabotapp/_statuscheck_list_service.html' with checks=service.jenkins_status_checks.all service=service checks_type="Jenkins" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=service.jenkins_status_checks.all service=service checks_type="Jenkins" %}
<hr> <hr>

View File

@ -6,14 +6,14 @@
<div class="row"> <div class="row">
<div class="col-xs-12"> <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-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-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> <div class="col-xs-2 text-right"><h2>
{% if check.polymorphic_ctype.model == 'jenkinsstatuscheck' %} {% if check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}
<a href="{% jenkins_human_url check.name %}" class=""><i class="glyphicon glyphicon-link"></i></a> <a href="{% jenkins_human_url check.name %}" class=""><i class="glyphicon glyphicon-link"></i></a>
{% endif %} {% 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> </h2></div>
</div> </div>
</div> </div>
@ -81,4 +81,4 @@
</script> </script>
{% endcompress %} {% endcompress %}
{% endblock js %} {% endblock js %}

View File

@ -21,4 +21,4 @@
$('.sparktristate').sparkline('html', {type: 'tristate'}) $('.sparktristate').sparkline('html', {type: 'tristate'})
</script> </script>
{% endcompress %} {% 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, run_status_check, graphite_api_data, twiml_callback, checks_run_recently,
GraphiteCheckCreateView, GraphiteCheckUpdateView, GraphiteCheckCreateView, GraphiteCheckUpdateView,
HttpCheckCreateView, HttpCheckUpdateView, HttpCheckCreateView, HttpCheckUpdateView,
ICMPCheckCreateView, ICMPCheckUpdateView,
JenkinsCheckCreateView, JenkinsCheckUpdateView, JenkinsCheckCreateView, JenkinsCheckUpdateView,
StatusCheckDeleteView, StatusCheckListView, StatusCheckDetailView, StatusCheckDeleteView, StatusCheckListView, StatusCheckDetailView,
StatusCheckResultDetailView, StatusCheckReportView) StatusCheckResultDetailView, StatusCheckReportView)
from cabotapp.views import (ServiceListView, ServiceDetailView, from cabotapp.views import (InstanceListView, InstanceDetailView,
InstanceUpdateView, InstanceCreateView, InstanceDeleteView, ServiceListView, ServiceDetailView,
ServiceUpdateView, ServiceCreateView, ServiceDeleteView, ServiceUpdateView, ServiceCreateView, ServiceDeleteView,
UserProfileUpdateView, ShiftListView, subscriptions) UserProfileUpdateView, ShiftListView, subscriptions)
from django.contrib import admin from django.contrib import admin
@ -43,6 +45,19 @@ urlpatterns = patterns('',
url(r'^service/(?P<pk>\d+)/', url(r'^service/(?P<pk>\d+)/',
view=ServiceDetailView.as_view(), name='service'), 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(), url(r'^checks/$', view=StatusCheckListView.as_view(),
name='checks'), name='checks'),
url(r'^check/run/(?P<pk>\d+)/', url(r'^check/run/(?P<pk>\d+)/',
@ -55,6 +70,11 @@ urlpatterns = patterns('',
url(r'^checks/report/$', url(r'^checks/report/$',
view=StatusCheckReportView.as_view(), name='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/', url(r'^graphitecheck/create/',
view=GraphiteCheckCreateView.as_view( view=GraphiteCheckCreateView.as_view(
), name='create-check'), ), name='create-check'),