Merge branch 'jmontineri-instances'

This commit is contained in:
David Buxton 2014-08-02 01:14:13 +01:00
commit cab908b24f
25 changed files with 1465 additions and 128 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,205 @@
# -*- 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)),
('name', self.gf('django.db.models.fields.TextField')()),
('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)),
('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 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 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'"}),
'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', [], {'db_index': 'True'}),
'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

@ -0,0 +1,158 @@
# -*- 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):
# Changing field 'StatusCheck.created_by'
db.alter_column('cabotapp_statuscheck', 'created_by_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True))
def backwards(self, orm):
# Changing field 'StatusCheck.created_by'
db.alter_column('cabotapp_statuscheck', 'created_by_id', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['auth.User']))
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'"}),
'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']", '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'}),
'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', [], {'db_index': 'True'}),
'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

@ -0,0 +1,179 @@
# -*- 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 'InstanceStatusSnapshot'
db.create_table('cabotapp_instancestatussnapshot', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('instance', self.gf('django.db.models.fields.related.ForeignKey')(related_name='snapshots', to=orm['cabotapp.Instance'])),
('time', self.gf('django.db.models.fields.DateTimeField')(db_index=True)),
('num_checks_active', self.gf('django.db.models.fields.IntegerField')(default=0)),
('num_checks_passing', self.gf('django.db.models.fields.IntegerField')(default=0)),
('num_checks_failing', self.gf('django.db.models.fields.IntegerField')(default=0)),
('overall_status', self.gf('django.db.models.fields.TextField')(default='PASSING')),
('did_send_alert', self.gf('django.db.models.fields.IntegerField')(default=False)),
))
db.send_create_signal('cabotapp', ['InstanceStatusSnapshot'])
def backwards(self, orm):
# Deleting model 'InstanceStatusSnapshot'
db.delete_table('cabotapp_instancestatussnapshot')
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'"}),
'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.instancestatussnapshot': {
'Meta': {'object_name': 'InstanceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': "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'})
},
'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']", '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'}),
'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', [], {'db_index': 'True'}),
'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

@ -3,20 +3,24 @@ from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from polymorphic import PolymorphicModel from polymorphic import PolymorphicModel
from django.db.models import F from django.db.models import F
from django.core.urlresolvers import reverse
from django.contrib.admin.models import User from django.contrib.admin.models import User
from celery.exceptions import SoftTimeLimitExceeded
from jenkins import get_job_status from jenkins import get_job_status
from .alert import send_alert from .alert import send_alert
from .calendar import get_events from .calendar import get_events
from .graphite import parse_metric from .graphite import parse_metric
from .alert import send_alert from .tasks import update_service, update_instance
from .tasks import update_service
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils import timezone from django.utils import timezone
from django.db import transaction
import json import json
import re import re
import time import time
import os
import subprocess
import requests import requests
from celery.utils.log import get_task_logger from celery.utils.log import get_task_logger
@ -65,7 +69,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,15 +96,16 @@ 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,
help_text='Users who should receive alerts.', help_text='Users who should receive alerts.',
) )
alerts_enabled = models.BooleanField(
default=True,
help_text='Alert when this service is not healthy.',
)
status_checks = models.ManyToManyField( status_checks = models.ManyToManyField(
'StatusCheck', 'StatusCheck',
blank=True, blank=True,
@ -113,10 +122,6 @@ class Service(models.Model):
default=False, default=False,
help_text='Must be enabled, and check importance set to Critical, to receive telephone alerts.', help_text='Must be enabled, and check importance set to Critical, to receive telephone alerts.',
) )
alerts_enabled = models.BooleanField(
default=True,
help_text='Alert when this service is not healthy.',
)
overall_status = models.TextField(default=PASSING_STATUS) overall_status = models.TextField(default=PASSING_STATUS)
old_overall_status = models.TextField(default=PASSING_STATUS) old_overall_status = models.TextField(default=PASSING_STATUS)
hackpad_id = models.TextField( hackpad_id = models.TextField(
@ -126,30 +131,10 @@ 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
def update_status(self):
self.old_overall_status = self.overall_status
# Only active checks feed into our calculation
status_checks_failed_count = self.all_failing_checks().count()
self.overall_status = self.most_severe(self.all_failing_checks())
self.snapshot = ServiceStatusSnapshot(
service=self,
num_checks_active=self.active_status_checks().count(),
num_checks_passing=self.active_status_checks(
).count() - status_checks_failed_count,
num_checks_failing=status_checks_failed_count,
overall_status=self.overall_status,
time=timezone.now(),
)
self.snapshot.save()
self.save()
if not (self.overall_status == Service.PASSING_STATUS and self.old_overall_status == Service.PASSING_STATUS):
self.alert()
def most_severe(self, check_list): def most_severe(self, check_list):
failures = [c.importance for c in check_list] failures = [c.importance for c in check_list]
@ -189,7 +174,6 @@ class Service(models.Model):
self.save() self.save()
self.snapshot.did_send_alert = True self.snapshot.did_send_alert = True
self.snapshot.save() self.snapshot.save()
# send_alert handles the logic of how exactly alerts should be handled
send_alert(self, duty_officers=get_duty_officers()) send_alert(self, duty_officers=get_duty_officers())
@property @property
@ -201,18 +185,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,9 +203,110 @@ 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):
def update_status(self):
self.old_overall_status = self.overall_status
# Only active checks feed into our calculation
status_checks_failed_count = self.all_failing_checks().count()
self.overall_status = self.most_severe(self.all_failing_checks())
self.snapshot = ServiceStatusSnapshot(
service=self,
num_checks_active=self.active_status_checks().count(),
num_checks_passing=self.active_status_checks(
).count() - status_checks_failed_count,
num_checks_failing=status_checks_failed_count,
overall_status=self.overall_status,
time=timezone.now(),
)
self.snapshot.save()
self.save()
if not (self.overall_status == Service.PASSING_STATUS and self.old_overall_status == Service.PASSING_STATUS):
self.alert()
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):
def duplicate(self):
checks = self.status_checks.all()
new_instance = self
new_instance.pk = None
new_instance.id = None
new_instance.name = "Copy of %s" % self.name
new_instance.save()
for check in checks:
check.duplicate(inst_set=[new_instance], serv_set=())
return new_instance.pk
def update_status(self):
self.old_overall_status = self.overall_status
# Only active checks feed into our calculation
status_checks_failed_count = self.all_failing_checks().count()
self.overall_status = self.most_severe(self.all_failing_checks())
self.snapshot = InstanceStatusSnapshot(
instance=self,
num_checks_active=self.active_status_checks().count(),
num_checks_passing=self.active_status_checks(
).count() - status_checks_failed_count,
num_checks_failing=status_checks_failed_count,
overall_status=self.overall_status,
time=timezone.now(),
)
self.snapshot.save()
self.save()
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)
def delete(self, *args, **kwargs):
self.icmp_status_checks().delete()
return super(Instance, self).delete(*args, **kwargs)
class Snapshot(models.Model):
class Meta:
abstract = True
class ServiceStatusSnapshot(models.Model):
service = models.ForeignKey(Service, related_name='snapshots')
time = models.DateTimeField(db_index=True) time = models.DateTimeField(db_index=True)
num_checks_active = models.IntegerField(default=0) num_checks_active = models.IntegerField(default=0)
num_checks_passing = models.IntegerField(default=0) num_checks_passing = models.IntegerField(default=0)
@ -241,9 +314,17 @@ class ServiceStatusSnapshot(models.Model):
overall_status = models.TextField(default=Service.PASSING_STATUS) overall_status = models.TextField(default=Service.PASSING_STATUS)
did_send_alert = models.IntegerField(default=False) did_send_alert = models.IntegerField(default=False)
class ServiceStatusSnapshot(Snapshot):
service = models.ForeignKey(Service, related_name='snapshots')
def __unicode__(self): def __unicode__(self):
return u"%s: %s" % (self.service.name, self.overall_status) 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): class StatusCheck(PolymorphicModel):
@ -278,7 +359,7 @@ class StatusCheck(PolymorphicModel):
null=True, null=True,
help_text='Number of successive failures permitted before check will be marked as failed. Default is 0, i.e. fail on first failure.' help_text='Number of successive failures permitted before check will be marked as failed. Default is 0, i.e. fail on first failure.'
) )
created_by = models.ForeignKey(User) created_by = models.ForeignKey(User, null=True)
calculated_status = models.CharField( calculated_status = models.CharField(
max_length=50, choices=Service.STATUSES, default=Service.CALCULATED_PASSING_STATUS, blank=True) max_length=50, choices=Service.STATUSES, default=Service.CALCULATED_PASSING_STATUS, blank=True)
last_run = models.DateTimeField(null=True) last_run = models.DateTimeField(null=True)
@ -353,11 +434,11 @@ class StatusCheck(PolymorphicModel):
return self.name return self.name
def recent_results(self): def recent_results(self):
return self.statuscheckresult_set.all().order_by('-time_complete').defer('raw_data')[:10] return StatusCheckResult.objects.filter(check=self).order_by('-time_complete').defer('raw_data')[:10]
def last_result(self): def last_result(self):
try: try:
return self.recent_results()[0] return StatusCheckResult.objects.filter(check=self).order_by('-time_complete').defer('raw_data')[0]
except: except:
return None return None
@ -365,6 +446,10 @@ class StatusCheck(PolymorphicModel):
start = timezone.now() start = timezone.now()
try: try:
result = self._run() result = self._run()
except SoftTimeLimitExceeded as e:
result = StatusCheckResult(check=self)
result.error = u'Error in performing check: Celery soft time limit exceeded'
result.succeeded = False
except Exception as e: except Exception as e:
result = StatusCheckResult(check=self) result = StatusCheckResult(check=self)
result.error = u'Error in performing check: %s' % (e,) result.error = u'Error in performing check: %s' % (e,)
@ -383,22 +468,90 @@ class StatusCheck(PolymorphicModel):
raise NotImplementedError('Subclasses should implement') raise NotImplementedError('Subclasses should implement')
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
recent_results = self.recent_results() if self.pk:
if calculate_debounced_passing(recent_results, self.debounce): # This should not be necessary
self.calculated_status = Service.CALCULATED_PASSING_STATUS with transaction.commit_manually():
try:
recent_results = list(self.recent_results())
if calculate_debounced_passing(recent_results, self.debounce):
self.calculated_status = Service.CALCULATED_PASSING_STATUS
else:
self.calculated_status = Service.CALCULATED_FAILING_STATUS
self.cached_health = serialize_recent_results(recent_results)
transaction.commit()
except SoftTimeLimitExceeded as e:
# Something weird with postgres
transaction.rollback()
logger.error('Celery time limit exceeded for getting results for %s' % self.pk)
self.calculated_status = Service.CALCULATED_FAILING_STATUS
self.cached_health = '-1'
except Exception as e:
transaction.rollback()
logger.error('Got exception when saving check: %s' % e)
self.calculated_status = Service.CALCULATED_FAILING_STATUS
self.cached_health = '-1'
try:
updated = StatusCheck.objects.get(pk=self.pk)
except StatusCheck.DoesNotExist as e:
logger.error('Cannot find myself (check %s) in the database, presumably have been deleted' % self.pk)
return
else: else:
self.calculated_status = Service.CALCULATED_FAILING_STATUS self.cached_health = ''
self.cached_health = serialize_recent_results(recent_results) self.calculated_status = Service.CALCULATED_PASSING_STATUS
ret = super(StatusCheck, self).save(*args, **kwargs) ret = super(StatusCheck, self).save(*args, **kwargs)
# Update linked services
self.update_related_services() self.update_related_services()
self.update_related_instances()
return ret return ret
def duplicate(self, inst_set=None, serv_set=None):
new_check = self
new_check.pk = None
new_check.id = None
new_check.save()
if inst_set is not None:
new_check.instance_set = inst_set
if serv_set is not None:
new_check.service_set = serv_set
new_check.save()
return new_check.pk
def update_related_services(self): def update_related_services(self):
services = self.service_set.all() services = self.service_set.all()
for service in services: for service in services:
update_service.delay(service.id) update_service.delay(service.id)
def update_related_instances(self):
instances = self.instance_set.all()
for instance in instances:
update_instance.delay(instance.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
# We need to read both STDOUT and STDERR because ping can write to both, depending on the kind of error. Thanks a lot, ping.
ping_process = subprocess.Popen("ping -c 1 " + target, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
response = ping_process.wait()
if response == 0:
result.succeeded = True
else:
output = ping_process.stdout.read()
result.succeeded = False
result.error = output
return result
class GraphiteStatusCheck(StatusCheck): class GraphiteStatusCheck(StatusCheck):
@ -532,7 +685,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

@ -72,6 +72,16 @@ def update_service(service_or_id):
service.update_status() service.update_status()
@task(ignore_result=True)
def update_instance(instance_or_id):
from .models import Instance
if not isinstance(instance_or_id, Instance):
instance = Instance.objects.get(id=instance_or_id)
else:
instance = instance_or_id
instance.update_status()
@task(ignore_result=True) @task(ignore_result=True)
def update_shifts(): def update_shifts():
from .models import update_shifts as _update_shifts from .models import update_shifts as _update_shifts

View File

@ -8,7 +8,7 @@ from django.contrib.auth.models import User
from django.test.client import Client from django.test.client import Client
from cabotapp.models import ( from cabotapp.models import (
GraphiteStatusCheck, JenkinsStatusCheck, GraphiteStatusCheck, JenkinsStatusCheck,
HttpStatusCheck, Service, StatusCheckResult) HttpStatusCheck, ICMPStatusCheck, Service, Instance, StatusCheckResult)
from cabotapp.views import StatusCheckReportForm from cabotapp.views import StatusCheckReportForm
from mock import Mock, patch from mock import Mock, patch
from twilio import rest from twilio import rest
@ -66,6 +66,7 @@ class LocalTestCase(TestCase):
self.service = Service.objects.create( self.service = Service.objects.create(
name='Service', name='Service',
) )
self.service.status_checks.add( self.service.status_checks.add(
self.graphite_check, self.jenkins_check, self.http_check) self.graphite_check, self.jenkins_check, self.http_check)
# Passing is most recent # Passing is most recent
@ -310,6 +311,22 @@ class TestWebInterface(LocalTestCase):
# Still the same # Still the same
self.assertEqual(reloaded.hackpad_id, snippet_link) self.assertEqual(reloaded.hackpad_id, snippet_link)
def test_create_instance(self):
instances = Instance.objects.all()
self.assertEqual(len(instances), 0)
self.client.login(username=self.username, password=self.password)
resp = self.client.post(
reverse('create-instance'),
data={
'name': 'My little instance',
},
follow=True,
)
instances = Instance.objects.all()
self.assertEqual(len(instances), 1)
instance = instances[0]
self.assertEqual(len(instance.status_checks.all()), 1)
def test_checks_report(self): def test_checks_report(self):
form = StatusCheckReportForm({ form = StatusCheckReportForm({
'service': self.service.id, 'service': self.service.id,

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
@ -38,7 +38,7 @@ class LoginRequiredMixin(object):
def subscriptions(request): def subscriptions(request):
""" Simple list of all checks """ """ Simple list of all checks """
t = loader.get_template('cabotapp/subscriptions.html') t = loader.get_template('cabotapp/subscriptions.html')
services = Service.objects.all().order_by('alerts_enabled') services = Service.objects.all()
users = User.objects.filter(is_active=True) users = User.objects.filter(is_active=True)
c = RequestContext(request, { c = RequestContext(request, {
'services': services, 'services': services,
@ -55,6 +55,32 @@ def run_status_check(request, pk):
return HttpResponseRedirect(reverse('check', kwargs={'pk': pk})) return HttpResponseRedirect(reverse('check', kwargs={'pk': pk}))
def duplicate_icmp_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-icmp-check', kwargs={'pk': npk}))
def duplicate_instance(request, pk):
instance = Instance.objects.get(pk=pk)
new_instance = instance.duplicate()
return HttpResponseRedirect(reverse('update-instance', kwargs={'pk': new_instance}))
def duplicate_http_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-http-check', kwargs={'pk': npk}))
def duplicate_graphite_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-graphite-check', kwargs={'pk': npk}))
def duplicate_jenkins_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-jenkins-check', kwargs={'pk': npk}))
class StatusCheckResultDetailView(LoginRequiredMixin, DetailView): class StatusCheckResultDetailView(LoginRequiredMixin, DetailView):
model = StatusCheckResult model = StatusCheckResult
context_object_name = 'result' context_object_name = 'result'
@ -90,7 +116,9 @@ 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 +131,18 @@ 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 +175,20 @@ 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:
@ -196,6 +250,53 @@ class UserProfileForm(forms.ModelForm):
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',
)
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):
class Meta: class Meta:
@ -206,6 +307,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 +322,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 +399,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,16 +431,22 @@ 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):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
class ICMPCheckUpdateView(CheckUpdateView):
model = ICMPStatusCheck
form_class = ICMPStatusCheckForm
class GraphiteCheckUpdateView(CheckUpdateView): 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
@ -359,7 +481,7 @@ class StatusCheckListView(LoginRequiredMixin, ListView):
context_object_name = 'checks' context_object_name = 'checks'
def get_queryset(self): def get_queryset(self):
return StatusCheck.objects.all().order_by('name').prefetch_related('service_set') return StatusCheck.objects.all().order_by('name').prefetch_related('service_set', 'instance_set')
class StatusCheckDeleteView(LoginRequiredMixin, DeleteView): class StatusCheckDeleteView(LoginRequiredMixin, DeleteView):
@ -397,6 +519,15 @@ 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 +535,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 +566,46 @@ class ServiceDetailView(LoginRequiredMixin, DetailView):
return context return context
class InstanceCreateView(LoginRequiredMixin, CreateView):
model = Instance
form_class = InstanceForm
def form_valid(self, form):
ret = super(InstanceCreateView, self).form_valid(form)
if self.object.status_checks.filter(polymorphic_ctype__model='icmpstatuscheck').count() == 0:
self.generate_default_ping_check(self.object)
return ret
def generate_default_ping_check(self, obj):
pc = ICMPStatusCheck(
name="Default Ping Check for %s" % obj.name,
frequency=5,
importance=Service.ERROR_STATUS,
debounce=0,
created_by=None,
)
pc.save()
obj.status_checks.add(pc)
def get_success_url(self):
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 +613,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
@ -444,6 +635,13 @@ class ServiceDeleteView(LoginRequiredMixin, DeleteView):
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
context_object_name = 'shifts' context_object_name = 'shifts'

View File

@ -6,6 +6,8 @@ CELERY_IMPORTS = ('app.cabotapp.tasks', )
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler"
CELERY_TASK_SERIALIZER = "json" CELERY_TASK_SERIALIZER = "json"
CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml'] CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml']
CELERYD_TASK_SOFT_TIME_LIMIT = 120
CELERYD_TASK_TIME_LIMIT = 150
CELERYBEAT_SCHEDULE = { CELERYBEAT_SCHEDULE = {
'run-all-checks': { 'run-all-checks': {

View File

@ -91,6 +91,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.transaction.TransactionMiddleware',
) )
ROOT_URLCONF = 'app.urls' ROOT_URLCONF = 'app.urls'

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="enabled">
<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 instance.overall_status == instance.PASSING_STATUS %}success{% else %}danger{% endif %}">{{ instance.overall_status|lower|capfirst }}</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

@ -2,18 +2,21 @@
<div class="row"> <div class="row">
<div class="col-xs-12"> <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-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-8"><h3>{{ checks_type }} checks</h3></div>
<div class="col-xs-3 text-right"> <div class="col-xs-3 text-right">
<h3> <h3>
&nbsp;{% if checks_type == "All" or checks_type == "Graphite" %} &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> <a href="{% url create-check %}?instance={{ instance.id }}&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 %} {% endif %}
{% if checks_type == "All" or checks_type == "Http" %} {% 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> &nbsp;<a href="{% url create-http-check %}?instance={{ instance.id }}&service={{ service.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-arrow-up"></i></a>
{% endif %} {% endif %}
{% 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 %}?instance={{ instance.id }}&service={{ service.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-ok"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "ICMP" %}
&nbsp;<a href="{% url create-icmp-check %}?instance={{ instance.id }}&service={{ service.id }}" class="" title="Add new ICMP check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-transfer"></i></a>
{% endif %} {% endif %}
</h3> </h3>
</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>
@ -62,11 +66,11 @@
</td> </td>
{% if checks_type == "All" %} {% if checks_type == "All" %}
<td class="text-center"> <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> <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>
</td> </td>
{% endif %} {% endif %}
<td title=""> <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 %} {% 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 == 'icmpstatuscheck' %}ICMP Reply from {{ check.instance_set.all.0.address }}{% 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>
<td>{{ check.get_importance_display }}</td> <td>{{ check.get_importance_display }}</td>
<td> <td>
@ -81,10 +85,25 @@
<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 == 'icmpstatuscheck' %}{% url update-icmp-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>
</a> </a>
<a class="btn btn-xs" href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url duplicate-graphite-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url duplicate-http-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}{% url duplicate-icmp-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url duplicate-jenkins-check pk=check.id %}{% endif %}">
<i class="fa fa-copy"></i><span class="break"></span>
</a>
<a class="btn btn-xs" href="{% url run-check pk=check.id %}"> <a class="btn btn-xs" href="{% url run-check pk=check.id %}">
<i class="glyphicon glyphicon-refresh"></i><span class="break"></span> <i class="glyphicon glyphicon-refresh"></i><span class="break"></span>
</a> </a>

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,209 @@
{% 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> </h2></div>
<div class="col-xs-2 text-right"><h2><a class = "" href="{% url duplicate-instance instance.id %}"><i class="fa fa-copy"></i> </a><a class = "" 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.html' with checks=instance.graphite_status_checks.all instance=instance checks_type="Graphite" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=instance.http_status_checks.all instance=instance checks_type="Http" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=instance.jenkins_status_checks.all instance=instance checks_type="Jenkins" %}
<hr>
{% include 'cabotapp/_statuscheck_list.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>
{% 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-instance 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,26 @@
{% 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">
{% include 'cabotapp/_instance_list.html' %}
</div>
</div>
{% endblock content %}
{% block js %}
{% load compress %}
{{ block.super }}
{% compress js %}
<script type="text/coffeescript">
</script>
{% endcompress %}
{% endblock js %}

View File

@ -55,7 +55,18 @@
</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> <hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=service.graphite_status_checks.all service=service checks_type="Graphite" %} {% include 'cabotapp/_statuscheck_list.html' with checks=service.graphite_status_checks.all service=service checks_type="Graphite" %}

View File

@ -10,44 +10,7 @@
</div> </div>
</div> </div>
<div class="col-xs-12"> <div class="col-xs-12">
{% if not services %} {% include 'cabotapp/_service_list.html' %}
No services
{% 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 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>
</table>
{% endif %}
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -6,14 +6,16 @@
<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 href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url duplicate-graphite-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url duplicate-http-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url duplicate-jenkins-check pk=check.id %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}{% url duplicate-icmp-check pk=check.id %}{% endif %}" class=""><i class="fa fa-copy"></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>
@ -49,7 +51,7 @@
</td> </td>
<td>{{ result.time_complete }}</td> <td>{{ result.time_complete }}</td>
<td>{{ result.took }}</td> <td>{{ result.took }}</td>
<td>{{ result.error|default:"" }}</td> <td>{% autoescape off %}{{ result.error|default:"" }}{% endautoescape %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -1,12 +1,15 @@
from django.conf.urls.defaults import patterns, include, url from django.conf.urls.defaults import patterns, include, url
from cabotapp.views import ( 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,
duplicate_icmp_check, duplicate_graphite_check, duplicate_http_check, duplicate_jenkins_check, duplicate_instance,
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 +46,21 @@ 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/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(), 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,22 +73,39 @@ 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'^icmpcheck/duplicate/(?P<pk>\d+)/',
view=duplicate_icmp_check, name='duplicate-icmp-check'),
url(r'^graphitecheck/create/', url(r'^graphitecheck/create/',
view=GraphiteCheckCreateView.as_view( view=GraphiteCheckCreateView.as_view(
), name='create-check'), ), name='create-check'),
url(r'^graphitecheck/update/(?P<pk>\d+)/', url(r'^graphitecheck/update/(?P<pk>\d+)/',
view=GraphiteCheckUpdateView.as_view( view=GraphiteCheckUpdateView.as_view(
), name='update-check'), ), name='update-check'),
url(r'^graphitecheck/duplicate/(?P<pk>\d+)/',
view=duplicate_graphite_check, name='duplicate-graphite-check'),
url(r'^httpcheck/create/', view=HttpCheckCreateView.as_view(), url(r'^httpcheck/create/', view=HttpCheckCreateView.as_view(),
name='create-http-check'), name='create-http-check'),
url(r'^httpcheck/update/(?P<pk>\d+)/', url(r'^httpcheck/update/(?P<pk>\d+)/',
view=HttpCheckUpdateView.as_view( view=HttpCheckUpdateView.as_view(
), name='update-http-check'), ), 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(), url(r'^jenkins_check/create/', view=JenkinsCheckCreateView.as_view(),
name='create-jenkins-check'), name='create-jenkins-check'),
url(r'^jenkins_check/update/(?P<pk>\d+)/', url(r'^jenkins_check/update/(?P<pk>\d+)/',
view=JenkinsCheckUpdateView.as_view( view=JenkinsCheckUpdateView.as_view(
), name='update-jenkins-check'), ), name='update-jenkins-check'),
url(r'^jenkins_check/duplicate/(?P<pk>\d+)/',
view=duplicate_jenkins_check, name='duplicate-jenkins-check'),
url(r'^result/(?P<service_id>\d+)/twiml_callback/', url(r'^result/(?P<service_id>\d+)/twiml_callback/',
view=twiml_callback, name='twiml-callback'), view=twiml_callback, name='twiml-callback'),

View File

@ -1,4 +1,4 @@
Django==1.4.10 Django==1.4.13
PyJWT==0.1.2 PyJWT==0.1.2
South==0.7.6 South==0.7.6
amqp==1.3.3 amqp==1.3.3