2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
28 # - Change all class to be "new style" classes
29 # - Consolidate ListChoice and DualListChoice to use the same class
30 # and rename to better name
31 # - Consolidate RadioChoice and DropdownChoice to use same class
32 # and rename to better name
33 # - Consolidate ListOf and ListOfStrings/ListOfIntegers
35 # -> rename to Boolean
36 # -> Add alternative rendering "dropdown"
52 from UserDict
import DictMixin
53 from StringIO
import StringIO
56 from Cryptodome
.PublicKey
import RSA
60 import cmk
.utils
.paths
61 import cmk
.utils
.defines
as defines
63 import cmk
.gui
.forms
as forms
64 import cmk
.gui
.utils
as utils
65 from cmk
.gui
.i18n
import _
66 from cmk
.gui
.pages
import page_registry
, Page
, AjaxPage
67 from cmk
.gui
.globals import html
68 from cmk
.gui
.htmllib
import HTML
69 from cmk
.gui
.exceptions
import MKUserError
, MKGeneralException
76 return type(v
).__name
__
78 return html
.attrencode(type(v
))
81 seconds_per_day
= 86400
84 # Abstract base class of all value declaration classes.
85 class ValueSpec(object):
86 def __init__(self
, **kwargs
):
87 super(ValueSpec
, self
).__init
__()
88 self
._title
= kwargs
.get("title")
89 self
._help
= kwargs
.get("help")
90 if "default_value" in kwargs
:
91 self
._default
_value
= kwargs
.get("default_value")
92 self
._validate
= kwargs
.get("validate")
94 # debug display allows to view the class name of a certain element in the gui.
95 # If set False, then the corresponding div is present, but hidden.
96 self
.debug_display
= False
102 if isinstance(self
._help
, (types
.FunctionType
, types
.MethodType
)):
106 # Create HTML-form elements that represent a given
107 # value and let the user edit that value. The varprefix
108 # is prepended to the HTML variable names and is needed
109 # in order to make the variable unique in case that another
110 # Value of the same type is being used as well.
111 # The function may assume that the type of the value is valid.
112 def render_input(self
, varprefix
, value
):
115 # Sets the input focus (cursor) into the most promiment
116 # field of the HTML code previously rendered with render_input()
117 def set_focus(self
, varprefix
):
118 html
.set_focus(varprefix
)
120 # Create a canonical, minimal, default value that
121 # matches the datatype of the value specification and
122 # fulfills also data validation.
123 def canonical_value(self
):
126 # Return a default value for this variable. This
127 # is optional and only used in the value editor
128 # for same cases where the default value is known.
129 def default_value(self
):
131 if isinstance(self
._default
_value
, (types
.FunctionType
, types
.MethodType
)):
132 return self
._default
_value
()
133 return self
._default
_value
135 return self
.canonical_value()
137 # Creates a text-representation of the value that can be
138 # used in tables and other contextes. It is to be read
139 # by the user and need not to be parsable.
140 # The function may assume that the type of the value is valid.
142 # In the current implementation this function is only used to
143 # render the object for html code. So it is allowed to add
144 # html code for better layout in the GUI.
145 def value_to_text(self
, value
):
148 # Create a value from the current settings of the
149 # HTML variables. This function must also check the validity
150 # and may raise a MKUserError in case of invalid set variables.
151 def from_html_vars(self
, varprefix
):
154 # Check if a given value matches the
155 # datatype of described by this class. This method will
156 # be used by cmk -X on the command line in order to
157 # validate main.mk (some happy day in future)
158 def validate_datatype(self
, value
, varprefix
):
161 # Check if a given value is within the ranges that are
162 # allowed for this type of value. This function should
163 # assume that the data type is valid (either because it
164 # has been returned by from_html_vars() or because it has
165 # been checked with validate_datatype()).
166 def validate_value(self
, value
, varprefix
):
167 self
.custom_validate(value
, varprefix
)
169 # Needed for implementation of customer validation
170 # functions that are configured by the user argument
171 # validate = .... Problem: this function must be
172 # called by *every* validate_value() function in all
173 # subclasses - explicitely.
174 def custom_validate(self
, value
, varprefix
):
176 self
._validate
(value
, varprefix
)
178 def classtype_info(self
):
179 superclass
= " ".join(base
.__name
__ for base
in self
.__class
__.__bases
__)
180 if not self
.debug_display
:
182 html
.div("Check_MK-Type: %s %s" % (superclass
, type(self
).__name
__), class_
="legend")
185 # A fixed non-editable value, e.g. to be use in "Alternative"
186 class FixedValue(ValueSpec
):
187 def __init__(self
, value
, **kwargs
):
188 ValueSpec
.__init
__(self
, **kwargs
)
190 self
._totext
= kwargs
.get("totext")
192 def canonical_value(self
):
195 def render_input(self
, varprefix
, value
):
196 html
.write(self
.value_to_text(value
))
198 def value_to_text(self
, value
):
199 if self
._totext
is not None:
201 elif isinstance(value
, unicode):
205 def from_html_vars(self
, varprefix
):
208 def validate_datatype(self
, value
, varprefix
):
209 if not self
._value
== value
:
210 raise MKUserError(varprefix
,
211 _("Invalid value, must be '%r' but is '%r'") % (self
._value
, value
))
213 def validate_value(self
, value
, varprefix
):
214 self
.validate_datatype(value
, varprefix
)
215 ValueSpec
.custom_validate(self
, value
, varprefix
)
219 class Age(ValueSpec
):
220 def __init__(self
, **kwargs
):
221 ValueSpec
.__init
__(self
, **kwargs
)
222 self
._label
= kwargs
.get("label")
223 self
._minvalue
= kwargs
.get("minvalue")
224 self
._display
= kwargs
.get("display", ["days", "hours", "minutes", "seconds"])
226 def canonical_value(self
):
228 return self
._minvalue
231 def render_input(self
, varprefix
, value
):
232 self
.classtype_info()
233 days
, rest
= divmod(value
, 60 * 60 * 24)
234 hours
, rest
= divmod(rest
, 60 * 60)
235 minutes
, seconds
= divmod(rest
, 60)
239 html
.write(self
._label
+ " ")
243 for uid
, title
, val
, tkovr_fac
in [("days", _("days"), days
, 24),
244 ("hours", _("hours"), hours
, 60),
245 ("minutes", _("mins"), minutes
, 60),
246 ("seconds", _("secs"), seconds
, 60)]:
247 if uid
in self
._display
:
250 html
.number_input(varprefix
+ "_" + uid
, val
, 3 if first
else 2)
251 html
.write(" %s " % title
)
254 takeover
= (takeover
+ val
) * tkovr_fac
257 def from_html_vars(self
, varprefix
):
258 # TODO: Validate for correct numbers!
259 return (utils
.saveint(html
.request
.var(varprefix
+ '_days', 0)) * 3600 * 24 +
260 utils
.saveint(html
.request
.var(varprefix
+ '_hours', 0)) * 3600 +
261 utils
.saveint(html
.request
.var(varprefix
+ '_minutes', 0)) * 60 + utils
.saveint(
262 html
.request
.var(varprefix
+ '_seconds', 0)))
264 def value_to_text(self
, value
):
265 days
, rest
= divmod(value
, 60 * 60 * 24)
266 hours
, rest
= divmod(rest
, 60 * 60)
267 minutes
, seconds
= divmod(rest
, 60)
269 for title
, count
in [
272 (_("minutes"), minutes
),
273 (_("seconds"), seconds
),
276 parts
.append("%d %s" % (count
, title
))
279 return " ".join(parts
)
282 def validate_datatype(self
, value
, varprefix
):
283 if not isinstance(value
, int):
286 _("The value %r has type %s, but must be of type int") % (value
, _type_name(value
)))
288 def validate_value(self
, value
, varprefix
):
289 if self
._minvalue
is not None and value
< self
._minvalue
:
292 _("%s is too low. The minimum allowed value is %s.") % (value
, self
._minvalue
))
295 # Editor for a single integer
296 class Integer(ValueSpec
):
297 def __init__(self
, **kwargs
):
298 ValueSpec
.__init
__(self
, **kwargs
)
299 self
._size
= kwargs
.get("size", 5)
300 # TODO: inconsistency with default_value. All should be named with underscore
301 self
._minvalue
= kwargs
.get("minvalue")
302 self
._maxvalue
= kwargs
.get("maxvalue")
303 self
._label
= kwargs
.get("label")
304 self
._unit
= kwargs
.get("unit", "")
305 self
._thousand
_sep
= kwargs
.get("thousand_sep")
306 self
._display
_format
= kwargs
.get("display_format", "%d")
307 self
._align
= kwargs
.get("align", "left")
309 if "size" not in kwargs
and "maxvalue" in kwargs
and kwargs
["maxvalue"] is not None:
310 self
._size
= 1 + int(math
.log10(self
._maxvalue
)) + \
311 (3 if isinstance(self
._maxvalue
, float) else 0)
313 def canonical_value(self
):
315 return self
._minvalue
318 def render_input(self
, varprefix
, value
):
319 self
.classtype_info()
321 html
.write(self
._label
)
323 if self
._align
== "right":
324 style
= "text-align: right;"
327 if value
== "": # This is needed for ListOfIntegers
328 html
.text_input(varprefix
, "", "number", size
=self
._size
, style
=style
)
330 html
.number_input(varprefix
, self
._render
_value
(value
), size
=self
._size
, style
=style
)
333 html
.write(self
._unit
)
335 def _render_value(self
, value
):
336 return self
._display
_format
% utils
.saveint(value
)
338 def from_html_vars(self
, varprefix
):
340 return int(html
.request
.var(varprefix
))
344 _("The text <b><tt>%s</tt></b> is not a valid integer number.") %
345 html
.request
.var(varprefix
))
347 def value_to_text(self
, value
):
348 text
= self
._display
_format
% value
349 if self
._thousand
_sep
:
353 sepped
= self
._thousand
_sep
+ rest
[-3:] + sepped
355 sepped
= rest
+ sepped
359 text
+= " " + self
._unit
362 def validate_datatype(self
, value
, varprefix
):
363 if not isinstance(value
, numbers
.Integral
):
366 _("The value %r has the wrong type %s, but must be of type int") %
367 (value
, _type_name(value
)))
369 def validate_value(self
, value
, varprefix
):
370 if self
._minvalue
is not None and value
< self
._minvalue
:
373 _("%s is too low. The minimum allowed value is %s.") % (value
, self
._minvalue
))
374 if self
._maxvalue
is not None and value
> self
._maxvalue
:
377 _("%s is too high. The maximum allowed value is %s.") % (value
, self
._maxvalue
))
378 ValueSpec
.custom_validate(self
, value
, varprefix
)
381 # Filesize in Byte, KByte, MByte, Gigabyte, Terabyte
382 class Filesize(Integer
):
383 def __init__(self
, **kwargs
):
384 Integer
.__init
__(self
, **kwargs
)
385 self
._names
= ['Byte', 'KByte', 'MByte', 'GByte', 'TByte']
387 def get_exponent(self
, value
):
388 for exp
, count
in ((exp
, 1024**exp
) for exp
in reversed(xrange(len(self
._names
)))):
391 if value
% count
== 0:
392 return exp
, value
/ count
394 def render_input(self
, varprefix
, value
):
395 self
.classtype_info()
396 exp
, count
= self
.get_exponent(value
)
397 html
.number_input(varprefix
+ '_size', count
, size
=self
._size
)
399 choices
= [(str(nr
), name
) for (nr
, name
) in enumerate(self
._names
)]
400 html
.dropdown(varprefix
+ '_unit', choices
, deflt
=str(exp
))
402 def from_html_vars(self
, varprefix
):
404 return int(html
.request
.var(varprefix
+ '_size')) * (1024**int(
405 html
.request
.var(varprefix
+ '_unit')))
407 raise MKUserError(varprefix
+ '_size', _("Please enter a valid integer number"))
409 def value_to_text(self
, value
):
410 exp
, count
= self
.get_exponent(value
)
411 return "%s %s" % (count
, self
._names
[exp
])
414 # Editor for a line of text
415 class TextAscii(ValueSpec
):
416 def __init__(self
, **kwargs
):
417 super(TextAscii
, self
).__init
__(**kwargs
)
418 self
._label
= kwargs
.get("label")
419 self
._size
= kwargs
.get("size", 25) # also possible: "max"
420 self
._try
_max
_width
= kwargs
.get("try_max_width", False) # If set, uses calc(100%-10px)
421 self
._cssclass
= kwargs
.get("cssclass", "text")
422 self
._strip
= kwargs
.get("strip", True)
423 self
._attrencode
= kwargs
.get("attrencode", True)
424 self
._allow
_empty
= kwargs
.get("allow_empty", _("none"))
425 self
._empty
_text
= kwargs
.get("empty_text", "")
426 self
._read
_only
= kwargs
.get("read_only")
427 self
._none
_is
_empty
= kwargs
.get("none_is_empty", False)
428 self
._forbidden
_chars
= kwargs
.get("forbidden_chars", "")
429 self
._regex
= kwargs
.get("regex")
430 self
._regex
_error
= kwargs
.get("regex_error",
431 _("Your input does not match the required format."))
432 self
._minlen
= kwargs
.get('minlen', None)
433 if isinstance(self
._regex
, str):
434 self
._regex
= re
.compile(self
._regex
)
435 self
._onkeyup
= kwargs
.get("onkeyup")
436 self
._autocomplete
= kwargs
.get("autocomplete", True)
437 self
._hidden
= kwargs
.get("hidden", False)
439 def canonical_value(self
):
442 def render_input(self
, varprefix
, value
):
443 self
.classtype_info()
444 value_text
= "%s" % value
if value
is not None else ""
447 html
.write(self
._label
)
450 type_
= "password" if self
._hidden
else "text"
454 attrs
["onkeyup"] = self
._onkeyup
460 try_max_width
=self
._try
_max
_width
,
461 read_only
=self
._read
_only
,
462 cssclass
=self
._cssclass
,
465 autocomplete
="off" if not self
._autocomplete
else None,
468 def value_to_text(self
, value
):
470 return self
._empty
_text
473 return html
.attrencode(value
)
476 def from_html_vars(self
, varprefix
):
477 value
= html
.request
.var(varprefix
, "")
479 value
= value
.strip()
480 if self
._none
_is
_empty
and not value
:
484 def validate_datatype(self
, value
, varprefix
):
485 if self
._none
_is
_empty
and value
is None:
488 if not isinstance(value
, str):
491 _("The value must be of type str, but it has type %s") % _type_name(value
))
493 def validate_value(self
, value
, varprefix
):
497 raise MKUserError(varprefix
, _("Non-ASCII characters are not allowed here."))
498 if self
._forbidden
_chars
:
499 for c
in self
._forbidden
_chars
:
501 raise MKUserError(varprefix
,
502 _("The character <tt>%s</tt> is not allowed here.") % c
)
503 if self
._none
_is
_empty
and value
== "":
504 raise MKUserError(varprefix
, _("An empty value must be represented with None here."))
505 if not self
._allow
_empty
and value
.strip() == "":
506 raise MKUserError(varprefix
, _("An empty value is not allowed here."))
507 if value
and self
._regex
:
508 if not self
._regex
.match(value
):
509 raise MKUserError(varprefix
, self
._regex
_error
)
511 if self
._minlen
is not None and len(value
) < self
._minlen
:
512 raise MKUserError(varprefix
,
513 _("You need to provide at least %d characters.") % self
._minlen
)
515 ValueSpec
.custom_validate(self
, value
, varprefix
)
518 class TextUnicode(TextAscii
):
519 def __init__(self
, **kwargs
):
520 TextAscii
.__init
__(self
, **kwargs
)
522 def from_html_vars(self
, varprefix
):
523 return html
.get_unicode_input(varprefix
, "").strip()
525 def validate_datatype(self
, value
, varprefix
):
526 if not isinstance(value
, six
.string_types
):
529 _("The value must be of type str or unicode, but it has type %s") %
533 # Internal ID as used in many places (for contact names, group name,
536 def __init__(self
, **kwargs
):
537 TextAscii
.__init
__(self
, **kwargs
)
538 self
._regex
= re
.compile('^[a-zA-Z_][-a-zA-Z0-9_]*$')
539 self
._regex
_error
= _("An identifier must only consist of letters, digits, dash and "
540 "underscore and it must start with a letter or underscore.")
543 # Same as the ID class, but allowing unicode objects
544 class UnicodeID(TextUnicode
):
545 def __init__(self
, **kwargs
):
546 TextUnicode
.__init
__(self
, **kwargs
)
547 self
._regex
= re
.compile(r
'^[\w][-\w0-9_]*$', re
.UNICODE
)
548 self
._regex
_error
= _("An identifier must only consist of letters, digits, dash and "
549 "underscore and it must start with a letter or underscore.")
552 class UserID(TextUnicode
):
553 def __init__(self
, **kwargs
):
554 TextUnicode
.__init
__(self
, **kwargs
)
555 self
._regex
= re
.compile(r
'^[\w][-\w0-9_\.@]*$', re
.UNICODE
)
556 self
._regex
_error
= _("An identifier must only consist of letters, digits, dash, dot, "
557 "at and underscore. But it must start with a letter or underscore.")
560 class RegExp(TextAscii
):
563 complete
= "complete"
565 def __init__(self
, mode
, **kwargs
):
567 self
._case
_sensitive
= kwargs
.get("case_sensitive", True)
569 TextAscii
.__init
__(self
, attrencode
=True, cssclass
=self
._css
_classes
(), **kwargs
)
571 self
._mingroups
= kwargs
.get("mingroups", 0)
572 self
._maxgroups
= kwargs
.get("maxgroups")
577 default_help_text
= TextAscii
.help(self
)
578 if default_help_text
is not None:
579 help_text
.append(default_help_text
+ "<br><br>")
581 help_text
.append(_("The text entered here is handled as a regular expression pattern."))
583 if self
._mode
== RegExp
.infix
:
585 _("The pattern is applied as infix search. Add a leading <tt>^</tt> "
586 "to make it match from the beginning and/or a tailing <tt>$</tt> "
587 "to match till the end of the text."))
588 elif self
._mode
== RegExp
.prefix
:
590 _("The pattern is matched from the beginning. Add a tailing "
591 "<tt>$</tt> to change it to a whole text match."))
592 elif self
._mode
== RegExp
.complete
:
594 _("The pattern is matching the whole text. You can add <tt>.*</tt> "
595 "in front or at the end of your pattern to make it either a prefix "
598 if self
._case
_sensitive
is True:
599 help_text
.append(_("The match is performed case sensitive."))
600 elif self
._case
_sensitive
is False:
601 help_text
.append(_("The match is performed case insensitive."))
604 _("Please note that any backslashes need to be escaped using a backslash, "
605 "for example you need to insert <tt>C:\\\\windows\\\\</tt> if you want to match "
606 "<tt>c:\\windows\\</tt>."))
608 return " ".join(help_text
)
610 def _css_classes(self
):
611 classes
= ["text", "regexp"]
613 if self
._case
_sensitive
is True:
614 classes
.append("case_sensitive")
615 elif self
._case
_sensitive
is False:
616 classes
.append("case_insensitive")
618 if self
._mode
is not None:
619 classes
.append(self
._mode
)
621 return " ".join(classes
)
623 def validate_value(self
, value
, varprefix
):
624 TextAscii
.validate_value(self
, value
, varprefix
)
626 # Check if the string is a valid regex
628 compiled
= re
.compile(value
)
629 except sre_constants
.error
as e
:
630 raise MKUserError(varprefix
, _('Invalid regular expression: %s') % e
)
632 if compiled
.groups
< self
._mingroups
:
635 _("Your regular expression containes <b>%d</b> groups. "
636 "You need at least <b>%d</b> groups.") % (compiled
.groups
, self
._mingroups
))
637 if self
._maxgroups
is not None and compiled
.groups
> self
._maxgroups
:
640 _("Your regular expression containes <b>%d</b> groups. "
641 "It must have at most <b>%d</b> groups.") % (compiled
.groups
, self
._maxgroups
))
643 ValueSpec
.custom_validate(self
, value
, varprefix
)
646 class RegExpUnicode(TextUnicode
, RegExp
):
647 def __init__(self
, **kwargs
):
648 TextUnicode
.__init
__(self
, attrencode
=True, **kwargs
)
649 RegExp
.__init
__(self
, **kwargs
)
651 def validate_value(self
, value
, varprefix
):
652 TextUnicode
.validate_value(self
, value
, varprefix
)
653 RegExp
.validate_value(self
, value
, varprefix
)
654 ValueSpec
.custom_validate(self
, value
, varprefix
)
657 class EmailAddress(TextAscii
):
658 def __init__(self
, **kwargs
):
659 kwargs
.setdefault("size", 40)
660 TextAscii
.__init
__(self
, **kwargs
)
661 # The "new" top level domains are very unlimited in length. Theoretically they can be
662 # up to 63 chars long. But currently the longest is 24 characters. Check this out with:
663 # wget -qO - http://data.iana.org/TLD/tlds-alpha-by-domain.txt | tail -n+2 | wc -L
664 self
._regex
= re
.compile(r
'^[A-Z0-9._%&+-]+@(localhost|[A-Z0-9.-]+\.[A-Z]{2,24})$', re
.I
)
665 self
._make
_clickable
= kwargs
.get("make_clickable", False)
667 def value_to_text(self
, value
):
669 return TextAscii
.value_to_text(self
, value
)
670 elif self
._make
_clickable
:
671 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
672 return "%s" % html
.render_a(HTML(value
), href
="mailto:%s" % value
)
676 class EmailAddressUnicode(TextUnicode
, EmailAddress
):
677 def __init__(self
, **kwargs
):
678 TextUnicode
.__init
__(self
, **kwargs
)
679 EmailAddress
.__init
__(self
, **kwargs
)
680 self
._regex
= re
.compile(r
'^[\w.%&+-]+@(localhost|[\w.-]+\.[\w]{2,24})$', re
.I | re
.UNICODE
)
682 def validate_value(self
, value
, varprefix
):
683 TextUnicode
.validate_value(self
, value
, varprefix
)
684 EmailAddress
.validate_value(self
, value
, varprefix
)
685 ValueSpec
.custom_validate(self
, value
, varprefix
)
688 # Same as IPv4Network, but allowing both IPv4 and IPv6
689 class IPNetwork(TextAscii
):
690 def __init__(self
, **kwargs
):
691 kwargs
.setdefault("size", 34)
692 super(IPNetwork
, self
).__init
__(**kwargs
)
695 return ipaddress
.ip_interface
697 def validate_value(self
, value
, varprefix
):
698 super(IPNetwork
, self
).validate_value(value
, varprefix
)
701 self
._ip
_class
()(value
.decode("utf-8"))
702 except ValueError as e
:
703 raise MKUserError(varprefix
, _("Invalid address: %s") % e
)
706 # Network as used in routing configuration, such as
707 # "10.0.0.0/8" or "192.168.56.1"
708 class IPv4Network(IPNetwork
):
709 def __init__(self
, **kwargs
):
710 kwargs
.setdefault("size", 18)
711 super(IPv4Network
, self
).__init
__(**kwargs
)
714 return ipaddress
.IPv4Interface
717 class IPv4Address(IPNetwork
):
718 def __init__(self
, **kwargs
):
719 kwargs
.setdefault("size", 16)
720 super(IPv4Address
, self
).__init
__(**kwargs
)
723 return ipaddress
.IPv4Address
726 class TextAsciiAutocomplete(TextAscii
):
727 def __init__(self
, completion_ident
, completion_params
, **kwargs
):
728 kwargs
["onkeyup"] = "cmk.valuespecs.autocomplete(this, %s, %s, %s);%s" % \
729 (json
.dumps(completion_ident
),
730 json
.dumps(completion_params
),
731 json
.dumps(kwargs
.get("onkeyup")),
732 kwargs
.get("onkeyup", ""))
733 kwargs
["autocomplete"] = False
734 super(TextAsciiAutocomplete
, self
).__init
__(**kwargs
)
739 for type_class
in cls
.__subclasses
__(): # pylint: disable=no-member
740 idents
[type_class
.ident
] = type_class
744 def ajax_handler(cls
):
745 ident
= html
.request
.var("ident")
747 raise MKUserError("ident", _("You need to set the \"%s\" parameter.") % "ident")
749 if ident
not in cls
.idents():
750 raise MKUserError("ident", _("Invalid ident: %s") % ident
)
752 raw_params
= html
.request
.var("params")
754 raise MKUserError("params", _("You need to set the \"%s\" parameter.") % "params")
757 params
= json
.loads(raw_params
)
758 except ValueError as e
: # Python 3: json.JSONDecodeError
759 raise MKUserError("params", _("Invalid parameters: %s") % e
)
761 value
= html
.request
.var("value")
763 raise MKUserError("params", _("You need to set the \"%s\" parameter.") % "value")
765 result_data
= cls
.idents()[ident
].autocomplete_choices(value
, params
)
767 # Check for correct result_data format
768 assert isinstance(result_data
, list)
770 assert isinstance(result_data
[0], (list, tuple))
771 assert len(result_data
[0]) == 2
773 html
.write(json
.dumps(result_data
))
775 #@abc.abstractclassmethod
777 def autocomplete_choices(cls
, value
, params
):
778 raise NotImplementedError()
781 class MonitoredHostname(TextAsciiAutocomplete
):
782 """Hostname input with dropdown completion
784 Renders an input field for entering a host name while providing an auto completion dropdown field.
785 Fetching the choices from the current live config via livestatus"""
786 ident
= "monitored_hostname"
788 def __init__(self
, **kwargs
):
789 super(MonitoredHostname
, self
).__init
__(
790 completion_ident
=self
.ident
, completion_params
={}, **kwargs
)
793 def autocomplete_choices(cls
, value
, params
):
794 """Return the matching list of dropdown choices
795 Called by the webservice with the current input field value and the completions_params to get the list of choices"""
796 import cmk
.gui
.sites
as sites
798 query
= ("GET hosts\n"
799 "Columns: host_name\n"
800 "Filter: host_name ~~ %s" % livestatus
.lqencode(value
))
801 hosts
= sorted(sites
.live().query_column_unique(query
))
803 return [(h
, h
) for h
in hosts
]
806 @page_registry.register_page("ajax_vs_autocomplete")
807 class PageVsAutocomplete(Page
):
809 # TODO: Move ajax_handler to this class? Should we also move the autocomplete_choices()?
810 TextAsciiAutocomplete
.ajax_handler()
813 # A host name with or without domain part. Also allow IP addresses
814 class Hostname(TextAscii
):
815 def __init__(self
, **kwargs
):
816 TextAscii
.__init
__(self
, **kwargs
)
817 self
._regex
= re
.compile('^[-0-9a-zA-Z_.]+$')
818 self
._regex
_error
= _("Please enter a valid hostname or IPv4 address. "
819 "Only letters, digits, dash, underscore and dot are allowed.")
820 if "allow_empty" not in kwargs
:
821 self
._allow
_empty
= False
824 # Use this for all host / ip address input fields!
825 class HostAddress(TextAscii
):
826 def __init__(self
, **kwargs
):
827 TextAscii
.__init
__(self
, **kwargs
)
828 self
._allow
_host
_name
= kwargs
.get("allow_host_name", True)
829 self
._allow
_ipv
4_address
= kwargs
.get("allow_ipv4_address", True)
830 self
._allow
_ipv
6_address
= kwargs
.get("allow_ipv6_address", True)
832 def validate_value(self
, value
, varprefix
):
833 if value
and self
._allow
_host
_name
and self
._is
_valid
_host
_name
(value
):
835 elif value
and self
._allow
_ipv
4_address
and self
._is
_valid
_ipv
4_address
(value
):
837 elif value
and self
._allow
_ipv
6_address
and self
._is
_valid
_ipv
6_address
(value
):
839 elif not self
._allow
_empty
:
842 _("Invalid host address. You need to specify the address "
843 "either as %s.") % ", ".join(self
._allowed
_type
_names
()))
845 ValueSpec
.custom_validate(self
, value
, varprefix
)
847 def _is_valid_host_name(self
, hostname
):
848 # http://stackoverflow.com/questions/2532053/validate-a-hostname-string/2532344#2532344
849 if len(hostname
) > 255:
852 if hostname
[-1] == ".":
853 hostname
= hostname
[:-1] # strip exactly one dot from the right, if present
855 # must be not all-numeric, so that it can't be confused with an IPv4 address.
856 # Host names may start with numbers (RFC 1123 section 2.1) but never the final part,
857 # since TLDs are alphabetic.
858 if re
.match(r
"[\d.]+$", hostname
):
861 allowed
= re
.compile(r
"(?!-)[A-Z_\d-]{1,63}(?<!-)$", re
.IGNORECASE
)
862 return all(allowed
.match(x
) for x
in hostname
.split("."))
864 def _is_valid_ipv4_address(self
, address
):
865 # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python/4017219#4017219
867 socket
.inet_pton(socket
.AF_INET
, address
)
868 except AttributeError: # no inet_pton here, sorry
870 socket
.inet_aton(address
)
874 return address
.count('.') == 3
876 except socket
.error
: # not a valid address
881 def _is_valid_ipv6_address(self
, address
):
882 # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python/4017219#4017219
884 socket
.inet_pton(socket
.AF_INET6
, address
)
885 except socket
.error
: # not a valid address
889 def _allowed_type_names(self
):
891 if self
._allow
_host
_name
:
892 allowed
.append(_("Host- or DNS name"))
894 if self
._allow
_ipv
4_address
:
895 allowed
.append(_("IPv4 address"))
897 if self
._allow
_ipv
6_address
:
898 allowed
.append(_("IPv6 address"))
903 class AbsoluteDirname(TextAscii
):
904 def __init__(self
, **kwargs
):
905 TextAscii
.__init
__(self
, **kwargs
)
906 self
._regex
= re
.compile('^(/|(/[^/]+)+)$')
907 self
._regex
_error
= _("Please enter a valid absolut pathname with / as a path separator.")
910 class Url(TextAscii
):
911 def __init__(self
, default_scheme
, allowed_schemes
, **kwargs
):
912 kwargs
.setdefault("size", 64)
913 self
._default
_scheme
= default_scheme
914 self
._allowed
_schemes
= allowed_schemes
915 self
._show
_as
_link
= kwargs
.get("show_as_link", False)
916 self
._link
_target
= kwargs
.get("target")
917 super(Url
, self
).__init
__(**kwargs
)
919 def validate_value(self
, value
, varprefix
):
920 super(Url
, self
).validate_value(value
, varprefix
)
922 if self
._allow
_empty
and value
== "":
923 ValueSpec
.custom_validate(self
, value
, varprefix
)
926 parts
= urlparse
.urlparse(value
)
927 if not parts
.scheme
or not parts
.netloc
:
928 raise MKUserError(varprefix
, _("Invalid URL given"))
930 if parts
.scheme
not in self
._allowed
_schemes
:
933 _("Invalid URL scheme. Must be one of: %s") % ", ".join(self
._allowed
_schemes
))
935 ValueSpec
.custom_validate(self
, value
, varprefix
)
937 def from_html_vars(self
, varprefix
):
938 value
= super(Url
, self
).from_html_vars(varprefix
)
939 if value
and "://" not in value
:
940 value
= self
._default
_scheme
+ "://" + value
943 def value_to_text(self
, value
):
944 if not any(value
.startswith(scheme
+ "://") for scheme
in self
._allowed
_schemes
):
945 value
= self
._default
_scheme
+ "://" + value
948 parts
= urlparse
.urlparse(value
)
949 if parts
.path
in ['', '/']:
952 text
= parts
.netloc
+ parts
.path
956 # Remove trailing / if the url does not contain any path component
957 if self
._show
_as
_link
:
958 return html
.render_a(
959 text
, href
=value
, target
=self
._link
_target
if self
._link
_target
else None)
965 """Valuespec for a HTTP or HTTPS Url, that automatically adds http:// to the value if no scheme has been specified"""
967 def __init__(self
, **kwargs
):
968 kwargs
.setdefault("show_as_link", True)
969 super(HTTPUrl
, self
).__init
__(
970 allowed_schemes
=["http", "https"], default_scheme
="http", **kwargs
)
973 def CheckMKVersion(**args
):
975 args
["regex"] = r
"[0-9]+\.[0-9]+\.[0-9]+([bpi][0-9]+|i[0-9]+p[0-9]+)?$"
976 args
["regex_error"] = _("This is not a valid Check_MK version number")
977 return TextAscii(**args
)
980 class TextAreaUnicode(TextUnicode
):
981 def __init__(self
, **kwargs
):
982 TextUnicode
.__init
__(self
, **kwargs
)
983 self
._cols
= kwargs
.get("cols", 60)
984 self
._try
_max
_width
= kwargs
.get("try_max_width", False) # If set, uses calc(100%-10px)
985 self
._rows
= kwargs
.get("rows", 20) # Allowed: "auto" -> Auto resizing
986 self
._minrows
= kwargs
.get("minrows", 0) # Minimum number of initial rows when "auto"
987 self
._monospaced
= kwargs
.get("monospaced", False) # select TT font
989 def value_to_text(self
, value
):
991 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
992 return "%s" % html
.render_pre(HTML(value
), class_
="ve_textarea")
993 return html
.attrencode(value
).replace("\n", "<br>")
995 def render_input(self
, varprefix
, value
):
996 self
.classtype_info()
998 value
= "" # should never happen, but avoids exception for invalid input
999 if self
._rows
== "auto":
1000 func
= 'cmk.valuespecs.textarea_resize(this, %s);' % json
.dumps(html
.get_theme())
1001 attrs
= {"onkeyup": func
, "onmousedown": func
, "onmouseup": func
, "onmouseout": func
}
1002 if html
.request
.has_var(varprefix
):
1003 rows
= len(html
.request
.var(varprefix
).splitlines())
1005 rows
= len(value
.splitlines())
1006 rows
= max(rows
, self
._minrows
)
1011 if self
._monospaced
:
1012 attrs
["class"] = "tt"
1020 try_max_width
=self
._try
_max
_width
)
1022 # Overridded because we do not want to strip() here and remove '\r'
1023 def from_html_vars(self
, varprefix
):
1024 text
= html
.get_unicode_input(varprefix
, "").replace('\r', '')
1025 if text
and not text
.endswith("\n"):
1026 text
+= "\n" # force newline at end
1030 # A variant of TextAscii() that validates a path to a filename that
1031 # lies in an existing directory.
1032 # TODO: Rename the valuespec here to ExistingFilename or somehting similar
1033 class Filename(TextAscii
):
1034 def __init__(self
, **kwargs
):
1035 TextAscii
.__init
__(self
, **kwargs
)
1036 if "size" not in kwargs
:
1038 if "default" in kwargs
:
1039 self
._default
_path
= kwargs
["default"]
1041 self
._default
_path
= "/tmp/foo"
1042 if "trans_func" in kwargs
:
1043 self
._trans
_func
= kwargs
["trans_func"]
1045 self
._trans
_func
= None
1047 def canonical_value(self
):
1048 return self
._default
_path
1050 def validate_value(self
, value
, varprefix
):
1051 # The transformation function only changes the value for validation. This is
1052 # usually a function which is later also used within the code which uses
1053 # this variable to e.g. replace macros
1054 if self
._trans
_func
:
1055 value
= self
._trans
_func
(value
)
1058 raise MKUserError(varprefix
, _("Please enter a filename."))
1063 _("Sorry, only absolute filenames are allowed. "
1064 "Your filename must begin with a slash."))
1065 if value
[-1] == "/":
1066 raise MKUserError(varprefix
, _("Your filename must not end with a slash."))
1068 directory
= value
.rsplit("/", 1)[0]
1069 if not os
.path
.isdir(directory
):
1072 _("The directory %s does not exist or is not a directory.") % directory
)
1074 # Write permissions to the file cannot be checked here since we run with Apache
1075 # permissions and the file might be created with Nagios permissions (on OMD this
1076 # is the same, but for others not)
1078 ValueSpec
.custom_validate(self
, value
, varprefix
)
1081 class ListOfStrings(ValueSpec
):
1082 def __init__(self
, **kwargs
):
1083 ValueSpec
.__init
__(self
, **kwargs
)
1084 if "valuespec" in kwargs
:
1085 self
._valuespec
= kwargs
.get("valuespec")
1086 elif "size" in kwargs
:
1087 self
._valuespec
= TextAscii(size
=kwargs
["size"])
1089 self
._valuespec
= TextAscii()
1090 self
._vertical
= kwargs
.get("orientation", "vertical") == "vertical"
1091 self
._allow
_empty
= kwargs
.get("allow_empty", True)
1092 self
._empty
_text
= kwargs
.get("empty_text", "")
1093 self
._max
_entries
= kwargs
.get("max_entries")
1094 self
._separator
= kwargs
.get("separator", "") # in case of float
1096 self
._split
_on
_paste
= kwargs
.get("split_on_paste", True)
1097 self
._split
_separators
= kwargs
.get("split_separators", ";")
1101 ValueSpec
.help(self
),
1102 self
._valuespec
.help(),
1105 if self
._split
_on
_paste
:
1107 _("You may paste a text from your clipboard which contains several "
1108 "parts separated by \"%s\" characters into the last input field. The text will "
1109 "then be split by these separators and the single parts are added into dedicated "
1110 "input fields.") % self
._split
_separators
)
1112 return " ".join([t
for t
in help_texts
if t
])
1114 def render_input(self
, varprefix
, value
):
1115 self
.classtype_info()
1116 # Form already submitted?
1117 if html
.request
.has_var(varprefix
+ "_0"):
1118 value
= self
.from_html_vars(varprefix
)
1119 # Remove variables from URL, so that they do not appear
1120 # in hidden_fields()
1122 while html
.request
.has_var(varprefix
+ "_%d" % nr
):
1123 html
.request
.del_var(varprefix
+ "_%d" % nr
)
1126 class_
= ["listofstrings"]
1128 class_
.append("vertical")
1130 class_
.append("horizontal")
1131 html
.open_div(id_
=varprefix
, class_
=class_
)
1133 for nr
, s
in enumerate(value
+ [""]):
1135 self
._valuespec
.render_input(varprefix
+ "_%d" % nr
, s
)
1136 if self
._vertical
!= "vertical" and self
._separator
:
1138 html
.write(self
._separator
)
1142 html
.div('', style
="clear:left;")
1143 html
.help(self
.help())
1144 html
.javascript("cmk.valuespecs.list_of_strings_init(%s, %s, %s);" %
1145 (json
.dumps(varprefix
), json
.dumps(self
._split
_on
_paste
),
1146 json
.dumps(self
._split
_separators
)))
1148 def canonical_value(self
):
1151 def value_to_text(self
, value
):
1153 return self
._empty
_text
1156 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
1158 html
.render_tr(html
.render_td(HTML(self
._valuespec
.value_to_text(v
))))
1161 return "%s" % html
.render_table(HTML().join(s
))
1162 return ", ".join([self
._valuespec
.value_to_text(v
) for v
in value
])
1164 def from_html_vars(self
, varprefix
):
1168 varname
= varprefix
+ "_%d" % nr
1169 if not html
.request
.has_var(varname
):
1171 if html
.request
.var(varname
, "").strip():
1172 value
.append(self
._valuespec
.from_html_vars(varname
))
1176 def validate_datatype(self
, value
, varprefix
):
1177 if not isinstance(value
, list):
1180 _("Expected data type is list, but your type is %s.") % _type_name(value
))
1181 for nr
, s
in enumerate(value
):
1182 self
._valuespec
.validate_datatype(s
, varprefix
+ "_%d" % nr
)
1184 def validate_value(self
, value
, varprefix
):
1185 if len(value
) == 0 and not self
._allow
_empty
:
1186 if self
._empty
_text
:
1187 msg
= self
._empty
_text
1189 msg
= _("Please specify at least one value")
1190 raise MKUserError(varprefix
+ "_0", msg
)
1192 if self
._max
_entries
is not None and len(value
) > self
._max
_entries
:
1193 raise MKUserError(varprefix
+ "_%d" % self
._max
_entries
,
1194 _("You can specify at most %d entries") % self
._max
_entries
)
1197 for nr
, s
in enumerate(value
):
1198 self
._valuespec
.validate_value(s
, varprefix
+ "_%d" % nr
)
1199 ValueSpec
.custom_validate(self
, value
, varprefix
)
1202 class ListOfIntegers(ListOfStrings
):
1203 def __init__(self
, **kwargs
):
1205 for key
in ["minvalue", "maxvalue"]:
1207 int_args
[key
] = kwargs
[key
]
1208 int_args
["display_format"] = "%s"
1209 int_args
["convfunc"] = lambda x
: x
if x
== '' else utils
.saveint(x
)
1210 int_args
["minvalue"] = 17
1211 int_args
["default_value"] = 34
1212 valuespec
= Integer(**int_args
)
1213 kwargs
["valuespec"] = valuespec
1214 ListOfStrings
.__init
__(self
, **kwargs
)
1217 # Generic list-of-valuespec ValueSpec with Javascript-based
1219 class ListOf(ValueSpec
):
1222 FLOATING
= "floating"
1224 def __init__(self
, valuespec
, **kwargs
):
1225 ValueSpec
.__init
__(self
, **kwargs
)
1226 self
._valuespec
= valuespec
1227 self
._magic
= kwargs
.get("magic", "@!@")
1228 self
._rowlabel
= kwargs
.get("row_label")
1229 self
._add
_label
= kwargs
.get("add_label", _("Add new element"))
1230 self
._del
_label
= kwargs
.get("del_label", _("Delete this entry"))
1231 self
._movable
= kwargs
.get("movable", True)
1232 self
._style
= kwargs
.get("style", ListOf
.Style
.REGULAR
)
1233 self
._totext
= kwargs
.get("totext") # pattern with option %d
1234 self
._text
_if
_empty
= kwargs
.get("text_if_empty", _("No entries"))
1235 self
._allow
_empty
= kwargs
.get("allow_empty", True)
1236 self
._empty
_text
= kwargs
.get("empty_text") # complain text if empty
1237 # Makes a sort button visible that can be used to sort the list in the GUI
1238 # (without submitting the form). But this currently only works for list of
1239 # tuples that contain input field elements directly. The value of sort_by
1240 # refers to the index of the sort values in the tuple
1241 self
._sort
_by
= kwargs
.get("sort_by")
1242 if not self
._empty
_text
:
1243 self
._empty
_text
= _("Please specify at least one entry")
1245 # Implementation idea: we render our element-valuespec
1246 # once in a hidden div that is not evaluated. All occurances
1247 # of a magic string are replaced with the actual number
1248 # of entry, while beginning with 1 (this makes visual
1249 # numbering in labels, etc. possible). The current number
1250 # of entries is stored in the hidden variable 'varprefix'
1251 def render_input(self
, varprefix
, value
):
1252 self
.classtype_info()
1254 html
.open_div(class_
=["valuespec_listof", self
._style
.value
])
1256 # Beware: the 'value' is only the default value in case the form
1257 # has not yet been filled in. In the complain phase we must
1258 # ignore 'value' but reuse the input from the HTML variables -
1259 # even if they are not syntactically correct. Calling from_html_vars
1260 # here is *not* an option since this might not work in case of
1261 # a wrong user input.
1263 # Render reference element for cloning
1264 self
._show
_reference
_entry
(varprefix
, self
._magic
, self
._valuespec
.default_value())
1266 # In the 'complain' phase, where the user already saved the
1267 # form but the validation failed, we must not display the
1268 # original 'value' but take the value from the HTML variables.
1269 if html
.request
.has_var("%s_count" % varprefix
):
1270 count
= len(self
.get_indexes(varprefix
))
1271 value
= [None] * count
# dummy for the loop
1276 '%s_count' % varprefix
, str(count
), id_
='%s_count' % varprefix
, add_var
=True)
1278 self
._show
_entries
(varprefix
, value
)
1283 html
.javascript("cmk.valuespecs.listof_update_indices(%s)" % json
.dumps(varprefix
))
1285 def _show_entries(self
, varprefix
, value
):
1286 if self
._style
== ListOf
.Style
.REGULAR
:
1287 self
._show
_current
_entries
(varprefix
, value
)
1289 self
._list
_buttons
(varprefix
)
1291 elif self
._style
== ListOf
.Style
.FLOATING
:
1296 self
._list
_buttons
(varprefix
)
1299 self
._show
_current
_entries
(varprefix
, value
)
1306 raise NotImplementedError()
1308 def _list_buttons(self
, varprefix
):
1310 varprefix
+ "_add", self
._add
_label
, "cmk.valuespecs.listof_add(%s, %s, %s)" %
1311 (json
.dumps(varprefix
), json
.dumps(self
._magic
), json
.dumps(self
._style
.value
)))
1313 if self
._sort
_by
is not None:
1315 varprefix
+ "_sort", _("Sort"), "cmk.valuespecs.listof_sort(%s, %s, %s)" %
1316 (json
.dumps(varprefix
), json
.dumps(self
._magic
), json
.dumps(self
._sort
_by
)))
1318 def _show_reference_entry(self
, varprefix
, index
, value
):
1319 if self
._style
== ListOf
.Style
.REGULAR
:
1320 html
.open_table(style
="display:none;")
1321 html
.open_tbody(id_
="%s_prototype" % varprefix
, class_
="vlof_prototype")
1323 self
._show
_entry
(varprefix
, index
, value
)
1328 elif self
._style
== ListOf
.Style
.FLOATING
:
1329 html
.open_div(id_
="%s_prototype" % varprefix
, style
="display:none;")
1331 self
._show
_entry
(varprefix
, index
, value
)
1336 raise NotImplementedError()
1338 def _show_current_entries(self
, varprefix
, value
):
1339 if self
._style
== ListOf
.Style
.REGULAR
:
1340 html
.open_table(class_
=["valuespec_listof"])
1341 html
.open_tbody(id_
="%s_container" % varprefix
)
1343 for nr
, v
in enumerate(value
):
1344 self
._show
_entry
(varprefix
, "%d" % (nr
+ 1), v
)
1349 elif self
._style
== ListOf
.Style
.FLOATING
:
1351 id_
="%s_container" % varprefix
, class_
=["valuespec_listof_floating_container"])
1353 for nr
, v
in enumerate(value
):
1354 self
._show
_entry
(varprefix
, "%d" % (nr
+ 1), v
)
1359 raise NotImplementedError()
1361 def _show_entry(self
, varprefix
, index
, value
):
1362 entry_id
= "%s_entry_%s" % (varprefix
, index
)
1364 if self
._style
== ListOf
.Style
.REGULAR
:
1365 html
.open_tr(id_
=entry_id
)
1366 self
._show
_entry
_cell
(varprefix
, index
, value
)
1369 elif self
._style
== ListOf
.Style
.FLOATING
:
1370 html
.open_table(id_
=entry_id
)
1373 self
._show
_entry
_cell
(varprefix
, index
, value
)
1379 raise NotImplementedError()
1381 def _show_entry_cell(self
, varprefix
, index
, value
):
1382 html
.open_td(class_
="vlof_buttons")
1385 varprefix
+ "_indexof_" + index
, "", add_var
=True,
1386 class_
="index") # reconstruct order after moving stuff
1388 varprefix
+ "_orig_indexof_" + index
, "", add_var
=True, class_
="orig_index")
1390 html
.element_dragger_js(
1392 drop_handler
="cmk.valuespecs.listof_drop_handler",
1395 "varprefix": varprefix
1397 self
._del
_button
(varprefix
, index
)
1399 html
.open_td(class_
="vlof_content")
1400 self
._valuespec
.render_input(varprefix
+ "_" + index
, value
)
1403 def _del_button(self
, vp
, nr
):
1404 js
= "cmk.valuespecs.listof_delete(%s, %s)" % (json
.dumps(vp
), json
.dumps(nr
))
1405 html
.icon_button("#", self
._del
_label
, "delete", onclick
=js
)
1407 def canonical_value(self
):
1410 def value_to_text(self
, value
):
1412 if "%d" in self
._totext
:
1413 return self
._totext
% len(value
)
1416 return self
._text
_if
_empty
1418 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
1419 s
= [html
.render_tr(html
.render_td(HTML(self
._valuespec
.value_to_text(v
)))) for v
in value
]
1420 return "%s" % html
.render_table(HTML().join(s
))
1422 def get_indexes(self
, varprefix
):
1423 count
= html
.get_integer_input(varprefix
+ "_count", 0)
1427 indexof
= html
.request
.var(varprefix
+ "_indexof_%d" % n
)
1428 # for deleted entries, we have removed the whole row, therefore indexof is None
1429 if indexof
is not None:
1430 indexes
[int(indexof
)] = n
1434 def from_html_vars(self
, varprefix
):
1435 indexes
= self
.get_indexes(varprefix
)
1440 val
= self
._valuespec
.from_html_vars(varprefix
+ "_%d" % indexes
[i
])
1444 def validate_datatype(self
, value
, varprefix
):
1445 if not isinstance(value
, list):
1446 raise MKUserError(varprefix
, _("The type must be list, but is %s") % _type_name(value
))
1447 for n
, v
in enumerate(value
):
1448 self
._valuespec
.validate_datatype(v
, varprefix
+ "_%d" % (n
+ 1))
1450 def validate_value(self
, value
, varprefix
):
1451 if not self
._allow
_empty
and len(value
) == 0:
1452 raise MKUserError(varprefix
, self
._empty
_text
)
1453 for n
, v
in enumerate(value
):
1454 self
._valuespec
.validate_value(v
, varprefix
+ "_%d" % (n
+ 1))
1455 ValueSpec
.custom_validate(self
, value
, varprefix
)
1458 # A generic valuespec where the user can choose from a list of sub-valuespecs.
1459 # Each sub-valuespec can be added only once
1460 class ListOfMultiple(ValueSpec
):
1461 def __init__(self
, choices
, **kwargs
):
1462 ValueSpec
.__init
__(self
, **kwargs
)
1463 self
._choices
= choices
1464 self
._choice
_dict
= dict(choices
)
1465 self
._size
= kwargs
.get("size")
1466 self
._add
_label
= kwargs
.get("add_label", _("Add element"))
1467 self
._del
_label
= kwargs
.get("del_label", _("Delete this entry"))
1468 self
._delete
_style
= kwargs
.get("delete_style", "default") # or "filter"
1470 def del_button(self
, varprefix
, ident
):
1471 js
= "cmk.valuespecs.listofmultiple_del('%s', '%s')" % (varprefix
, ident
)
1472 html
.icon_button("#", self
._del
_label
, "delete", onclick
=js
)
1474 def render_input(self
, varprefix
, value
):
1475 self
.classtype_info()
1476 # Beware: the 'value' is only the default value in case the form
1477 # has not yet been filled in. In the complain phase we must
1478 # ignore 'value' but reuse the input from the HTML variables -
1479 # even if they are not syntactically correct. Calling from_html_vars
1480 # here is *not* an option since this might not work in case of
1481 # a wrong user input.
1483 # Special styling for filters
1484 extra_css
= "filter" if self
._delete
_style
== "filter" else None
1486 # In the 'complain' phase, where the user already saved the
1487 # form but the validation failed, we must not display the
1488 # original 'value' but take the value from the HTML variables.
1489 if html
.request
.var("%s_active" % varprefix
):
1490 value
= self
.from_html_vars(varprefix
)
1492 # Save all selected items
1494 '%s_active' % varprefix
,
1495 ';'.join([k
for k
in value
.keys() if k
in self
._choice
_dict
]),
1496 id_
='%s_active' % varprefix
,
1499 # Actual table of currently existing entries
1500 html
.open_table(id_
="%s_table" % varprefix
, class_
=["valuespec_listof", extra_css
])
1502 def render_content():
1503 html
.open_td(class_
=["vlof_content", extra_css
])
1504 vs
.render_input(prefix
, value
.get(ident
))
1508 html
.open_td(class_
=["vlof_buttons", extra_css
])
1509 self
.del_button(varprefix
, ident
)
1512 for ident
, vs
in self
._choices
:
1513 cls
= 'unused' if ident
not in value
else ''
1514 prefix
= varprefix
+ '_' + ident
1515 html
.open_tr(id_
="%s_row" % prefix
, class_
=cls
)
1516 if self
._delete
_style
== "filter":
1526 choices
= [('', '')] + [(ident
, vs
.title()) for ident
, vs
in self
._choices
]
1528 varprefix
+ '_choice',
1530 style
="width: %dex" % self
._size
if self
._size
is not None else None,
1531 class_
="vlof_filter" if self
._delete
_style
== "filter" else None)
1532 html
.javascript('cmk.valuespecs.listofmultiple_init(\'%s\');' % varprefix
)
1533 html
.jsbutton(varprefix
+ '_add', self
._add
_label
,
1534 "cmk.valuespecs.listofmultiple_add('%s')" % varprefix
)
1536 def canonical_value(self
):
1539 def value_to_text(self
, value
):
1540 table_content
= HTML()
1541 for ident
, val
in value
:
1542 vs
= self
._choice
_dict
[ident
]
1543 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
1544 table_content
+= html
.render_tr(html
.render_td(vs
.title())\
1545 + html
.render_td( HTML(vs
.value_to_text(val
)) ))
1546 return "%s" % html
.render_table(table_content
)
1548 def from_html_vars(self
, varprefix
):
1550 active
= html
.request
.var('%s_active' % varprefix
).strip()
1554 for ident
in active
.split(';'):
1555 vs
= self
._choice
_dict
[ident
]
1556 value
[ident
] = vs
.from_html_vars(varprefix
+ '_' + ident
)
1559 def validate_datatype(self
, value
, varprefix
):
1560 if not isinstance(value
, dict):
1561 raise MKUserError(varprefix
, _("The type must be dict, but is %s") % _type_name(value
))
1562 for ident
, val
in value
.items():
1563 self
._choice
_dict
[ident
].validate_datatype(val
, varprefix
+ '_' + ident
)
1565 def validate_value(self
, value
, varprefix
):
1566 for ident
, val
in value
.items():
1567 self
._choice
_dict
[ident
].validate_value(val
, varprefix
+ '_' + ident
)
1568 ValueSpec
.custom_validate(self
, value
, varprefix
)
1571 # Same but for floating point values
1572 class Float(Integer
):
1573 def __init__(self
, **kwargs
):
1574 Integer
.__init
__(self
, **kwargs
)
1575 self
._decimal
_separator
= kwargs
.get("decimal_separator", ".")
1576 self
._display
_format
= kwargs
.get("display_format", "%.2f")
1577 self
._allow
_int
= kwargs
.get("allow_int", False)
1579 def render_input(self
, varprefix
, value
):
1580 self
.classtype_info()
1581 Integer
.render_input(self
, varprefix
, value
)
1583 def _render_value(self
, value
):
1584 return self
._display
_format
% utils
.savefloat(value
)
1586 def canonical_value(self
):
1587 return float(Integer
.canonical_value(self
))
1589 def value_to_text(self
, value
):
1590 return Integer
.value_to_text(self
, value
).replace(".", self
._decimal
_separator
)
1592 def from_html_vars(self
, varprefix
):
1594 return float(html
.request
.var(varprefix
))
1598 _("The text <b><tt>%s</tt></b> is not a valid floating point number.") %
1599 html
.request
.var(varprefix
))
1601 def validate_datatype(self
, value
, varprefix
):
1602 if isinstance(value
, float):
1605 if isinstance(value
, numbers
.Integral
) and self
._allow
_int
:
1610 _("The value %r has type %s, but must be of type float%s") %
1611 (value
, _type_name(value
), _(" or int") if self
._allow
_int
else ''))
1614 class Percentage(Float
):
1615 def __init__(self
, **kwargs
):
1616 Float
.__init
__(self
, **kwargs
)
1617 if "minvalue" not in kwargs
:
1618 self
._minvalue
= 0.0
1619 if "maxvalue" not in kwargs
:
1620 self
._maxvalue
= 101.0
1621 if "unit" not in kwargs
:
1623 if "display_format" not in kwargs
:
1624 self
._display
_format
= "%.1f"
1626 self
._allow
_int
= kwargs
.get("allow_int", False)
1628 def value_to_text(self
, value
):
1629 return (self
._display
_format
+ "%%") % value
1631 def validate_datatype(self
, value
, varprefix
):
1633 if not isinstance(value
, (int, float)):
1636 _("The value %r has type %s, but must be either float or int") %
1637 (value
, _type_name(value
)))
1639 Float
.validate_datatype(self
, value
, varprefix
)
1642 class Checkbox(ValueSpec
):
1643 def __init__(self
, **kwargs
):
1644 ValueSpec
.__init
__(self
, **kwargs
)
1645 self
._label
= kwargs
.get("label")
1646 self
._true
_label
= kwargs
.get("true_label", _("on"))
1647 self
._false
_label
= kwargs
.get("false_label", _("off"))
1648 self
._onclick
= kwargs
.get("onclick")
1650 def canonical_value(self
):
1653 def render_input(self
, varprefix
, value
):
1654 self
.classtype_info()
1655 html
.checkbox(varprefix
, value
, label
=self
._label
, onclick
=self
._onclick
)
1657 def value_to_text(self
, value
):
1658 return self
._true
_label
if value
else self
._false
_label
1660 def from_html_vars(self
, varprefix
):
1661 return bool(html
.request
.var(varprefix
))
1663 def validate_datatype(self
, value
, varprefix
):
1664 if not isinstance(value
, bool):
1667 _("The value %r has type %s, but must be of type bool") % (value
,
1671 # A type-save dropdown choice. Parameters:
1672 # help_separator: if you set this to a character, e.g. "-", then
1673 # value_to_texg will omit texts from the character up to the end of
1675 # Note: The list of choices may contain 2-tuples or 3-tuples.
1676 # The format is (value, text {, icon} )
1677 # choices may also be a function that returns - when called
1678 # wihtout arguments - such a tuple list. That way the choices
1679 # can by dynamically computed
1680 class DropdownChoice(ValueSpec
):
1681 def __init__(self
, **kwargs
):
1682 super(DropdownChoice
, self
).__init
__(**kwargs
)
1683 self
._choices
= kwargs
["choices"]
1684 self
._help
_separator
= kwargs
.get("help_separator")
1685 self
._label
= kwargs
.get("label")
1686 self
._prefix
_values
= kwargs
.get("prefix_values", False)
1687 self
._sorted
= kwargs
.get("sorted", False)
1688 self
._empty
_text
= kwargs
.get("empty_text",
1689 _("There are no elements defined for this selection yet."))
1690 self
._invalid
_choice
= kwargs
.get("invalid_choice", "complain") # also possible: "replace"
1691 self
._invalid
_choice
_title
= kwargs
.get("invalid_choice_title",
1692 _("Element '%r' does not exist anymore"))
1693 self
._invalid
_choice
_error
= kwargs
.get(
1694 "invalid_choice_error",
1695 _("The selected element is not longer available. Please select something else."))
1696 self
._no
_preselect
= kwargs
.get("no_preselect", False)
1697 self
._no
_preselect
_value
= kwargs
.get("no_preselect_value", None)
1698 self
._no
_preselect
_title
= kwargs
.get("no_preselect_title", "") # if not preselected
1699 self
._no
_preselect
_error
= kwargs
.get("no_preselect_error", _("Please make a selection"))
1700 self
._on
_change
= kwargs
.get("on_change")
1701 self
._read
_only
= kwargs
.get("read_only", False)
1702 self
._encode
_value
= kwargs
.get("encode_value", True)
1706 if isinstance(self
._choices
, list):
1707 result
= self
._choices
1708 elif isinstance(self
._choices
, dict):
1709 result
= ListChoice
.dict_choices(self
._choices
)
1711 result
= self
._choices
()
1713 if self
._no
_preselect
:
1714 return [(self
._no
_preselect
_value
, self
._no
_preselect
_title
)] + result
1717 def canonical_value(self
):
1718 choices
= self
.choices()
1719 if len(choices
) > 0:
1720 return choices
[0][0]
1723 def render_input(self
, varprefix
, value
):
1724 self
.classtype_info()
1726 html
.write("%s " % self
._label
)
1728 choices
= self
.choices()
1730 defval
= choices
[0][0] if choices
else None
1732 for entry
in self
.choices():
1733 if self
._prefix
_values
:
1734 entry
= (entry
[0], "%s - %s" % entry
)
1736 options
.append(entry
)
1737 if entry
[0] == value
:
1740 # In complain mode: Use the value received from the HTML variable
1741 if self
._invalid
_choice
== "complain" and value
is not None and self
._value
_is
_invalid
(
1744 options
.append((defval
, self
._get
_invalid
_choice
_title
(value
)))
1746 if value
is None and not options
:
1747 html
.write(self
._empty
_text
)
1750 if len(options
) == 0:
1751 html
.write(self
._empty
_text
)
1752 elif len(options
[0]) == 3:
1754 varprefix
, self
._options
_for
_html
(options
), deflt
=self
._option
_for
_html
(defval
))
1758 self
._options
_for
_html
(options
),
1759 deflt
=self
._option
_for
_html
(defval
),
1760 onchange
=self
._on
_change
,
1761 ordered
=self
._sorted
,
1762 read_only
=self
._read
_only
)
1764 def _get_invalid_choice_title(self
, value
):
1765 if "%s" in self
._invalid
_choice
_title
or "%r" in self
._invalid
_choice
_title
:
1766 return self
._invalid
_choice
_title
% (value
,)
1767 return self
._invalid
_choice
_title
1769 def value_to_text(self
, value
):
1770 for entry
in self
.choices():
1771 val
, title
= entry
[:2]
1773 if self
._help
_separator
:
1774 return html
.attrencode(title
.split(self
._help
_separator
, 1)[0].strip())
1775 return html
.attrencode(title
)
1776 return html
.attrencode(self
._get
_invalid
_choice
_title
(value
))
1778 def from_html_vars(self
, varprefix
):
1779 choices
= self
.choices()
1781 for entry
in choices
:
1782 val
, _title
= entry
[:2]
1783 if self
._is
_selected
_option
_from
_html
(varprefix
, val
):
1786 if self
._invalid
_choice
== "replace":
1787 return self
.default_value() # garbled URL or len(choices) == 0
1789 raise MKUserError(varprefix
, self
._empty
_text
)
1791 raise MKUserError(varprefix
, self
._invalid
_choice
_error
)
1793 def _is_selected_option_from_html(self
, varprefix
, val
):
1794 selected_value
= html
.request
.var(varprefix
)
1795 return selected_value
== self
._option
_for
_html
(val
)
1797 def _option_for_html(self
, value
):
1798 if self
._encode
_value
:
1799 return self
.option_id(value
)
1802 def _options_for_html(self
, orig_options
):
1804 for val
, title
in orig_options
:
1805 options
.append((self
._option
_for
_html
(val
), title
))
1810 return "%s" % hashlib
.sha256(repr(val
)).hexdigest()
1812 def validate_value(self
, value
, varprefix
):
1813 if self
._no
_preselect
and value
== self
._no
_preselect
_value
:
1814 raise MKUserError(varprefix
, self
._no
_preselect
_error
)
1816 if self
._invalid
_choice
== "complain" and self
._value
_is
_invalid
(value
):
1817 if value
is not None:
1818 raise MKUserError(varprefix
, self
._invalid
_choice
_error
)
1820 raise MKUserError(varprefix
, self
._empty
_text
)
1822 ValueSpec
.custom_validate(self
, value
, varprefix
)
1824 def _value_is_invalid(self
, value
):
1825 for entry
in self
.choices():
1826 if entry
[0] == value
:
1831 # Special conveniance variant for monitoring states
1832 # TODO: Rename to ServiceState() or something like this
1833 class MonitoringState(DropdownChoice
):
1834 def __init__(self
, **kwargs
):
1841 kwargs
.setdefault("default_value", 0)
1842 DropdownChoice
.__init
__(self
, choices
=choices
, **kwargs
)
1845 class HostState(DropdownChoice
):
1846 def __init__(self
, **kwargs
):
1850 (2, _("UNREACHABLE")),
1852 kwargs
.setdefault("default_value", 0)
1853 DropdownChoice
.__init
__(self
, choices
=choices
, **kwargs
)
1856 # A Dropdown choice where the elements are ValueSpecs.
1857 # The currently selected ValueSpec will be displayed.
1858 # The text representations of the ValueSpecs will be used as texts.
1859 # A ValueSpec of None is also allowed and will return
1860 # the value None. It is also allowed to leave out the
1861 # value spec for some of the choices (which is the same as
1863 # The resulting value is either a single value (if no
1864 # value spec is defined for the selected entry) or a pair
1865 # of (x, y) where x is the value of the selected entry and
1866 # y is the value of the valuespec assigned to that entry.
1867 # choices is a list of triples: [ ( value, title, vs ), ... ]
1868 class CascadingDropdown(ValueSpec
):
1871 foldable
= "foldable"
1873 def __init__(self
, **kwargs
):
1874 ValueSpec
.__init
__(self
, **kwargs
)
1876 if isinstance(kwargs
["choices"], list):
1877 self
._choices
= self
.normalize_choices(kwargs
["choices"])
1879 self
._choices
= kwargs
["choices"] # function, store for later
1881 self
._label
= kwargs
.get("label")
1882 self
._separator
= kwargs
.get("separator", ", ")
1883 self
._sorted
= kwargs
.get("sorted", True)
1884 self
._orientation
= kwargs
.get("orientation", "vertical") # or horizontal
1885 self
._render
= kwargs
.get("render", CascadingDropdown
.Render
.normal
)
1886 if kwargs
.get("encoding", "tuple") == "list":
1887 self
._encoding
_type
= list
1889 self
._encoding
_type
= tuple
1891 self
._no
_elements
_text
= kwargs
.get("no_elements_text",
1892 _("There are no elements defined for this selection"))
1894 self
._no
_preselect
= kwargs
.get("no_preselect", False)
1895 self
._no
_preselect
_value
= kwargs
.get("no_preselect_value", None)
1896 self
._no
_preselect
_title
= kwargs
.get("no_preselect_title", "") # if not preselected
1897 self
._no
_preselect
_error
= kwargs
.get("no_preselect_error", _("Please make a selection"))
1899 def normalize_choices(self
, choices
):
1901 for entry
in choices
:
1902 if len(entry
) == 2: # plain entry with no sub-valuespec
1903 entry
= entry
+ (None,) # normlize to three entries
1904 new_choices
.append(entry
)
1908 if isinstance(self
._choices
, list):
1909 result
= self
._choices
1911 result
= self
.normalize_choices(self
._choices
())
1913 if self
._no
_preselect
:
1914 result
= [(self
._no
_preselect
_value
, self
._no
_preselect
_title
, None)] \
1919 def canonical_value(self
):
1920 choices
= self
.choices()
1925 return self
._encoding
_type
((choices
[0][0], choices
[0][2].canonical_value()))
1926 return choices
[0][0]
1928 def default_value(self
):
1930 return self
._default
_value
1932 choices
= self
.choices()
1937 return self
._encoding
_type
((choices
[0][0], choices
[0][2].default_value()))
1938 return choices
[0][0]
1940 def render_input(self
, varprefix
, value
):
1941 self
.classtype_info()
1944 choices
= self
.choices()
1946 html
.write(self
._no
_elements
_text
)
1949 for nr
, (val
, title
, vs
) in enumerate(choices
):
1950 options
.append((str(nr
), title
))
1951 # Determine the default value for the select, so the
1952 # the dropdown pre-selects the line corresponding with value.
1953 # Note: the html.dropdown() with automatically show the modified
1954 # selection, if the HTML variable varprefix_sel aleady
1956 if value
== val
or (isinstance(value
, self
._encoding
_type
) and value
[0] == val
):
1959 vp
= varprefix
+ "_sel"
1960 onchange
= "cmk.valuespecs.cascading_change(this, '%s', %d);" % (varprefix
, len(choices
))
1962 vp
, options
, deflt
=def_val
, onchange
=onchange
, ordered
=self
._sorted
, label
=self
._label
)
1964 # make sure, that the visibility is done correctly, in both
1966 # 1. Form painted for the first time (no submission yet, vp missing in URL)
1967 # 2. Form already submitted -> honor URL variable vp for visibility
1968 cur_val
= html
.request
.var(vp
)
1970 if self
._orientation
== "vertical":
1974 for nr
, (val
, title
, vs
) in enumerate(choices
):
1976 vp
= varprefix
+ "_%d" % nr
1977 # Form already submitted once (and probably in complain state)
1978 if cur_val
is not None:
1980 def_val_2
= vs
.from_html_vars(vp
)
1982 def_val_2
= vs
.default_value()
1983 if cur_val
== str(nr
):
1987 else: # form painted the first time
1989 or (isinstance(value
, self
._encoding
_type
) and value
[0] == val
):
1990 if isinstance(value
, self
._encoding
_type
):
1991 def_val_2
= value
[1]
1993 def_val_2
= vs
.default_value()
1996 def_val_2
= vs
.default_value()
1998 html
.open_span(id_
="%s_%s_sub" % (varprefix
, nr
), style
="display:%s;" % disp
)
1999 html
.help(vs
.help())
2000 vs
.render_input(vp
, def_val_2
)
2003 def value_to_text(self
, value
):
2004 choices
= self
.choices()
2005 for val
, title
, vs
in choices
:
2006 if (vs
and value
and value
[0] == val
) or \
2011 rendered_value
= vs
.value_to_text(value
[1])
2012 if not rendered_value
:
2015 if self
._render
== CascadingDropdown
.Render
.foldable
:
2016 with html
.plugged():
2017 html
.begin_foldable_container(
2018 "foldable_cascading_dropdown",
2019 id_
=hashlib
.sha256(repr(value
)).hexdigest(),
2023 html
.write(vs
.value_to_text(value
[1]))
2024 html
.end_foldable_container()
2027 return title
+ self
._separator
+ \
2028 vs
.value_to_text(value
[1])
2030 return "" # Nothing selected? Should never happen
2032 def from_html_vars(self
, varprefix
):
2033 choices
= self
.choices()
2035 # No choices and "no elements text" is shown: The html var is
2036 # not present and no choice can be made. So fallback to default
2037 # value and let the validation methods lead to an error message.
2039 return self
.default_value()
2042 sel
= int(html
.request
.var(varprefix
+ "_sel"))
2045 val
, _title
, vs
= choices
[sel
]
2047 val
= self
._encoding
_type
((val
, vs
.from_html_vars(varprefix
+ "_%d" % sel
)))
2050 def validate_datatype(self
, value
, varprefix
):
2051 choices
= self
.choices()
2052 for nr
, (val
, _title
, vs
) in enumerate(choices
):
2053 if value
== val
or (isinstance(value
, self
._encoding
_type
) and value
[0] == val
):
2055 if not isinstance(value
, self
._encoding
_type
) or len(value
) != 2:
2058 _("Value must be a %s with two elements.") %
2059 self
._encoding
_type
.__name
__)
2060 vs
.validate_datatype(value
[1], varprefix
+ "_%d" % nr
)
2062 raise MKUserError(varprefix
+ "_sel", _("Value %r is not allowed here.") % value
)
2064 def validate_value(self
, value
, varprefix
):
2065 if self
._no
_preselect
and value
== self
._no
_preselect
_value
:
2066 raise MKUserError(varprefix
+ "_sel", self
._no
_preselect
_error
)
2068 choices
= self
.choices()
2069 for nr
, (val
, _title
, vs
) in enumerate(choices
):
2070 if value
== val
or (isinstance(value
, self
._encoding
_type
) and value
[0] == val
):
2072 vs
.validate_value(value
[1], varprefix
+ "_%d" % nr
)
2073 ValueSpec
.custom_validate(self
, value
, varprefix
)
2075 raise MKUserError(varprefix
+ "_sel", _("Value %r is not allowed here.") % (value
,))
2078 # The same logic as the dropdown choice, but rendered
2079 # as a group of radio buttons.
2080 # columns is None or unset -> separate with " "
2081 class RadioChoice(DropdownChoice
):
2082 def __init__(self
, **kwargs
):
2083 DropdownChoice
.__init
__(self
, **kwargs
)
2084 self
._columns
= kwargs
.get("columns")
2085 # Allow orientation as corner cases of columns
2086 orientation
= kwargs
.get("orientation")
2087 if orientation
== "vertical":
2089 elif orientation
== "horizontal":
2090 self
._columns
= 9999999
2092 def render_input(self
, varprefix
, value
):
2093 self
.classtype_info()
2094 html
.begin_radio_group()
2095 if self
._columns
is not None:
2096 html
.open_table(class_
=["radiochoice"])
2100 choices
= self
._choices
[:]
2101 choices
.sort(cmp=lambda a
, b
: cmp(a
[1], b
[1]))
2103 choices
= self
._choices
2105 for index
, entry
in enumerate(choices
):
2106 if self
._columns
is not None:
2109 if len(entry
) > 2 and entry
[2] is not None: # icon!
2110 label
= html
.render_icon(entry
[2], entry
[1])
2114 html
.radiobutton(varprefix
, self
.option_id(entry
[0]), value
== entry
[0], label
)
2116 if len(entry
) > 3 and entry
[3]:
2118 html
.write(entry
[3])
2121 if self
._columns
is not None:
2123 if (index
+ 1) % self
._columns
== 0 and (index
+ 1) < len(self
._choices
):
2128 if self
._columns
is not None:
2129 mod
= len(self
._choices
) % self
._columns
2131 for _td_counter
in range(self
._columns
- mod
- 1):
2136 html
.end_radio_group()
2139 # A list of checkboxes representing a list of values
2140 class ListChoice(ValueSpec
):
2142 def dict_choices(choices
):
2143 return [("%s" % type_id
, "%d - %s" % (type_id
, type_name
))
2144 for (type_id
, type_name
) in sorted(choices
.items())]
2146 def __init__(self
, **kwargs
):
2147 ValueSpec
.__init
__(self
, **kwargs
)
2148 self
._choices
= kwargs
.get("choices")
2149 self
._columns
= kwargs
.get("columns", 1)
2150 self
._allow
_empty
= kwargs
.get("allow_empty", True)
2151 self
._empty
_text
= kwargs
.get("empty_text", _("(nothing selected)"))
2152 self
._loaded
_at
= None
2153 self
._render
_function
= kwargs
.get("render_function", lambda id, val
: val
)
2154 self
._toggle
_all
= kwargs
.get("toggle_all", False)
2155 self
._render
_orientation
= kwargs
.get("render_orientation", "horizontal") # other: vertical
2156 self
._no
_elements
_text
= kwargs
.get("no_elements_text",
2157 _("There are no elements defined for this selection"))
2159 # In case of overloaded functions with dynamic elements
2160 def load_elements(self
):
2161 if self
._choices
is not None:
2162 if isinstance(self
._choices
, list):
2163 self
._elements
= self
._choices
2164 elif isinstance(self
._choices
, dict):
2165 self
._elements
= ListChoice
.dict_choices(self
._choices
)
2167 self
._elements
= self
._choices
()
2170 if self
._loaded
_at
!= id(html
):
2171 self
._elements
= self
.get_elements()
2172 self
._loaded
_at
= id(html
) # unique for each query!
2174 def get_elements(self
):
2175 raise NotImplementedError()
2177 def canonical_value(self
):
2180 def _draw_listchoice(self
, varprefix
, value
, elements
, columns
, toggle_all
):
2182 if self
._toggle
_all
:
2184 _("Check / Uncheck all"),
2185 href
="javascript:cmk.valuespecs.list_choice_toggle_all('%s')" % varprefix
)
2187 html
.open_table(id_
="%s_tbl" % varprefix
, class_
=["listchoice"])
2188 for nr
, (key
, title
) in enumerate(elements
):
2189 if nr
% self
._columns
== 0:
2194 html
.checkbox("%s_%d" % (varprefix
, nr
), key
in value
, label
=title
)
2199 def render_input(self
, varprefix
, value
):
2200 self
.classtype_info()
2201 self
.load_elements()
2202 if not self
._elements
:
2203 html
.write(self
._no
_elements
_text
)
2206 self
._draw
_listchoice
(varprefix
, value
, self
._elements
, self
._columns
, self
._toggle
_all
)
2208 # Make sure that at least one variable with the prefix is present
2209 html
.hidden_field(varprefix
, "1", add_var
=True)
2211 def value_to_text(self
, value
):
2213 return self
._empty
_text
2215 self
.load_elements()
2216 d
= dict(self
._elements
)
2217 texts
= [self
._render
_function
(v
, d
.get(v
, v
)) for v
in value
]
2218 if self
._render
_orientation
== "horizontal":
2219 return ", ".join(texts
)
2221 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
2222 return "%s" % html
.render_table(
2223 html
.render_tr(html
.render_td(html
.render_br().join(HTML(x
) for x
in texts
))))
2224 #OLD: return "<table><tr><td>" + "<br>".join(texts) + "</td></tr></table>"
2226 def from_html_vars(self
, varprefix
):
2227 self
.load_elements()
2230 for nr
, (key
, _title
) in enumerate(self
._elements
):
2231 if html
.get_checkbox("%s_%d" % (varprefix
, nr
)):
2235 def validate_datatype(self
, value
, varprefix
):
2236 self
.load_elements()
2238 if not isinstance(value
, list):
2239 raise MKUserError(varprefix
,
2240 _("The datatype must be list, but is %s") % _type_name(value
))
2243 if self
._value
_is
_invalid
(v
):
2244 raise MKUserError(varprefix
, _("%s is not an allowed value") % v
)
2246 def validate_value(self
, value
, varprefix
):
2247 if not self
._allow
_empty
and not value
:
2248 raise MKUserError(varprefix
, _('You have to select at least one element.'))
2249 ValueSpec
.custom_validate(self
, value
, varprefix
)
2251 def _value_is_invalid(self
, value
):
2252 d
= dict(self
._elements
)
2253 return value
not in d
2256 # Implements a choice of items which is realized with
2257 # two ListChoices select fields. One contains all available
2258 # items and one contains all selected items.
2259 # Optionally you can have the user influance the order of
2260 # the entries by simply clicking them in a certain order.
2261 # If that feature is not being used, then the original order
2262 # of the elements is always being kept.
2263 # TODO: Beware: the keys in this choice are not type safe.
2264 # They can only be strings. They must not contain | or other
2265 # dangerous characters. We should fix this and make it this
2266 # compatible to DropdownChoice()
2267 class DualListChoice(ListChoice
):
2268 def __init__(self
, **kwargs
):
2269 super(DualListChoice
, self
).__init
__(**kwargs
)
2270 self
._autoheight
= kwargs
.get("autoheight", False)
2271 self
._custom
_order
= kwargs
.get("custom_order", False)
2272 self
._instant
_add
= kwargs
.get("instant_add", False)
2273 self
._enlarge
_active
= kwargs
.get("enlarge_active", False)
2274 if "rows" in kwargs
:
2275 self
._rows
= kwargs
.get("rows", 5)
2276 self
._autoheight
= False
2279 self
._size
= kwargs
.get("size") # Total width in ex
2281 def render_input(self
, varprefix
, value
):
2282 self
.classtype_info()
2284 self
.load_elements()
2285 if not self
._elements
:
2286 html
.write_text(_("There are no elements for selection."))
2289 # Use values from HTTP request in complain mode
2291 value
= self
.from_html_vars(varprefix
)
2295 if self
._custom
_order
:
2296 edict
= dict(self
._elements
)
2297 allowed_keys
= edict
.keys()
2299 if v
in allowed_keys
:
2300 selected
.append((v
, edict
[v
]))
2302 for v
, _name
in self
._elements
:
2304 unselected
.append((v
, edict
[v
]))
2306 for e
in self
._elements
:
2310 unselected
.append(e
)
2312 select_func
= 'cmk.valuespecs.duallist_switch(\'unselected\', \'%s\', %d);' % (
2313 varprefix
, 1 if self
._custom
_order
else 0)
2314 unselect_func
= 'cmk.valuespecs.duallist_switch(\'selected\', \'%s\', %d);' % (
2315 varprefix
, 1 if self
._custom
_order
else 0)
2318 class_
=["vs_duallist"],
2319 style
="width: %dpx;" % (self
._size
* 6.4) if self
._size
else None)
2322 html
.open_td(class_
="head")
2323 html
.write_text(_('Available'))
2324 if not self
._instant
_add
:
2325 html
.a(">", href
="javascript:%s;" % select_func
, class_
=["control", "add"])
2328 html
.open_td(class_
="head")
2329 html
.write_text(_('Selected'))
2330 if not self
._instant
_add
:
2331 html
.a("<", href
="javascript:%s;" % unselect_func
, class_
=["control", "del"])
2336 for suffix
, choices
, select_func
in [
2337 ("unselected", unselected
, select_func
),
2338 ("selected", selected
, unselect_func
),
2341 onchange_func
= select_func
if self
._instant
_add
else ''
2342 if self
._enlarge
_active
:
2343 onchange_func
= 'cmk.valuespecs.duallist_enlarge(%s, %s);' % (json
.dumps(suffix
),
2344 json
.dumps(varprefix
))
2347 'multiple': 'multiple',
2348 'style': 'height:auto' if self
._autoheight
else "height: %dpx" % (self
._rows
* 16),
2349 'ondblclick': select_func
if not self
._instant
_add
else '',
2353 attrs
["onchange"] = onchange_func
2355 "%s_%s" % (varprefix
, suffix
),
2358 ordered
=self
._custom
_order
,
2365 varprefix
, '|'.join([k
for k
, v
in selected
]), id_
=varprefix
, add_var
=True)
2367 def validate_value(self
, value
, varprefix
):
2369 ListChoice
.validate_value(self
, value
, varprefix
)
2370 except MKUserError
as e
:
2371 raise MKUserError(e
.varname
+ "_selected", e
.message
)
2373 def from_html_vars(self
, varprefix
):
2374 self
.load_elements()
2375 selected
= html
.request
.var(varprefix
, '').split('|')
2377 if self
._custom
_order
:
2378 edict
= dict(self
._elements
)
2379 allowed_keys
= edict
.keys()
2381 if v
in allowed_keys
:
2384 for key
, _title
in self
._elements
:
2390 # A type-save dropdown choice with one extra field that
2391 # opens a further value spec for entering an alternative
2393 class OptionalDropdownChoice(DropdownChoice
):
2394 def __init__(self
, **kwargs
):
2395 DropdownChoice
.__init
__(self
, **kwargs
)
2396 self
._explicit
= kwargs
["explicit"]
2397 self
._otherlabel
= kwargs
.get("otherlabel", _("Other"))
2399 def canonical_value(self
):
2400 return self
._explicit
.canonical_value()
2402 def value_is_explicit(self
, value
):
2403 return value
not in [c
[0] for c
in self
.choices()]
2405 def render_input(self
, varprefix
, value
):
2406 self
.classtype_info()
2409 for n
, (val
, title
) in enumerate(self
.choices()):
2410 options
.append((str(n
), title
))
2414 options
.sort(cmp=lambda a
, b
: cmp(a
[1], b
[1]))
2415 options
.append(("other", self
._otherlabel
))
2419 deflt
=defval
, # style="float:left;",
2420 onchange
="cmk.valuespecs.toggle_dropdown(this, '%s_ex');" % varprefix
)
2421 if html
.request
.has_var(varprefix
):
2422 div_is_open
= html
.request
.var(varprefix
) == "other"
2424 div_is_open
= self
.value_is_explicit(value
)
2427 id_
="%s_ex" % varprefix
,
2428 style
=["white-space: nowrap;", None if div_is_open
else "display:none;"])
2431 if defval
== "other":
2434 input_value
= self
._explicit
.default_value()
2435 html
.help(self
._explicit
.help())
2436 self
._explicit
.render_input(varprefix
+ "_ex", input_value
)
2439 def value_to_text(self
, value
):
2440 for val
, title
in self
.choices():
2443 return self
._explicit
.value_to_text(value
)
2445 def from_html_vars(self
, varprefix
):
2446 choices
= self
.choices()
2447 sel
= html
.request
.var(varprefix
)
2449 return self
._explicit
.from_html_vars(varprefix
+ "_ex")
2451 for n
, (val
, _title
) in enumerate(choices
):
2454 return choices
[0][0] # can only happen if user garbled URL
2456 def validate_value(self
, value
, varprefix
):
2457 if self
.value_is_explicit(value
):
2458 self
._explicit
.validate_value(value
, varprefix
)
2459 # else valid_datatype already has made the job
2460 ValueSpec
.custom_validate(self
, value
, varprefix
)
2462 def validate_datatype(self
, value
, varprefix
):
2463 for val
, _title
in self
.choices():
2466 self
._explicit
.validate_datatype(value
, varprefix
+ "_ex")
2469 # Input of date with optimization for nearby dates
2470 # in the future. Useful for example for alarms. The
2471 # date is represented by a UNIX timestamp where the
2472 # seconds are silently ignored.
2474 return int(t
) / seconds_per_day
* seconds_per_day
2478 return round_date(time
.time())
2481 class Weekday(DropdownChoice
):
2482 def __init__(self
, **kwargs
):
2483 kwargs
['choices'] = sorted(defines
.weekdays().items())
2484 DropdownChoice
.__init
__(self
, **kwargs
)
2487 class RelativeDate(OptionalDropdownChoice
):
2488 def __init__(self
, **kwargs
):
2493 weekday
= time
.localtime(today()).tm_wday
2494 for w
in range(2, 7):
2495 wd
= (weekday
+ w
) % 7
2496 choices
.append((w
, defines
.weekday_name(wd
)))
2497 for w
in range(0, 7):
2498 wd
= (weekday
+ w
) % 7
2500 title
= _(" next week")
2502 title
= _(" in %d days") % (w
+ 7)
2503 choices
.append((w
+ 7, defines
.weekday_name(wd
) + title
))
2505 kwargs
['choices'] = choices
2506 kwargs
['explicit'] = Integer()
2507 kwargs
['otherlabel'] = _("in ... days")
2509 OptionalDropdownChoice
.__init
__(self
, **kwargs
)
2511 if "default_days" in kwargs
:
2512 self
._default
_value
= kwargs
["default_days"] * seconds_per_day
+ today()
2514 self
._default
_value
= today()
2516 def canonical_value(self
):
2517 return self
._default
_value
2519 def render_input(self
, varprefix
, value
):
2520 self
.classtype_info()
2521 reldays
= (round_date(value
) - today()) / seconds_per_day
2522 OptionalDropdownChoice
.render_input(self
, varprefix
, reldays
)
2524 def value_to_text(self
, value
):
2525 reldays
= (round_date(value
) - today()) / seconds_per_day
2527 return _("yesterday")
2529 return _("two days ago")
2531 return _("%d days ago") % -reldays
2532 elif reldays
< len(self
._choices
):
2533 return self
._choices
[reldays
][1]
2534 return _("in %d days") % reldays
2536 def from_html_vars(self
, varprefix
):
2537 reldays
= OptionalDropdownChoice
.from_html_vars(self
, varprefix
)
2538 return today() + reldays
* seconds_per_day
2540 def validate_datatype(self
, value
, varprefix
):
2541 if not isinstance(value
, (int, float)):
2542 raise MKUserError(varprefix
, _("Date must be a number value"))
2544 def validate_value(self
, value
, varprefix
):
2545 ValueSpec
.custom_validate(self
, value
, varprefix
)
2548 # A ValueSpec for editing a date. The date is
2549 # represented as a UNIX timestamp x where x % seconds_per_day
2550 # is zero (or will be ignored if non-zero), as long as
2551 # include_time is not set to True
2552 class AbsoluteDate(ValueSpec
):
2553 def __init__(self
, **kwargs
):
2554 ValueSpec
.__init
__(self
, **kwargs
)
2555 self
._show
_titles
= kwargs
.get("show_titles", True)
2556 self
._label
= kwargs
.get("label")
2557 self
._include
_time
= kwargs
.get("include_time", False)
2558 self
._format
= kwargs
.get("format", "%F %T" if self
._include
_time
else "%F")
2559 self
._default
_value
= kwargs
.get("default_value", None)
2560 self
._allow
_empty
= kwargs
.get('allow_empty', False)
2561 # The default is that "None" means show current date/time in the
2562 # input fields. This option changes the input fields to be empty by default
2563 # and makes the value able to be None when no time is set.
2564 # FIXME: Shouldn't this be the default?
2565 self
._none
_means
_empty
= kwargs
.get("none_means_empty", False)
2567 def default_value(self
):
2568 if self
._default
_value
is not None:
2569 return self
._default
_value
2571 if self
._allow
_empty
:
2574 if self
._include
_time
:
2578 def canonical_value(self
):
2579 return self
.default_value()
2581 def split_date(self
, value
):
2582 if self
._none
_means
_empty
and value
is None:
2584 lt
= time
.localtime(value
)
2585 return lt
.tm_year
, lt
.tm_mon
, lt
.tm_mday
, \
2586 lt
.tm_hour
, lt
.tm_min
, lt
.tm_sec
2588 def render_input(self
, varprefix
, value
):
2590 self
.classtype_info()
2593 html
.write("%s" % self
._label
)
2596 year
, month
, day
, hour
, mmin
, sec
= self
.split_date(value
)
2599 ("_month", month
, 2),
2602 if self
._include
_time
:
2610 if not self
._show
_titles
:
2611 titles
= [_("Year"), _("Month"), _("Day")]
2612 if self
._include
_time
:
2613 titles
+= ['', _("Hour"), _("Minute"), _("Sec.")]
2615 html
.open_table(class_
=["vs_date"])
2618 map(html
.th
, titles
)
2627 html
.number_input(varprefix
+ val
[0], val
[1], size
=val
[2])
2634 for count
, val
in enumerate(values
):
2636 html
.write_text(" ")
2640 html
.number_input(varprefix
+ val
[0], val
[1], size
=val
[2])
2642 def set_focus(self
, varprefix
):
2643 html
.set_focus(varprefix
+ "_year")
2645 def value_to_text(self
, value
):
2646 return time
.strftime(self
._format
, time
.localtime(value
))
2648 def from_html_vars(self
, varprefix
):
2651 ("year", _("year"), 1970, 2038),
2652 ("month", _("month"), 1, 12),
2653 ("day", _("day"), 1, 31),
2656 if self
._include
_time
:
2658 ("hour", _("hour"), 0, 23),
2659 ("min", _("min"), 0, 59),
2660 ("sec", _("sec"), 0, 59),
2663 for what
, title
, mmin
, mmax
in entries
:
2665 varname
= varprefix
+ "_" + what
2666 part
= int(html
.request
.var(varname
))
2668 if self
._allow
_empty
:
2671 raise MKUserError(varname
, _("Please enter a valid number"))
2672 if part
< mmin
or part
> mmax
:
2675 _("The value for %s must be between %d and %d") % (title
, mmin
, mmax
))
2678 # Construct broken time from input fields. Assume no-dst
2679 parts
+= [0] * (3 if self
._include
_time
else 6)
2681 epoch
= time
.mktime(tuple(parts
))
2682 # Convert back to localtime in order to know DST setting
2683 localtime
= time
.localtime(epoch
)
2684 # Enter DST setting of that time
2685 parts
[-1] = localtime
.tm_isdst
2686 # Convert to epoch again
2687 return time
.mktime(tuple(parts
))
2689 def validate_datatype(self
, value
, varprefix
):
2690 if value
is None and self
._allow
_empty
:
2692 if not isinstance(value
, (int, float)):
2695 _("The type of the timestamp must be int or float, but is %s") % _type_name(value
))
2697 def validate_value(self
, value
, varprefix
):
2698 if (not self
._allow
_empty
and value
is None) or value
< 0 or int(value
) > (2**31 - 1):
2699 return MKUserError(varprefix
, _("%s is not a valid UNIX timestamp") % value
)
2700 ValueSpec
.custom_validate(self
, value
, varprefix
)
2703 # Valuespec for entering times like 00:35 or 16:17. Currently
2704 # no seconds are supported. But this could easily be added.
2705 # The value itself is stored as a pair of integers, a.g.
2706 # (0, 35) or (16, 17). If the user does not enter a time
2707 # the vs will return None.
2708 class Timeofday(ValueSpec
):
2709 def __init__(self
, **kwargs
):
2710 ValueSpec
.__init
__(self
, **kwargs
)
2711 self
._allow
_24_00 = kwargs
.get("allow_24_00", False)
2712 self
._allow
_empty
= kwargs
.get("allow_empty", True)
2714 def canonical_value(self
):
2715 if self
._allow
_empty
:
2719 def render_input(self
, varprefix
, value
):
2720 self
.classtype_info()
2721 text
= ("%02d:%02d" % value
) if value
else ''
2722 html
.text_input(varprefix
, text
, size
=5)
2724 def value_to_text(self
, value
):
2727 return "%02d:%02d" % value
2729 def from_html_vars(self
, varprefix
):
2731 text
= html
.request
.var(varprefix
, "").strip()
2735 if re
.match("^(24|[0-1][0-9]|2[0-3]):[0-5][0-9]$", text
):
2736 return tuple(map(int, text
.split(":")))
2745 _("Invalid time format '<tt>%s</tt>', please use <tt>24:00</tt> format.") % text
)
2747 def validate_datatype(self
, value
, varprefix
):
2748 if self
._allow
_empty
and value
is None:
2751 if not isinstance(value
, tuple):
2752 raise MKUserError(varprefix
,
2753 _("The datatype must be tuple, but ist %s") % _type_name(value
))
2758 _("The tuple must contain two elements, but you have %d") % len(value
))
2761 if not isinstance(x
, int):
2764 _("All elements of the tuple must be of type int, you have %s") % _type_name(x
))
2766 def validate_value(self
, value
, varprefix
):
2767 if not self
._allow
_empty
and value
is None:
2768 raise MKUserError(varprefix
, _("Please enter a time."))
2769 if self
._allow
_24_00:
2772 max_value
= (23, 59)
2773 if value
> max_value
:
2774 raise MKUserError(varprefix
,
2775 _("The time must not be greater than %02d:%02d.") % max_value
)
2776 elif value
[0] < 0 or value
[1] < 0 or value
[0] > 24 or value
[1] > 59:
2777 raise MKUserError(varprefix
, _("Hours/Minutes out of range"))
2778 ValueSpec
.custom_validate(self
, value
, varprefix
)
2781 # Range like 00:15 - 18:30
2782 class TimeofdayRange(ValueSpec
):
2783 def __init__(self
, **kwargs
):
2784 ValueSpec
.__init
__(self
, **kwargs
)
2785 self
._allow
_empty
= kwargs
.get("allow_empty", True)
2787 Timeofday(allow_empty
=self
._allow
_empty
, allow_24_00
=True),
2788 Timeofday(allow_empty
=self
._allow
_empty
, allow_24_00
=True),
2791 def canonical_value(self
):
2792 if self
._allow
_empty
:
2794 return (0, 0), (24, 0)
2796 def render_input(self
, varprefix
, value
):
2797 self
.classtype_info()
2799 value
= (None, None)
2800 self
._bounds
[0].render_input(varprefix
+ "_from", value
[0])
2802 html
.write_text("-")
2804 self
._bounds
[1].render_input(varprefix
+ "_until", value
[1])
2806 def value_to_text(self
, value
):
2810 return self
._bounds
[0].value_to_text(value
[0]) + "-" + \
2811 self
._bounds
[1].value_to_text(value
[1])
2813 def from_html_vars(self
, varprefix
):
2814 from_value
= self
._bounds
[0].from_html_vars(varprefix
+ "_from")
2815 until_value
= self
._bounds
[1].from_html_vars(varprefix
+ "_until")
2816 if (from_value
is None) != (until_value
is None):
2818 varprefix
+ "_from",
2819 _("Please leave either both from and until empty or enter two times."))
2820 if from_value
is None:
2822 return (from_value
, until_value
)
2824 def validate_datatype(self
, value
, varprefix
):
2825 if self
._allow
_empty
and value
is None:
2828 if not isinstance(value
, tuple):
2829 raise MKUserError(varprefix
,
2830 _("The datatype must be tuple, but ist %s") % _type_name(value
))
2835 _("The tuple must contain two elements, but you have %d") % len(value
))
2837 self
._bounds
[0].validate_datatype(value
[0], varprefix
+ "_from")
2838 self
._bounds
[1].validate_datatype(value
[1], varprefix
+ "_until")
2840 def validate_value(self
, value
, varprefix
):
2842 if self
._allow
_empty
:
2845 raise MKUserError(varprefix
+ "_from", _("Please enter a valid time of day range"))
2848 self
._bounds
[0].validate_value(value
[0], varprefix
+ "_from")
2849 self
._bounds
[1].validate_value(value
[1], varprefix
+ "_until")
2850 if value
[0] > value
[1]:
2852 varprefix
+ "_until",
2853 _("The <i>from</i> time must not be later then the <i>until</i> time."))
2854 ValueSpec
.custom_validate(self
, value
, varprefix
)
2857 class TimeHelper(object):
2859 def round(timestamp
, unit
):
2860 time_s
= list(time
.localtime(timestamp
))
2861 time_s
[3] = time_s
[4] = time_s
[5] = 0
2864 return time
.mktime(time_s
)
2866 days
= time_s
[6] # 0 based
2868 days
= time_s
[2] - 1 # 1 based
2870 days
= time_s
[7] - 1 # 1 based
2872 raise MKGeneralException("invalid time unit %s" % unit
)
2874 return TimeHelper
.round(time
.mktime(time_s
) - days
* 86400 + 3600, 'd')
2877 def add(timestamp
, count
, unit
):
2879 return timestamp
+ 3600 * count
2881 return timestamp
+ 86400 * count
2883 return timestamp
+ (7 * 86400) * count
2885 time_s
= list(time
.localtime(timestamp
))
2886 years
, months
= divmod(abs(count
), 12)
2896 time_s
[1] = 12 - time_s
[1]
2898 return time
.mktime(time_s
)
2900 time_s
= list(time
.localtime(timestamp
))
2902 return time
.mktime(time_s
)
2904 MKGeneralException("invalid time unit %s" % unit
)
2907 class Timerange(CascadingDropdown
):
2908 def __init__(self
, **kwargs
):
2909 self
._title
= _('Time range')
2910 self
._allow
_empty
= kwargs
.get("allow_empty", False)
2911 self
._include
_time
= kwargs
.get("include_time", False)
2912 self
._fixed
_choices
= kwargs
.get("choices", [])
2913 kwargs
['choices'] = self
._prepare
_choices
2914 CascadingDropdown
.__init
__(self
, **kwargs
)
2916 def _prepare_choices(self
):
2917 choices
= list(self
._fixed
_choices
)
2919 if self
._allow
_empty
:
2920 choices
+= [(None, '')]
2922 choices
+= self
._get
_graph
_timeranges
() + [
2924 ("d1", _("Yesterday")),
2925 ("w0", _("This week")),
2926 ("w1", _("Last week")),
2927 ("m0", _("This month")),
2928 ("m1", _("Last month")),
2929 ("y0", _("This year")),
2930 ("y1", _("Last year")),
2931 ("age", _("The last..."), Age()),
2932 ("date", _("Date range"),
2934 orientation
="horizontal",
2937 AbsoluteDate(title
=_("From:")),
2938 AbsoluteDate(title
=_("To:")),
2942 if self
._include
_time
:
2943 choices
+= [("time", _("Date & time range"),
2945 orientation
="horizontal",
2959 def _get_graph_timeranges(self
):
2961 import cmk
.gui
.config
as config
# FIXME
2962 return [(('age', timerange_attrs
["duration"]), timerange_attrs
['title'])
2963 for timerange_attrs
in config
.graph_timeranges
]
2965 except AttributeError: # only available in cee
2967 ("4h", _("The last 4 hours")),
2968 ("25h", _("The last 25 hours")),
2969 ("8d", _("The last 8 days")),
2970 ("35d", _("The last 35 days")),
2971 ("400d", _("The last 400 days")),
2974 def compute_range(self
, rangespec
):
2975 if rangespec
is None:
2978 # Compatibility with previous versions
2979 elif rangespec
[0] == "pnp_view":
2986 }.get(rangespec
[1], "4h")
2990 if rangespec
[0] == 'age':
2991 from_time
= now
- rangespec
[1]
2993 title
= _("The last ") + Age().value_to_text(rangespec
[1])
2994 return (from_time
, until_time
), title
2996 elif rangespec
[0] in ['date', 'time']:
2997 from_time
, until_time
= rangespec
[1]
2998 if from_time
> until_time
:
2999 raise MKUserError("avo_rangespec_9_0_year",
3000 _("The end date must be after the start date"))
3001 if rangespec
[0] == 'date':
3002 # add 25 hours, then round to 00:00 of that day. This accounts for
3003 # daylight-saving time
3004 until_time
= TimeHelper
.round(TimeHelper
.add(until_time
, 25, 'h'), 'd')
3005 title
= AbsoluteDate().value_to_text(from_time
) + " ... " + \
3006 AbsoluteDate().value_to_text(until_time
)
3007 return (from_time
, until_time
), title
3011 if rangespec
[0].isdigit(): # 4h, 400d
3012 count
= int(rangespec
[:-1])
3013 from_time
= TimeHelper
.add(now
, count
* -1, rangespec
[-1])
3014 unit_name
= {'d': "days", 'h': "hours"}[rangespec
[-1]]
3015 title
= _("Last %d %s") % (count
, unit_name
)
3016 return (from_time
, now
), title
3018 year
, month
= time
.localtime(now
)[:2]
3019 # base time is current time rounded down to the nearest unit (day, week, ...)
3020 from_time
= TimeHelper
.round(now
, rangespec
[0])
3021 # derive titles from unit ()
3023 'd': (_("Today"), _("Yesterday")),
3024 'w': (_("This week"), _("Last week")),
3025 'y': (str(year
), str(year
- 1)),
3026 'm': ("%s %d" % (defines
.month_name(month
- 1), year
),
3027 "%s %d" % (defines
.month_name((month
+ 10) % 12), year
- int(month
== 1))),
3030 if rangespec
[1] == '0':
3031 return (from_time
, now
), titles
[0]
3034 prev_time
= TimeHelper
.add(from_time
, -1, rangespec
[0])
3035 # add one hour to the calculated time so that if dst started in that period,
3036 # we don't round down a whole day
3037 prev_time
= TimeHelper
.round(prev_time
+ 3600, 'd')
3038 return (prev_time
, from_time
), titles
[1]
3041 # A selection of various date formats
3042 def DateFormat(**args
):
3043 args
.setdefault("title", _("Date format"))
3044 args
.setdefault("default_value", "%Y-%m-%d")
3046 ("%Y-%m-%d", "1970-12-18"),
3047 ("%d.%m.%Y", "18.12.1970"),
3048 ("%m/%d/%Y", "12/18/1970"),
3049 ("%d.%m.", "18.12."),
3052 return DropdownChoice(**args
)
3055 def TimeFormat(**args
):
3056 args
.setdefault("title", _("Time format"))
3057 args
.setdefault("default_value", "%H:%M:%S")
3059 ("%H:%M:%S", "18:27:36"),
3060 ("%l:%M:%S %p", "12:27:36 PM"),
3062 ("%l:%M %p", "6:27 PM"),
3066 return DropdownChoice(**args
)
3069 # Make a configuration value optional, i.e. it may be None.
3070 # The user has a checkbox for activating the option. Example:
3071 # debug_log: it is either None or set to a filename.
3072 class Optional(ValueSpec
):
3073 def __init__(self
, valuespec
, **kwargs
):
3074 ValueSpec
.__init
__(self
, **kwargs
)
3075 self
._valuespec
= valuespec
3076 self
._label
= kwargs
.get("label")
3077 self
._negate
= kwargs
.get("negate", False)
3078 self
._none
_label
= kwargs
.get("none_label", _("(unset)"))
3079 self
._none
_value
= kwargs
.get("none_value", None)
3080 self
._sameline
= kwargs
.get("sameline", False)
3081 self
._indent
= kwargs
.get("indent", True)
3083 def canonical_value(self
):
3084 return self
._none
_value
3086 def render_input(self
, varprefix
, value
):
3087 self
.classtype_info()
3088 div_id
= "option_" + varprefix
3089 checked
= html
.get_checkbox(varprefix
+ "_use")
3092 checked
= value
== self
._none
_value
3094 checked
= value
!= self
._none
_value
3098 if self
._label
is not None:
3101 label
= self
.title()
3103 label
= _(" Ignore this option")
3105 label
= _(" Activate this option")
3108 "%s_use" % varprefix
,
3111 onclick
="cmk.valuespecs.toggle_option(this, %r, %r)" % (div_id
,
3112 1 if self
._negate
else 0))
3128 "margin-left: %dpx;" % indent
, "display:none;" if checked
== self
._negate
else None
3130 if value
== self
._none
_value
:
3131 value
= self
._valuespec
.default_value()
3132 if self
._valuespec
.title():
3133 html
.write(self
._valuespec
.title() + " ")
3134 self
._valuespec
.render_input(varprefix
+ "_value", value
)
3137 def value_to_text(self
, value
):
3138 if value
== self
._none
_value
:
3139 return self
._none
_label
3140 return self
._valuespec
.value_to_text(value
)
3142 def from_html_vars(self
, varprefix
):
3143 checkbox_checked
= html
.get_checkbox(varprefix
+ "_use") is True # not None or False
3144 if checkbox_checked
!= self
._negate
:
3145 return self
._valuespec
.from_html_vars(varprefix
+ "_value")
3146 return self
._none
_value
3148 def validate_datatype(self
, value
, varprefix
):
3149 if value
!= self
._none
_value
:
3150 self
._valuespec
.validate_datatype(value
, varprefix
+ "_value")
3152 def validate_value(self
, value
, varprefix
):
3153 if value
!= self
._none
_value
:
3154 self
._valuespec
.validate_value(value
, varprefix
+ "_value")
3155 ValueSpec
.custom_validate(self
, value
, varprefix
)
3158 # Makes a configuration value optional, while displaying the current
3159 # value as text with a checkbox in front of it. When the checkbox is being checked,
3160 # the text hides and the encapsulated valuespec is being shown.
3161 class OptionalEdit(Optional
):
3162 def __init__(self
, valuespec
, **kwargs
):
3163 Optional
.__init
__(self
, valuespec
, **kwargs
)
3166 def render_input(self
, varprefix
, value
):
3167 self
.classtype_info()
3168 div_id
= "option_" + varprefix
3169 checked
= html
.get_checkbox(varprefix
+ "_use")
3171 checked
= self
._negate
3175 if self
._label
is not None:
3178 label
= self
.title()
3180 label
= _(" Ignore this option")
3182 label
= _(" Activate this option")
3185 "%s_use" % varprefix
,
3189 "cmk.valuespecs.toggle_option(this, %r, %r);cmk.valuespecs.toggle_option(this, %r, %r)"
3190 % (div_id
+ '_on', 1 if self
._negate
else 0, div_id
+ '_off', 0 if self
._negate
else 1))
3196 value
= self
._valuespec
.default_value()
3199 id_
="%s_off" % div_id
, style
="display:none;" if checked
!= self
._negate
else None)
3204 id_
="%s_on" % div_id
, style
="display:none;" if checked
== self
._negate
else None)
3205 if self
._valuespec
.title():
3206 html
.write(self
._valuespec
.title() + " ")
3207 self
._valuespec
.render_input(varprefix
+ "_value", value
)
3210 def from_html_vars(self
, varprefix
):
3211 return self
._valuespec
.from_html_vars(varprefix
+ "_value")
3214 # Handle case when there are several possible allowed formats
3215 # for the value (e.g. strings, 4-tuple or 6-tuple like in SNMP-Communities)
3216 # The different alternatives must have different data types that can
3217 # be distinguished with validate_datatype.
3218 class Alternative(ValueSpec
):
3219 def __init__(self
, **kwargs
):
3220 super(Alternative
, self
).__init
__(**kwargs
)
3221 self
._elements
= kwargs
["elements"]
3222 self
._match
= kwargs
.get("match") # custom match function, returns index in elements
3223 self
._style
= kwargs
.get("style", "radio") # alternative: "dropdown"
3224 self
._show
_alternative
_title
= kwargs
.get("show_alternative_title")
3225 self
._on
_change
= kwargs
.get("on_change") # currently only working for style="dropdown"
3226 self
._orientation
= kwargs
.get("orientation",
3227 "vertical") # or horizontal: for style="dropdown"
3229 # Return the alternative (i.e. valuespec)
3230 # that matches the datatype of a given value. We assume
3231 # that always one matches. No error handling here.
3232 # This may also tranform the input value in case it gets
3233 # "decorated" in the from_html_vars function
3234 def matching_alternative(self
, value
):
3236 return self
._elements
[self
._match
(value
)], value
3238 for vs
in self
._elements
:
3240 vs
.validate_datatype(value
, "")
3247 def render_input(self
, varprefix
, value
):
3248 self
.classtype_info()
3249 if self
._style
== "radio":
3250 self
.render_input_radio(varprefix
, value
)
3252 self
.render_input_dropdown(varprefix
, value
)
3254 def render_input_dropdown(self
, varprefix
, value
):
3255 mvs
, value
= self
.matching_alternative(value
)
3257 sel_option
= html
.request
.var(varprefix
+ "_use")
3258 for nr
, vs
in enumerate(self
._elements
):
3259 if not sel_option
and vs
== mvs
:
3260 sel_option
= str(nr
)
3261 options
.append((str(nr
), vs
.title()))
3262 onchange
= "cmk.valuespecs.cascading_change(this, '%s', %d);" % (varprefix
, len(options
))
3264 onchange
+= self
._on
_change
3265 if self
._orientation
== "horizontal":
3269 html
.dropdown(varprefix
+ "_use", options
, deflt
=sel_option
, onchange
=onchange
)
3270 if self
._orientation
== "vertical":
3274 for nr
, vs
in enumerate(self
._elements
):
3275 if str(nr
) == sel_option
:
3280 cur_val
= vs
.default_value()
3282 if self
._orientation
== "horizontal":
3285 html
.open_span(id_
="%s_%s_sub" % (varprefix
, nr
), style
="display:%s" % disp
)
3286 html
.help(vs
.help())
3287 vs
.render_input(varprefix
+ "_%d" % nr
, cur_val
)
3290 if self
._orientation
== "horizontal":
3295 def render_input_radio(self
, varprefix
, value
):
3296 mvs
, value
= self
.matching_alternative(value
)
3297 for nr
, vs
in enumerate(self
._elements
):
3298 if html
.request
.has_var(varprefix
+ "_use"):
3299 checked
= html
.request
.var(varprefix
+ "_use") == str(nr
)
3303 html
.help(vs
.help())
3305 if not title
and nr
:
3309 html
.radiobutton(varprefix
+ "_use", str(nr
), checked
, title
)
3315 val
= vs
.default_value()
3316 vs
.render_input(varprefix
+ "_%d" % nr
, val
)
3320 def set_focus(self
, varprefix
):
3321 # TODO: Set focus to currently active option
3324 def canonical_value(self
):
3325 return self
._elements
[0].canonical_value()
3327 def default_value(self
):
3329 if isinstance(self
._default
_value
, type(lambda: True)):
3330 return self
._default
_value
()
3331 return self
._default
_value
3333 return self
._elements
[0].default_value()
3335 def value_to_text(self
, value
):
3336 vs
, value
= self
.matching_alternative(value
)
3339 if self
._show
_alternative
_title
and vs
.title():
3340 output
= "%s<br>" % vs
.title()
3341 return output
+ vs
.value_to_text(value
)
3343 return _("invalid:") + " " + html
.attrencode(str(value
))
3345 def from_html_vars(self
, varprefix
):
3346 nr
= int(html
.request
.var(varprefix
+ "_use"))
3347 vs
= self
._elements
[nr
]
3348 return vs
.from_html_vars(varprefix
+ "_%d" % nr
)
3350 def validate_datatype(self
, value
, varprefix
):
3351 for vs
in self
._elements
:
3353 vs
.validate_datatype(value
, "")
3359 _("The data type of the value does not match any of the "
3360 "allowed alternatives."))
3362 def validate_value(self
, value
, varprefix
):
3363 vs
, value
= self
.matching_alternative(value
)
3364 for nr
, v
in enumerate(self
._elements
):
3366 vs
.validate_value(value
, varprefix
+ "_%d" % nr
)
3367 ValueSpec
.custom_validate(self
, value
, varprefix
)
3370 # Edit a n-tuple (with fixed size) of values
3371 class Tuple(ValueSpec
):
3372 def __init__(self
, **kwargs
):
3373 ValueSpec
.__init
__(self
, **kwargs
)
3374 self
._elements
= kwargs
["elements"]
3375 self
._show
_titles
= kwargs
.get("show_titles", True)
3376 self
._orientation
= kwargs
.get("orientation", "vertical") # also: horizontal, float
3377 self
._separator
= kwargs
.get("separator", " ") # in case of float
3378 self
._title
_br
= kwargs
.get("title_br", True)
3380 def canonical_value(self
):
3381 return tuple([x
.canonical_value() for x
in self
._elements
])
3383 def default_value(self
):
3384 return tuple([x
.default_value() for x
in self
._elements
])
3386 def render_input(self
, varprefix
, value
):
3387 self
.classtype_info()
3388 if self
._orientation
!= "float":
3389 html
.open_table(class_
=["valuespec_tuple", self
._orientation
])
3390 if self
._orientation
== "horizontal":
3393 for no
, element
in enumerate(self
._elements
):
3397 val
= element
.default_value()
3398 vp
= varprefix
+ "_" + str(no
)
3399 if self
._orientation
== "vertical":
3401 elif self
._orientation
== "float":
3402 html
.write(self
._separator
)
3404 if self
._show
_titles
:
3405 elem_title
= element
.title()
3407 title
= element
.title()[0].upper() + element
.title()[1:]
3410 if self
._orientation
== "vertical":
3411 html
.open_td(class_
="tuple_left")
3414 html
.help(element
.help())
3416 elif self
._orientation
== "horizontal":
3417 html
.open_td(class_
="tuple_td")
3418 html
.open_span(class_
=["title"])
3421 html
.help(element
.help())
3426 html
.write_text(" ")
3428 html
.write_text(" ")
3429 html
.help(element
.help())
3432 if self
._orientation
== "horizontal":
3433 html
.open_td(class_
="tuple_td")
3435 if self
._orientation
== "vertical":
3436 html
.open_td(class_
="tuple_right")
3438 element
.render_input(vp
, val
)
3439 if self
._orientation
!= "float":
3441 if self
._orientation
== "vertical":
3443 if self
._orientation
== "horizontal":
3445 if self
._orientation
!= "float":
3448 def set_focus(self
, varprefix
):
3449 self
._elements
[0].set_focus(varprefix
+ "_0")
3451 def value_to_text(self
, value
):
3452 return "" + ", ".join(
3453 [element
.value_to_text(val
) for (element
, val
) in zip(self
._elements
, value
)]) + ""
3455 def from_html_vars(self
, varprefix
):
3457 for no
, element
in enumerate(self
._elements
):
3458 vp
= varprefix
+ "_" + str(no
)
3459 value
.append(element
.from_html_vars(vp
))
3462 def validate_value(self
, value
, varprefix
):
3463 for no
, (element
, val
) in enumerate(zip(self
._elements
, value
)):
3464 vp
= varprefix
+ "_" + str(no
)
3465 element
.validate_value(val
, vp
)
3466 ValueSpec
.custom_validate(self
, value
, varprefix
)
3468 def validate_datatype(self
, value
, varprefix
):
3469 if not isinstance(value
, tuple):
3470 raise MKUserError(varprefix
,
3471 _("The datatype must be a tuple, but is %s") % _type_name(value
))
3472 if len(value
) != len(self
._elements
):
3475 _("The number of elements in the tuple must be exactly %d.") % len(self
._elements
))
3477 for no
, (element
, val
) in enumerate(zip(self
._elements
, value
)):
3478 vp
= varprefix
+ "_" + str(no
)
3479 element
.validate_datatype(val
, vp
)
3482 class Dictionary(ValueSpec
):
3483 def __init__(self
, **kwargs
):
3484 super(Dictionary
, self
).__init
__(**kwargs
)
3485 self
._elements
= kwargs
["elements"]
3486 self
._empty
_text
= kwargs
.get("empty_text", _("(no parameters)"))
3487 # Optionally a text can be specified to be shown by value_to_text()
3488 # when the value equal the default value of the value spec. Normally
3489 # the default values are shown.
3490 self
._default
_text
= kwargs
.get("default_text", None)
3491 self
._required
_keys
= kwargs
.get("required_keys", [])
3492 self
._ignored
_keys
= kwargs
.get("ignored_keys", [])
3493 self
._default
_keys
= kwargs
.get("default_keys", []) # keys present in default value
3494 if "optional_keys" in kwargs
:
3495 ok
= kwargs
["optional_keys"]
3496 if isinstance(ok
, list) and ok
:
3497 self
._required
_keys
= \
3498 [ e
[0] for e
in self
._get
_elements
() if e
[0] not in ok
]
3499 self
._optional
_keys
= True
3501 self
._optional
_keys
= True
3503 self
._optional
_keys
= False
3505 self
._optional
_keys
= True
3506 if "hidden_keys" in kwargs
:
3507 self
._hidden
_keys
= kwargs
["hidden_keys"]
3509 self
._hidden
_keys
= []
3511 self
._columns
= kwargs
.get("columns", 1) # possible: 1 or 2
3512 self
._render
= kwargs
.get("render", "normal") # also: "form" -> use forms.section()
3513 self
._form
_narrow
= kwargs
.get("form_narrow", False) # used if render == "form"
3514 self
._form
_isopen
= kwargs
.get("form_isopen", True) # used if render == "form"
3515 self
._headers
= kwargs
.get("headers") # "sup" -> small headers in oneline mode
3516 self
._migrate
= kwargs
.get("migrate") # value migration from old tuple version
3517 self
._indent
= kwargs
.get("indent", True)
3519 def migrate(self
, value
):
3521 return self
._migrate
(value
)
3524 def _get_elements(self
):
3525 if callable(self
._elements
) or isinstance(self
._elements
, types
.MethodType
):
3526 return self
._elements
()
3527 elif isinstance(self
._elements
, list):
3528 return self
._elements
3531 def render_input_as_form(self
, varprefix
, value
):
3532 self
.classtype_info()
3533 value
= self
.migrate(value
)
3534 if not isinstance(value
, (dict, DictMixin
)):
3535 value
= {} # makes code simpler in complain phase
3537 self
._render
_input
_form
(varprefix
, value
)
3539 def render_input(self
, varprefix
, value
):
3540 self
.classtype_info()
3541 value
= self
.migrate(value
)
3542 if not isinstance(value
, (dict, DictMixin
)):
3543 value
= {} # makes code simpler in complain phase
3545 if self
._render
== "form":
3546 self
._render
_input
_form
(varprefix
, value
)
3547 elif self
._render
== "form_part":
3548 self
._render
_input
_form
(varprefix
, value
, as_part
=True)
3550 self
._render
_input
_normal
(varprefix
, value
, self
._render
== "oneline")
3552 def _render_input_normal(self
, varprefix
, value
, oneline
=False):
3553 headers_sup
= oneline
and self
._headers
== "sup"
3554 if headers_sup
or not oneline
:
3555 html
.open_table(class_
=["dictionary"])
3558 for param
, vs
in self
._get
_elements
():
3559 if param
in self
._hidden
_keys
:
3563 html
.open_td(class_
="dictleft")
3565 div_id
= varprefix
+ "_d_" + param
3566 vp
= varprefix
+ "_p_" + param
3567 colon_printed
= False
3568 if self
._optional
_keys
and param
not in self
._required
_keys
:
3569 visible
= html
.get_checkbox(vp
+ "_USE")
3571 visible
= param
in value
3573 if self
._columns
== 2:
3575 colon_printed
= True
3580 onclick
="cmk.valuespecs.toggle_option(this, %r)" % div_id
)
3586 html
.open_b(class_
=["header"])
3587 html
.write(" %s" % vs
.title())
3589 if self
._headers
== "sup":
3593 html
.write_text(": ")
3595 if self
._columns
== 2:
3596 if vs
.title() and not colon_printed
:
3597 html
.write_text(':')
3598 html
.help(vs
.help())
3601 html
.open_td(class_
="dictright")
3609 class_
=["dictelement", "indent" if (self
._indent
and self
._columns
== 1) else None],
3610 style
="display:none;" if not visible
else
3611 ("display:inline-block;" if oneline
else None))
3613 if self
._columns
== 1:
3614 html
.help(vs
.help())
3615 # Remember: in complain mode we do not render 'value' (the default value),
3616 # but re-display the values from the HTML variables. We must not use 'value'
3618 if isinstance(value
, dict):
3619 vs
.render_input(vp
, value
.get(param
, vs
.default_value()))
3621 vs
.render_input(vp
, None)
3631 elif oneline
and self
._headers
== "sup":
3635 def _render_input_form(self
, varprefix
, value
, as_part
=False):
3637 for entry
in self
._headers
:
3639 header
, section_elements
= entry
3642 header
, css
, section_elements
= entry
3643 self
.render_input_form_header(
3644 varprefix
, value
, header
, section_elements
, as_part
, css
=css
)
3646 self
.render_input_form_header(
3647 varprefix
, value
, self
.title() or _("Properties"), None, as_part
, css
=None)
3652 def render_input_form_header(self
, varprefix
, value
, title
, section_elements
, as_part
, css
):
3654 forms
.header(title
, isopen
=self
._form
_isopen
, narrow
=self
._form
_narrow
)
3656 for param
, vs
in self
._get
_elements
():
3657 if param
in self
._hidden
_keys
:
3660 if section_elements
and param
not in section_elements
:
3663 div_id
= varprefix
+ "_d_" + param
3664 vp
= varprefix
+ "_p_" + param
3665 if self
._optional
_keys
and param
not in self
._required
_keys
:
3666 visible
= html
.get_checkbox(vp
+ "_USE")
3668 visible
= param
in value
3669 checkbox_code
= html
.render_checkbox(
3672 onclick
="cmk.valuespecs.toggle_option(this, %r)" % div_id
)
3673 forms
.section(vs
.title(), checkbox
=checkbox_code
, css
=css
)
3676 forms
.section(vs
.title(), css
=css
)
3678 html
.open_div(id_
=div_id
, style
="display:none;" if not visible
else None)
3679 html
.help(vs
.help())
3680 vs
.render_input(vp
, value
.get(param
, vs
.default_value()))
3683 def set_focus(self
, varprefix
):
3684 elements
= self
._get
_elements
()
3686 elements
[0][1].set_focus(varprefix
+ "_p_" + elements
[0][0])
3688 def canonical_value(self
):
3689 return dict([(name
, vs
.canonical_value())
3690 for (name
, vs
) in self
._get
_elements
()
3691 if name
in self
._required
_keys
or not self
._optional
_keys
])
3693 def default_value(self
):
3695 for name
, vs
in self
._get
_elements
():
3696 if name
in self
._required
_keys
or not self
._optional
_keys
or name
in self
._default
_keys
:
3697 def_val
[name
] = vs
.default_value()
3701 def value_to_text(self
, value
):
3702 value
= self
.migrate(value
)
3703 oneline
= self
._render
== "oneline"
3705 return self
._empty
_text
3707 if self
._default
_text
and value
== self
.default_value():
3708 return self
._default
_text
3710 elem
= self
._get
_elements
()
3711 s
= '' if oneline
else HTML()
3712 for param
, vs
in elem
:
3714 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
3715 text
= HTML(vs
.value_to_text(value
[param
]))
3717 if param
!= elem
[0][0]:
3719 s
+= "%s: %s" % (vs
.title(), text
)
3721 s
+= html
.render_tr(
3722 html
.render_td("%s: " % vs
.title(), class_
="title") +
3723 html
.render_td(text
))
3725 s
= html
.render_table(s
)
3728 def from_html_vars(self
, varprefix
):
3730 for param
, vs
in self
._get
_elements
():
3731 vp
= varprefix
+ "_p_" + param
3732 if not self
._optional
_keys \
3733 or param
in self
._required
_keys \
3734 or html
.get_checkbox(vp
+ "_USE"):
3735 value
[param
] = vs
.from_html_vars(vp
)
3738 def validate_datatype(self
, value
, varprefix
):
3739 value
= self
.migrate(value
)
3741 if not isinstance(value
, dict):
3744 _("The type must be a dictionary, but it is a %s") % _type_name(value
))
3746 for param
, vs
in self
._get
_elements
():
3748 vp
= varprefix
+ "_p_" + param
3750 vs
.validate_datatype(value
[param
], vp
)
3751 except MKUserError
as e
:
3752 raise MKUserError(e
.varname
, _("%s: %s") % (vs
.title(), e
))
3753 elif not self
._optional
_keys
or param
in self
._required
_keys
:
3754 raise MKUserError(varprefix
, _("The entry %s is missing") % vs
.title())
3756 # Check for exceeding keys
3757 allowed_keys
= [p
for p
, _v
in self
._get
_elements
()]
3758 if self
._ignored
_keys
:
3759 allowed_keys
+= self
._ignored
_keys
3760 for param
in value
.keys():
3761 if param
not in allowed_keys
:
3764 _("Undefined key '%s' in the dictionary. Allowed are %s.") %
3765 (param
, ", ".join(allowed_keys
)))
3767 def validate_value(self
, value
, varprefix
):
3768 value
= self
.migrate(value
)
3770 for param
, vs
in self
._get
_elements
():
3772 vp
= varprefix
+ "_p_" + param
3773 vs
.validate_value(value
[param
], vp
)
3774 elif not self
._optional
_keys
or param
in self
._required
_keys
:
3775 raise MKUserError(varprefix
, _("The entry %s is missing") % vs
.title())
3776 ValueSpec
.custom_validate(self
, value
, varprefix
)
3779 # Base class for selection of a Nagios element out
3780 # of a given list that must be loaded from a file.
3781 # Example: GroupSelection. Child class must define
3782 # a function get_elements() that returns a dictionary
3783 # from element keys to element titles.
3784 class ElementSelection(ValueSpec
):
3785 def __init__(self
, **kwargs
):
3786 ValueSpec
.__init
__(self
, **kwargs
)
3787 self
._loaded
_at
= None
3788 self
._label
= kwargs
.get("label")
3789 self
._empty
_text
= kwargs
.get("empty_text",
3790 _("There are no elements defined for this selection yet."))
3792 def load_elements(self
):
3793 if self
._loaded
_at
!= id(html
):
3794 self
._elements
= self
.get_elements()
3795 self
._loaded
_at
= id(html
) # unique for each query!
3797 def get_elements(self
):
3798 raise NotImplementedError()
3800 def canonical_value(self
):
3801 self
.load_elements()
3802 if len(self
._elements
) > 0:
3803 return self
._elements
.keys()[0]
3805 def render_input(self
, varprefix
, value
):
3806 self
.classtype_info()
3807 self
.load_elements()
3808 if len(self
._elements
) == 0:
3809 html
.write(self
._empty
_text
)
3812 html
.write("%s" % self
._label
)
3814 html
.dropdown(varprefix
, self
._elements
.items(), deflt
=value
, ordered
=True)
3816 def value_to_text(self
, value
):
3817 self
.load_elements()
3818 return html
.attrencode(self
._elements
.get(value
, value
))
3820 def from_html_vars(self
, varprefix
):
3821 return html
.request
.var(varprefix
)
3823 def validate_value(self
, value
, varprefix
):
3824 self
.load_elements()
3825 if len(self
._elements
) == 0:
3826 raise MKUserError(varprefix
, _("You cannot save this rule.") + ' ' + self
._empty
_text
)
3827 if value
not in self
._elements
:
3828 raise MKUserError(varprefix
,
3829 _("%s is not an existing element in this selection.") % (value
,))
3830 ValueSpec
.custom_validate(self
, value
, varprefix
)
3832 def validate_datatype(self
, value
, varprefix
):
3833 self
.load_elements()
3834 # When no elements exists the default value is None and e.g. in wato.mode_edit_rule()
3835 # handed over to validate_datatype() before rendering the input form. Disable the
3836 # validation in this case to prevent validation errors. A helpful message is shown
3837 # during render_input()
3838 if len(self
._elements
) == 0 and value
is None:
3841 if not isinstance(value
, str):
3842 raise MKUserError(varprefix
,
3843 _("The datatype must be str (string), but is %s") % _type_name(value
))
3846 class AutoTimestamp(FixedValue
):
3847 def __init__(self
, **kwargs
):
3848 FixedValue
.__init
__(self
, **kwargs
)
3850 def canonical_value(self
):
3853 def from_html_vars(self
, varprefix
):
3856 def value_to_text(self
, value
):
3857 return time
.strftime("%F %T", time
.localtime(value
))
3859 def validate_datatype(self
, value
, varprefix
):
3860 if not isinstance(value
, (int, float)):
3861 return MKUserError(varprefix
, _("Invalid datatype of timestamp: must be int or float."))
3864 # Fully transparant VS encapsulating a vs in a foldable
3866 class Foldable(ValueSpec
):
3867 def __init__(self
, valuespec
, **kwargs
):
3868 ValueSpec
.__init
__(self
, **kwargs
)
3869 self
._valuespec
= valuespec
3870 self
._open
= kwargs
.get("open", False)
3871 self
._title
_function
= kwargs
.get("title_function", None)
3873 def render_input(self
, varprefix
, value
):
3874 self
.classtype_info()
3877 if html
.form_submitted():
3879 title_value
= self
._valuespec
.from_html_vars(varprefix
)
3882 title
= self
._title
_function
(title_value
)
3884 title
= self
._valuespec
.title()
3886 title
= _("(no title)")
3887 html
.begin_foldable_container("valuespec_foldable", varprefix
, self
._open
, title
, False)
3888 html
.help(self
._valuespec
.help())
3889 self
._valuespec
.render_input(varprefix
, value
)
3890 html
.end_foldable_container()
3892 def set_focus(self
, varprefix
):
3893 self
._valuespec
.set_focus(varprefix
)
3895 def canonical_value(self
):
3896 return self
._valuespec
.canonical_value()
3898 def default_value(self
):
3899 return self
._valuespec
.default_value()
3901 def value_to_text(self
, value
):
3902 return self
._valuespec
.value_to_text(value
)
3904 def from_html_vars(self
, varprefix
):
3905 return self
._valuespec
.from_html_vars(varprefix
)
3907 def validate_datatype(self
, value
, varprefix
):
3908 self
._valuespec
.validate_datatype(value
, varprefix
)
3910 def validate_value(self
, value
, varprefix
):
3911 self
._valuespec
.validate_value(value
, varprefix
)
3912 ValueSpec
.custom_validate(self
, value
, varprefix
)
3915 # Transforms the value from one representation to
3916 # another while being completely transparent to the user.
3917 # forth: function that converts a value into the representation
3918 # needed by the encapsulated vs
3919 # back: function that converts a value created by the encapsulated
3920 # vs back to the outer representation
3923 class Transform(ValueSpec
):
3924 def __init__(self
, valuespec
, **kwargs
):
3925 ValueSpec
.__init
__(self
, **kwargs
)
3926 self
._valuespec
= valuespec
3927 self
._back
= kwargs
.get("back")
3928 self
._forth
= kwargs
.get("forth")
3930 def forth(self
, value
):
3932 return self
._forth
(value
)
3935 def back(self
, value
):
3937 return self
._back
(value
)
3943 return self
._valuespec
.title()
3948 return self
._valuespec
.help()
3950 def render_input(self
, varprefix
, value
):
3951 self
.classtype_info()
3952 self
._valuespec
.render_input(varprefix
, self
.forth(value
))
3954 def render_input_as_form(self
, varprefix
, value
):
3955 if not isinstance(self
._valuespec
, Dictionary
):
3956 raise NotImplementedError()
3957 self
.classtype_info()
3958 self
._valuespec
.render_input_as_form(varprefix
, self
.forth(value
))
3960 def set_focus(self
, varprefix
):
3961 self
._valuespec
.set_focus(varprefix
)
3963 def canonical_value(self
):
3964 return self
.back(self
._valuespec
.canonical_value())
3966 def default_value(self
):
3967 return self
.back(self
._valuespec
.default_value())
3969 def value_to_text(self
, value
):
3970 return self
._valuespec
.value_to_text(self
.forth(value
))
3972 def from_html_vars(self
, varprefix
):
3973 return self
.back(self
._valuespec
.from_html_vars(varprefix
))
3975 def validate_datatype(self
, value
, varprefix
):
3976 self
._valuespec
.validate_datatype(self
.forth(value
), varprefix
)
3978 def validate_value(self
, value
, varprefix
):
3979 self
._valuespec
.validate_value(self
.forth(value
), varprefix
)
3980 ValueSpec
.custom_validate(self
, value
, varprefix
)
3983 class LDAPDistinguishedName(TextUnicode
):
3984 def __init__(self
, **kwargs
):
3985 TextUnicode
.__init
__(self
, **kwargs
)
3986 self
.enforce_suffix
= kwargs
.get('enforce_suffix')
3988 def validate_value(self
, value
, varprefix
):
3989 TextAscii
.validate_value(self
, value
, varprefix
)
3991 # Check whether or not the given DN is below a base DN
3992 if self
.enforce_suffix
and value
and not value
.lower().endswith(
3993 self
.enforce_suffix
.lower()):
3994 raise MKUserError(varprefix
, _('Does not ends with "%s".') % self
.enforce_suffix
)
3995 ValueSpec
.custom_validate(self
, value
, varprefix
)
3998 class Password(TextAscii
):
3999 def __init__(self
, **kwargs
):
4000 self
._is
_stored
_plain
= kwargs
.get("is_stored_plain", True)
4001 kwargs
.setdefault("autocomplete", False)
4003 if self
._is
_stored
_plain
:
4004 plain_help
= _("The password entered here is stored in plain text within the "
4005 "monitoring site. This usually needed because the monitoring "
4006 "process needs to have access to the unencrypted password "
4007 "because it needs to submit it to authenticate with remote systems. ")
4009 if "help" in kwargs
:
4010 kwargs
["help"] += "<br><br>" + plain_help
4012 kwargs
["help"] = plain_help
4014 TextAscii
.__init
__(self
, attrencode
=True, **kwargs
)
4016 def render_input(self
, varprefix
, value
):
4017 self
.classtype_info()
4022 html
.write(self
._label
)
4029 if self
._autocomplete
is False:
4030 kwargs
["autocomplete"] = "new-password"
4032 html
.password_input(varprefix
, str(value
), **kwargs
)
4034 def password_plaintext_warning(self
):
4035 if self
._is
_stored
_plain
:
4037 _("<br>Please note that Check_MK needs this password in clear"
4038 "<br>text during normal operation and thus stores it unencrypted"
4039 "<br>on the Check_MK server."))
4041 def value_to_text(self
, value
):
4047 class PasswordSpec(Password
):
4048 def __init__(self
, hidden
=True, **kwargs
):
4049 super(PasswordSpec
, self
).__init
__(hidden
=hidden
, **kwargs
)
4051 def render_input(self
, varprefix
, value
):
4052 self
.classtype_info()
4053 TextAscii
.render_input(self
, varprefix
, value
)
4057 _(u
"Randomize password"),
4059 onclick
="cmk.valuespecs.passwordspec_randomize(this);")
4063 _(u
"Show/Hide password"),
4065 onclick
="cmk.valuespecs.toggle_hidden(this);")
4067 self
.password_plaintext_warning()
4070 class FileUpload(ValueSpec
):
4071 def __init__(self
, **kwargs
):
4072 ValueSpec
.__init
__(self
, **kwargs
)
4073 self
._allow
_empty
= kwargs
.get('allow_empty', True)
4074 self
._allowed
_extensions
= kwargs
.get('allowed_extensions')
4075 self
._allow
_empty
_content
= kwargs
.get('allow_empty_content', True)
4077 def canonical_value(self
):
4078 if self
._allow
_empty
:
4082 def validate_value(self
, value
, varprefix
):
4083 file_name
, _mime_type
, content
= value
4085 if not self
._allow
_empty
and (content
== '' or file_name
== ''):
4086 raise MKUserError(varprefix
, _('Please select a file.'))
4088 if not self
._allow
_empty
_content
and len(content
) == 0:
4089 raise MKUserError(varprefix
,
4090 _('The selected file is empty. Please select a non-empty file.'))
4091 if self
._allowed
_extensions
is not None:
4093 for extension
in self
._allowed
_extensions
:
4094 if file_name
.endswith(extension
):
4100 _("Invalid file name extension. Allowed are: %s") % ", ".join(
4101 self
._allowed
_extensions
))
4103 self
.custom_validate(value
, varprefix
)
4105 def render_input(self
, varprefix
, value
):
4106 self
.classtype_info()
4107 html
.upload_file(varprefix
)
4109 def from_html_vars(self
, varprefix
):
4110 # returns a triple of (filename, mime-type, content)
4111 return html
.request
.uploaded_file(varprefix
)
4114 class ImageUpload(FileUpload
):
4115 def __init__(self
, max_size
=None, show_current_image
=False, **kwargs
):
4116 self
._max
_size
= max_size
4117 self
._show
_current
_image
= show_current_image
4118 FileUpload
.__init
__(self
, **kwargs
)
4120 def render_input(self
, varprefix
, value
):
4121 self
.classtype_info()
4123 if self
._show
_current
_image
and value
:
4126 html
.td(_("Current image:"))
4127 html
.td(html
.render_img("data:image/png;base64,%s" % base64
.b64encode(value
)))
4130 html
.td(_("Upload new:"))
4132 super(ImageUpload
, self
).render_input(varprefix
, value
)
4137 super(ImageUpload
, self
).render_input(varprefix
, value
)
4139 def validate_value(self
, value
, varprefix
):
4140 file_name
, mime_type
, content
= value
4142 if file_name
[-4:] != '.png' \
4143 or mime_type
!= 'image/png' \
4144 or not content
.startswith('\x89PNG'):
4145 raise MKUserError(varprefix
, _('Please choose a PNG image.'))
4148 im
= Image
.open(StringIO(content
))
4150 raise MKUserError(varprefix
, _('Please choose a valid PNG image.'))
4154 max_w
, max_h
= self
._max
_size
4155 if w
> max_w
or h
> max_h
:
4156 raise MKUserError(varprefix
, _('Maximum image size: %dx%dpx') % (max_w
, max_h
))
4158 ValueSpec
.custom_validate(self
, value
, varprefix
)
4161 class UploadOrPasteTextFile(Alternative
):
4162 def __init__(self
, **kwargs
):
4163 file_title
= kwargs
.get("file_title", _("File"))
4164 allow_empty
= kwargs
.get("allow_empty", True)
4165 kwargs
["elements"] = [
4166 FileUpload(title
=_("Upload %s") % file_title
, allow_empty
=allow_empty
),
4168 title
=_("Content of %s") % file_title
,
4169 allow_empty
=allow_empty
,
4174 if kwargs
.get("default_mode", "text") == "upload":
4175 kwargs
["match"] = lambda *args
: 0
4177 kwargs
["match"] = lambda *args
: 1
4179 kwargs
.setdefault("style", "dropdown")
4180 Alternative
.__init
__(self
, **kwargs
)
4182 def from_html_vars(self
, varprefix
):
4183 value
= Alternative
.from_html_vars(self
, varprefix
)
4184 # Convert textarea value to format of upload field
4185 if not isinstance(value
, tuple):
4186 value
= (None, None, value
)
4190 class TextOrRegExp(Alternative
):
4191 _text_valuespec_class
= TextAscii
4192 _regex_valuespec_class
= RegExp
4194 def __init__(self
, **kwargs
):
4195 allow_empty
= kwargs
.pop("allow_empty", True)
4197 if "text_valuespec" in kwargs
:
4198 vs_text
= kwargs
.pop("text_valuespec")
4200 vs_text
= self
._text
_valuespec
_class
(
4201 title
=_("Explicit match"),
4202 allow_empty
=allow_empty
,
4205 vs_regex
= self
._regex
_valuespec
_class
(
4207 title
=_("Regular expression match"),
4208 allow_empty
=allow_empty
,
4216 forth
=lambda v
: v
[1:], # strip of "~"
4217 back
=lambda v
: "~" + v
, # add "~"
4220 # Use RegExp field when value is prefixed with "~"
4221 "match": lambda v
: 1 if v
and v
[0] == "~" else 0,
4222 "style": "dropdown",
4223 "orientation": "horizontal",
4226 super(TextOrRegExp
, self
).__init
__(**kwargs
)
4229 class TextOrRegExpUnicode(TextOrRegExp
):
4230 _default_valuespec_class
= TextUnicode
4231 _regex_valuespec_class
= RegExpUnicode
4234 class Labels(ValueSpec
):
4235 """Valuespec to render and input a collection of object labels"""
4242 EXPLICIT
= "explicit"
4244 DISCOVERED
= "discovered"
4246 def __init__(self
, world
, label_source
=None, **kwargs
):
4248 # Set this source to mark the labels that have no explicit label source set
4249 self
._label
_source
= label_source
4250 kwargs
.setdefault("help", "")
4251 kwargs
["help"] += _("Labels need to be in the format <tt>[KEY]:[VALUE]</tt>. "
4252 "For example <tt>os:windows</tt>.")
4253 super(Labels
, self
).__init
__(**kwargs
)
4255 def canonical_value(self
):
4258 def from_html_vars(self
, varprefix
):
4260 e
["value"].split(":", 1) for e
in json
.loads(html
.get_unicode_input(varprefix
) or "[]"))
4262 def value_to_text(self
, value
):
4263 from cmk
.gui
.view_utils
import render_labels
4264 label_sources
= {k
: self
._label
_source
.value
for k
in value
.keys()
4265 } if self
._label
_source
else {}
4266 return render_labels(value
, "host", with_links
=False, label_sources
=label_sources
)
4268 def render_input(self
, varprefix
, value
):
4269 html
.help(self
.help())
4272 default_value
=json
.dumps(_encode_labels_for_tagify(value
.items())).decode("utf-8"),
4275 "placeholder": _("Add some label"),
4276 "data-world": self
._world
.value
,
4280 @page_registry.register_page("ajax_autocomplete_labels")
4281 class PageAutocompleteLabels(AjaxPage
):
4282 """Return all known labels to support tagify label input dropdown completion"""
4285 request
= html
.get_request()
4286 return _encode_labels_for_tagify(
4287 self
._get
_labels
(Labels
.World(request
["world"]), request
["search_label"]))
4289 def _get_labels(self
, world
, search_label
):
4290 if world
== Labels
.World
.CONFIG
:
4291 return self
._get
_labels
_from
_config
(search_label
)
4293 if world
== Labels
.World
.CORE
:
4294 return self
._get
_labels
_from
_core
(search_label
)
4296 raise NotImplementedError()
4298 def _get_labels_from_config(self
, search_label
):
4299 return [] # TODO: Implement me
4301 # TODO: Provide information about the label source
4302 # Would be better to optimize this kind of query somehow. The best we can
4303 # do without extending livestatus is to use the Cache header for liveproxyd
4304 def _get_labels_from_core(self
, search_label
):
4305 import cmk
.gui
.sites
as sites
4306 query
= ("GET services\n" \
4308 "Columns: host_labels labels\n")
4311 for row
in sites
.live().query(query
):
4312 labels
.update(row
[0].items())
4313 labels
.update(row
[1].items())
4318 def _encode_labels_for_tagify(labels
):
4319 return [{"value": "%s:%s" % e
} for e
in labels
]
4322 class IconSelector(ValueSpec
):
4323 def __init__(self
, **kwargs
):
4324 ValueSpec
.__init
__(self
, **kwargs
)
4325 self
._allow
_empty
= kwargs
.get('allow_empty', True)
4326 self
._empty
_img
= kwargs
.get('emtpy_img', 'empty')
4334 def categories(cls
):
4335 import cmk
.gui
.config
as config
# FIXME: Clean this up. But how?
4336 return config
.wato_icon_categories
4339 def category_alias(cls
, category_name
):
4340 return dict(cls
.categories()).get(category_name
, category_name
)
4342 # All icons within the images/icons directory have the ident of a category
4343 # witten in the PNG meta data. For the default images we have done this scripted.
4344 # During upload of user specific icons, the meta data is added to the images.
4345 def available_icons(self
, only_local
=False):
4347 os
.path
.join(cmk
.utils
.paths
.omd_root
, "local/share/check_mk/web/htdocs/images/icons"),
4351 os
.path
.join(cmk
.utils
.paths
.omd_root
, "share/check_mk/web/htdocs/images/icons"))
4353 valid_categories
= dict(self
.categories()).keys()
4356 # Read all icons from the icon directories
4359 for directory
in dirs
:
4361 files
= os
.listdir(directory
)
4365 for file_name
in files
:
4366 file_path
= directory
+ "/" + file_name
4367 if file_name
[-4:] == '.png' and os
.path
.isfile(file_path
):
4369 # extract the category from the meta data
4371 im
= Image
.open(file_path
)
4372 except IOError as e
:
4373 if "%s" % e
== "cannot identify image file":
4374 continue # Silently skip invalid files
4378 category
= im
.info
.get('Comment')
4379 if category
not in valid_categories
:
4382 icon_name
= file_name
[:-4]
4383 icons
[icon_name
] = category
4385 for exclude
in self
._exclude
:
4393 def available_icons_by_category(self
, icons
):
4395 for icon_name
, category_name
in icons
.items():
4396 by_cat
.setdefault(category_name
, [])
4397 by_cat
[category_name
].append(icon_name
)
4399 icon_categories
= []
4400 for category_name
, category_alias
in self
.categories():
4401 if category_name
in by_cat
:
4402 icon_categories
.append((category_name
, category_alias
, by_cat
[category_name
]))
4403 return icon_categories
4405 def render_icon(self
, icon_name
, onclick
='', title
='', id_
=''):
4407 icon_name
= self
._empty
_img
4409 icon
= html
.render_icon(icon_name
, title
=title
, middle
=True, id_
=id_
)
4411 icon
= html
.render_a(icon
, href
="javascript:void(0)", onclick
=onclick
)
4415 def render_input(self
, varprefix
, value
):
4416 # Handle complain phase with validation errors correctly and get the value
4417 # from the HTML vars
4419 value
= html
.request
.var(varprefix
+ "_value")
4421 self
.classtype_info()
4423 value
= self
._empty
_img
4425 html
.hidden_field(varprefix
+ "_value", value
or '', varprefix
+ "_value", add_var
=True)
4428 content
= self
.render_icon(value
, '', _('Choose another Icon'), id_
=varprefix
+ '_img')
4430 content
= _('Select an Icon')
4434 varprefix
+ '_icon_selector',
4438 ('varprefix', varprefix
),
4439 ('allow_empty', '1' if self
._allow
_empty
else '0'),
4440 ('back', html
.makeuri([])),
4445 def render_popup_input(self
, varprefix
, value
):
4446 html
.open_div(class_
="icons", id_
="%s_icons" % varprefix
)
4448 icons
= self
.available_icons()
4449 available_icons
= self
.available_icons_by_category(icons
)
4450 active_category
= icons
.get(value
, available_icons
[0][0])
4452 # Render tab navigation
4454 for category_name
, category_alias
, icons
in available_icons
:
4455 html
.open_li(class_
="active" if active_category
== category_name
else None)
4459 href
="javascript:cmk.valuespecs.iconselector_toggle(\'%s\', \'%s\')" %
4460 (varprefix
, category_name
),
4461 id_
="%s_%s_nav" % (varprefix
, category_name
),
4462 class_
="%s_nav" % varprefix
)
4466 # Now render the icons grouped by category
4467 empty
= ['empty'] if self
._allow
_empty
else []
4468 for category_name
, category_alias
, icons
in available_icons
:
4470 id_
="%s_%s_container" % (varprefix
, category_name
),
4471 class_
=["icon_container", "%s_container" % varprefix
],
4472 style
="display:none;" if active_category
!= category_name
else None)
4474 for icon
in empty
+ sorted(icons
):
4478 onclick
='cmk.valuespecs.iconselector_select(event, \'%s\', \'%s\')' %
4483 html
.write_html(self
.render_icon(icon
, id_
=varprefix
+ '_i_' + icon
, title
=icon
))
4491 html
.open_div(class_
="buttons")
4496 onclick
="cmk.valuespecs.iconselector_toggle_names(event, %s)" % json
.dumps(varprefix
))
4498 import cmk
.gui
.config
as config
# FIXME: Clean this up. But how?
4499 if config
.user
.may('wato.icons'):
4500 back_param
= '&back=' + html
.urlencode(
4501 html
.get_url_input('back')) if html
.request
.has_var('back') else ''
4502 html
.buttonlink('wato.py?mode=icons' + back_param
, _('Manage'))
4508 def from_html_vars(self
, varprefix
):
4509 icon
= html
.request
.var(varprefix
+ '_value')
4514 def value_to_text(self
, value
):
4515 # TODO: This is a workaround for a bug. This function needs to return str objects right now.
4516 return "%s" % self
.render_icon(value
)
4518 def validate_datatype(self
, value
, varprefix
):
4519 if value
is not None and not isinstance(value
, str):
4520 raise MKUserError(varprefix
, _("The type is %s, but should be str") % type(value
))
4522 def validate_value(self
, value
, varprefix
):
4523 if not self
._allow
_empty
and not value
:
4524 raise MKUserError(varprefix
, _("You need to select an icon."))
4526 if value
and value
not in self
.available_icons():
4527 raise MKUserError(varprefix
, _("The selected icon image does not exist."))
4530 class ListOfTimeRanges(ListOf
):
4531 def __init__(self
, **kwargs
):
4532 super(ListOfTimeRanges
, self
).__init
__(
4538 add_label
=_("Add time range"),
4539 del_label
=_("Delete time range"),
4540 style
=ListOf
.Style
.FLOATING
,
4545 # Kept for compatibility reasons (removed in 1.6)
4546 TimeofdayRanges
= ListOfTimeRanges
4549 class Fontsize(Float
):
4550 def __init__(self
, **kwargs
):
4551 kwargs
.setdefault("title", _("Font size"))
4552 kwargs
.setdefault("default_value", 10)
4554 kwargs
["unit"] = _("pt")
4555 super(Fontsize
, self
).__init
__(**kwargs
)
4558 class Color(ValueSpec
):
4559 def __init__(self
, **kwargs
):
4560 kwargs
["regex"] = "#[0-9]{3,6}"
4561 kwargs
["regex_error"] = _("The color needs to be given in hex format.")
4562 ValueSpec
.__init
__(self
, **kwargs
)
4563 self
._on
_change
= kwargs
.get("on_change")
4564 self
._allow
_empty
= kwargs
.get("allow_empty", True)
4566 def render_input(self
, varprefix
, value
):
4567 self
.classtype_info()
4571 # Holds the actual value for form submission
4572 html
.hidden_field(varprefix
+ "_value", value
or '', varprefix
+ "_value", add_var
=True)
4574 indicator
= html
.render_div(
4576 id_
="%s_preview" % varprefix
,
4577 class_
="cp-preview",
4578 style
="background-color:%s" % value
)
4580 # TODO(rh): Please take a look at this hard coded HTML
4581 # FIXME: Rendering with HTML class causes bug in html popup_trigger function.
4582 # Reason is HTML class and the escaping.
4583 menu_content
= "<div id=\"%s_picker\" class=\"cp-small\"></div>" % varprefix
4584 menu_content
+= "<div class=\"cp-input\">" \
4586 "<input id=\"%s_input\" type=\"text\"></input></div>" % \
4587 (_("Hex color:"), varprefix
)
4589 menu_content
+= "<script language=\"javascript\">" \
4590 "cmk.valuespecs.add_color_picker(%s, %s)" \
4591 "</script>" % (json
.dumps(varprefix
), json
.dumps(value
))
4595 varprefix
+ '_popup',
4596 menu_content
=menu_content
,
4597 cssclass
="colorpicker",
4598 onclose
=self
._on
_change
)
4600 def from_html_vars(self
, varprefix
):
4601 color
= html
.request
.var(varprefix
+ '_value')
4606 def value_to_text(self
, value
):
4609 def validate_datatype(self
, value
, varprefix
):
4610 if value
is not None and not isinstance(value
, str):
4611 raise MKUserError(varprefix
, _("The type is %s, but should be str") % type(value
))
4613 def validate_value(self
, value
, varprefix
):
4614 if not self
._allow
_empty
and not value
:
4615 raise MKUserError(varprefix
, _("You need to select a color."))
4618 class SSHKeyPair(ValueSpec
):
4619 def __init__(self
, **kwargs
):
4620 ValueSpec
.__init
__(self
, **kwargs
)
4622 def render_input(self
, varprefix
, value
):
4623 self
.classtype_info()
4625 html
.write(_("Fingerprint: %s") % self
.value_to_text(value
))
4626 html
.hidden_field(varprefix
, self
._encode
_key
_for
_url
(value
), add_var
=True)
4628 html
.write(_("Key pair will be generated when you save."))
4630 def value_to_text(self
, value
):
4631 return self
._get
_key
_fingerprint
(value
)
4633 def from_html_vars(self
, varprefix
):
4634 if html
.request
.has_var(varprefix
):
4635 return self
._decode
_key
_from
_url
(html
.request
.var(varprefix
))
4636 return self
._generate
_ssh
_key
(varprefix
)
4639 def _encode_key_for_url(value
):
4640 return "|".join(value
)
4643 def _decode_key_from_url(text
):
4644 return text
.split("|")
4647 def _generate_ssh_key(cls
, varprefix
):
4648 key
= RSA
.generate(4096)
4649 private_key
= key
.exportKey('PEM')
4650 pubkey
= key
.publickey()
4651 public_key
= pubkey
.exportKey('OpenSSH')
4652 return (private_key
, public_key
)
4655 def _get_key_fingerprint(cls
, value
):
4656 _private_key
, public_key
= value
4657 key
= base64
.b64decode(public_key
.strip().split()[1].encode('ascii'))
4658 fp_plain
= hashlib
.md5(key
).hexdigest()
4659 return ':'.join(a
+ b
for a
, b
in zip(fp_plain
[::2], fp_plain
[1::2]))
4662 class SchedulePeriod(CascadingDropdown
):
4663 def __init__(self
, from_end
=True, **kwargs
):
4666 ("month_end", _("At the end of every month at day"),
4667 Integer(minvalue
=1, maxvalue
=28, unit
=_("from the end"))),
4670 from_end_choice
= []
4672 CascadingDropdown
.__init
__(
4675 orientation
="horizontal",
4677 ("day", _("Every day")),
4678 ("week", _("Every week on..."), Weekday(title
=_("Day of the week"))),
4679 ("month_begin", _("At the beginning of every month at day"),
4680 Integer(minvalue
=1, maxvalue
=28)),
4681 ] + from_end_choice
)
4684 class CAorCAChain(UploadOrPasteTextFile
):
4685 def __init__(self
, **args
):
4686 args
.setdefault("title", _("Certificate Chain (Root / Intermediate Certificate)"))
4687 args
.setdefault("file_title", _("CRT/PEM File"))
4688 UploadOrPasteTextFile
.__init
__(self
, **args
)
4690 def from_html_vars(self
, varprefix
):
4691 value
= Alternative
.from_html_vars(self
, varprefix
)
4692 if isinstance(value
, tuple):
4693 value
= value
[2] # FileUpload sends (filename, mime-type, content)
4696 def validate_value(self
, value
, varprefix
):
4698 self
.analyse_cert(value
)
4700 # FIXME TODO: Cleanup this general exception catcher
4701 raise MKUserError(varprefix
, _("Invalid certificate file"))
4703 def analyse_cert(self
, value
):
4704 from OpenSSL
import crypto
4705 cert
= crypto
.load_certificate(crypto
.FILETYPE_PEM
, value
)
4708 "ST": _("State or Province Name"),
4709 "L": _("Locality Name"),
4710 "O": _("Organization Name"),
4711 "CN": _("Common Name"),
4715 ("issuer", cert
.get_issuer()),
4716 ("subject", cert
.get_subject()),
4718 cert_info
[what
] = {}
4719 for key
, val
in x509
.get_components():
4721 cert_info
[what
][titles
[key
]] = val
.decode("utf8")
4724 def value_to_text(self
, value
):
4725 cert_info
= self
.analyse_cert(value
)
4727 for what
, title
in [
4728 ("issuer", _("Issuer")),
4729 ("subject", _("Subject")),
4731 text
+= "<tr><td>%s:</td><td>" % title
4732 for title1
, val
in sorted(cert_info
[what
].items()):
4733 text
+= "%s: %s<br>" % (title1
, val
)
4739 def ListOfCAs(**args
):
4740 args
.setdefault("title", _("CAs to accept"))
4743 _("Only accepting HTTPS connections with a server which certificate "
4744 "is signed with one of the CAs that are listed here. That way it is guaranteed "
4745 "that it is communicating only with the authentic update server. "
4746 "If you use self signed certificates for you server then enter that certificate "
4748 args
.setdefault("add_label", _("Add new CA certificate or chain"))
4751 _("You need to enter at least one CA. Otherwise no SSL connection can be made."))
4752 args
.setdefault("allow_empty", False)
4753 return ListOf(CAorCAChain(), movable
=False, **args
)
4756 class SiteChoice(DropdownChoice
):
4757 def __init__(self
, **kwargs
):
4758 kwargs
.setdefault("title", _("Site"))
4759 kwargs
.setdefault("help", _("Specify the site of your choice"))
4760 kwargs
.setdefault("default_value", self
._site
_default
_value
)
4761 kwargs
.setdefault("invalid_choice_error",
4762 _("The configured site is not known to this site."))
4765 "choices": self
._site
_choices
,
4766 "invalid_choice": "complain",
4767 "invalid_choice_title": _("Unknown site (%s)"),
4770 super(SiteChoice
, self
).__init
__(**kwargs
)
4772 def _site_default_value(self
):
4773 import cmk
.gui
.config
as config
4774 if config
.is_wato_slave_site():
4777 default_value
= config
.site_attribute_default_value()
4779 return default_value
4780 return self
.canonical_value()
4782 def _site_choices(self
):
4783 import cmk
.gui
.config
as config
# FIXME
4784 return config
.site_attribute_choices()
4787 class LogLevelChoice(DropdownChoice
):
4788 def __init__(self
, **kwargs
):
4789 kwargs
.setdefault("default_value", cmk
.utils
.log
.INFO
)
4792 (cmk
.utils
.log
.CRITICAL
, _("Critical")),
4793 (cmk
.utils
.log
.ERROR
, _("Error")),
4794 (cmk
.utils
.log
.WARNING
, _("Warning")),
4795 (cmk
.utils
.log
.INFO
, _("Informational")),
4796 (cmk
.utils
.log
.VERBOSE
, _("Verbose")),
4797 (cmk
.utils
.log
.DEBUG
, _("Debug")),
4800 super(LogLevelChoice
, self
).__init
__(**kwargs
)