Basic alert acknowledgement mechanism

This commit is contained in:
David Buxton 2015-10-01 21:17:23 -04:00
parent 257bc4a819
commit b4ab215ad2
8 changed files with 437 additions and 118 deletions

View File

@ -1,18 +1,33 @@
import os
# Credentials for Graphite server to monitor
GRAPHITE_API = os.environ.get('GRAPHITE_API')
GRAPHITE_USER = os.environ.get('GRAPHITE_USER')
GRAPHITE_PASS = os.environ.get('GRAPHITE_PASS')
GRAPHITE_FROM = os.getenv('GRAPHITE_FROM', '-10minute')
# Credentials for Jenkins server to monitor
JENKINS_API = os.environ.get('JENKINS_API')
JENKINS_USER = os.environ.get('JENKINS_USER')
JENKINS_PASS = os.environ.get('JENKINS_PASS')
# Point at a public calendar you want to use to schedule a duty rota
CALENDAR_ICAL_URL = os.environ.get('CALENDAR_ICAL_URL')
# So that links back to the Cabot instance display correctly
WWW_HTTP_HOST = os.environ.get('WWW_HTTP_HOST')
WWW_SCHEME = os.environ.get('WWW_SCHEME', "https")
HTTP_USER_AGENT = os.environ.get('HTTP_USER_AGENT', 'Cabot')
# How often should alerts be sent for important failures?
ALERT_INTERVAL = int(os.environ.get('ALERT_INTERVAL', 10))
# How often should notifications be sent for less important issues?
NOTIFICATION_INTERVAL = int(os.environ.get('NOTIFICATION_INTERVAL', 120))
# How long should an acknowledgement silence alerts for?
ACKNOWLEDGEMENT_EXPIRY = int(os.environ.get('ACKNOWLEDGEMENT_EXPIRY', 20))
# Default plugins are used if the user has not specified.
CABOT_PLUGINS_ENABLED = os.environ.get('CABOT_PLUGINS_ENABLED', 'cabot_alert_hipchat,cabot_alert_twilio,cabot_alert_email')

View File

@ -48,8 +48,20 @@ def send_alert(service, duty_officers=None):
for alert in service.alerts.all():
try:
alert.send_alert(service, users, duty_officers)
except Exception:
logging.exception('Could not sent ' + alert.name + ' alert')
except Exception as e:
logging.exception('Could not send %s alert: %s' % (alert.name, e))
def send_alert_update(service, duty_officers=None):
users = service.users_to_notify.filter(is_active=True)
for alert in service.alerts.all():
if hasattr(alert, 'send_alert_update'):
try:
alert.send_alert_update(service, users, duty_officers)
except Exception as e:
logger.exception('Could not send %s alert update: %s' % (alert.name, e))
else:
logger.warning('No send_alert_update method present for %s' % alert.name)
def update_alert_plugins():
for plugin_subclass in AlertPlugin.__subclasses__():

View File

@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as 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 'AlertAcknowledgement'
db.create_table(u'cabotapp_alertacknowledgement', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('time', self.gf('django.db.models.fields.DateTimeField')()),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('service', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cabotapp.Service'])),
))
db.send_create_signal(u'cabotapp', ['AlertAcknowledgement'])
def backwards(self, orm):
# Deleting model 'AlertAcknowledgement'
db.delete_table(u'cabotapp_alertacknowledgement')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'cabotapp.alertacknowledgement': {
'Meta': {'object_name': 'AlertAcknowledgement'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cabotapp.Service']"}),
'time': ('django.db.models.fields.DateTimeField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
},
u'cabotapp.alertplugin': {
'Meta': {'object_name': 'AlertPlugin'},
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'polymorphic_cabotapp.alertplugin_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'title': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'cabotapp.alertpluginuserdata': {
'Meta': {'unique_together': "(('title', 'user'),)", 'object_name': 'AlertPluginUserData'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'polymorphic_cabotapp.alertpluginuserdata_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cabotapp.UserProfile']"})
},
u'cabotapp.instance': {
'Meta': {'ordering': "['name']", 'object_name': 'Instance'},
'address': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'alerts': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cabotapp.AlertPlugin']", 'symmetrical': 'False', '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'}),
u'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'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"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': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
u'cabotapp.instancestatussnapshot': {
'Meta': {'object_name': 'InstanceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': u"orm['cabotapp.Instance']"}),
'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'"}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
u'cabotapp.service': {
'Meta': {'ordering': "['name']", 'object_name': 'Service'},
'alerts': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cabotapp.AlertPlugin']", 'symmetrical': 'False', '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'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instances': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"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': u"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': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
u'cabotapp.servicestatussnapshot': {
'Meta': {'object_name': 'ServiceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
u'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': u"orm['cabotapp.Service']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
u'cabotapp.shift': {
'Meta': {'object_name': 'Shift'},
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'end': ('django.db.models.fields.DateTimeField', [], {}),
u'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': u"orm['auth.User']"})
},
u'cabotapp.statuscheck': {
'Meta': {'ordering': "['name']", 'object_name': 'StatusCheck'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'allowed_num_failures': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': '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': u"orm['auth.User']", 'null': 'True'}),
'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'}),
u'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': "u'polymorphic_cabotapp.statuscheck_set'", 'null': 'True', 'to': u"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'})
},
u'cabotapp.statuscheckresult': {
'Meta': {'ordering': "['-time_complete']", 'object_name': 'StatusCheckResult'},
'check': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cabotapp.StatusCheck']"}),
'error': ('django.db.models.fields.TextField', [], {'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'raw_data': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'succeeded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'time_complete': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'})
},
u'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'}),
u'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': u"orm['auth.User']"})
},
u'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'}),
u'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

@ -8,7 +8,13 @@ from django.contrib.auth.models import User
from celery.exceptions import SoftTimeLimitExceeded
from .jenkins import get_job_status
from .alert import (send_alert, AlertPlugin, AlertPluginUserData, update_alert_plugins)
from .alert import (
send_alert,
send_alert_update,
AlertPlugin,
AlertPluginUserData,
update_alert_plugins
)
from .calendar import get_events
from .graphite import parse_metric
from .graphite import get_data
@ -178,9 +184,28 @@ class CheckGroupMixin(models.Model):
# We don't count "back to normal" as an alert
self.last_alert_sent = None
self.save()
self.snapshot.did_send_alert = True
self.snapshot.save()
send_alert(self, duty_officers=get_duty_officers())
if self.unexpired_acknowledgement():
send_alert_update(self, duty_officers=get_duty_officers())
else:
self.snapshot.did_send_alert = True
self.snapshot.save()
send_alert(self, duty_officers=get_duty_officers())
def acknowledge_alert(self, user):
acknowledgement = AlertAcknowledgement.objects.create(
user=user,
time=timezone.now(),
service=self,
)
def unexpired_acknowledgement(self):
unexpired_acknowledgements = self.alertacknowledgement_set.all().filter(
time__gte=timezone.now()-timedelta(minutes=settings.ACKNOWLEDGEMENT_EXPIRY),
).order_by('-time')
try:
return unexpired_acknowledgements[0]
except:
return None
@property
def recent_snapshots(self):
@ -221,6 +246,7 @@ class CheckGroupMixin(models.Model):
def all_failing_checks(self):
return self.active_status_checks().exclude(calculated_status=self.CALCULATED_PASSING_STATUS)
class Service(CheckGroupMixin):
def update_status(self):
@ -308,6 +334,7 @@ class Instance(CheckGroupMixin):
self.icmp_status_checks().delete()
return super(Instance, self).delete(*args, **kwargs)
class Snapshot(models.Model):
class Meta:
@ -320,18 +347,21 @@ class Snapshot(models.Model):
overall_status = models.TextField(default=Service.PASSING_STATUS)
did_send_alert = models.IntegerField(default=False)
class ServiceStatusSnapshot(Snapshot):
service = models.ForeignKey(Service, related_name='snapshots')
def __unicode__(self):
return u"%s: %s" % (self.service.name, self.overall_status)
class InstanceStatusSnapshot(Snapshot):
instance = models.ForeignKey(Instance, related_name='snapshots')
def __unicode__(self):
return u"%s: %s" % (self.instance.name, self.overall_status)
class StatusCheck(PolymorphicModel):
"""
@ -522,6 +552,7 @@ class StatusCheck(PolymorphicModel):
for instance in instances:
update_instance.delay(instance.id)
class ICMPStatusCheck(StatusCheck):
class Meta(StatusCheck.Meta):
@ -702,6 +733,7 @@ class HttpStatusCheck(StatusCheck):
result.succeeded = True
return result
class JenkinsStatusCheck(StatusCheck):
class Meta(StatusCheck.Meta):
@ -819,6 +851,18 @@ class StatusCheckResult(models.Model):
return super(StatusCheckResult, self).save(*args, **kwargs)
class AlertAcknowledgement(models.Model):
time = models.DateTimeField()
user = models.ForeignKey(User)
service = models.ForeignKey(Service)
def unexpired(self):
return self.expires() > timezone.now()
def expires(self):
return self.time + timedelta(minutes=settings.ACKNOWLEDGEMENT_EXPIRY)
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
@ -845,6 +889,7 @@ class UserProfile(models.Model):
hipchat_alias = models.CharField(max_length=50, blank=True, default='')
fallback_alert_user = models.BooleanField(default=False)
class Shift(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()

View File

@ -170,7 +170,6 @@ def throws_timeout(*args, **kwargs):
class TestCheckRun(LocalTestCase):
def test_calculate_service_status(self):
self.assertEqual(self.graphite_check.calculated_status,
Service.CALCULATED_PASSING_STATUS)
@ -208,6 +207,31 @@ class TestCheckRun(LocalTestCase):
self.service.update_status()
self.assertEqual(self.service.overall_status, Service.PASSING_STATUS)
@patch('cabot.cabotapp.models.send_alert')
@patch('cabot.cabotapp.models.send_alert_update')
def test_alert_acknowledgement(self, fake_send_alert_update, fake_send_alert):
self.assertEqual(self.service.overall_status, Service.PASSING_STATUS)
self.most_recent_result.succeeded = False
self.most_recent_result.save()
self.graphite_check.last_run = timezone.now()
self.graphite_check.save()
self.assertEqual(self.graphite_check.calculated_status,
Service.CALCULATED_FAILING_STATUS)
self.service.update_status()
fake_send_alert.assert_called_with(self.service, duty_officers=[])
fake_send_alert.reset_mock()
self.service.last_alert_sent = timezone.now() - timedelta(minutes=30)
self.service.update_status()
fake_send_alert.assert_called_with(self.service, duty_officers=[])
fake_send_alert.reset_mock()
self.service.acknowledge_alert(user=self.user)
self.service.last_alert_sent = timezone.now() - timedelta(minutes=30)
self.service.update_status()
self.assertEqual(self.service.unexpired_acknowledgement().user, self.user)
fake_send_alert_update.assert_called_with(self.service, duty_officers=[])
@patch('cabot.cabotapp.graphite.requests.get', fake_graphite_response)
def test_graphite_run(self):
checkresults = self.graphite_check.statuscheckresult_set.all()

View File

@ -51,14 +51,12 @@ def subscriptions(request):
})
return HttpResponse(t.render(c))
@login_required
def run_status_check(request, pk):
"""Runs a specific check"""
_run_status_check(check_or_id=pk)
return HttpResponseRedirect(reverse('check', kwargs={'pk': pk}))
def duplicate_icmp_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
@ -666,6 +664,14 @@ class InstanceCreateView(LoginRequiredMixin, CreateView):
return initial
@login_required
def acknowledge_alert(request, service_id):
service = Service.objects.get(pk=service_id)
service.acknowledge_alert(user=request.user)
return HttpResponseRedirect(reverse('service', kwargs={'pk': pk}))
class ServiceCreateView(LoginRequiredMixin, CreateView):
model = Service
form_class = ServiceForm
@ -674,6 +680,7 @@ 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
@ -681,6 +688,7 @@ class InstanceUpdateView(LoginRequiredMixin, UpdateView):
def get_success_url(self):
return reverse('instance', kwargs={'pk': self.object.id})
class ServiceUpdateView(LoginRequiredMixin, UpdateView):
model = Service
form_class = ServiceForm

View File

@ -11,6 +11,18 @@
<div class="col-xs-4 text-right"><h2><span class="label label-{% if service.overall_status == service.PASSING_STATUS %}success{% else %}danger{% endif %}">{{ service.overall_status|lower|capfirst }}</span> <span class="label label-{% if service.alerts_enabled %}success{% else %}warning{% endif %}">{% if service.alerts_enabled %}Alerts enabled{%else %}Alerts disabled{% endif %}</span></h2></div>
<div class="col-xs-2 text-right"><h2><a href="{% url "update-service" service.id %}"><i class="glyphicon glyphicon-edit"></i></a></h2></div>
</div>
{% if service.overall_status != service.PASSING_STATUS %}
{% if service.unexpired_acknowledgement %}
<div class="col-xs-12 alert alert-primary">
<i class="glyphicon glyphicon-pause"></i> {{ service.unexpired_acknowledgement.user.email }} acknowledged an alert against this service at {{ service.unexpired_acknowledgement.time }} - alerts are currently paused. The acknowledgement will expire at {{ service.unexpired_acknowledgement.expires }}.
</div>
{% else %}
<div class="col-xs-12 alert alert-primary">
<a class="btn btn-primary" href="{% url 'acknowledge-alert' pk=service.id %}"><i class=""></i>Acknowledge alert</a>
By acknowledging this failure you will pause alerts temporarily.
</div>
{% endif %}
{% endif %}
</div>
<hr>
<div class="row">

View File

@ -1,20 +1,21 @@
from django.conf.urls import patterns, include, url
from django.conf import settings
from cabot.cabotapp.views import (
run_status_check, graphite_api_data, checks_run_recently,
duplicate_icmp_check, duplicate_graphite_check, duplicate_http_check, duplicate_jenkins_check, duplicate_instance,
GraphiteCheckCreateView, GraphiteCheckUpdateView,
HttpCheckCreateView, HttpCheckUpdateView,
ICMPCheckCreateView, ICMPCheckUpdateView,
JenkinsCheckCreateView, JenkinsCheckUpdateView,
StatusCheckDeleteView, StatusCheckListView, StatusCheckDetailView,
StatusCheckResultDetailView, StatusCheckReportView, UserProfileUpdateAlert)
run_status_check, graphite_api_data, checks_run_recently,
duplicate_icmp_check, duplicate_graphite_check, duplicate_http_check, duplicate_jenkins_check,
duplicate_instance, acknowledge_alert,
GraphiteCheckCreateView, GraphiteCheckUpdateView,
HttpCheckCreateView, HttpCheckUpdateView,
ICMPCheckCreateView, ICMPCheckUpdateView,
JenkinsCheckCreateView, JenkinsCheckUpdateView,
StatusCheckDeleteView, StatusCheckListView, StatusCheckDetailView,
StatusCheckResultDetailView, StatusCheckReportView, UserProfileUpdateAlert)
from cabot.cabotapp.views import (InstanceListView, InstanceDetailView,
InstanceUpdateView, InstanceCreateView, InstanceDeleteView,
ServiceListView, ServiceDetailView,
ServiceUpdateView, ServiceCreateView, ServiceDeleteView,
UserProfileUpdateView, ShiftListView, subscriptions)
InstanceUpdateView, InstanceCreateView, InstanceDeleteView,
ServiceListView, ServiceDetailView,
ServiceUpdateView, ServiceCreateView, ServiceDeleteView,
UserProfileUpdateView, ShiftListView, subscriptions)
from cabot import rest_urls
@ -29,117 +30,119 @@ import logging
logger = logging.getLogger(__name__)
urlpatterns = patterns('',
url(r'^$', view=RedirectView.as_view(url='services/', permanent=False),
name='dashboard'),
url(r'^subscriptions/', view=subscriptions,
name='subscriptions'),
url(r'^accounts/login/', view=login, name='login'),
url(r'^accounts/logout/', view=logout, name='logout'),
url(r'^accounts/password-reset/',
view=password_reset, name='password-reset'),
url(r'^accounts/password-reset-done/',
view=password_reset_done, name='password-reset-done'),
url(r'^accounts/password-reset-confirm/',
view=password_reset_confirm, name='password-reset-confirm'),
url(r'^status/', view=checks_run_recently,
name='system-status'),
url(r'^$', view=RedirectView.as_view(url='services/', permanent=False),
name='dashboard'),
url(r'^subscriptions/', view=subscriptions,
name='subscriptions'),
url(r'^accounts/login/', view=login, name='login'),
url(r'^accounts/logout/', view=logout, name='logout'),
url(r'^accounts/password-reset/',
view=password_reset, name='password-reset'),
url(r'^accounts/password-reset-done/',
view=password_reset_done, name='password-reset-done'),
url(r'^accounts/password-reset-confirm/',
view=password_reset_confirm, name='password-reset-confirm'),
url(r'^status/', view=checks_run_recently,
name='system-status'),
url(r'^services/', view=ServiceListView.as_view(),
name='services'),
url(r'^service/create/', view=ServiceCreateView.as_view(),
name='create-service'),
url(r'^service/update/(?P<pk>\d+)/',
view=ServiceUpdateView.as_view(
), name='update-service'),
url(r'^service/delete/(?P<pk>\d+)/',
view=ServiceDeleteView.as_view(
), name='delete-service'),
url(r'^service/(?P<pk>\d+)/',
view=ServiceDetailView.as_view(), name='service'),
url(r'^services/', view=ServiceListView.as_view(),
name='services'),
url(r'^service/create/', view=ServiceCreateView.as_view(),
name='create-service'),
url(r'^service/update/(?P<pk>\d+)/',
view=ServiceUpdateView.as_view(
), name='update-service'),
url(r'^service/delete/(?P<pk>\d+)/',
view=ServiceDeleteView.as_view(
), name='delete-service'),
url(r'^service/(?P<pk>\d+)/',
view=ServiceDetailView.as_view(), name='service'),
url(r'^service/acknowledge_alert/(?P<pk>\d+)/',
view=acknowledge_alert, name='acknowledge-alert'),
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/duplicate/(?P<pk>\d+)/',
view=duplicate_instance, name='duplicate-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'^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/duplicate/(?P<pk>\d+)/',
view=duplicate_instance, name='duplicate-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+)/',
view=run_status_check, name='run-check'),
url(r'^check/delete/(?P<pk>\d+)/',
view=StatusCheckDeleteView.as_view(
), name='delete-check'),
url(r'^check/(?P<pk>\d+)/',
view=StatusCheckDetailView.as_view(), name='check'),
url(r'^checks/report/$',
view=StatusCheckReportView.as_view(), name='checks-report'),
url(r'^checks/$', view=StatusCheckListView.as_view(),
name='checks'),
url(r'^check/run/(?P<pk>\d+)/',
view=run_status_check, name='run-check'),
url(r'^check/delete/(?P<pk>\d+)/',
view=StatusCheckDeleteView.as_view(
), name='delete-check'),
url(r'^check/(?P<pk>\d+)/',
view=StatusCheckDetailView.as_view(), name='check'),
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'^icmpcheck/duplicate/(?P<pk>\d+)/',
view=duplicate_icmp_check, name='duplicate-icmp-check'),
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'^icmpcheck/duplicate/(?P<pk>\d+)/',
view=duplicate_icmp_check, name='duplicate-icmp-check'),
url(r'^graphitecheck/create/',
view=GraphiteCheckCreateView.as_view(
), name='create-graphite-check'),
url(r'^graphitecheck/update/(?P<pk>\d+)/',
view=GraphiteCheckUpdateView.as_view(
), name='update-graphite-check'),
url(r'^graphitecheck/duplicate/(?P<pk>\d+)/',
view=duplicate_graphite_check, name='duplicate-graphite-check'),
url(r'^graphitecheck/create/',
view=GraphiteCheckCreateView.as_view(
), name='create-graphite-check'),
url(r'^graphitecheck/update/(?P<pk>\d+)/',
view=GraphiteCheckUpdateView.as_view(
), name='update-graphite-check'),
url(r'^graphitecheck/duplicate/(?P<pk>\d+)/',
view=duplicate_graphite_check, name='duplicate-graphite-check'),
url(r'^httpcheck/create/', view=HttpCheckCreateView.as_view(),
name='create-http-check'),
url(r'^httpcheck/update/(?P<pk>\d+)/',
view=HttpCheckUpdateView.as_view(
), name='update-http-check'),
url(r'^httpcheck/duplicate/(?P<pk>\d+)/',
view=duplicate_http_check, name='duplicate-http-check'),
url(r'^httpcheck/create/', view=HttpCheckCreateView.as_view(),
name='create-http-check'),
url(r'^httpcheck/update/(?P<pk>\d+)/',
view=HttpCheckUpdateView.as_view(
), name='update-http-check'),
url(r'^httpcheck/duplicate/(?P<pk>\d+)/',
view=duplicate_http_check, name='duplicate-http-check'),
url(r'^jenkins_check/create/', view=JenkinsCheckCreateView.as_view(),
name='create-jenkins-check'),
url(r'^jenkins_check/update/(?P<pk>\d+)/',
view=JenkinsCheckUpdateView.as_view(
), name='update-jenkins-check'),
url(r'^jenkins_check/duplicate/(?P<pk>\d+)/',
view=duplicate_jenkins_check, name='duplicate-jenkins-check'),
url(r'^result/(?P<pk>\d+)/',
view=StatusCheckResultDetailView.as_view(
), name='result'),
url(r'^jenkins_check/create/', view=JenkinsCheckCreateView.as_view(),
name='create-jenkins-check'),
url(r'^jenkins_check/update/(?P<pk>\d+)/',
view=JenkinsCheckUpdateView.as_view(
), name='update-jenkins-check'),
url(r'^jenkins_check/duplicate/(?P<pk>\d+)/',
view=duplicate_jenkins_check, name='duplicate-jenkins-check'),
url(r'^result/(?P<pk>\d+)/',
view=StatusCheckResultDetailView.as_view(
), name='result'),
url(r'^shifts/', view=ShiftListView.as_view(),
name='shifts'),
url(r'^shifts/', view=ShiftListView.as_view(),
name='shifts'),
url(r'^graphite/', view=graphite_api_data,
name='graphite-data'),
url(r'^graphite/', view=graphite_api_data,
name='graphite-data'),
url(r'^user/(?P<pk>\d+)/profile/$',
view=UserProfileUpdateView.as_view(), name='user-profile'),
url(r'^user/(?P<pk>\d+)/profile/(?P<alerttype>.+)',
view=UserProfileUpdateAlert.as_view(
), name='update-alert-user-data'),
url(r'^user/(?P<pk>\d+)/profile/$',
view=UserProfileUpdateView.as_view(), name='user-profile'),
url(r'^user/(?P<pk>\d+)/profile/(?P<alerttype>.+)',
view=UserProfileUpdateAlert.as_view(
), name='update-alert-user-data'),
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', include(admin.site.urls)),
# Comment below line to disable browsable rest api
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# Comment below line to disable browsable rest api
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api/', include(rest_urls.router.urls)),
)
url(r'^api/', include(rest_urls.router.urls)),
)
def append_plugin_urls():
"""