Module: check_mk
Branch: master
Commit: 00333ad4d974f4553dbd48a34f0f189d6973cd67
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=00333ad4d974f4…
Author: Mathias Kettner <mk(a)mathias-kettner.de>
Date: Thu Feb 2 15:23:20 2012 +0100
Update nexus_temp, renamed to cisco_temp_sensor
---
ChangeLog | 3 +-
checkman/{nexus_temp => cisco_temp_sensor} | 9 +++-
checks/{nexus_temp => cisco_temp_sensor} | 47 ++++++++++---------
...xus_temp.php => check_mk-cisco_temp_sensor.php} | 3 +-
web/plugins/perfometer/check_mk.py | 1 +
5 files changed, 36 insertions(+), 27 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index d0a8bc8..4af43e1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -44,7 +44,8 @@
"Temperature %s", in order to be consistent with the other checks.
* mounts: exclude changes of the commit option (might change on laptops),
make only switch to ro critical, other changes warning.
- * nexus_temp: new check for Cisco NEXUS temperature sensors (first version)
+ * cisco_temp_sensor: new check for temperature sensors of Cisco NEXUS
+ and other new Cisco devices
* oracle_tablespace: Fixed tablespace size/free space calculations
Multisite:
diff --git a/checkman/nexus_temp b/checkman/cisco_temp_sensor
similarity index 64%
rename from checkman/nexus_temp
rename to checkman/cisco_temp_sensor
index 412a3c8..55b912d 100644
--- a/checkman/nexus_temp
+++ b/checkman/cisco_temp_sensor
@@ -1,11 +1,11 @@
-title: Check temperature sensors on Cisco NEXUS devices
+title: Check temperature sensors on Cisco devices that support CISCO-ENTITY-SENSOR-MIB
agents: snmp
author: Mathias Kettner <mk(a)mathias-kettner.de>
license: GPL
distribution: check_mk
description:
- This check monitors all temperature sensors for Cisco NEXUS
- devices. It uses a combination of data from the standardized
+ This check monitors all temperature sensors for Cisco devices.
+ It uses a combination of data from the standardized
sensors MIB and vendor specific information.
Warning and critical levels are retrieved from the device
@@ -15,6 +15,9 @@ item:
The name of the sensors as found in the OID tree below.
.1.3.6.1.2.1.47.1.1.1.1.2.
+ The sensor is retrived from the OID tree below.
+ .1.3.6.1.4.1.9.9.91
+
perfdata:
One value: the current temperature including warning and critical levels.
diff --git a/checks/nexus_temp b/checks/cisco_temp_sensor
similarity index 86%
rename from checks/nexus_temp
rename to checks/cisco_temp_sensor
index 29eccb2..b816b30 100644
--- a/checks/nexus_temp
+++ b/checks/cisco_temp_sensor
@@ -108,58 +108,60 @@
# Create a dictionary with the information about each
# sensor. The key into the dict is the end OID of the
# sensor.
-def parse_nexus_temp(info):
+def parse_cisco_temp_sensor(info):
description_info, state_info, levels_info = info
# Create dict of sensor descriptions
descriptions = dict(description_info)
-
+
# Create dict with thresholds
thresholds = {}
+ for id, sensortype, value, sensorstate in state_info:
+ thresholds.setdefault(id, [])
+
for endoid, level in levels_info:
# endoid is e.g. 21549.9 or 21459.10
id, subid = endoid.split('.')
thresholds.setdefault(id, []).append(saveint(level))
-
+
# Create main dictionary (only of temperature sensors)
sensors = []
for id, sensortype, value, sensorstate in state_info:
sensors.append( ( id, descriptions.get(id), sensortype,
saveint(value), sensorstate, thresholds[id] ) )
-
return sensors
-def inventory_nexus_temp(info):
- sensors = parse_nexus_temp(info)
- # Use all temperature sensors with a non-empty description
+def inventory_cisco_temp_sensor(info):
+ sensors = parse_cisco_temp_sensor(info)
+ # Use all temperature sensors with a non-empty description and valid threshold
return [ (entry[1], None) for entry
in sensors if entry[1] != None
- and entry[2] == '8' ]
+ and entry[2] == '8'
+ and len(entry[5]) == 2 ]
-def check_nexus_temp(item, _no_params, info):
- sensors = parse_nexus_temp(info)
+def check_cisco_temp_sensor(item, _no_params, info):
+ sensors = parse_cisco_temp_sensor(info)
for id, descr, sensortype, value, sensorstate, levels in sensors:
if item == descr:
warn, crit = levels
if sensorstate == "2":
- return (3, "UNKNOWN - data from sensor currently not available")
+ return (3, "UNKNOWN - data from sensor currently not available")
elif sensorstate == "3":
- return (3, "UNKNOWN - sensor is broken")
+ return (3, "UNKNOWN - sensor is broken")
if value >= crit:
- state = 2
+ state = 2
elif value >= warn:
- state = 1
+ state = 1
else:
- state = 0
+ state = 0
return (state, nagios_state_names[state] + " - %dC (levels at %d/%d)" % (value, warn, crit), [
- ( "temperature", value, warn, crit ) ])
-
- return (3, "UKNOWN - sensor not found in SNMP data")
+ ( "temperature", value, warn, crit ) ])
+ return (3, "UNKNOWN - sensor not found in SNMP data")
-check_info['nexus_temp'] = (check_nexus_temp, "Temperature %s", 1, inventory_nexus_temp)
+check_info['cisco_temp_sensor'] = (check_cisco_temp_sensor, "Temperature %s", 1, inventory_cisco_temp_sensor)
-snmp_info['nexus_temp'] = [
+snmp_info['cisco_temp_sensor'] = [
# Description of sensors
( ".1.3.6.1.2.1.47.1.1.1.1", [
OID_END,
@@ -181,5 +183,6 @@ snmp_info['nexus_temp'] = [
]),
]
-snmp_scan_functions['nexus_temp'] = \
- lambda oid: "nx-os" in oid(".1.3.6.1.2.1.1.1.0").lower()
+snmp_scan_functions['cisco_temp_sensor'] = \
+ lambda oid: "cisco" in oid(".1.3.6.1.2.1.1.1.0").lower() and \
+ oid(".1.3.6.1.4.1.9.9.91.1.1.1.1.*") != None
diff --git a/pnp-templates/check_mk-nexus_temp.php b/pnp-templates/check_mk-cisco_temp_sensor.php
similarity index 96%
rename from pnp-templates/check_mk-nexus_temp.php
rename to pnp-templates/check_mk-cisco_temp_sensor.php
index b6e5a84..2ff82a2 100644
--- a/pnp-templates/check_mk-nexus_temp.php
+++ b/pnp-templates/check_mk-cisco_temp_sensor.php
@@ -23,8 +23,9 @@
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301 USA.
+
$title = str_replace("_", " ", $servicedesc);
-$opt[1] = "--vertical-label \"Celsius\" -l 0 -u 40 --title \"$title\" ";
+$opt[1] = "--vertical-label \"Celsius\" --title \"$title\" -h 140 ";
$def[1] = "DEF:var1=$RRDFILE[1]:$DS[1]:MAX ";
$def[1] .= "AREA:var1#2080ff:\"Temperature\:\" ";
diff --git a/web/plugins/perfometer/check_mk.py b/web/plugins/perfometer/check_mk.py
index ca05468..39bf3d7 100644
--- a/web/plugins/perfometer/check_mk.py
+++ b/web/plugins/perfometer/check_mk.py
@@ -208,6 +208,7 @@ def perfometer_check_mk_ipmi_sensors(row, check_command, perf_data):
# Also all checks dealing with temperature can use this perfometer
perfometers["check_mk-ipmi_sensors"] = perfometer_check_mk_ipmi_sensors
perfometers["check_mk-nvidia.temp"] = perfometer_check_mk_ipmi_sensors
+perfometers["check_mk-cisco_temp_sensor"] = perfometer_check_mk_ipmi_sensors
def perfometer_check_mk_if(row, check_command, perf_data):
txt = []
Module: check_mk
Branch: master
Commit: 17db9695fa9628eaf9c1340bfc1251f01ad7976f
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=17db9695fa9628…
Author: Mathias Kettner <mk(a)mathias-kettner.de>
Date: Thu Feb 2 15:09:57 2012 +0100
Updated bug entries #0630
---
.bugs/630 | 13 +++++++++++++
1 files changed, 13 insertions(+), 0 deletions(-)
diff --git a/.bugs/630 b/.bugs/630
new file mode 100644
index 0000000..be89306
--- /dev/null
+++ b/.bugs/630
@@ -0,0 +1,13 @@
+Title: Reset counter in case of time step
+Component: core
+State: open
+Date: 2012-02-02 15:08:44
+Targetversion: 1.2.0
+Class: bug
+
+If get_counter detects a future timestamp then it
+should reset the counter to the current time so
+that at least the next time the counter will work.
+This happens e.g. in at Windows, because the agent
+was sending invalid timestamps in older versions
+that are lying in the future and are now repaired.
Module: check_mk
Branch: master
Commit: b2210ce0c40f76a5a605e93f8c529588c23633da
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=b2210ce0c40f76…
Author: Mathias Kettner <mk(a)mathias-kettner.de>
Date: Thu Feb 2 11:39:21 2012 +0100
WATO: fixed several check parameter rules
---
checks/cpu | 2 +-
checks/if.include | 2 +-
web/htdocs/valuespec.py | 25 +++++++++++++++++++------
web/htdocs/wato.py | 13 ++++++++++---
web/plugins/wato/check_parameters.py | 27 +++++++++++++++++++--------
5 files changed, 50 insertions(+), 19 deletions(-)
diff --git a/checks/cpu b/checks/cpu
index 811b33b..e2bb254 100644
--- a/checks/cpu
+++ b/checks/cpu
@@ -29,7 +29,7 @@
cpuload_default_levels = (5, 10)
-threads_default_levels = (2000.0, 4000.0)
+threads_default_levels = (2000, 4000)
def inventory_cpu_load(info):
if len(info) == 1 and len(info[0]) >= 5:
diff --git a/checks/if.include b/checks/if.include
index a003d2a..dedc1fd 100644
--- a/checks/if.include
+++ b/checks/if.include
@@ -116,7 +116,7 @@ def inventory_if_common(info):
paramstring = "{"
if if_inventory_monitor_state:
- paramstring += '"state" : "%s",' % ifOperStatus
+ paramstring += '"state" : ["%s"],' % ifOperStatus
if ifSpeed != "" and if_inventory_monitor_speed:
paramstring += '"speed" : %d,' % int(ifSpeed)
diff --git a/web/htdocs/valuespec.py b/web/htdocs/valuespec.py
index 8707a3f..21e3d65 100644
--- a/web/htdocs/valuespec.py
+++ b/web/htdocs/valuespec.py
@@ -163,7 +163,7 @@ class Age(ValueSpec):
def validate_datatype(self, value, varprefix):
if type(value) != int:
- raise MKUserError(varprefix, _("The value has type %s, but must be of type int") % (type(value)))
+ raise MKUserError(varprefix, _("The value %r has type %s, but must be of type int") % (value, type(value)))
# Editor for a single integer
@@ -220,7 +220,8 @@ class Integer(ValueSpec):
def validate_datatype(self, value, varprefix):
if type(value) != int:
- raise MKUserError(varprefix, _("The value has type %s, but must be of type int") % (type(value)))
+ raise MKUserError(varprefix, _("The value %r has the wrong type %s, but must be of type int")
+ % (value, type(value)))
def validate_value(self, value, varprefix):
if self._minvalue != None and value < self._minvalue:
@@ -487,7 +488,7 @@ class Float(Integer):
def validate_datatype(self, value, varprefix):
if type(value) != float:
- raise MKUserError(varprefix, _("The value has type %s, but must be of type float") % (type(value)))
+ raise MKUserError(varprefix, _("The value %r has type %s, but must be of type float") % (value, type(value)))
class Percentage(Float):
@@ -499,10 +500,19 @@ class Percentage(Float):
self._maxvalue = 101.0
if "unit" not in kwargs:
self._unit = "%"
+ self._allow_int = kwargs.get("allow_int", False)
def value_to_text(self, value):
return "%.1f%%" % value
+ def validate_datatype(self, value, varprefix):
+ if self._allow_int:
+ if type(value) not in [ int, float ]:
+ raise MKUserError(varprefix, _("The value %r has type %s, but must be either float or int")
+ % (value, type(value)))
+ else:
+ Float.validate_datatype(self, value, varprefix)
+
class Checkbox(ValueSpec):
def __init__(self, **kwargs):
@@ -526,7 +536,7 @@ class Checkbox(ValueSpec):
def validate_datatype(self, value, varprefix):
if type(value) != bool:
- raise MKUserError(varprefix, _("The value has type %s, but must be either True or False") % (type(value)))
+ raise MKUserError(varprefix, _("The value %r has type %s, but must be of type bool") % (value, type(value)))
# A type-save dropdown choice. Parameters:
# help_separator: if you set this to a character, e.g. "-", then
@@ -1256,7 +1266,10 @@ class Dictionary(ValueSpec):
for param, vs in self._elements:
if param in value:
vp = varprefix + "_" + param
- vs.validate_datatype(value[param], vp)
+ try:
+ vs.validate_datatype(value[param], vp)
+ except MKUserError, e:
+ raise MKUserError(e.varname, _("%s: %s") % (vs.title(), e.message))
elif not self._optional_keys:
raise MKUserError(varprefix, _("The entry %s is missing") % vp.title())
@@ -1265,7 +1278,7 @@ class Dictionary(ValueSpec):
for param in value.keys():
if param not in allowed_keys:
raise MKUserError(varprefix, _("Undefined key '%s' in the dictionary. Allowed are %s.") %
- ", ".join(allowed_keys))
+ (param, ", ".join(allowed_keys)))
def validate_value(self, value, varprefix):
for param, vs in self._elements:
diff --git a/web/htdocs/wato.py b/web/htdocs/wato.py
index 8585138..6648131 100644
--- a/web/htdocs/wato.py
+++ b/web/htdocs/wato.py
@@ -2027,9 +2027,16 @@ def show_service_table(host, firsttime):
("varname", varname),
("host", hostname),
("item", repr(item))])
- title = _("Edit rules for this check parameter")
- title = "Check parameters for this service: " + \
- rulespec["valuespec"].value_to_text(params)
+ try:
+ rulespec["valuespec"].validate_datatype(params, "")
+ rulespec["valuespec"].validate_value(params, "")
+ paramtext = rulespec["valuespec"].value_to_text(params)
+ except Exception, e:
+ if config.debug:
+ raise
+ paramtext = _("Invalid check parameter: %s!") % e
+
+ title = "Check parameters for this service: " + paramtext
html.write('<a href="%s"><img title="%s" class=icon src="images/icon_rulesets.png"></a>' %
(url, title))
diff --git a/web/plugins/wato/check_parameters.py b/web/plugins/wato/check_parameters.py
index 2e1b0f6..ed1ce73 100644
--- a/web/plugins/wato/check_parameters.py
+++ b/web/plugins/wato/check_parameters.py
@@ -453,8 +453,8 @@ checkgroups.append((
Tuple(
title = _("Levels for the used space"),
elements = [
- Percentage(title = _("Warning at"), label = _("% usage")),
- Percentage(title = _("Critical at"), label = _("% usage"))])),
+ Percentage(title = _("Warning at"), label = _("% usage"), allow_int = True),
+ Percentage(title = _("Critical at"), label = _("% usage"), allow_int = True)])),
( "magic",
Float(
title = _("Magic factor (automatic level adaptation for large filesystems)"),
@@ -465,11 +465,22 @@ checkgroups.append((
title = _("Reference size for magic factor"),
minvalue = 1,
label = _("GB"))),
+ ( "levels_low",
+ Tuple(
+ title = _("Minimum levels if using magic factor"),
+ help = _("The filesystem levels will never fall below these values, when using "
+ "the magic factor and the filesystem is very small."),
+ elements = [
+ Percentage(title = _("Warning at"), label = _("% usage"), allow_int = True),
+ Percentage(title = _("Critical at"), label = _("% usage"), allow_int = True)])),
( "trend_range",
- Integer(
- title = _("Range for filesystem trend computation"),
- minvalue = 1,
- label= _("hours"))),
+ Optional(
+ Integer(
+ title = _("Range for filesystem trend computation"),
+ minvalue = 1,
+ label= _("hours")),
+ title = _("Trend computation"),
+ label = _("Enable trend computation"))),
( "trend_mb",
Tuple(
title = _("Levels on trends in MB per range"),
@@ -612,8 +623,8 @@ checkgroups.append((
Tuple(
title = _("Specify levels in percentage of total RAM"),
elements = [
- Percentage(title = _("Warning at a usage of"), label = _("% of RAM")),
- Percentage(title = _("Critical at a usage of"), label = _("% of RAM"))]),
+ Percentage(title = _("Warning at a usage of"), label = _("% of RAM"), max_value = None),
+ Percentage(title = _("Critical at a usage of"), label = _("% of RAM"), max_value = None)]),
Tuple(
title = _("Specify levels in absolute usage values"),
elements = [
Module: check_mk
Branch: master
Commit: 9f9078e77dc5da89216e7990a9ef137593945a99
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=9f9078e77dc5da…
Author: Mathias Kettner <mk(a)mathias-kettner.de>
Date: Thu Feb 2 10:42:52 2012 +0100
WATO: force user to have at least one role
---
web/htdocs/wato.py | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/web/htdocs/wato.py b/web/htdocs/wato.py
index 27e027f..8585138 100644
--- a/web/htdocs/wato.py
+++ b/web/htdocs/wato.py
@@ -6729,6 +6729,10 @@ def mode_edit_user(phase):
'the user member of a contact group which has hosts assigned '
'in order to be able to receive emails.'))
+ if not new_user["roles"]:
+ raise MKUserError("role_user",
+ _("Your user has no roles. Please assign at least one role."))
+
ntp = html.var("notification_period")
if ntp not in timeperiods:
ntp = "24X7"
Module: check_mk
Branch: master
Commit: ff3917d11b10fef8d1f385b02704d7c39188fa57
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=ff3917d11b10fe…
Author: Mathias Kettner <mk(a)mathias-kettner.de>
Date: Thu Feb 2 10:39:24 2012 +0100
cmk --notify: new macros $MONITORING_HOST$, $OMD_ROOT$ and $OMD_SITE$
---
ChangeLog | 1 +
modules/notify.py | 14 +++++++++++++-
web/plugins/wato/globals_notification.py | 4 ++++
3 files changed, 18 insertions(+), 1 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 48f6e5c..f586e33 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -219,6 +219,7 @@
* Drop RRA-configuration files for PNP4Nagios completely
* New configuration variable ping_levels for configuring parameters
for the host checks.
+ * cmk --notify: new macros $MONITORING_HOST$, $OMD_ROOT$ and $OMD_SITE$
Checks & Agents:
* if/if64: new ruleset if_disable_if64_hosts, that force if on
diff --git a/modules/notify.py b/modules/notify.py
index 01676bd..f15ba30 100644
--- a/modules/notify.py
+++ b/modules/notify.py
@@ -97,6 +97,15 @@ def do_notify(args):
in os.environ.items()
if var.startswith("NOTIFY_")
and not re.match('^\$[A-Z]+\$$', value)])
+
+ # Add a few further helper variables
+ import socket
+ context["MONITORING_HOST"] = socket.gethostname()
+ if omd_root:
+ context["OMD_ROOT"] = omd_root
+ context["OMD_SITE"] = os.getenv("OMD_SITE", "")
+
+
if notification_logging >= 2:
notify_log("Notification context:\n"
+ "\n".join(["%s=%s" % v for v in sorted(context.items())]))
@@ -140,7 +149,10 @@ def do_notify(args):
sys.stderr.write("Details have been logged to %s.\n" % notification_log)
sys.exit(1)
except Exception, e:
- file(var_dir + "/notify/crash.log", "a").write("CRASH:\n%s\n\n" % format_exception())
+ crash_dir = var_dir + "/notify"
+ if not os.path.exists(crash_dir):
+ os.makedirs(crash_dir)
+ file(crash_dir + "/crash.log", "a").write("CRASH:\n%s\n\n" % format_exception())
def format_exception():
import traceback, StringIO, sys
diff --git a/web/plugins/wato/globals_notification.py b/web/plugins/wato/globals_notification.py
index ea47a21..9857039 100644
--- a/web/plugins/wato/globals_notification.py
+++ b/web/plugins/wato/globals_notification.py
@@ -115,6 +115,10 @@ register_configvar(group,
"<tt><b>$LONGSERVICEOUTPUT$</b></tt>: the long output of the check command, "
"<tt><b>$SERVICEPERFDATA$</b></tt>: the performance data of the check, "
"<tt><b>$SERVICECHECKCOMMAND$</b></tt>: the name of the service check command"
+ "<br><br>"
+ "<tt><b>$MONITORING_HOST$</b></tt>: the host name of the monitoring server "
+ "<tt><b>$OMD_ROOT$</b></tt>: the home directory of the OMD site (only on OMD) "
+ "<tt><b>$OMD_SITE$</b></tt>: the name of the OMD site (only on OMD) "
),
),
domain = "check_mk")
Module: check_mk
Branch: master
Commit: fb8e41444256dd4f71726c48b29d4fdf2635eac0
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=fb8e41444256dd…
Author: Mathias Kettner <mk(a)mathias-kettner.de>
Date: Wed Feb 1 17:20:46 2012 +0100
New cool ValueSpec: CascadingDropdown
---
web/htdocs/forms.py | 8 +++--
web/htdocs/js/checkmk.js | 16 ++++++++
web/htdocs/valuespec.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 109 insertions(+), 3 deletions(-)
diff --git a/web/htdocs/forms.py b/web/htdocs/forms.py
index 6c76846..05ef548 100644
--- a/web/htdocs/forms.py
+++ b/web/htdocs/forms.py
@@ -27,7 +27,7 @@
from lib import *
-def edit_dictionary(entries, value, focus=None, hover_help=True, validate=None):
+def edit_dictionary(entries, value, focus=None, hover_help=True, validate=None, buttontext = None):
new_value = value.copy()
if html.var("filled_in") == "form" and html.check_transaction():
messages = []
@@ -60,7 +60,7 @@ def edit_dictionary(entries, value, focus=None, hover_help=True, validate=None):
html.write("<tr><td ")
if vs.help() and hover_help:
html.write('title="%s" ' % vs.help().replace('"', """))
- html.write("class=legend>%s" % vs.title())
+ html.write("class=legend>%s" % (vs.title() or ""))
if vs.help() and not hover_help:
html.write("<br><i>%s</i>" % vs.help())
html.write("</td><td class=content>")
@@ -73,7 +73,9 @@ def edit_dictionary(entries, value, focus=None, hover_help=True, validate=None):
vs.set_focus(name)
first = False
html.write("<tr><td class=buttons colspan=2>")
- html.button("save", _("Save"))
+ if buttontext == None:
+ buttontext = _("Save")
+ html.button("save", buttontext)
html.write("</td></tr>\n")
html.write("</table>\n")
html.hidden_fields()
diff --git a/web/htdocs/js/checkmk.js b/web/htdocs/js/checkmk.js
index d33e94c..bf4093d 100644
--- a/web/htdocs/js/checkmk.js
+++ b/web/htdocs/js/checkmk.js
@@ -1422,3 +1422,19 @@ function list_of_strings_extend(oInput, j) {
// Remove handle from old last element
oInput.onfocus = null;
}
+
+function valuespec_cascading_change(oSelect, varprefix, count) {
+ var nr = parseInt(oSelect.value);
+
+ for (var i=0; i<count; i++) {
+ var oDiv = document.getElementById(varprefix + "_" + i + "_sub");
+ if (oDiv) {
+ if (nr == i) {
+ oDiv.style.display = "";
+ }
+ else
+ oDiv.style.display = "none";
+ }
+ }
+}
+
diff --git a/web/htdocs/valuespec.py b/web/htdocs/valuespec.py
index f5e3ead..11bb85a 100644
--- a/web/htdocs/valuespec.py
+++ b/web/htdocs/valuespec.py
@@ -580,6 +580,94 @@ class DropdownChoice(ValueSpec):
raise MKUserError(varprefix, _("Invalid value %s, must be in %s") %
", ".join([v for (v,t) in self._choices]))
+
+# A Dropdown choice where the elements are ValueSpecs.
+# The currently selected ValueSpec will be displayed.
+# The text representations of the ValueSpecs will be used as texts.
+# A ValueSpec of None is also allowed and will return
+# the value None.
+# The resulting value is either a single value (if no
+# value spec is defined for the selected entry) or a pair
+# of (x, y) where x is the value of the selected entry and
+# y is the value of the valuespec assigned to that entry.
+# choices is a list of triples: [ ( value, title, vs ), ... ]
+class CascadingDropdown(ValueSpec):
+ def __init__(self, **kwargs):
+ ValueSpec.__init__(self, **kwargs)
+ self._choices = kwargs["choices"]
+ self._separator = kwargs.get("separator", ", ")
+ self._html_separator = kwargs.get("html_separator", "<br>")
+
+ def canonical_value(self):
+ if self._choices[0][2]:
+ return (self._choices[0][0], self._choices[0][2].canonical_value())
+ else:
+ return self._choices[0][0]
+
+ def render_input(self, varprefix, value):
+ def_val = 0
+ options = []
+ for nr, (val, title, vs) in enumerate(self._choices):
+ options.append((str(nr), title))
+ if value == val or (
+ type(value) == tuple and value[0] == val):
+ def_val = nr
+ html.select(varprefix + "_sel", options, def_val,
+ onchange="valuespec_cascading_change(this, '%s', %d);" % (varprefix, len(self._choices)))
+ html.write(self._html_separator)
+ for nr, (val, title, vs) in enumerate(self._choices):
+ if vs:
+ vp = varprefix + "_%d" % nr
+ if value == val or (
+ type(value) == tuple and value[0] == val):
+ def_val = value[1]
+ disp = ""
+ else:
+ def_val = vs.default_value()
+ disp = "none"
+ html.write('<div id="%s_%s_sub" style="display: %s">' % (varprefix, nr, disp))
+ vs.render_input(vp, def_val)
+ html.write('</div>')
+
+ def value_to_text(self, value):
+ for val, title, vs in self._choices:
+ if value[0] == val:
+ if not vs:
+ return title
+ else:
+ return title + self._separator + \
+ vs.value_to_text(value[1])
+ return "" # Nothing selected? Should never happen
+
+ def from_html_vars(self, varprefix):
+ sel = int(html.var(varprefix + "_sel"))
+ val, title, vs = self._choices[sel]
+ if vs:
+ val = (val, vs.from_html_vars(varprefix + "_%d" % sel))
+ return val
+
+ def validate_datatype(self, value, varprefix):
+ for nr, (val, title, vs) in enumerate(self._choices):
+ if value == val or (
+ type(value) == tuple and value[0] == val):
+ if vs:
+ if type(value) != tuple or len(value) != 2:
+ raise MKUserError(varprefix + "_sel", _("Value must a tuple with two elements."))
+ vs.validate_datatype(value[1], varprefix + "_%d" % nr)
+ return
+ raise MKUserError(_("Value %r is not allowed here.") % value)
+
+ def validate_value(self, value, varprefix):
+ for nr, (val, title, vs) in enumerate(self._choices):
+ if value == val or (
+ type(value) == tuple and value[0] == val):
+ if vs:
+ vs.validate_value(value[1], varprefix + "_%d" % nr)
+ return
+ raise MKUserError(varprefix, _("Value %r is not allowed here.") % (value, ))
+
+
+
# The same logic as the dropdown choice, but rendered
# as a group of radio buttons.
# columns == None or unset -> separate with " "