فهرست منبع

Initial commit

Signed-off-by: Sven Velt <sven@velt.de>
Sven Velt 10 سال پیش
کامیت
c4e513aec1
77فایلهای تغییر یافته به همراه3040 افزوده شده و 0 حذف شده
  1. 4 0
      .gitignore
  2. 12 0
      initial_state_config.sql
  3. 22 0
      initial_trap_config.sql
  4. 10 0
      manage.py
  5. 0 0
      mymontools/__init__.py
  6. 17 0
      mymontools/paginate.py
  7. 104 0
      mymontools/settings.py
  8. 196 0
      mymontools/static/css/icinga/common.css
  9. 22 0
      mymontools/static/css/icinga/showlog.css
  10. 144 0
      mymontools/static/css/icinga/status.css
  11. 4 0
      mymontools/static/css/montrap.css
  12. 100 0
      mymontools/static/css/nagtrap.css
  13. 7 0
      mymontools/static/images/dropline/AUTHORS
  14. 342 0
      mymontools/static/images/dropline/COPYING
  15. 22 0
      mymontools/static/images/dropline/README
  16. BIN
      mymontools/static/images/dropline/archive.png
  17. BIN
      mymontools/static/images/dropline/arrow.png
  18. BIN
      mymontools/static/images/dropline/attention.png
  19. BIN
      mymontools/static/images/dropline/comment.png
  20. BIN
      mymontools/static/images/dropline/delete.png
  21. BIN
      mymontools/static/images/dropline/mark.png
  22. BIN
      mymontools/static/images/dropline/next.png
  23. BIN
      mymontools/static/images/dropline/previous.png
  24. BIN
      mymontools/static/images/dropline/search.png
  25. 19 0
      mymontools/templates/base.html
  26. 9 0
      mymontools/templates/mini.html
  27. 13 0
      mymontools/templates/snippet_obj_prev_next.html
  28. 0 0
      mymontools/templatetags/__init__.py
  29. 51 0
      mymontools/templatetags/add_get_parameter.py
  30. 40 0
      mymontools/tools.py
  31. 13 0
      mymontools/urls.py
  32. 14 0
      mymontools/wsgi.py
  33. 0 0
      nagtrap/__init__.py
  34. 3 0
      nagtrap/admin.py
  35. 22 0
      nagtrap/forms.py
  36. 0 0
      nagtrap/migrations/__init__.py
  37. 113 0
      nagtrap/models.py
  38. 93 0
      nagtrap/templates/nagtrap/nagtrap_index.html
  39. 25 0
      nagtrap/templates/nagtrap/snippet_trap_as_tr.html
  40. 5 0
      nagtrap/templates/search.html
  41. 34 0
      nagtrap/tools.py
  42. 9 0
      nagtrap/urls.py
  43. 77 0
      nagtrap/views.py
  44. 120 0
      plugin.py
  45. 0 0
      plugin/__init__.py
  46. 3 0
      plugin/admin.py
  47. 0 0
      plugin/migrations/__init__.py
  48. 13 0
      plugin/models.py
  49. 3 0
      plugin/views.py
  50. 177 0
      state_correlator.py
  51. 0 0
      states/__init__.py
  52. 3 0
      states/admin.py
  53. 24 0
      states/forms.py
  54. 75 0
      states/models.py
  55. 20 0
      states/templates/states/snippet_cfgstate_as_tr.html
  56. 20 0
      states/templates/states/snippet_state_as_tr.html
  57. 57 0
      states/templates/states/state_config_index.html
  58. 89 0
      states/templates/states/state_index.html
  59. 36 0
      states/tools.py
  60. 9 0
      states/urls.py
  61. 109 0
      states/views.py
  62. 112 0
      traphandler.py
  63. 0 0
      traps/__init__.py
  64. 3 0
      traps/admin.py
  65. 32 0
      traps/forms.py
  66. 68 0
      traps/models.py
  67. 24 0
      traps/templates/traps/config_trapoid.html
  68. 21 0
      traps/templates/traps/resolv_trapoid.html
  69. 12 0
      traps/templates/traps/snippet_cfgtrap_as_tr.html
  70. 17 0
      traps/templates/traps/snippet_cfgtrap_prev_next.html
  71. 29 0
      traps/templates/traps/snippet_trap_as_tr.html
  72. 69 0
      traps/templates/traps/trap_config_index.html
  73. 91 0
      traps/templates/traps/trap_index.html
  74. 60 0
      traps/tools.py
  75. 47 0
      traps/translate.py
  76. 12 0
      traps/urls.py
  77. 139 0
      traps/views.py

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+*.pyc
+*.sw*
+
+/static/

+ 12 - 0
initial_state_config.sql

@@ -0,0 +1,12 @@
+INSERT INTO `states_cfgstate` (`id`, `name`, `sub_varbind`) VALUES (1,'linkDown','.1.3.6.1.2.1.2.2.1.1.');
+INSERT INTO `states_cfgstate` (`id`, `name`, `sub_varbind`) VALUES (2,'upsOnBattery',NULL);
+INSERT INTO `states_cfgstate` (`id`, `name`, `sub_varbind`) VALUES (3,'smartBoostOn',NULL);
+
+INSERT INTO `states_cfgstatestartevent` (`id`, `trapoid`, `state_id`) VALUES (1,'.1.3.6.1.6.3.1.1.5.3',1);
+INSERT INTO `states_cfgstatestartevent` (`id`, `trapoid`, `state_id`) VALUES (2,'.1.3.6.1.4.1.318.0.5',2);
+INSERT INTO `states_cfgstatestartevent` (`id`, `trapoid`, `state_id`) VALUES (3,'.1.3.6.1.4.1.318.0.6',3);
+
+INSERT INTO `states_cfgstatestopevent` (`id`, `trapoid`, `state_id`) VALUES (1,'.1.3.6.1.6.3.1.1.5.4',1);
+INSERT INTO `states_cfgstatestopevent` (`id`, `trapoid`, `state_id`) VALUES (2,'.1.3.6.1.4.1.318.0.9',2);
+INSERT INTO `states_cfgstatestopevent` (`id`, `trapoid`, `state_id`) VALUES (3,'.1.3.6.1.4.1.318.0.34',3);
+

+ 22 - 0
initial_trap_config.sql

@@ -0,0 +1,22 @@
+# mysql -u <USER> -p mymontools <initial_trap_config.sql
+
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.6.3.1.1.5.1','SNMPv2-MIB::coldStart','System/SNMP','INFO');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.6.3.1.1.5.2','SNMPv2-MIB::warmStart','System/SNMP','INFO');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.6.3.1.1.5.3','IF-MIB::linkDown','Network/Link','WARNING');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.6.3.1.1.5.4','IF-MIB::linkUp','Network/Link','OK');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.6.3.1.1.5.5','SNMPv2-MIB::authenticationFailure','System/Auth','WARNING');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.2.1.17.0.1','BRIDGE-MIB::newRoot','Network/Bridge','WARNING');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.2.1.17.0.2','BRIDGE-MIB::topologyChange','Network/Bridge','OK');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.1','PowerNet-MIB::communicationLost','USV/System','CRITICAL');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.2','PowerNet-MIB::upsOverload','USV/Power','CRITICAL');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.3','PowerNet-MIB::upsDiagnosticsFailed','USV/System','CRITICAL');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.4','PowerNet-MIB::upsDischarged','USV/Power','CRITICAL');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.5','PowerNet-MIB::upsOnBattery','USV/Power','WARNING');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.6','PowerNet-MIB::smartBoostOn','USV/Power','WARNING');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.7','PowerNet-MIB::lowBattery','USV/Power','CRITICAL');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.8','PowerNet-MIB::communicationEstablished','USV/System','OK');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.9','PowerNet-MIB::powerRestored','USV/Power','OK');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.10','PowerNet-MIB::upsDiagnosticsPassed','USV/System','OK');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.11','PowerNet-MIB::returnFromLowBattery','USV/Power','OK');
+INSERT INTO `traps_cfgtrap` (`trapoid`, `trapname`, `category`, `severity`) VALUES ('.1.3.6.1.4.1.318.0.17','PowerNet-MIB::upsBatteryNeedsReplacement','USV/Power','CRITICAL');
+

+ 10 - 0
manage.py

@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mymontools.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)

+ 0 - 0
mymontools/__init__.py


+ 17 - 0
mymontools/paginate.py

@@ -0,0 +1,17 @@
+from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
+
+##############################################################################
+
+def paginate(objs, page, count=50):
+	paginator = Paginator(objs, count)
+	try:
+		objs = paginator.page(page)
+	except PageNotAnInteger:
+		# If page is not an integer, deliver first page.
+		objs = paginator.page(1)
+	except EmptyPage:
+		# If page is out of range (e.g. 9999), deliver last page of results.
+		objs = paginator.page(paginator.num_pages)
+
+	return objs
+

+ 104 - 0
mymontools/settings.py

@@ -0,0 +1,104 @@
+"""
+Django settings for mymontools project.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.6/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.6/ref/settings/
+"""
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+import os
+BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'jq^jz@^d81$-f$=7h8m$6@uv-svs*mb@j&f%1zda2d_jirufvy'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+TEMPLATE_DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = (
+	'django.contrib.admin',
+	'django.contrib.auth',
+	'django.contrib.contenttypes',
+	'django.contrib.sessions',
+	'django.contrib.messages',
+	'django.contrib.staticfiles',
+
+	'mymontools',
+	'traps',
+	'states',
+	'nagtrap',
+)
+
+MIDDLEWARE_CLASSES = (
+	'django.contrib.sessions.middleware.SessionMiddleware',
+	'django.middleware.common.CommonMiddleware',
+	'django.middleware.csrf.CsrfViewMiddleware',
+	'django.contrib.auth.middleware.AuthenticationMiddleware',
+	'django.contrib.messages.middleware.MessageMiddleware',
+	'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'mymontools.urls'
+
+WSGI_APPLICATION = 'mymontools.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.6/ref/settings/#databases
+
+DATABASES = {
+	'default': {
+		'ENGINE': 'django.db.backends.mysql',
+		'NAME': 'snmptt',
+		'USER': 'snmptt',
+		'PASSWORD': 'snmptt',
+		'HOST': '',
+		'PORT': '',
+	}
+}
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.6/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'Europe/Berlin'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.6/howto/static-files/
+
+STATIC_URL = '/static/'
+STATIC_ROOT = os.path.join(BASE_DIR, 'static')
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+	'django.contrib.auth.context_processors.auth',
+	'django.core.context_processors.request',
+	'django.core.context_processors.debug',
+	'django.core.context_processors.i18n',
+	'django.core.context_processors.media',
+	'django.core.context_processors.static',
+	'django.core.context_processors.tz',
+	'django.contrib.messages.context_processors.messages',
+	)
+

+ 196 - 0
mymontools/static/css/icinga/common.css

@@ -0,0 +1,196 @@
+body {
+	font-family: arial, verdana, serif;
+	font-weight: normal;
+	font-size: 10pt;
+	}
+
+
+/* navbar */
+body.navbar {
+	background-color: black;
+	color: white;
+	font-family: verdana,arial,serif;
+	}
+
+.navbarlogo {
+	margin: 0 0 10px 0;
+	}
+
+.navsection {
+	margin: 5px 0 10px 0;
+	color: #DEE7C6;
+	}
+
+.navsectiontitle {
+	font-size: 10pt;
+	font-weight: bold;
+	border:1px solid #DEE7C6;
+	padding: 2px;
+	}
+
+div.navsectionlinks {
+	margin: 3px 0 0 0;
+	}
+
+ul.navsectionlinks {
+	margin: 0px;
+	padding: 0px;
+	list-style: none;
+	}
+
+ul.navsectionlinks li {
+	}
+
+ul.navsectionlinks li ul {
+	margin: 0px;
+	padding: 0 0 0 30px;
+	list-style: disc;
+	}
+
+ul.navsectionlinks li ul li {
+	}
+
+ul.navsectionlinks li ul li a {
+	color: #DEE7C6;
+	background: none;
+	padding: 0;
+	}
+
+ul.navsectionlinks li ul li a:hover {
+	color: #FFC47F;
+	background: none;
+	}
+
+ul.navsectionlinks li ul li ul {
+	margin: 0px;
+	padding: 0 0 0 15px;
+	list-style: circle;
+	}
+
+
+.navbarsearch {
+	margin: 5px 0 0 0;
+	}
+
+.navbarsearch fieldset {
+	border: none;
+	}
+
+.navbarsearch fieldset legend {
+	font-size: 8pt;
+	}
+
+.navbarsearch input{
+	font-size: 8pt;
+	color: black;
+	background-color: white;
+	}
+
+
+
+/* main page */
+#splashpage{
+	text-align: center;
+	}
+
+#mainbrandsplash{
+	font-size: 12pt;
+	font-weight: bold;
+	margin: 0 0 35px 0;
+	}
+
+#maincopy{
+	margin: 0 0 15px 0;
+	}
+
+#currentversioninfo{
+	font-size: 12pt;
+	}
+#currentversioninfo .version{
+	font-size: 14pt;
+	font-weight: bold;
+	}
+#currentversioninfo .releasedate{
+	font-size: 11pt;
+	margin: 5px 0 0 0;
+	}
+#currentversioninfo .whatsnew{
+	font-size: 11pt;
+	font-weight: bold;
+	margin: 10px 0 0 0;
+	}
+
+#developer{
+        font-size: 10pt;
+        }
+#developer .doxygen{
+        font-size: 10pt;
+        font-weight: bold;
+	margin: 10px 0 0 0;
+	}
+
+#updateversioninfo{
+	margin: 15px auto 50px auto;
+	width: 400px;
+	}
+.updatechecksdisabled{
+	background-color: #FF9F9F;
+	border: 1px solid red;
+	padding: 10px;
+	}
+.updatechecksdisabled div.warningmessage{
+	font-weight: bold;
+	}
+#updateversioninfo div.submessage{
+	clear: left;
+	}
+.updateavailable{
+	background-color: #9FD4FF;
+	border: 1px solid blue;
+	padding: 10px;
+	}
+.updateavailable div.updatemessage{
+	font-size: 12pt;
+	font-weight: bold;
+	}
+
+#splashpage #mainfooter{
+	margin: 25px 0 0 0;
+	font-size: 8pt;
+	}
+#splashpage #mainfooter .disclaimer{
+	width: 80%;
+	margin: auto;
+	}
+#splashpage #mainfooter .logos{
+	margin: 15px 0 0 0;
+	}
+
+
+a img {
+	border: none;
+	}
+
+.csv_export_link{
+	text-align: right;
+}
+
+.errorBox {
+	border:1px red solid;
+	background-color: #FFE5E5;
+	width:600px;
+}
+
+.errorMessage {
+	text-align:left;
+	font-weight: bold;
+	margin:1em;
+	font-size: 10pt;
+}
+
+.errorDescription,
+.successMessage {
+	text-align:left;
+	margin:1em;
+	font-size: 10pt;
+}

+ 22 - 0
mymontools/static/css/icinga/showlog.css

@@ -0,0 +1,22 @@
+
+.showlog { font-family: arial,serif; font-size: 8pt; background-color: white; color: black; }
+
+a { color: #6e7475; text-decoration: none; }
+a:hover { color: #000; }
+th { text-align: left; padding: 0 5px; border: 0; }
+td { font-size: 8pt; border: 0; }
+hr { border: 0; color: #ededed; }
+
+.warningMessage { text-align: center; color: #ff3300; font-weight: bold; font-size: 10pt; }
+.infoMessage { text-align: center; color: #ff3300; font-weight: bold; }
+
+.infoBox { color: #000; padding: 2; border: 0;  }
+.infoBoxTitle { font-size: 10pt; font-weight: bold; color: #000; }
+.infoBoxBadProcStatus { color: #ff3300; }
+a.homepageURL:Hover { color: #ff3300; }
+
+.navBoxTitle { font-weight: bold; }
+.navBoxItem { color: #707677 }
+.navBoxFile { color: #707677; font-weight: bold; text-align: center; }
+
+.dateTimeBreak { font-size: 10pt; font-weight: bold; background-color: #ededed; color: #707677; }

+ 144 - 0
mymontools/static/css/icinga/status.css

@@ -0,0 +1,144 @@
+
+.status { font-family: arial,serif; font-size: 8pt; background-color: #fff; color: #000; }
+
+a { color: #000;  text-decoration: none; }
+a:hover { color: #6e7475; }
+th { text-align: left; font-size: 8pt; padding: 0 5px; border: 0; }
+td { font-size: 8pt; border: 0; }
+
+.warningMessage { text-align: center; color: #ff3300; font-weight: bold; font-size: 10pt; }
+.infoMessage { text-align: center; color: #ff3300; font-weight: bold; }
+
+.infoBox { color: #000; padding: 2px; border: 0; }
+.infoBoxTitle { font-size: 10pt; font-weight: bold; color: #000; }
+.infoBoxBadProcStatus { color: #ff3300; }
+a.homepageURL:Hover { color: #ff3300; }
+
+.linkBox { border: 0; }
+table.linkBox { margin-top: 20px; }
+td.linkBox a { margin-left: 5px; padding-left: 10px; background: url(../images/interface/menu_li1.gif) 0 0.35em no-repeat; }
+td.linkBox a:hover { background: url(../images/interface/menu_li2.gif) 0 0.35em no-repeat; }
+
+.filter { border: 0; }
+.filterTitle { font-size: 10pt; font-weight: bold; }
+.filterValue { color: #707677 }
+
+.itemTotalsTitle { text-align: center; margin-top:5px; }
+
+.statusTitle { text-align: center; font-weight: bold; font-size: 10pt; }
+
+table.status { background-color: #fff; padding: 2px; border: 0; }
+th.status { font-size: 10pt; background-color: #707677; color: #fff; border: 0; }
+div.status { font-size: 10pt; text-align: center; }
+.statusOdd { background-color: #cfcfcf; }
+.statusEven { background-color: #e7e7e7; }
+
+.statusPENDING { text-align: center; background-color: #acacac; color: #fff; }
+.statusDOWNTIME { text-align: center; background-color: #acacac; color: #fff; }
+.statusOK { text-align: center;  background-color: #00CC33; color: #fff; }
+.statusRECOVERY { text-align: center; background-color: #00CC33; color: #fff; }
+.statusUNKNOWN { text-align: center; background-color: #E066FF; color: #fff; }
+.statusWARNING { text-align: center; background-color: #ffa500; color: #fff; }
+.statusCRITICAL { text-align: center; background-color: #ff3300; color: #fff; }
+.statusPENDING a, .statusDOWNTIME a, .statusOK a, .statusRECOVERY a, .statusUNKNOWN a, .statusWARNING a, .statusCRITICAL a { color: #000; }
+.statusPENDING a:hover, .statusDOWNTIME a:hover, .statusOK a:hover, .statusRECOVERY a:hover, .statusUNKNOWN a:hover, .statusWARNING a:hover, .statusCRITICAL a:hover { color: #fff; }
+
+.statusHOSTPENDING { text-align: center; background-color: #acacac; color: #fff; }
+.statusHOSTDOWNTIME { text-align: center; background-color: #acacac; color: #fff; }
+.statusHOSTUP { text-align: center; background-color: #00cc33; color: #fff; }
+.statusHOSTDOWN { text-align: center; background-color: #ff3300; color: #fff; }
+.statusHOSTDOWNACK { text-align: center; background-color: #ff3300; color: #fff; }
+.statusHOSTDOWNSCHED { text-align: center; background-color: #ff3300; color: #fff; }
+.statusHOSTUNREACHABLE { text-align: center; background-color: #E066FF; color: #fff; }
+.statusHOSTUNREACHABLEACK { text-align: center; background-color: #E066FF; color: #fff; }
+.statusHOSTUNREACHABLESCHED { text-align: center; background-color: #E066FF; color: #fff; }
+.statusHOSTPENDING a, .statusHOSTDOWNTIME a, .statusHOSTUP a, .statusHOSTDOWN a, .statusHOSTDOWNACK a, .statusHOSTDOWNSCHED a, .statusHOSTUNREACHABLE a, .statusHOSTUNREACHABLEACK a, .statusHOSTUNREACHABLESCHED a { color: #000; }
+.statusHOSTPENDING a:hover, .statusHOSTDOWNTIME a:hover, .statusHOSTUP a:hover, .statusHOSTDOWN a:hover, .statusHOSTDOWNACK a:hover, .statusHOSTDOWNSCHED a:hover, .statusHOSTUNREACHABLE a:hover, .statusHOSTUNREACHABLEACK a:hover, .statusHOSTUNREACHABLESCHED a:hover { color: #fff; }
+
+.statusBGUNKNOWN { background-color: #EEAEEE; }
+.statusBGUNKNOWNACK { background-color: #EEAEEE; }
+.statusBGUNKNOWNSCHED { background-color: #EEAEEE; }
+.statusBGWARNING { background-color: #ffda9f; }
+.statusBGWARNINGACK { background-color: #ffda9f; }
+.statusBGWARNINGSCHED { background-color: #ffda9f; }
+.statusBGCRITICAL { background-color: #ffd4c9; }
+.statusBGCRITICALACK { background-color: #ffd4c9; }
+.statusBGCRITICALSCHED { background-color: #ffd4c9; }
+.statusBGDOWN { background-color: #ffd4c9; }
+.statusBGDOWNACK { background-color: #ffd4c9; }
+.statusBGDOWNSCHED { background-color: #ffd4c9; }
+.statusBGUNREACHABLE { background-color: #EEAEEE; }
+.statusBGUNREACHABLEACK { background-color: #EEAEEE; }
+.statusBGUNREACHABLESCHED { background-color: #EEAEEE; }
+
+div.serviceTotals { text-align: center; font-weight: bold; font-size: 10pt; }
+table.serviceTotals { font-size: 10pt; background-color: #fff; padding: 2px; border: 0; }
+th.serviceTotals,A.serviceTotals { text-align: center; font-size: 10pt; background-color: #707677; color: #fff; border: 0; }
+td.serviceTotals { text-align: center; background-color: #e9e9e9; }
+a.serviceTotals:hover { color: #000; }
+
+.serviceTotalsOK { text-align: center; background-color: #00CC33; color: #fff; border: 0 }
+.serviceTotalsBGOK { border: 1px #00CC33 solid; }
+.serviceTotalsWARNING { text-align: center; background-color: #ffa500; color: #fff; border: 0 }
+.serviceTotalsBGWARNING { border: 1px #ffa500 solid; }
+.serviceTotalsUNKNOWN { text-align: center; background-color: #E066FF; color: #fff; border: 0 }
+.serviceTotalsBGUNKNOWN { border: 1px #E066FF solid; }
+.serviceTotalsCRITICAL { text-align: center; background-color: #ff3300; color: #fff; border: 0 }
+.serviceTotalsBGCRITICAL { border: 1px #ff3300 solid; }
+.serviceTotalsPENDING { text-align: center; background-color: #acacac; color: #fff; border: 0 }
+.serviceTotalsBGPENDING { border: 1px #acacac solid; }
+.serviceTotalsPROBLEMS { text-align: center; background-color: #6ec2fd; color: #fff; border: 0 }
+.serviceTotalsBGPROBLEMS { border: 1px #6ec2fd solid; }
+
+div.serviceTotalsCommands { text-align: right; font-weight: bold; font-size: 10pt; }
+table.serviceTotalsCommands { font-size: 10pt; background-color: #fff; padding: 2px; border: 0; }
+th.serviceTotalsCommands,A.serviceTotalsCommands { text-align: right; font-size: 10pt; background-color: #707677; color: #fff; border: 0; }
+td.serviceTotalsCommands { text-align: right; background-color: #e9e9e9; border: 0; }
+a.serviceTotalsCommands:hover { color: #000; }
+serviceTotalsCommands a:hover { color: #fff; }
+
+div.hostTotals { text-align: center; font-weight: bold; font-size: 10pt; }
+table.hostTotals { font-size: 10pt; background-color: #fff; padding: 2px; border: 0; }
+th.hostTotals,A.hostTotals { text-align: center; font-size: 10pt; background-color: #707677; color: #fff; border: 0; }
+td.hostTotals { text-align: center; background-color: #e9e9e9; }
+a.hostTotals:hover { color: #000; }
+
+.hostTotalsUP { text-align: center; background-color: #00cc33; color: #fff; border: 0 }
+.hostTotalsBGUP { border: 1px #00cc33 solid; }
+.hostTotalsDOWN { text-align: center; background-color: #ff3300; color: #fff; border: 0 }
+.hostTotalsBGDOWN { border: 1px #ff3300 solid; }
+.hostTotalsUNREACHABLE { text-align: center; background-color: #E066FF; color: #fff; border: 0 }
+.hostTotalsBGUNREACHABLE { border: 1px #E066FF solid; }
+.hostTotalsPENDING { text-align: center; background-color: #acacac; color: #fff; border: 0 }
+.hostTotalsBGPENDING { border: 1px #acacac solid; }
+.hostTotalsPROBLEMS { text-align: center; background-color: #6ec2fd; color: #fff; border: 0 }
+.hostTotalsBGPROBLEMS { border: 1px #6ec2fd solid; }
+
+div.hostTotalsCommands { text-align: right; font-weight: bold; font-size: 10pt; }
+table.hostTotalsCommands { font-size: 10pt; background-color: #fff; padding: 2px; border: 0; }
+th.hostTotalsCommands,A.hostTotalsCommands { text-align: right; font-size: 10pt; background-color: #707677; color: #fff; border: 0; }
+td.hostTotalsCommands { text-align: right; background-color: #e9e9e9; border: 0; }
+a.hostTotalsCommands:hover { color: #000; }
+hostTotalsCommands a:hover { color: #fff; }
+
+.miniStatusPENDING { background-color: #acacac;  text-align: center; }
+.miniStatusOK { background-color: #00cc33;  text-align: center; }
+.miniStatusUNKNOWN { background-color: #bf44b2;  text-align: center; }
+.miniStatusWARNING { background-color: #ffa500;  text-align: center; }
+.miniStatusCRITICAL { background-color: #ff3300;  text-align: center; }
+.miniStatusPENDING a, .miniStatusOK a, .miniStatusUNKNOWN a, .miniStatusWARNING a, .miniStatusCRITICAL a { color: #000; }
+.miniStatusPENDING a:hover, .miniStatusOK a:hover, .miniStatusUNKNOWN a:hover, .miniStatusWARNING a:hover, .miniStatusCRITICAL a:hover { color: #fff; }
+
+.miniStatusUP { background-color: #00cc33; text-align: center; }
+.miniStatusDOWN { background-color: #ff3300; text-align: center; }
+.miniStatusUNREACHABLE { background-color: #bf44b2; text-align: center; }
+.miniStatusUP a, .miniStatusDOWN a, .miniStatusUNREACHABLE a { color: #000; }
+.miniStatusUP a:hover, .miniStatusDOWN a:hover, .miniStatusUNREACHABLE a:hover { color: #fff; }
+
+.hostImportantProblem { background-color: #ff3300; color: #000; }
+.hostUnimportantProblem { background-color: #ffcccc; color: #000; }
+
+.serviceImportantProblem { background-color: #ff3300; color: #000; }
+.serviceUnimportantProblem { background-color: #ffcccc; color: #000; }
+
+.highlightRow { background-color: #BBC3BB; }

+ 4 - 0
mymontools/static/css/montrap.css

@@ -0,0 +1,4 @@
+.statusOKread { text-align: center;  color: #0c3; font-weight: bold; }
+.statusWARNINGread { text-align: center;  color: #fa0; font-weight: bold; }
+.statusCRITICALread { text-align: center;  color: #f30; font-weight: bold; }
+

+ 100 - 0
mymontools/static/css/nagtrap.css

@@ -0,0 +1,100 @@
+/*
+###########################################################################
+#
+# nagtrap.css -  NagTrap class with funtions to create the frontend
+#
+# Copyright (c) 2006 - 2007 Michael Luebben (michael_luebben@web.de)
+# Last Modified: 25.12.2007
+#
+# License:
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+###########################################################################*/
+
+/* ******************** ----- Links ----- ******************** */
+a:link  {
+   font-family: arial,serif;
+   font-size: 8pt;	 
+   color: #000000;
+   text-decoration: none;
+}
+
+a:visited {
+   font-family: arial,serif;
+   font-size: 8pt;	 
+   color: #000000;
+   text-decoration: none;
+}
+	
+a:hover {
+   font-family: arial,serif;
+   font-size: 8pt;	 
+   color: #339966;
+   text-decoration: none;
+}
+
+/* ******************** ----- Status ----- ******************** */	
+td.statusNormal {
+   font-family: arial,serif;  
+   font-size: 8pt;  
+   background-color: #FFFFFF;
+}
+
+/* ******************** ----- Search ----- ******************** */
+div.updateSearch {
+   width: 350px;
+   background: #FFFFFF;
+   font-family: verdana,arial,serif; color: #000000;
+   font-size: 8pt;
+}	
+
+div.updateSearch ul {
+   border: 1px solid #888;
+   margin: 0;
+   padding: 0;
+   width: 100%;
+   list-style-type: none;
+   font-size: 8pt;
+}
+
+div.updateSearch ul li {
+   margin: 0;
+   padding: 3px;
+   font-size: 8pt;
+}
+
+div.updateSearch ul li.selected {
+   background-color: #F0E68C
+}
+
+div.updateSearch ul strong.highlight {
+   color: #C3C4C3;
+   margin:0;
+   padding:0;
+}
+
+tr.searchField {
+   background: #FFFFE0;
+}
+
+td.searchField {
+   border: 1px solid #888;
+}
+
+input.searchField {
+   background: #FFFFE0;
+   border-style: none;
+   display: block;
+   width: 100%;
+}

+ 7 - 0
mymontools/static/images/dropline/AUTHORS

@@ -0,0 +1,7 @@
+Name: Silvestre Herrera
+Nickname: ertz
+Location: La Plata, Buenos Aires. ARGENTINA.
+E-mail: silvestre.herrera(at)gmail.com
+Website(s): http://www.silvestre.com.ar/
+
+_______________________________________________

+ 342 - 0
mymontools/static/images/dropline/COPYING

@@ -0,0 +1,342 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+
+

+ 22 - 0
mymontools/static/images/dropline/README

@@ -0,0 +1,22 @@
+Dropline Neu
+
+A Scalable Vector Graphic (SVG) icon set for GNOME Desktop.
+
+This time Dropline Neu has been redesigned almost from scratch, it
+has all generic icons you would expect from other projects (like Tango) 
+and with time I'll add all the application icons the previous version had.
+Please note that this is a major improvement, though some icons that
+used to be here now are missing is just becouse I couldn't apply the
+new style yet (but will do soon).
+
+On a side note, donations are always welcome. School is taking alot
+of time from me which I would love to spend on making icons for the
+GNU/Linux and GNOME comunities. For more information check the
+DONATE file included in the icon set package.
+
+Released under the terms of the GNU General Public License version 2
+check the COPYING file for more info about the license.
+
+______________________________________________________________
+
+Have fun!, if you have any questions check the AUTHORS file :)

BIN
mymontools/static/images/dropline/archive.png


BIN
mymontools/static/images/dropline/arrow.png


BIN
mymontools/static/images/dropline/attention.png


BIN
mymontools/static/images/dropline/comment.png


BIN
mymontools/static/images/dropline/delete.png


BIN
mymontools/static/images/dropline/mark.png


BIN
mymontools/static/images/dropline/next.png


BIN
mymontools/static/images/dropline/previous.png


BIN
mymontools/static/images/dropline/search.png


+ 19 - 0
mymontools/templates/base.html

@@ -0,0 +1,19 @@
+{% load staticfiles %}
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; CHARSET=UTF-8"/>
+<title>MyMonTools{% block titlemore %}{% endblock %} 0.1.0</title>
+<link href="{% static "css/nagtrap.css" %}" rel="stylesheet" type="text/css">
+<link href="{% static "css/montrap.css" %}" rel="stylesheet" type="text/css">
+<link href="{% static "css/icinga/status.css" %}" rel="stylesheet" type="text/css">
+<link href="{% static "css/icinga/showlog.css" %}" rel="stylesheet" type="text/css">
+<link href="{% static "css/icinga/common.css" %}" rel="stylesheet" type="text/css">
+</head>
+<body class="status">
+	<h1>MyMonTools{% block h1more %}{% endblock %} 0.1.0</h1>
+	<p>2012-2014 by Sven Velt</p>
+
+{% block body %}{% endblock %}
+
+</body>
+</html>

+ 9 - 0
mymontools/templates/mini.html

@@ -0,0 +1,9 @@
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+	<title>{% block title %}MyMonTools{% endblock %}</title>
+</head>
+<body>
+{% block body %}{% endblock %}
+</body>
+</html>

+ 13 - 0
mymontools/templates/snippet_obj_prev_next.html

@@ -0,0 +1,13 @@
+{% load staticfiles %}
+{% load add_get_parameter %}
+
+			<table border=1 width="100%">
+				<td width="45%" align="right">{% if objs.has_previous %}<a href="{% add_get page=objs.previous_page_number %}"><img src="{% static "images/dropline/previous.png" %}" /></a>{% endif %}</td>
+				<td align="center">
+					<div style="font-weight:bold;">
+						{{ objname }} {{ objs.start_index }}-{{ objs.end_index }} (of {{ objcount }})<br />
+						Page {{ objs.number }} of {{ objs.paginator.num_pages }}</div>
+				</td>
+				<td width="45%" align="left">{% if objs.has_next %}<a href="{% add_get page=objs.next_page_number %}"><img src="{% static "images/dropline/next.png" %}" /></a>{% endif %}</td>
+			</table>
+

+ 0 - 0
mymontools/templatetags/__init__.py


+ 51 - 0
mymontools/templatetags/add_get_parameter.py

@@ -0,0 +1,51 @@
+from django.template import Library, Node, resolve_variable
+
+register = Library()
+
+"""
+The tag generates a parameter string in form '?param1=val1&param2=val2'.
+The parameter list is generated by taking all parameters from current
+request.GET and optionally overriding them by providing parameters to the tag.
+
+This is a cleaned up version of http://djangosnippets.org/snippets/2105/. It
+solves a couple of issues, namely:
+ * parameters are optional
+ * parameters can have values from request, e.g. request.GET.foo
+ * native parsing methods are used for better compatibility and readability
+ * shorter tag name
+
+Usage: place this code in your appdir/templatetags/add_get_parameter.py
+In template:
+{% load add_get_parameter %}
+<a href="{% add_get param1='const' param2=variable_in_context %}">
+Link with modified params
+</a>
+
+It's required that you have 'django.core.context_processors.request' in
+TEMPLATE_CONTEXT_PROCESSORS
+
+Original version's URL: http://django.mar.lt/2010/07/add-get-parameter-tag.html
+"""
+
+class AddGetParameter(Node):
+	def __init__(self, values):
+		self.values = values
+
+	def render(self, context):
+		req = resolve_variable('request', context)
+		params = req.GET.copy()
+		for key, value in self.values.items():
+			resolved = value.resolve(context)
+			if resolved:
+				params[key] = value.resolve(context)
+		return '?%s' %  params.urlencode()
+
+
+@register.tag
+def add_get(parser, token):
+	pairs = token.split_contents()[1:]
+	values = {}
+	for pair in pairs:
+		s = pair.split('=', 1)
+		values[s[0]] = parser.compile_filter(s[1])
+	return AddGetParameter(values)

+ 40 - 0
mymontools/tools.py

@@ -0,0 +1,40 @@
+from django.db.models import Q
+
+##############################################################################
+
+def build_q_for_fields_and_querydict(fields, params):
+	q = None
+	for filterkey in fields:
+		if filterkey in params:
+			#print filterkey
+			qsub = None
+			#print 'getlist: %s' % params.getlist(filterkey)
+			for value in params.getlist(filterkey):
+				if value:
+					if value == '__':
+						value = ''
+					elif value == '_NULL_':
+						value = None
+					elif value == 'True':
+						value = True
+					elif value == 'False':
+						value = False
+					dd = dict( [(filterkey.replace('exclude_', ''), value), ] )
+					#print 'dd: %s' % dd
+					if qsub:
+						qsub |= Q(**dd)
+					else:
+						qsub = Q(**dd)
+					#print 'qsub: %s' % qsub
+			if qsub:
+				if q:
+					q &= qsub
+				else:
+					q = qsub
+				#print 'q: %s' % q
+
+	#print q
+	return q or Q()
+
+
+

+ 13 - 0
mymontools/urls.py

@@ -0,0 +1,13 @@
+from django.conf.urls import patterns, include, url
+from django.contrib import admin
+from django.views.generic.base import RedirectView
+
+
+urlpatterns = patterns('',
+	url(r'^$', RedirectView.as_view(url='/traps/', permanent=False)),
+	url(r'^traps/', include('traps.urls') ),
+	url(r'^states/', include('states.urls') ),
+	url(r'^nagtrap/', include('nagtrap.urls') ),
+
+	url(r'^admin/', include(admin.site.urls)),
+)

+ 14 - 0
mymontools/wsgi.py

@@ -0,0 +1,14 @@
+"""
+WSGI config for mymontools project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
+"""
+
+import os
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mymontools.settings")
+
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()

+ 0 - 0
nagtrap/__init__.py


+ 3 - 0
nagtrap/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 22 - 0
nagtrap/forms.py

@@ -0,0 +1,22 @@
+from django import forms
+
+##############################################################################
+
+class NagtrapSimpleFilter(forms.Form):
+	hostname = forms.MultipleChoiceField(required=False)
+	eventid = forms.MultipleChoiceField(label='Traps', required=False)
+	category = forms.MultipleChoiceField(required=False)
+	severity = forms.MultipleChoiceField(required=False)
+	trapread = forms.MultipleChoiceField(label='Read', required=False)
+
+
+
+class NagtrapSimpleExclude(forms.Form):
+	exclude_hostname = forms.MultipleChoiceField(required=False)
+	exclude_eventid = forms.MultipleChoiceField(label='Exclude Traps', required=False)
+	exclude_category = forms.MultipleChoiceField(required=False)
+	exclude_severity = forms.MultipleChoiceField(required=False)
+	exclude_trapread = forms.MultipleChoiceField(label='Exclude Read', required=False)
+
+
+

+ 0 - 0
nagtrap/migrations/__init__.py


+ 113 - 0
nagtrap/models.py

@@ -0,0 +1,113 @@
+from django.db import models
+
+##############################################################################
+
+class Snmptt(models.Model):
+	eventname = models.CharField(max_length=50, blank=True)
+	eventid = models.CharField(max_length=50, blank=True)
+	trapoid = models.CharField(max_length=100, blank=True)
+	enterprise = models.CharField(max_length=100, blank=True)
+	community = models.CharField(max_length=20, blank=True)
+	hostname = models.CharField(max_length=100, blank=True)
+	agentip = models.CharField(max_length=16, blank=True)
+	category = models.CharField(max_length=20, blank=True)
+	severity = models.CharField(max_length=20, blank=True)
+	uptime = models.CharField(max_length=20, blank=True)
+	traptime = models.CharField(max_length=30, blank=True)
+	formatline = models.CharField(max_length=255, blank=True)
+	trapread = models.IntegerField(blank=True, null=True)
+
+
+	def __unicode__(self):
+		return u'%s: %s - %s - %s - %s' % (self.id, self.hostname, self.severity, self.eventname, self.traptime)
+
+
+	class Meta:
+		db_table = 'snmptt'
+
+
+
+class SnmpttArchive(models.Model):
+	snmptt_id = models.IntegerField()
+	eventname = models.CharField(max_length=50, blank=True)
+	eventid = models.CharField(max_length=50, blank=True)
+	trapoid = models.CharField(max_length=100, blank=True)
+	enterprise = models.CharField(max_length=100, blank=True)
+	community = models.CharField(max_length=20, blank=True)
+	hostname = models.CharField(max_length=100, blank=True)
+	agentip = models.CharField(max_length=16, blank=True)
+	category = models.CharField(max_length=20, blank=True)
+	severity = models.CharField(max_length=20, blank=True)
+	uptime = models.CharField(max_length=20, blank=True)
+	traptime = models.CharField(max_length=30, blank=True)
+	formatline = models.CharField(max_length=255, blank=True)
+	trapread = models.IntegerField(blank=True, null=True)
+
+	class Meta:
+		db_table = 'snmptt_archive'
+
+
+
+class SnmpttUnknown(models.Model):
+	trapoid = models.CharField(max_length=100, blank=True)
+	enterprise = models.CharField(max_length=100, blank=True)
+	community = models.CharField(max_length=20, blank=True)
+	hostname = models.CharField(max_length=100, blank=True)
+	agentip = models.CharField(max_length=16, blank=True)
+	uptime = models.CharField(max_length=20, blank=True)
+	traptime = models.CharField(max_length=30, blank=True)
+	formatline = models.CharField(max_length=255, blank=True)
+	trapread = models.IntegerField(blank=True, null=True)
+
+	class Meta:
+		db_table = 'snmptt_unknown'
+
+
+
+class SnmpttJobs(models.Model):
+	type = models.CharField(max_length=50)
+	jobstate = models.IntegerField()
+	count = models.IntegerField()
+	jobtime = models.IntegerField()
+	message = models.CharField(max_length=255)
+	class Meta:
+		managed = False
+		db_table = 'snmptt_jobs'
+
+
+
+class SnmpttStatistics(models.Model):
+	stat_time = models.CharField(max_length=30, blank=True)
+	total_received = models.BigIntegerField(blank=True, null=True)
+	total_translated = models.BigIntegerField(blank=True, null=True)
+	total_ignored = models.BigIntegerField(blank=True, null=True)
+	total_unknown = models.BigIntegerField(blank=True, null=True)
+	class Meta:
+		managed = False
+		db_table = 'snmptt_statistics'
+
+
+
+#class NagtrapState(models.Model):
+#	hostname = models.CharField(max_length=100, blank=True)
+#	state = models.ForeignKey('CfgState')
+#	sub = models.CharField(max_length=100, default=None, null=True)
+#	start = models.ForeignKey(Snmptt, related_name='trap_start', blank=True, null=True)
+#	start_time = models.DateTimeField(blank=True, null=True)
+#	stop = models.ForeignKey(Snmptt, related_name='trap_stop', blank=True, null=True)
+#	stop_time = models.DateTimeField(blank=True, null=True)
+#
+#	def __unicode__(self):
+#		if self.start and self.stop:
+#			statetype = u'finished'
+#		elif self.start and not self.stop:
+#			statetype = u'active'
+#		elif not self.start and self.stop:
+#			statetype = u'weird finished'
+#		else:
+#			statetype = u'empty'
+#
+#		return u'%s:%s %s' % (self.hostname, self.state, statetype)
+
+
+

+ 93 - 0
nagtrap/templates/nagtrap/nagtrap_index.html

@@ -0,0 +1,93 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+{% load add_get_parameter %}
+
+{% block titlemore %}/NagTrap{% endblock %}
+{% block h1more %}/NagTraps{% endblock %}
+
+{% block body %}
+
+	<form action="" method="get">
+		<table border=1>
+			<tr>
+				<th colspan=5 style="text-align:center;">Filter NagTraps</th>
+				<th style="text-align:center;">Statistics</th>
+				<th style="text-align:center;">Links</th>
+			</tr>
+			<tr>
+				{% for field in nagtrapfilter %}
+				<th>{{ field.label_tag }}</th>{% endfor %}
+			</tr>
+			<tr>
+				{% for field in nagtrapfilter %}
+				<td>{{ field }}</td>{% endfor %}
+				<td>
+					<!-- Stats -->
+				</td>
+				<td>
+					<!-- Navigation -->
+				</td>
+			</tr>
+			<!--
+			<tr>
+				<th colspan=5 style="text-align:center;">Exclude NagTraps</th>
+			</tr>
+			-->
+			<tr>
+				{% for field in nagtrapexclude %}
+				<th>{{ field.label_tag }}</th>{% endfor %}
+			</tr>
+			<tr>
+				{% for field in nagtrapexclude %}
+				<td>{{ field }}</td>{% endfor %}
+			</tr>
+			<tr>
+				<td colspan=4 style="text-align:center;"><input type="submit" value="Filter now"></td>
+				<td colspan=1 style="text-align:center;"><a href="{% url "nagtrap_index" %}">Reset all</a></td>
+			</tr>
+		</table>
+	</form>
+
+	<table border=1 width="100%">
+		<form action="{% url "nagtrap_modify" %}{% add_get %}" method="post">
+		{% csrf_token %}
+
+		<!-- Previous - Next -->
+		<tr><td colspan=7>
+			{% include "snippet_obj_prev_next.html" with objs=traps objcount=trapcount objname="Traps" %}
+		</tr></td>
+
+		<!-- Header line -->
+		<tr>
+			<th class="status" width="6%"></th>
+			<th class="status" width="7%">Time</th>
+			<th class="status" width="17%">TrapOID</th>
+			<th class="status" width="17%">Host</th>
+			<th class="status" width="10%">Category</th>
+			<th class="status" width="7%">Severity</th>
+			<th class="status" width="*">Message</th>
+		</tr>
+
+		<!-- Traps as table row -->
+		{% include "nagtrap/snippet_trap_as_tr.html" %}
+
+		<tr>
+			<td colspan=3 class="linkBox">
+				<!-- <img src="./images/dropline/arrow.png" border="0"> -->
+				<!-- FIXME:#47 <input type="checkbox" name="checkbox" value="checkbox" onClick="checkAll('yes'); return true;"> -->
+				<button type="submit" name="action" value="read"><img src="{% static "images/dropline/mark.png" %}" /></button>
+				<button type="submit" name="action" value="delete"><img src="{% static "images/dropline/delete.png" %}" /></button>
+				<!-- FIXME:#45 <button type="submit" name="action" value="archive"><img src="{% static "images/dropline/archive.png" %}" /></button> -->
+			</td>
+		</tr>
+
+		<!-- Previous - Next -->
+		<tr><td colspan=7>
+			{% include "snippet_obj_prev_next.html" with objs=traps objcount=trapcount objname="Traps" %}
+		</tr></td>
+
+		</form>
+	</table>
+
+{% endblock %}

+ 25 - 0
nagtrap/templates/nagtrap/snippet_trap_as_tr.html

@@ -0,0 +1,25 @@
+	{% load cycle from future %}
+	{% load add_get_parameter %}
+	{% for trap in traps %}
+	{% cycle 'Odd' 'Even' as OddEven silent %}
+	<tr>
+		<td width="6%" class="status{{OddEven}}">
+			<input type="checkbox" name="trapIDs" value="{{ trap.id }}" >
+			{% if not trap.trapread %}<a href="{% url "nagtrap_modify" %}{% add_get action="read" trapID=trap.id %}"><img src="{{ STATIC_URL }}images/dropline/mark.png" width=22 height=22 border="0" title="Mark as read"></a>{% else %}<img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" width=22 height=22>{% endif %}
+			<a href="{% url "nagtrap_modify" %}{% add_get action="delete" trapID=trap.id %}"><img src="{{ STATIC_URL }}images/dropline/delete.png" width=22 height=22 border="0" title="Delete"></a>
+			<!-- FIXME:#45 <a href="{% url "nagtrap_modify" %}{% add_get action="archive" trapID=trap.id %}"><img src="{{ STATIC_URL }}images/dropline/archive.png" width=22 height=22 border="0" title="Archive"></a> -->
+		</td>
+		<td width="7%" class="status{{OddEven}}"><span>{{ trap.traptime }}</span></td>
+		<td width="17%" class="status{{OddEven}}">
+			<span{% if trap.trapread %} style="text-decoration: line-through;"{% endif %}>{{ trap.trapoid }}</span><br />
+			<a href="{% url "nagtrap_index" %}{% add_get eventid=trap.eventid %}">{{ trap.eventid }}</a>
+		</td>
+		<td width="17%" class="status{{OddEven}}">
+			<span><a href="{% add_get hostname=trap.hostname %}">{{ trap.hostname }}</a><br />
+				{{ trap.agentip }}</span>
+		</td>
+		<td width="10%" class="status{{OddEven}}"><span{% if trap.trapread %} style="text-decoration: line-through;"{% endif %}>{{ trap.category }}</span></td>
+		<td width="7%" class="status{{OddEven}} status{{trap.severity}}{% if trap.trapread %}read{% endif %}" align="center">{{ trap.severity }}</span></td>
+		<td width="*" class="status{{OddEven}}">{{ trap.formatline }}</td>
+	</tr>
+	{% endfor %}

+ 5 - 0
nagtrap/templates/search.html

@@ -0,0 +1,5 @@
+<ul>
+	{% for entry in results %}<li>{{entry}}</li>
+	{% endfor %}
+</ul>
+

+ 34 - 0
nagtrap/tools.py

@@ -0,0 +1,34 @@
+from mymontools.tools import build_q_for_fields_and_querydict
+
+from .models import Snmptt
+
+##############################################################################
+
+def build_q_for_trap_filter(params):
+	fields = (
+		'hostname',
+		'eventid',
+		'category',
+		'severity',
+		'trapread',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def build_q_for_trap_exclude(params):
+	fields = (
+		'exclude_hostname',
+		'exclude_eventid',
+		'exclude_category',
+		'exclude_severity',
+		'exclude_trapread',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def nagtrap_get_filtered_traps_for_querydict(params, sortorder='-id'):
+	traps = Snmptt.objects.order_by(sortorder)
+	return traps.filter( build_q_for_trap_filter(params) ).exclude( build_q_for_trap_exclude(params) )
+

+ 9 - 0
nagtrap/urls.py

@@ -0,0 +1,9 @@
+from django.conf.urls import patterns, url
+
+
+urlpatterns = patterns('',
+	url(r'^$', 'nagtrap.views.nagtrap_index', name='nagtrap_index'),
+	url(r'^modify/$', 'nagtrap.views.nagtrap_modify', name='nagtrap_modify'),
+	url(r'^search/(?P<searchtype>[a-z]+)/', 'nagtrap.views.nagtrap_search', name='nagtrap_search'),
+)
+

+ 77 - 0
nagtrap/views.py

@@ -0,0 +1,77 @@
+# -*- encoding: utf-8 -*-
+
+from django.core.urlresolvers import reverse
+from django.db import connection
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render
+
+from mymontools.paginate import paginate
+
+from .models import *
+from .forms import *
+from .tools import nagtrap_get_filtered_traps_for_querydict
+
+from pprint import pprint
+
+# Paginator
+##############################################################################
+
+def nagtrap_index(Request):
+	env = {}
+
+	page = Request.GET.get('page')
+	pprint( Request.GET )
+	traps = nagtrap_get_filtered_traps_for_querydict(Request.GET)
+	env['traps'] = paginate(traps, page, 50)
+	env['trapcount'] = traps.count()
+
+	nsf = NagtrapSimpleFilter(Request.GET)
+	nsf.fields['hostname'].choices = [ (x, x) for x in Snmptt.objects.exclude(hostname='').values_list('hostname', flat=True).order_by('hostname').distinct() ]
+	nsf.fields['eventid'].choices = [ (eventid, trapoid) for eventid, trapoid in Snmptt.objects.exclude(trapoid='').values_list('eventid', 'trapoid').order_by('trapoid').distinct() ]
+	nsf.fields['severity'].choices = [ (x, x) for x in Snmptt.objects.exclude(severity='').values_list('severity', flat=True).order_by('severity').distinct() ]
+	nsf.fields['category'].choices = [ (x, x) for x in Snmptt.objects.exclude(category='').values_list('category', flat=True).order_by('category').distinct() ]
+	nsf.fields['trapread'].choices = [ ('False', 'Unread'), ('True', 'Read') ]
+	env['nagtrapfilter'] = nsf
+
+	nse = NagtrapSimpleExclude(Request.GET)
+	env['nagtrapexclude'] = nse
+	for field in nsf.fields.keys():
+		nse.fields[u'exclude_%s' % field].choices = nsf.fields[field].choices
+
+	return render(Request, 'nagtrap/nagtrap_index.html', env)
+
+
+
+def nagtrap_modify(Request):
+	action = Request.POST.get('action') or Request.GET.get('action')
+	trapID = Request.GET.get('trapID')
+	trapIDs= Request.POST.getlist('trapIDs')
+
+	# FIXME:#45 'archive']:
+	if not action in ['read', 'delete', ]:
+		return HttpResponse(u'Action "%s" not implemented' % action )
+
+	if trapID:
+		trapIDs.append(trapID)
+
+	if action == 'read':
+		Snmptt.objects.filter(id__in=trapIDs).update(trapread=True)
+	elif action == 'delete':
+		Snmptt.objects.filter(id__in=trapIDs).delete()
+
+	params = Request.GET.copy()
+	if 'action' in params:
+		params.pop('action')
+	if trapID in params:
+		params.pop('trapID')
+	return HttpResponseRedirect( reverse('nagtrap_index') + '?%s' % params.urlencode() )
+
+
+
+def nagtrap_search(Request, searchtype=None):
+	f = {}
+	f[searchtype+'__contains'] = Request.POST.get('q') or Request.GET.get('q')
+	results = [ value[searchtype] for value in Snmptt.objects.filter(**f).order_by(searchtype).values(searchtype).distinct() ]
+	return render(Request, 'search.html', {'results': results,})
+
+

+ 120 - 0
plugin.py

@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+
+import os
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'mymontools.settings'
+
+import django
+django.setup()
+
+##############################################################################
+
+import optparse
+import sys
+
+from django.http import QueryDict
+
+from traps.models import Trap
+from traps.tools import get_filtered_traps_for_querydict
+
+##############################################################################
+
+parser = optparse.OptionParser()
+
+parser.add_option('-H', '--hostname', dest='hostname', default=None, help='Filter hostname(s), separated by ","')
+parser.add_option('-S', '--severity', dest='severity', default=None, help='Filter severity(s), separated by ","')
+parser.add_option('-C', '--category', dest='category', default=None, help='Filter category(s), separated by ","')
+
+parser.add_option('', '--exclude-hostname', dest='exclude_hostname', default=None, help='Exclude hostname(s), separated by ","')
+parser.add_option('', '--exclude-severity', dest='exclude_severity', default=None, help='Exclude severity(s), separated by ","')
+parser.add_option('', '--exclude-category', dest='exclude_category', default=None, help='Exclude category(s), separated by ","')
+
+parser.add_option('', '--memory', dest='memory', default=None, help='Memory name to save ids for already seen traps')
+parser.add_option('', '--simulate-maxid', dest='simmaxid', type=long, default=0, help='Simulate remembered maxid')
+
+parser.add_option('-v', '--verbose', dest='verb', action='count', default=0, help='Be verbose')
+
+(opts, args) = parser.parse_args()
+if opts.verb >=3:
+	print opts
+
+##############################################################################
+
+retstring = ['OK', 'WARNING', 'CRITICAL']
+
+query = QueryDict('', mutable=True)
+
+if opts.hostname:
+	[ query.update( {u'hostname': hostname} ) for hostname in opts.hostname.split(',') ]
+if opts.severity:
+	[ query.update( {u'severity': severity} ) for severity in opts.severity.split(',') ]
+if opts.category:
+	[ query.update( {u'category': category} ) for category in opts.category.split(',') ]
+
+if opts.exclude_hostname:
+	[ query.update( {u'exclude_hostname': hostname} ) for hostname in opts.exclude_hostname.split(',') ]
+if opts.exclude_severity:
+	[ query.update( {u'exclude_severity': severity} ) for severity in opts.exclude_severity.split(',') ]
+if opts.exclude_category:
+	[ query.update( {u'exclude_category': category} ) for category in opts.exclude_category.split(',') ]
+
+if opts.verb >=2:
+	print(query)
+
+traps = get_filtered_traps_for_querydict(query)
+
+if opts.memory:
+	try:
+		mem = PluginMemory.objects.get(tag=opts.memory)
+		if opts.verb >=2:
+			print 'Found in DB'
+	except PluginMemory.DoesNotExist:
+		mem = PluginMemory(tag=opts.memory)
+		if opts.verb >=2:
+			print 'Not found in DB'
+
+	maxid = mem.maxid
+
+elif opts.simmaxid:
+	maxid = opts.simmaxid
+
+else:
+	maxid = 0
+
+if opts.verb >=2:
+	print 'Maxid is %s' % maxid
+
+if maxid:
+	traps = traps.filter(id__gt=maxid)
+
+if opts.verb >=3:
+	for trap in traps:
+		print trap
+		maxid = max(maxid, trap.id)
+
+if opts.verb >=2:
+	print 'Max ID: %s' % maxid
+
+if opts.memory:
+	mem.maxid = maxid
+	mem.save()
+
+warn_cnt = traps.filter(severity='WARNING').count()
+crit_cnt = traps.filter(severity='CRITICAL').count()
+if opts.verb >=2:
+	print 'Warning traps: %s' % warn_cnt
+	print 'Critical traps: %s' % crit_cnt
+
+out = []
+retcode = 0
+
+if crit_cnt:
+	retcode = max(2, retcode)
+	out.append('%s critical traps' % crit_cnt)
+if warn_cnt:
+	retcode = max(1, retcode)
+	out.append('%s warning traps' % warn_cnt)
+
+print 'Traps %s: %s%s' % (retstring[retcode], ' and '.join(out), out and ' found in DB' )
+sys.exit(retcode)
+

+ 0 - 0
plugin/__init__.py


+ 3 - 0
plugin/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 0 - 0
plugin/migrations/__init__.py


+ 13 - 0
plugin/models.py

@@ -0,0 +1,13 @@
+from django.db import models
+
+##############################################################################
+
+class PluginMemory(models.Model):
+	tag = models.CharField(max_length=10)
+	maxid = models.IntegerField(default=0)
+
+	def __unicode__(self):
+		return u'%s: %s' % (self.tag, self.maxid)
+
+
+

+ 3 - 0
plugin/views.py

@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

+ 177 - 0
state_correlator.py

@@ -0,0 +1,177 @@
+#!/usr/bin/env python
+
+import os
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'mymontools.settings'
+
+import django
+django.setup()
+
+##############################################################################
+
+import datetime
+import optparse
+import sys
+
+from django.http import QueryDict
+from django.utils import timezone
+
+from states.models import *
+from traps.tools import get_filtered_traps_for_querydict
+
+##############################################################################
+
+def get_sub(trap, event):
+	sub_varbind = event.state.sub_varbind
+
+	if sub_varbind[-1] == '.':
+		try:
+			sub = trap.trapvarbind_set.get(oid__startswith=sub_varbind)
+		except (trap.DoesNotExist, trap.MultipleObjectsReturned):
+			sub = None
+
+	else:
+		try:
+			sub = trap.trapvarbind_set.get(oid=sub_varbind)
+		except (trap.DoesNotExist, trap.MultipleObjectsReturned):
+			sub = None
+
+	return sub.value
+
+##############################################################################
+
+parser = optparse.OptionParser()
+
+parser.add_option('-H', '--hostname', dest='hostname', default='', help='Hostname(s) to act on, separated by ","')
+parser.add_option('-S', '--severity', dest='severity', default=None, help='Filter severity(s), separated by ","')
+parser.add_option('-C', '--category', dest='category', default=None, help='Filter category(s), separated by ","')
+
+parser.add_option('', '--exclude-hostname', dest='exclude_hostname', default=None, help='Exclude hostname(s), separated by ","')
+parser.add_option('', '--exclude-severity', dest='exclude_severity', default=None, help='Exclude severity(s), separated by ","')
+parser.add_option('', '--exclude-category', dest='exclude_category', default=None, help='Exclude category(s), separated by ","')
+
+parser.add_option('', '--simulate-maxid', dest='simmaxid', type=long, default=0, help='Simulate remembered maxid')
+
+parser.add_option('-v', '--verbose', dest='verb', action='count', default=0, help='Be verbose')
+
+(opts, args) = parser.parse_args()
+if opts.verb >= 3:
+	print opts
+
+##############################################################################
+
+query = QueryDict('', mutable=True)
+
+if opts.hostname:
+	[ query.update( {u'hostname': hostname} ) for hostname in opts.hostname.split(',') ]
+if opts.severity:
+	[ query.update( {u'severity': severity} ) for severity in opts.severity.split(',') ]
+if opts.category:
+	[ query.update( {u'category': category} ) for category in opts.category.split(',') ]
+
+if opts.exclude_hostname:
+	[ query.update( {u'exclude_hostname': hostname} ) for hostname in opts.exclude_hostname.split(',') ]
+if opts.exclude_severity:
+	[ query.update( {u'exclude_severity': severity} ) for severity in opts.exclude_severity.split(',') ]
+if opts.exclude_category:
+	[ query.update( {u'exclude_category': category} ) for category in opts.exclude_category.split(',') ]
+
+if opts.verb >= 2:
+	print(query)
+
+traps = get_filtered_traps_for_querydict(query, sortorder='id')
+
+maxid = 0
+if opts.simmaxid:
+	maxid = opts.simmaxid
+else:
+	if opts.verb >= 2:
+		print u'Hostname (for memory): -%s-' % opts.hostname
+	try:
+		mem = CorrelatorMemory.objects.get(tag=opts.hostname)
+		if opts.verb >= 3:
+			print 'Found in DB'
+	except CorrelatorMemory.DoesNotExist:
+		mem = CorrelatorMemory(tag=opts.hostname)
+		print 'Not found in DB, new memory'
+
+	maxid = mem.maxid
+
+if opts.verb >= 3:
+	print 'maxid is %s' % maxid
+
+traps = traps.filter(id__gt=maxid)
+
+start = {}
+stop = {}
+
+for event in CfgStateStartEvent.objects.all():
+	start[event.trapoid] = event
+for event in CfgStateStopEvent.objects.all():
+	stop[event.trapoid] = event
+
+for trap in traps:
+	if trap.trapoid in start:
+		event = start[trap.trapoid]
+		if opts.verb >= 2:
+			print trap
+			print '    +++ Starting %s' % event
+		state = State()
+		state.hostname = trap.hostname
+		state.state = event.state
+		# FIXME: "sub" ermitteln
+		if event.state.sub_varbind:
+			state.sub = get_sub(trap, event)
+		# state.sub = None
+		state.start = trap
+		if type(trap.traptime) == datetime.datetime:
+			state.start_time = trap.traptime
+		else:
+			try:
+				state.start_time = timezone.make_aware( datetime.datetime.strptime(trap.traptime, '%a %b %d %H:%M:%S %Y') , timezone.get_current_timezone() )
+			except ValueError:
+				state.start_time = None
+		state.save()
+		if opts.verb >= 1:
+			print u'    (%s) %s' % (state.id, state)
+
+	elif trap.trapoid in stop:
+		if opts.verb >= 2:
+			print trap
+			print '    --- Stopping %s' % stop[trap.trapoid]
+		# FIXME: "sub" ermitteln
+		if event.state.sub_varbind:
+			sub = get_sub(trap, event)
+		else:
+			sub = None
+		state = State.objects.filter(hostname=trap.hostname, state=stop[trap.trapoid].state, sub=sub, stop=None, start_id__lt=trap.id).order_by('id')
+		if len(state) == 0:
+			state = State()
+			state.hostname = trap.hostname
+			state.state = stop[trap.trapoid].state
+			# state.sub = None
+		else:
+			state = state[0]
+
+		state.stop = trap
+		if type(trap.traptime) == datetime.datetime:
+			state.stop_time = trap.traptime
+		else:
+			try:
+				state.stop_time = timezone.make_aware( datetime.datetime.strptime(trap.traptime, '%a %b %d %H:%M:%S %Y') , timezone.get_current_timezone() )
+			except ValueError:
+				state.start_time = None
+		state.save()
+
+		if opts.verb >= 1:
+			print u'    (%s) %s' % (state.id, state)
+
+	maxid = max(maxid, trap.id)
+
+if opts.verb >= 2:
+	print u'\n\nMax-ID: %s' % maxid
+
+if not opts.simmaxid:
+	mem.maxid = maxid
+	mem.save()
+

+ 0 - 0
states/__init__.py


+ 3 - 0
states/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 24 - 0
states/forms.py

@@ -0,0 +1,24 @@
+from django import forms
+
+from .models import CfgState
+
+##############################################################################
+
+class CfgStateForm(forms.ModelForm):
+	class Meta:
+		model = CfgState
+		fields = ['name', 'category', 'severity', ]
+
+
+
+##############################################################################
+
+class StateSimpleFilter(forms.Form):
+	hostname = forms.MultipleChoiceField(required=False)
+	state = forms.MultipleChoiceField(required=False)
+	start_time = forms.MultipleChoiceField(required=False)
+	stop_time = forms.MultipleChoiceField(required=False)
+	read = forms.MultipleChoiceField(label='Read', required=False)
+
+
+

+ 75 - 0
states/models.py

@@ -0,0 +1,75 @@
+from django.db import models
+
+from traps.models import Trap
+
+##############################################################################
+
+class CfgState(models.Model):
+	name = models.CharField(max_length=255)
+	sub_varbind = models.CharField(max_length=255, null=True)
+
+	category = models.CharField(max_length=255, blank=True, default='')
+	severity = models.CharField(max_length=255, blank=True, default='')
+
+	def __unicode__(self):
+		return u'%s' % self.name
+
+
+
+class CfgStateStartEvent(models.Model):
+	trapoid = models.CharField(max_length=255, blank=True)
+	state = models.ForeignKey(CfgState, related_name='startevents')
+
+	def __unicode__(self):
+		return u'%s (%s)' % (self.state, self.trapoid)
+
+
+
+class CfgStateStopEvent(models.Model):
+	trapoid = models.CharField(max_length=255, blank=True)
+	state = models.ForeignKey(CfgState, related_name='stopevents')
+
+	def __unicode__(self):
+		return u'%s (%s)' % (self.state, self.trapoid)
+
+
+
+class State(models.Model):
+	hostname = models.CharField(max_length=100, blank=True)
+	state = models.ForeignKey(CfgState)
+	sub = models.CharField(max_length=100, default=None, null=True)
+	start = models.ForeignKey(Trap, related_name='trap_start', blank=True, null=True)
+	start_time = models.DateTimeField(blank=True, null=True)
+	stop = models.ForeignKey(Trap, related_name='trap_stop', blank=True, null=True)
+	stop_time = models.DateTimeField(blank=True, null=True)
+	category = models.CharField(max_length=255, blank=True, default='')
+	severity = models.CharField(max_length=255, blank=True, default='')
+	read = models.BooleanField(default=False)
+
+	def __unicode__(self):
+		return u'%s:%s %s' % (self.hostname, self.state, self.statetype())
+
+
+	def statetype(self):
+		if self.start and self.stop:
+			statetype = u'finished'
+		elif self.start and not self.stop:
+			statetype = u'active'
+		elif not self.start and self.stop:
+			statetype = u'weird finished'
+		else:
+			statetype = u'empty'
+
+		return statetype
+
+
+
+class CorrelatorMemory(models.Model):
+	tag = models.CharField(max_length=255)
+	maxid = models.IntegerField(default=0)
+
+	def __unicode__(self):
+		return u'%s: %s' % (self.tag, self.maxid)
+
+
+

+ 20 - 0
states/templates/states/snippet_cfgstate_as_tr.html

@@ -0,0 +1,20 @@
+	{% load cycle from future %}
+	{% load add_get_parameter %}
+	{% for cfgstate in cfgstates %}
+	{% cycle 'Odd' 'Even' as OddEven silent %}
+	<tr>
+		<!--
+		<td width="25%" class="status{{OddEven}}"><a href="{% url "state_config_state" cfgstate.id %}">{{ cfgstate.name }}</a></td>
+		-->
+		<td width="25%" class="status{{OddEven}}">{{ cfgstate.name }}</td>
+		<td width="25%" class="status{{OddEven}}">
+			{% for event in cfgstate.startevents.all %}{{ event }}<br />
+			{% endfor %}
+		</td>
+		<td width="25%" class="status{{OddEven}}">
+			{% for event in cfgstate.stopevents.all %}{{ event }}<br />
+			{% endfor %}
+		</td>
+		<td width="25%" class="status{{OddEven}}">{% if cfgstate.sub_varbind %}{{ cfgstate.sub_varbind }}{% endif %}</td>
+	</tr>
+	{% endfor %}

+ 20 - 0
states/templates/states/snippet_state_as_tr.html

@@ -0,0 +1,20 @@
+	{% load cycle from future %}
+	{% load add_get_parameter %}
+	{% for state in states %}
+	{% cycle 'Odd' 'Even' as OddEven silent %}
+	<tr>
+		<td width="10%" class="status{{OddEven}}">
+			<input type="checkbox" name="stateIDs" value="{{ state.id }}" >
+			{% if not state.read %}<a href="{% url "state_modify" %}{% add_get action="read" stateID=state.id %}"><img src="{{ STATIC_URL }}images/dropline/mark.png" width=22 height=22 border="0" title="Mark as read"></a>{% else %}<img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" width=22 height=22>{% endif %}
+			<a href="{% url "state_modify" %}{% add_get action="delete" stateID=state.id %}"><img src="{{ STATIC_URL }}images/dropline/delete.png" width=22 height=22 border="0" title="Delete"></a>
+			<!-- FIXME:#46 <a href="{% url "state_modify" %}{% add_get action="archive" stateID=state.id %}"><img src="{{ STATIC_URL }}images/dropline/archive.png" width=22 height=22 border="0" title="Archive"></a> -->
+		</td>
+		<td width="15%" class="status{{OddEven}}"><span><a href="{% url "state_index" %}{% add_get hostname=state.hostname %}">{{ state.hostname }}</a><br />{{ state.agentip }}</span></td>
+		<td width="10%" class="status{{OddEven}}"><span{% if state.read %} style="text-decoration: line-through;"{% endif %}>{{ state.state.name }}</span></td>
+		<td width="10%" class="status{{OddEven}}"><span{% if state.read %} style="text-decoration: line-through;"{% endif %}>{{ state.statetype }}</span></td>
+		<td width="15%" class="status{{OddEven}}"><span>{{ state.start_time|date:"Y-m-d H:i:s" }}</span></td>
+		<td width="15%" class="status{{OddEven}}"><span>{{ state.stop_time|date:"Y-m-d H:i:s" }}</span></td>
+		<td width="10%" class="status{{OddEven}}"><span>{{ state.start_time|timesince:state.stop_time }}</span></td>
+		<td width="*" class="status{{OddEven}}"><span>{{ state.sub }}</span></td>
+	</tr>
+	{% endfor %}

+ 57 - 0
states/templates/states/state_config_index.html

@@ -0,0 +1,57 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+{% load add_get_parameter %}
+
+{% block titlemore %}/States{% endblock %}
+{% block h1more %}/States{% endblock %}
+
+{% block body %}
+
+	<form action="" method="get">
+		<table border=1>
+			<tr>
+				<th style="text-align:center;">Statistics</th>
+				<th style="text-align:center;">Links</th>
+			</tr>
+			<tr>
+				<td>
+				</td>
+				<td>
+					<ul>
+						<li><a href="{% url "state_index" %}">States</a>
+					</ul>
+					<ul>
+						<li><a href="{% url "trap_index" %}">Traps</a>
+					</ul>
+				</td>
+			</tr>
+		</table>
+	</form>
+
+	<table border=1 width="100%">
+		<!-- Previous - Next -->
+		<tr><td colspan=4>
+			{% include "snippet_obj_prev_next.html" with objs=cfgstate objcount=cfgstatecount objname="State configs" %}
+		</td></tr>
+
+		<!-- Header line -->
+		<tr>
+			<th class="status" width="25%">State name</th>
+			<th class="status" width="25%">Start event</th>
+			<th class="status" width="25%">Stop event</th>
+			<th class="status" width="25%">Varbind Sub</th>
+		</tr>
+
+		<!-- CfgStates as table row -->
+		{% include "states/snippet_cfgstate_as_tr.html" %}
+
+		<!-- Previous - Next -->
+		<tr><td colspan=4>
+			{% include "snippet_obj_prev_next.html" with objs=cfgstate objcount=cfgstatecount objname="State configs" %}
+		</td></tr>
+
+		</form>
+	</table>
+
+{% endblock %}

+ 89 - 0
states/templates/states/state_index.html

@@ -0,0 +1,89 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+{% load add_get_parameter %}
+
+{% block titlemore %}/States{% endblock %}
+{% block h1more %}/States{% endblock %}
+
+{% block body %}
+	<form action="" method="get">
+		<table border=1>
+			<tr>
+				<th colspan=5 style="text-align:center;">Filter States</th>
+				<th style="text-align:center;">Statistics</th>
+				<th style="text-align:center;">Links</th>
+			</tr>
+			<tr>
+				{% for field in statefilter %}
+				<th>{{ field.label_tag }}</th>{% endfor %}
+			</tr>
+			<tr>
+				{% for field in statefilter %}
+				<td>{{ field }}</td>{% endfor %}
+				<td>
+					<ul>
+						{% if stats_states_active %}<li><a href="{% url "state_index" %}?stop_time=_NULL_">{{ stats_states_active }} active states</a></li>{% endif %}
+						{% if stats_states_finished %}<li>{{ stats_states_finished }} finished states</li>{% endif %}
+						{% if stats_states_weired %}<li><a href="{% url "state_index" %}?start_time=_NULL_">{{ stats_states_weired }} weired states</a></li>{% endif %}
+					</ul>
+				</td>
+				<td>
+					<ul>
+						<li><a href="{% url "trap_index" %}">Traps</a></li>
+					</ul>
+					<ul>
+						<li><a href="{% url "state_config_index" %}">State configurations</a></li>
+					</ul>
+				</td>
+				</td>
+			</tr>
+			<tr>
+				<td colspan=4 style="text-align:center;"><input type="submit" value="Filter now"></td>
+				<td colspan=4 style="text-align:center;"><a href="{% url "state_index" %}">Reset all</a>
+			</tr>
+		</table>
+	</form>
+
+	<table border=1 width="100%">
+		<form action="{% url "state_modify" %}{% add_get %}" method="post">
+		{% csrf_token %}
+
+		<!-- Previous - Next -->
+		<tr><td colspan=8>
+			{% include "snippet_obj_prev_next.html" with objs=states objcount=statecount objname="States" %}
+		</td></tr>
+
+		<!-- Header line -->
+		<tr>
+			<th class="status" width="10%"></th>
+			<th class="status" width="15%">Host</th>
+			<th class="status" width="10%">Event</th>
+			<th class="status" width="10%">Status</th>
+			<th class="status" width="15%">Start</th>
+			<th class="status" width="15%">Stop</th>
+			<th class="status" width="15%">Duration</th>
+			<th class="status" width="*">Sub</th>
+		</tr>
+
+		<!-- States as table row -->
+		{% include "states/snippet_state_as_tr.html" %}
+
+		<tr>
+			<td colspan=3 class="linkBox">
+				<!-- <img src="./images/dropline/arrow.png" border="0"> -->
+				<!-- FIXME:#48 <input type="checkbox" name="checkbox" value="checkbox" onClick="checkAll('yes'); return true;"> -->
+				<button type="submit" name="action" value="read"><img src="{% static "images/dropline/mark.png" %}" /></button>
+				<button type="submit" name="action" value="delete"><img src="{% static "images/dropline/delete.png" %}" /></button>
+				<!-- FIXME:#46 <button type="submit" name="action" value="archive"><img src="{% static "images/dropline/archive.png" %}" /></button> -->
+			</td>
+		</tr>
+
+		<!-- Previous - Next -->
+		<tr><td colspan=8>
+			{% include "snippet_obj_prev_next.html" with objs=states objcount=statecount objname="States" %}
+		</td></tr>
+
+	</table>
+
+{% endblock %}

+ 36 - 0
states/tools.py

@@ -0,0 +1,36 @@
+from mymontools.tools import build_q_for_fields_and_querydict
+
+from .models import State, CfgState
+
+##############################################################################
+
+def build_q_for_state_filter(params):
+	fields = (
+		'hostname',
+		'state',
+		'sub',
+		'start_time',
+		'stop_time',
+		'read',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def build_q_for_state_exclude(params):
+	fields = (
+		'exclude_state',
+		'exclude_sub',
+		'exclude_start_time',
+		'exclude_stop_time',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def get_filtered_states_for_querydict(params, sortorder='-id'):
+	states = State.objects.order_by(sortorder)
+	return states.filter( build_q_for_state_filter(params) ).exclude( build_q_for_state_exclude(params) )
+
+
+

+ 9 - 0
states/urls.py

@@ -0,0 +1,9 @@
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('',
+	url(r'^$', 'states.views.state_index', name='state_index'),
+	url(r'^config/$', 'states.views.state_config_index', name='state_config_index'),
+	url(r'^config/state/(?P<stateid>[0-9]+)/$', 'states.views.state_config_state', name='state_config_state'),
+	url(r'^modify/$', 'states.views.state_modify', name='state_modify'),
+)
+

+ 109 - 0
states/views.py

@@ -0,0 +1,109 @@
+# -*- encoding: utf-8 -*-
+
+from django.core.urlresolvers import reverse
+from django.db import connection
+from django.db.models import Q
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render
+
+from mymontools.paginate import paginate
+
+from states.models import *
+from states.forms import *
+from .tools import *
+
+from pprint import pprint
+
+# paginate
+# get_filtered_states_for_querydict
+# get_filtered_cfgstates_for_querydict
+##############################################################################
+
+def state_index(Request):
+	env = {}
+
+	page = Request.GET.get('page')
+	states =  get_filtered_states_for_querydict(Request.GET)
+	print states
+
+	env['states'] = paginate(states, page, 30)
+
+	ssf = StateSimpleFilter(Request.GET)
+	ssf.fields['hostname'].choices = [ (x, x) for x in State.objects.exclude(hostname='').values_list('hostname', flat=True).order_by('hostname').distinct() ]
+	ssf.fields['state'].choices = [ (sid, sname) for sid, sname in State.objects.values_list('state', 'state__name').order_by('state__name').distinct() ]
+	ssf.fields['start_time'].choices = [ ('_NULL_', '<EMPTY>'), ]
+	ssf.fields['stop_time'].choices = [ ('_NULL_', '<EMPTY>'), ]
+	ssf.fields['read'].choices = [ ('False', 'Unread'), ('True', 'Read') ]
+	env['statefilter'] = ssf
+
+	env['stats_states_active'] = State.objects.filter(stop_time__isnull=True).count()
+	env['stats_states_finished'] = State.objects.filter(start_time__isnull=False, stop_time__isnull=False).count()
+	env['stats_states_weired'] = State.objects.filter( Q(start_time__isnull=True, stop_time__isnull=False) | Q(start_time__isnull=True, stop_time__isnull=True)).count()
+	
+	return render(Request, 'states/state_index.html', env)
+
+
+
+def state_config_index(Request):
+	env = {}
+
+	page = Request.GET.get('page')
+	# FIXME: Filterbar machen?
+	#cfgstates = get_filtered_cfgtraps_for_querydict(Request.GET)
+	cfgstates = CfgState.objects.all()
+	env['cfgstates'] = paginate(cfgstates, page, 50)
+	env['cfgstatecount'] = cfgstates.count()
+
+	return render(Request, 'states/state_config_index.html', env)
+
+
+
+def state_config_state(Request, stateid):
+	env = {}
+	try:
+		# FIXME:#56 Mehrere, aktiv/inaktiv
+		cfgtrap = CfgTrap.objects.get(trapoid=trapoid)
+	except CfgTrap.DoesNotExist:
+		cfgtrap = CfgTrap(trapoid=trapoid)
+	env['snmptranslate_trapoid'] = external_snmptranslate(trapoid)
+
+	if Request.method == 'POST':
+		form = CfgTrapForm(Request.POST, instance=cfgtrap)
+		if form.is_valid():
+			form.save()
+			return HttpResponseRedirect( reverse('trap_config_index') )
+	else:
+		form = CfgTrapForm(instance=cfgtrap)
+
+	form.fields['trapname'].label = 'Trap name (in DB)'
+	env['form'] = form
+	return render(Request, 'states/config_state.html', env)
+
+
+
+def state_modify(Request):
+	action = Request.POST.get('action') or Request.GET.get('action')
+	stateID = Request.GET.get('stateID')
+	stateIDs= Request.POST.getlist('stateIDs')
+
+	# FIXME:#46 'archive']:
+	if not action in ['read', 'delete', ]:
+		return HttpResponse(u'Action "%s" not implemented' % action )
+
+	if stateID:
+		stateIDs.append(stateID)
+
+	if action == 'read':
+		State.objects.filter(id__in=stateIDs).update(read=True)
+	elif action == 'delete':
+		State.objects.filter(id__in=stateIDs).delete()
+
+	params = Request.GET.copy()
+	if 'action' in params:
+		params.pop('action')
+	if stateID in params:
+		params.pop('stateID')
+	return HttpResponseRedirect( reverse('state_index') + '?%s' % params.urlencode() )
+
+
+

+ 112 - 0
traphandler.py

@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+import os
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'mymontools.settings'
+
+import django
+django.setup()
+
+##############################################################################
+
+import re
+import sys
+import syslog
+
+from django.core.validators import validate_ipv46_address, ValidationError
+from django.utils import timezone
+
+from traps.models import *
+from traps.tools import *
+from traps.translate import translate_trapoid_to_name
+
+##############################################################################
+
+syslog.openlog(ident='MyMonTools Traphandler', logoption=syslog.LOG_PID, facility=syslog.LOG_DAEMON)
+syslog.syslog(syslog.LOG_INFO, 'MyMonTools Traphandler started')
+
+lines = sys.stdin.readlines()
+
+if len(lines) < 3:
+	sys.exit(1)
+
+trap = Trap()
+# FIXME: Strip DNS-Domain
+trap.hostname = lines[0].replace('.treuchtlingen.stadt', '').replace('.treuchtlingen.de', '').lstrip().rstrip()
+trap.agent = lines[1].lstrip().rstrip()
+trap.traptime = timezone.make_aware( datetime.datetime.now() , timezone.get_current_timezone() )
+
+ip = re.search(r'\[([\d\.:a-fA-F]+)\]', trap.agent)
+if ip:
+	try:
+		ip = ip.groups()[0]
+		validate_ipv46_address( ip )
+		trap.agentip = ip
+	except (ValidationError, IndexError):
+		pass
+
+varbinds = {}
+for line in lines[2:]:
+	(oid, value) = line.split(' ',1)
+	varbinds[oid] = value
+
+
+# SNMPv2-MIB::snmpTrapOID.0
+try:
+	trap.trapoid = varbinds.pop('.1.3.6.1.6.3.1.1.4.1.0').lstrip().rstrip()
+except KeyError:
+	sys.exit(2)
+
+# SNMP-COMMUNITY-MIB::snmpTrapAddress.0
+#try:
+#	trap.agentip = varbinds.pop('.1.3.6.1.6.3.18.1.3.0').lstrip().rstrip()
+#except KeyError:
+#	pass
+
+# SNMP-COMMUNITY-MIB::snmpTrapCommunity.0
+try:
+	trap.community = varbinds.pop('.1.3.6.1.6.3.18.1.4.0').lstrip().rstrip()
+except KeyError:
+	pass
+
+# SNMPv2-MIB::snmpTrapEnterprise.0
+try:
+	trap.enterprise = varbinds.pop('.1.3.6.1.6.3.1.1.4.3.0').lstrip().rstrip()
+except KeyError:
+	pass
+
+trap.save()
+syslog.syslog(syslog.LOG_NOTICE, u'MyMonTools Trap saved: %s' % trap)
+
+for (oid, value) in varbinds.iteritems():
+	tvb = TrapVarbind()
+	tvb.trap = trap
+	tvb.oid = oid.lstrip().rstrip()
+	tvb.value = value.lstrip().rstrip()
+	tvb.save()
+syslog.syslog(syslog.LOG_INFO, u'MyMonTools VarBindings saved')
+
+##############################################################################
+
+oidname = translate_trapoid_to_name(trap.trapoid)
+if oidname:
+	trap.trapname = oidname
+	trap.save()
+syslog.syslog(syslog.LOG_INFO, u'MyMonTools Trap-OID translated')
+
+##############################################################################
+
+# FIXME:#56 Auch je nach VarBinding unterschiedliche Configs/Category/Severity
+try:
+	cfgtrap = CfgTrap.objects.get(trapoid=trap.trapoid)
+except CfgTrap.DoesNotExist:
+	cfgtrap = None
+
+if cfgtrap:
+	trap.category = cfgtrap.category
+	trap.severity = cfgtrap.severity
+	trap.save()
+	syslog.syslog(syslog.LOG_INFO, u'MyMonTools Trap categorized')
+else:
+	syslog.syslog(syslog.LOG_INFO, u'MyMonTools No trap configuration found')
+

+ 0 - 0
traps/__init__.py


+ 3 - 0
traps/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 32 - 0
traps/forms.py

@@ -0,0 +1,32 @@
+from django import forms
+
+from .models import CfgTrap
+
+##############################################################################
+
+class CfgTrapForm(forms.ModelForm):
+	class Meta:
+		model = CfgTrap
+		fields = ['trapname', 'category', 'severity', ]
+
+
+
+##############################################################################
+
+class TrapSimpleFilter(forms.Form):
+	hostname = forms.MultipleChoiceField(required=False)
+	trapoid = forms.MultipleChoiceField(label='Traps', required=False)
+	category = forms.MultipleChoiceField(required=False)
+	severity = forms.MultipleChoiceField(required=False)
+	#FIXME: Umbenennen auf "read" - inkompatibel mit NagTrap
+	#trapread = forms.BooleanField(required=False)
+	trapread = forms.MultipleChoiceField(label='Read', required=False)
+
+
+
+class CfgTrapSimpleFilter(forms.Form):
+	category = forms.MultipleChoiceField(required=False)
+	severity = forms.MultipleChoiceField(required=False)
+
+
+

+ 68 - 0
traps/models.py

@@ -0,0 +1,68 @@
+from django.db import models
+
+import datetime
+
+##############################################################################
+
+class Trap(models.Model):
+	traptime = models.DateTimeField(null=True, default=datetime.datetime.now)
+	trapoid = models.CharField(max_length=255)
+	hostname = models.CharField(max_length=255)
+	agentip = models.GenericIPAddressField(unpack_ipv4=True, null=True)
+	agent = models.CharField(max_length=255)
+
+	trapname = models.CharField(max_length=255, blank=True, default='')
+	category = models.CharField(max_length=255, blank=True, default='')
+	severity = models.CharField(max_length=255, blank=True, default='')
+	enterprise = models.CharField(max_length=255, blank=True, default='')
+	community = models.CharField(max_length=255, blank=True, default='')
+	uptime = models.CharField(max_length=255, blank=True, default='')
+
+	eventid = models.CharField(max_length=255, blank=True, default='')
+
+	#FIXME: Umbenennen auf "read" - inkompatibel mit NagTrap
+	trapread = models.BooleanField(default=False)
+
+
+	def __unicode__(self):
+		#return u'%s: %s - %s - %s - %s' % (self.id, self.hostname, self.severity, self.eventid, self.traptime)
+		return u'%s: %s - %s - %s - %s' % (self.id, self.hostname, self.severity, self.trapoid, self.traptime)
+
+
+	def short(self):
+		#return u'%s/%s/%s' % (self.id, self.hostname, self.eventid)
+		return u'%s/%s/%s' % (self.id, self.hostname, self.trapoid)
+
+
+	def eventname(self):
+		'''Return trapname, be compatible with Snmptt'''
+		return self.trapname
+
+
+
+class TrapVarbind(models.Model):
+	trap = models.ForeignKey(Trap)
+	oid = models.CharField(max_length=255)
+	oidname = models.CharField(max_length=255, blank=True, default='')
+	value = models.CharField(max_length=255)
+	valuename = models.CharField(max_length=255, blank=True, default='')
+
+
+	def __unicode__(self):
+		return u'%s -> %s: %s' % (self.trap.short(), self.oid, self.value)
+
+
+	def nice_oid(self):
+		return u'%s' % (self.oidname or self.oid)
+
+
+
+class CfgTrap(models.Model):
+	trapoid = models.CharField(max_length=255, unique=True)
+	trapname = models.CharField(max_length=255, blank=True, default='')
+	category = models.CharField(max_length=255, blank=True, default='')
+	severity = models.CharField(max_length=255, blank=True, default='')
+	# FIXME: Mehrere, aktiv/inaktiv
+
+
+

+ 24 - 0
traps/templates/traps/config_trapoid.html

@@ -0,0 +1,24 @@
+{% extends "mini.html" %}
+
+{% block body %}
+	<table border=1>
+		<form action="." method="post">
+			{% csrf_token %}
+			<tr>
+				<th colspan=2>Edit trap configuration</th>
+			</tr>
+			<tr>
+				<th>Trap OID</th>
+				<td>{{ form.instance.trapoid }}</td>
+			</tr>
+			<tr>
+				<th>Trap name (external resolver)</th>
+				<td>{{ snmptranslate_trapoid }}</td>
+			</tr>
+			{{ form }}
+			<tr>
+				<td colspan=2><input type="submit" value="Submit" /></td>
+			</tr>
+		</form>
+	</table>
+{% endblock %}

+ 21 - 0
traps/templates/traps/resolv_trapoid.html

@@ -0,0 +1,21 @@
+{% extends "mini.html" %}
+
+{% block body %}
+	<table border=1>
+		<tr>
+			<td>Trap OID</td>
+			<td>{{ trapoid }}</td>
+		</tr>
+		<tr>
+			<td>Resolvs to</td>
+			<td>{{ trapname }}</td>
+		</tr>
+		<tr>
+			<td>Traps in DB</td>
+			<td>{{ trapcount }}</td>
+		</tr>
+		<tr>
+			<td colspan=2><a href="{% url "trap_resolve_db_trapoid" trapoid %}">Update all traps in database</a></td>
+		</tr>
+	</table>
+{% endblock %}

+ 12 - 0
traps/templates/traps/snippet_cfgtrap_as_tr.html

@@ -0,0 +1,12 @@
+	{% load cycle from future %}
+	{% load add_get_parameter %}
+	{% for cfgtrap in cfgtraps %}
+	{% cycle 'Odd' 'Even' as OddEven silent %}
+	<tr>
+		<td width="25%" class="status{{OddEven}}"><a href="{% url "trap_config_trapoid" cfgtrap.trapoid %}"{% if cfgtrap.trapname %}>{{ cfgtrap.trapname }}{% else %} style="color: #c00;">[ADMIN: Configure]{% endif %}</a></td>
+		<td width="25%" class="status{{OddEven}}"><a href="{% url "trap_config_trapoid" cfgtrap.trapoid %}">{{ cfgtrap.trapoid }}</a></td>
+		<td width="25%" class="status{{OddEven}}"><a href="{% url "trap_config_trapoid" cfgtrap.trapoid %}"{% if cfgtrap.category %}>{{ cfgtrap.category }}{% else %} style="color: #c00;">[ADMIN: Configure]{% endif %}</a></td>
+		<td width="25%" class="status{{OddEven}}"><a href="{% url "trap_config_trapoid" cfgtrap.trapoid %}"{% if cfgtrap.severity %}>{{ cfgtrap.severity }}{% else %} style="color: #c00;">[ADMIN: Configure]{% endif %}</a></td>
+
+	</tr>
+	{% endfor %}

+ 17 - 0
traps/templates/traps/snippet_cfgtrap_prev_next.html

@@ -0,0 +1,17 @@
+{% load staticfiles %}
+{% load add_get_parameter %}
+
+		<tr><td colspan=4><table border=1 width="100%"><tr>
+			<td width="45%" align="right">
+				{% if cfgtraps.has_previous %}<a href="{% add_get page=cfgtraps.previous_page_number %}"><img src="{% static "images/dropline/previous.png" %}" /></a>{% endif %}
+			</td>
+			<td align="center">
+				<div style="font-weight:bold;">
+					Trap configs {{ cfgtraps.start_index }}-{{ cfgtraps.end_index }} (of {{ cfgtrapcount }})<br />
+					Page {{ cfgtraps.number }} of {{ cfgtraps.paginator.num_pages }}</div>
+			</td>
+			<td width="45%" align="left">
+				{% if cfgtraps.has_next %}<a href="{% add_get page=cfgtraps.next_page_number %}"><img src="{% static "images/dropline/next.png" %}" /></a>{% endif %}
+			</td>
+		</tr></table></td></tr>
+

+ 29 - 0
traps/templates/traps/snippet_trap_as_tr.html

@@ -0,0 +1,29 @@
+	{% load cycle from future %}
+	{% load add_get_parameter %}
+	{% for trap in traps %}
+	{% cycle 'Odd' 'Even' as OddEven silent %}
+	<tr>
+		<td width="6%" class="status{{OddEven}}">
+			<input type="checkbox" name="trapIDs" value="{{ trap.id }}" >
+			{% if not trap.trapread %}<a href="{% url "trap_modify" %}{% add_get action="read" trapID=trap.id %}"><img src="{{ STATIC_URL }}images/dropline/mark.png" width=22 height=22 border="0" title="Mark as read"></a>{% else %}<img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" width=22 height=22>{% endif %}
+			<a href="{% url "trap_modify" %}{% add_get action="delete" trapID=trap.id %}"><img src="{{ STATIC_URL }}images/dropline/delete.png" width=22 height=22 border="0" title="Delete"></a>
+			<!-- FIXME:#45 <a href="{% url "trap_modify" %}{% add_get action="archive" trapID=trap.id %}"><img src="{{ STATIC_URL }}images/dropline/archive.png" width=22 height=22 border="0" title="Archive"></a> -->
+		</td>
+		<td width="7%" class="status{{OddEven}}"><span>{{ trap.traptime|date:"Y-m-d H:i:s" }}</span></td>
+		<td width="17%" class="status{{OddEven}}">
+			{% if trap.trapname %}<span{% if trap.trapread %} style="text-decoration: line-through;"{% endif %}>{{ trap.trapname }}</span><br />{% endif %}
+			<a href="{% url "trap_index" %}{% add_get trapoid=trap.trapoid %}">{{ trap.trapoid }}</a>{% if not trap.trapname %} <a href="{% url "trap_resolve_trapoid" trap.trapoid%}" style="color: #cc0000;">[ADMIN: Resolve]</a>{% endif %}
+		</td>
+		<td width="17%" class="status{{OddEven}}">
+			<span><a href="{% add_get hostname=trap.hostname %}">{{ trap.hostname }}</a><br />
+				{{ trap.agentip }}</span>
+		</td>
+		<td width="10%" class="status{{OddEven}}">{% if trap.category %}<span{% if trap.trapread %} style="text-decoration: line-through;"{% endif %}>{{ trap.category }}</span>{% else %}<a href="{% url "trap_config_trapoid" trap.trapoid %}" style="color: #cc0000;">[ADMIN: Configure]</a>{% endif %}</td>
+		<td width="7%" class="status{{OddEven}} status{{trap.severity}}{% if trap.trapread %}read{% endif %}" align="center">{% if trap.severity %}{{ trap.severity }}{% else %}<a href="{% url "trap_config_trapoid" trap.trapoid %}" style="color: #cc0000;">[ADMIN: Configure]</a>{% endif %}</td>
+		<td width="*" class="status{{OddEven}}">
+			<span>{% for vb in trap.trapvarbind_set.all %}{{ vb.nice_oid }}: {{ vb.value }}<br />
+				{% endfor %}
+			</span>
+		</td>
+	</tr>
+	{% endfor %}

+ 69 - 0
traps/templates/traps/trap_config_index.html

@@ -0,0 +1,69 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+{% load add_get_parameter %}
+
+{% block titlemore %}/Traps{% endblock %}
+{% block h1more %}/Traps{% endblock %}
+
+{% block body %}
+
+	<form action="" method="get">
+		<table border=1>
+			<tr>
+				<th colspan=2 style="text-align:center;">Filter Trap configs</th>
+				<th span=2 style="text-align:center;">Statistics</th>
+				<th span=2 style="text-align:center;">Links</th>
+			</tr>
+			<tr>
+				{% for field in cfgtrapfilter %}
+				<th>{{ field.label_tag }}</th>{% endfor %}
+			</tr>
+			<tr>
+				{% for field in cfgtrapfilter %}
+				<td>{{ field }}</td>{% endfor %}
+				<td>
+					<ul>
+						{% if stats_cfgtraps_wo_category %}<li><a href="{% url "trap_config_index" %}?category=__" style="color:#c00;">{{ stats_cfgtraps_wo_category }} trap configuration(s) without category</a></li>{% endif %}
+						{% if stats_cfgtraps_wo_severity %}<li><a href="{% url "trap_config_index" %}?severity=__" style="color:#c00;">{{ stats_cfgtraps_wo_severity }} trap configuration(s) without severity</a></li>{% endif %}
+					</ul>
+				</td>
+				<td>
+					<ul>
+						<li><a href="{% url "trap_index" %}">Traps</a>
+					</ul>
+				</td>
+			</tr>
+			<tr>
+				<td colspan=1 style="text-align:center;"><input type="submit" value="Filter now"></td>
+				<td colspan=1 style="text-align:center;"><a href="{% url "trap_config_index" %}">Reset all</a></td>
+			</tr>
+		</table>
+	</form>
+
+	<table border=1 width="100%">
+		<!-- Previous - Next -->
+		<tr><td colspan=4>
+			{% include "snippet_obj_prev_next.html" with objs=cfgtraps objcount=cfgtrapcount objname="Trap configs" %}
+		</td></tr>
+
+		<!-- Header line -->
+		<tr>
+			<th class="status" width="25%">Trap name</th>
+			<th class="status" width="25%">Trap OID</th>
+			<th class="status" width="25%">Category</th>
+			<th class="status" width="25%">Severity</th>
+		</tr>
+
+		<!-- Traps as table row -->
+		{% include "traps/snippet_cfgtrap_as_tr.html" %}
+
+		<!-- Previous - Next -->
+		<tr><td colspan=4>
+			{% include "snippet_obj_prev_next.html" with objs=cfgtraps objcount=cfgtrapcount objname="Trap configs" %}
+		</td></tr>
+
+		</form>
+	</table>
+
+{% endblock %}

+ 91 - 0
traps/templates/traps/trap_index.html

@@ -0,0 +1,91 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+{% load add_get_parameter %}
+
+{% block titlemore %}/Traps{% endblock %}
+{% block h1more %}/Traps{% endblock %}
+
+{% block body %}
+
+	<form action="" method="get">
+		<table border=1>
+			<tr>
+				<th colspan=5 style="text-align:center;">Filter Traps</th>
+				<th style="text-align:center;">Statistics</th>
+				<th style="text-align:center;">Links</th>
+			</tr>
+			<tr>
+				{% for field in trapfilter %}
+				<th>{{ field.label_tag }}</th>{% endfor %}
+			</tr>
+			<tr>
+				{% for field in trapfilter %}
+				<td>{{ field }}</td>{% endfor %}
+				<td>
+					<ul>
+						{% if stats_traps_wo_trapname %}<li><a href="{% url "trap_index" %}?trapname=__">{{ stats_traps_wo_trapname }} trap(s) without resolved OID</a></li>{% endif %}
+						{% if stats_traps_wo_category %}<li><a href="{% url "trap_index" %}?category=__">{{ stats_traps_wo_category }} trap(s) without category</a></li>{% endif %}
+						{% if stats_traps_wo_severity %}<li><a href="{% url "trap_index" %}?severity=__">{{ stats_traps_wo_severity }} trap(s) without severity</a></li>{% endif %}
+						{% if stats_cfgtraps_wo_category %}<li><a href="{% url "trap_config_index" %}?category=__" style="color:#c00;">{{ stats_cfgtraps_wo_category }} trap configuration(s) without category</a></li>{% endif %}
+						{% if stats_cfgtraps_wo_severity %}<li><a href="{% url "trap_config_index" %}?severity=__" style="color:#c00;">{{ stats_cfgtraps_wo_severity }} trap configuration(s) without severity</a></li>{% endif %}
+					</ul>
+				</td>
+				<td>
+					<ul>
+						<li><a href="{% url "state_index" %}?stop_time=_NULL_">State correlator</a></li>
+					</ul>
+					<ul>
+						<li><a href="{% url "trap_config_index" %}">Trap configurations</a></li>
+					</ul>
+				</td>
+			</tr>
+			<tr>
+				<td colspan=4 style="text-align:center;"><input type="submit" value="Filter now"></td>
+				<td colspan=1 style="text-align:center;"><a href="{% url "trap_index" %}">Reset all</a></td>
+			</tr>
+		</table>
+	</form>
+
+	<table border=1 width="100%">
+		<form action="{% url "trap_modify" %}{% add_get %}" method="post">
+		{% csrf_token %}
+
+		<!-- Previous - Next -->
+		<tr><td colspan=7>
+			{% include "snippet_obj_prev_next.html" with objs=traps objcount=trapcount objname="Traps" %}
+		</tr></td>
+
+		<!-- Header line -->
+		<tr>
+			<th class="status" width="6%"></th>
+			<th class="status" width="7%">Time</th>
+			<th class="status" width="17%">TrapOID</th>
+			<th class="status" width="17%">Host</th>
+			<th class="status" width="10%">Category</th>
+			<th class="status" width="7%">Severity</th>
+			<th class="status" width="*">Message</th>
+		</tr>
+
+		<!-- Traps as table row -->
+		{% include "traps/snippet_trap_as_tr.html" %}
+
+		<tr>
+			<td colspan=3 class="linkBox">
+				<!-- <img src="./images/dropline/arrow.png" border="0"> -->
+				<!-- FIXME:#47 <input type="checkbox" name="checkbox" value="checkbox" onClick="checkAll('yes'); return true;"> -->
+				<button type="submit" name="action" value="read"><img src="{% static "images/dropline/mark.png" %}" /></button>
+				<button type="submit" name="action" value="delete"><img src="{% static "images/dropline/delete.png" %}" /></button>
+				<!-- FIXME:#45 <button type="submit" name="action" value="archive"><img src="{% static "images/dropline/archive.png" %}" /></button> -->
+			</td>
+		</tr>
+
+		<!-- Previous - Next -->
+		<tr><td colspan=7>
+			{% include "snippet_obj_prev_next.html" with objs=traps objcount=trapcount objname="Traps" %}
+		</tr></td>
+
+		</form>
+	</table>
+
+{% endblock %}

+ 60 - 0
traps/tools.py

@@ -0,0 +1,60 @@
+from mymontools.tools import build_q_for_fields_and_querydict
+
+from traps.models import Trap, CfgTrap
+
+##############################################################################
+
+def build_q_for_trap_filter(params):
+	fields = (
+		'hostname',
+		'trapoid',
+		'category',
+		'severity',
+		'trapread',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def build_q_for_trap_exclude(params):
+	fields = (
+		'exclude_hostname',
+		'exclude_trapoid',
+		'exclude_category',
+		'exclude_severity',
+		'exclude_trapread',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def get_filtered_traps_for_querydict(params, sortorder='-id'):
+	traps = Trap.objects.order_by(sortorder)
+	return traps.filter( build_q_for_trap_filter(params) ).exclude( build_q_for_trap_exclude(params) )
+
+
+##############################################################################
+
+def build_q_for_cfgtrap_filter(params):
+	fields = (
+		'severity',
+		'category',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def build_q_for_cfgtrap_exclude(params):
+	fields = (
+		'exclude_severity',
+		'exclude_category',
+		)
+
+	return build_q_for_fields_and_querydict(fields, params)
+
+
+def get_filtered_cfgtraps_for_querydict(params, sortorder='trapname'):
+	cfgtraps = CfgTrap.objects.order_by(sortorder)
+	return cfgtraps.filter( build_q_for_trap_filter(params) ).exclude( build_q_for_trap_exclude(params) )
+
+

+ 47 - 0
traps/translate.py

@@ -0,0 +1,47 @@
+from traps.models import *
+
+import subprocess
+
+##############################################################################
+
+def translate_trapoid_to_name(oid):
+	try:
+		# FIXME: Mehrere erlauben, dann aber nur einer aktiv
+		return CfgTrap.objects.get(trapoid=oid).trapname
+	except CfgTrap.DoesNotExist:
+		return None
+	except CfgTrap.MultipleObjectsReturned:
+		# Should not happen
+		return None
+
+
+##############################################################################
+
+def call_snmptranslate(oid, allmibs=False):
+	cmdline = ['snmptranslate', ]
+	if allmibs:
+		cmdline.append('-mALL')
+	cmdline.append(oid)
+
+	try:
+		cmd = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+		(stdout, stderr) = cmd.communicate()
+	except OSError:
+		return None
+
+	return stdout.rstrip()
+
+
+def external_snmptranslate(oid):
+	allmibs=False
+	if oid.startswith('.1.3.6.1.4.1'):
+		allmibs=True
+
+	oidname = call_snmptranslate(oid, allmibs)
+
+	if oidname.count('.') > 1 and allmibs==False:
+		oidname = call_snmptranslate(oid, True)
+
+	return oidname
+
+

+ 12 - 0
traps/urls.py

@@ -0,0 +1,12 @@
+from django.conf.urls import patterns, url
+
+
+urlpatterns = patterns('',
+	url(r'^$', 'traps.views.trap_index', name='trap_index'),
+	url(r'^config/$', 'traps.views.config_index', name='trap_config_index'),
+	url(r'^config/trapoid/(?P<trapoid>\.[0-9\.]+)/$', 'traps.views.config_trapoid', name='trap_config_trapoid'),
+	url(r'^modify/$', 'traps.views.trap_modify', name='trap_modify'),
+	url(r'^resolve/trapoid/(?P<trapoid>\.[0-9\.]+)/$', 'traps.views.resolve_trapoid', name='trap_resolve_trapoid'),
+	url(r'^resolve/db/trapoid/(?P<trapoid>\.[0-9\.]+)/$', 'traps.views.resolve_trapoid', {'db':True,}, name='trap_resolve_db_trapoid'),
+)
+

+ 139 - 0
traps/views.py

@@ -0,0 +1,139 @@
+# -*- encoding: utf-8 -*-
+
+from django.core.urlresolvers import reverse
+from django.db import connection
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render
+
+from mymontools.paginate import paginate
+
+from .models import *
+from .forms import *
+from .tools import get_filtered_traps_for_querydict, get_filtered_cfgtraps_for_querydict
+from .translate import *
+
+from pprint import pprint
+
+##############################################################################
+
+def trap_index(Request):
+	env = {}
+
+	page = Request.GET.get('page')
+
+	pprint( Request.GET )
+	traps = get_filtered_traps_for_querydict(Request.GET)
+	env['traps'] = paginate(traps, page, 50)
+	env['trapcount'] = traps.count()
+
+	tsf = TrapSimpleFilter(Request.GET)
+	tsf.fields['hostname'].choices = [ (x, x) for x in Trap.objects.exclude(hostname='').values_list('hostname', flat=True).order_by('hostname').distinct() ]
+	tsf.fields['trapoid'].choices = [ (oid, trapname or oid) for oid, trapname in Trap.objects.exclude(trapoid='').values_list('trapoid','trapname').order_by('trapname').distinct() ]
+	tsf.fields['severity'].choices = [ (x, x) for x in Trap.objects.exclude(severity='').values_list('severity', flat=True).order_by('severity').distinct() ] + [ ('__', '<EMPTY>'), ]
+	tsf.fields['category'].choices = [ (x, x) for x in Trap.objects.exclude(category='').values_list('category', flat=True).order_by('category').distinct() ] + [ ('__', '<EMPTY>'), ]
+	tsf.fields['trapread'].choices = [ ('False', 'Unread'), ('True', 'Read') ]
+	env['trapfilter'] = tsf
+
+	env['stats_traps_wo_trapname'] = Trap.objects.filter(trapname='').count()
+	env['stats_traps_wo_category'] = Trap.objects.filter(category='').count()
+	env['stats_traps_wo_severity'] = Trap.objects.filter(severity='').count()
+	env['stats_cfgtraps_wo_category'] = CfgTrap.objects.filter(category='').count()
+	env['stats_cfgtraps_wo_severity'] = CfgTrap.objects.filter(severity='').count()
+
+	return render(Request, 'traps/trap_index.html', env)
+
+
+
+def trap_modify(Request):
+	action = Request.POST.get('action') or Request.GET.get('action')
+	trapID = Request.GET.get('trapID')
+	trapIDs= Request.POST.getlist('trapIDs')
+
+	# FIXME:#45 'archive']:
+	if not action in ['read', 'delete', ]:
+		return HttpResponse(u'Action "%s" not implemented' % action )
+
+	if trapID:
+		trapIDs.append(trapID)
+
+	if action == 'read':
+		Trap.objects.filter(id__in=trapIDs).update(trapread=True)
+	elif action == 'delete':
+		Trap.objects.filter(id__in=trapIDs).delete()
+
+	params = Request.GET.copy()
+	if 'action' in params:
+		params.pop('action')
+	if trapID in params:
+		params.pop('trapID')
+	return HttpResponseRedirect( reverse('trap_index') + '?%s' % params.urlencode() )
+
+
+
+def config_index(Request):
+	env = {}
+
+	page = Request.GET.get('page')
+	cfgtraps = get_filtered_cfgtraps_for_querydict(Request.GET)
+	env['cfgtraps'] = paginate(cfgtraps, page, 50)
+	env['cfgtrapcount'] = cfgtraps.count()
+
+	ctsf = CfgTrapSimpleFilter(Request.GET)
+	ctsf.fields['severity'].choices = [ (x, x) for x in CfgTrap.objects.exclude(severity='').values_list('severity', flat=True).order_by('severity').distinct() ] + [ ('__', '<EMPTY>'), ]
+	ctsf.fields['category'].choices = [ (x, x) for x in CfgTrap.objects.exclude(category='').values_list('category', flat=True).order_by('category').distinct() ] + [ ('__', '<EMPTY>'), ]
+	env['cfgtrapfilter'] = ctsf
+
+	env['stats_cfgtraps_wo_category'] = CfgTrap.objects.filter(category='').count()
+	env['stats_cfgtraps_wo_severity'] = CfgTrap.objects.filter(severity='').count()
+
+	return render(Request, 'traps/trap_config_index.html', env)
+
+
+
+def config_trapoid(Request, trapoid):
+	env = {}
+	try:
+		# FIXME:#56 Mehrere, aktiv/inaktiv
+		cfgtrap = CfgTrap.objects.get(trapoid=trapoid)
+	except CfgTrap.DoesNotExist:
+		cfgtrap = CfgTrap(trapoid=trapoid)
+	env['snmptranslate_trapoid'] = external_snmptranslate(trapoid)
+
+	if Request.method == 'POST':
+		form = CfgTrapForm(Request.POST, instance=cfgtrap)
+		if form.is_valid():
+			form.save()
+			return HttpResponseRedirect( reverse('trap_config_index') )
+	else:
+		form = CfgTrapForm(instance=cfgtrap)
+
+	form.fields['trapname'].label = 'Trap name (in DB)'
+	env['form'] = form
+	return render(Request, 'traps/config_trapoid.html', env)
+
+
+
+def resolve_trapoid(Request, trapoid, db=False):
+	env = {}
+	env['trapoid'] = trapoid
+	env['trapname'] = external_snmptranslate(trapoid)
+	env['trapcount'] = Trap.objects.filter(trapoid=trapoid).count()
+
+	if db:
+		try:
+			# FIXME: Mehrere, aktiv/inaktiv
+			cfgtrap = CfgTrap.objects.get(trapoid=trapoid)
+		except CfgTrap.DoesNotExist:
+			cfgtrap = CfgTrap()
+			cfgtrap.trapoid = trapoid
+		cfgtrap.trapname = env['trapname']
+		cfgtrap.save()
+
+		with connection.cursor() as c:
+			c.execute('UPDATE ' + Trap._meta.db_table + ' SET trapname=%s WHERE trapoid=%s', [env['trapname'], trapoid,])
+		return HttpResponseRedirect( reverse('trap_index') + '?trapoid=%s' % trapoid )
+
+	return render(Request, 'traps/resolv_trapoid.html', env)
+
+
+