コード例 #1
0
ファイル: werks.py プロジェクト: LinuxHaus/checkmk
    def page(self) -> cmk.gui.pages.PageResult:
        breadcrumb = make_simple_page_breadcrumb(
            mega_menu_registry["help_links"], _("Info"))
        make_header(
            html,
            self._title(),
            breadcrumb=breadcrumb,
        )

        html.open_div(id_="info_title")
        html.h1(_("Your monitoring machine"))
        html.a(
            HTMLWriter.render_img(theme.url("images/tribe29.svg")),
            "https://tribe29.com",
            target="_blank",
        )
        html.close_div()

        html.div(None, id_="info_underline")

        html.open_div(id_="info_intro_text")
        html.span(_("Open. Effective. Awesome."))
        html.span(
            _("May we present? Monitoring as it's supposed to be: "
              "incredibly quick to install, infinetely scalable, highly customizable and "
              "designed for admins."))
        html.span(
            _("Visit our %s to learn more about Checkmk and about the %s.") % (
                HTMLWriter.render_a(
                    _("website"), "https://checkmk.com", target="_blank"),
                HTMLWriter.render_a(
                    _("latest version"),
                    "https://checkmk.com/product/latest-version",
                    target="_blank",
                ),
            ))
        html.close_div()

        version_major_minor = re.sub(r".\d+$", "",
                                     Version(__version__).version_base)
        if version_major_minor:
            current_version_link = "https://checkmk.com/product/checkmk-%s" % version_major_minor
        else:
            current_version_link = "https://checkmk.com/product/latest-version"

        html.open_div(id="info_image")
        html.open_a(href=current_version_link, target="_blank")
        html.img(theme.url("images/monitoring-machine.png"))
        html.close_a()
        html.close_div()

        html.close_div()

        html.open_div(id_="info_footer")
        html.span(
            _("© %s tribe29 GmbH. All Rights Reserved.") % time.strftime("%Y"))
        html.a(_("License agreement"),
               href="https://checkmk.com/legal.html",
               target="_blank")
        html.close_div()
コード例 #2
0
ファイル: checkmk.py プロジェクト: LinuxHaus/checkmk
def _page_not_found() -> Response:
    # TODO: This is a page handler. It should not be located in generic application
    # object. Move it to another place
    if request.has_var("_plain_error"):
        html.write_text(_("Page not found"))
    else:
        title = _("Page not found")
        make_header(
            html,
            title,
            Breadcrumb([
                BreadcrumbItem(
                    title="Nowhere",
                    url=None,
                ),
                BreadcrumbItem(
                    title=title,
                    url="javascript:document.location.reload(false)",
                ),
            ]),
        )
        html.show_error(_("This page was not found. Sorry."))
    html.footer()

    return response
コード例 #3
0
ファイル: logwatch.py プロジェクト: LinuxHaus/checkmk
def show_log_list():
    title = _("All problematic logfiles")
    breadcrumb = make_simple_page_breadcrumb(
        mega_menu_registry.menu_monitoring(), title)
    make_header(html, title, breadcrumb, _log_list_page_menu(breadcrumb))

    if request.has_var("_ack") and not request.var("_do_actions") == _("No"):
        do_log_ack(site=None, host_name=None, file_name=None)
        return

    for site, host_name, logs in all_logs():
        if not logs:
            continue

        all_logs_empty = not any(
            parse_file(site, host_name, file_name) for file_name in logs)

        if all_logs_empty:
            continue  # Logfile vanished

        html.h3(
            HTMLWriter.render_a(
                host_name,
                href=makeuri(
                    request,
                    [("site", site), ("host", host_name)],
                ),
            ),
            class_="table",
        )
        list_logs(site, host_name, logs)
    html.footer()
コード例 #4
0
ファイル: __init__.py プロジェクト: LinuxHaus/checkmk
def page_add_snapin() -> None:
    if not user.may("general.configure_sidebar"):
        raise MKGeneralException(
            _("You are not allowed to change the sidebar."))

    title = _("Add sidebar element")
    breadcrumb = make_simple_page_breadcrumb(
        mega_menu_registry.menu_customize(), title)
    make_header(html, title, breadcrumb, _add_snapins_page_menu(breadcrumb))

    used_snapins = _used_snapins()

    html.open_div(class_=["add_snapin"])
    for name, snapin_class in sorted(snapin_registry.items()):
        if name in used_snapins:
            continue
        if not snapin_class.may_see():
            continue  # not allowed for this user

        html.open_div(
            class_="snapinadder",
            onmouseover="this.style.cursor='pointer';",
            onclick="window.top.cmk.sidebar.add_snapin('%s')" % name,
        )

        html.open_div(class_=["snapin_preview"])
        html.div("", class_=["clickshield"])
        SidebarRenderer().render_snapin(
            UserSidebarSnapin.from_snapin_type_id(name))
        html.close_div()
        html.div(snapin_class.description(), class_=["description"])
        html.close_div()

    html.close_div()
    html.footer()
コード例 #5
0
ファイル: activate_changes.py プロジェクト: LinuxHaus/checkmk
    def action(self) -> ActionResult:
        if request.var("_action") != "discard":
            return None

        if not transactions.check_transaction():
            return None

        if not self._may_discard_changes():
            return None

        if not self.has_changes():
            return None

        # Now remove all currently pending changes by simply restoring the last automatically
        # taken snapshot. Then activate the configuration. This should revert all pending changes.
        file_to_restore = self._get_last_wato_snapshot_file()

        if not file_to_restore:
            raise MKUserError(None,
                              _("There is no WATO snapshot to be restored."))

        msg = _("Discarded pending changes (Restored %s)") % file_to_restore

        # All sites and domains can be affected by a restore: Better restart everything.
        _changes.add_change(
            "changes-discarded",
            msg,
            domains=ABCConfigDomain.enabled_domains(),
            need_restart=True,
        )

        self._extract_snapshot(file_to_restore)
        activate_changes.execute_activate_changes([
            d.get_domain_request([])
            for d in ABCConfigDomain.enabled_domains()
        ])

        for site_id in activation_sites():
            self.confirm_site_changes(site_id)

        build_index_background()

        make_header(
            html,
            self.title(),
            breadcrumb=self.breadcrumb(),
            show_body_start=display_options.enabled(display_options.H),
            show_top_heading=display_options.enabled(display_options.T),
        )
        html.open_div(class_="wato")

        html.show_message(_("Successfully discarded all pending changes."))
        html.javascript("hide_changes_buttons();")
        html.footer()
        return FinalizeRequest(code=200)
コード例 #6
0
ファイル: notifications.py プロジェクト: LinuxHaus/checkmk
    def _show_page(self, acktime: float, failed_notifications: LivestatusResponse) -> None:
        title = _("Confirm failed notifications")
        breadcrumb = make_simple_page_breadcrumb(mega_menu_registry.menu_monitoring(), title)

        page_menu = self._page_menu(acktime, failed_notifications, breadcrumb)

        make_header(html, title, breadcrumb, page_menu)

        self._show_notification_table(failed_notifications)

        html.footer()
コード例 #7
0
 def show_topology(self, topology_settings: TopologySettings) -> None:
     visual_spec = ParentChildTopologyPage.visual_spec()
     breadcrumb = make_topic_breadcrumb(
         mega_menu_registry.menu_monitoring(),
         PagetypeTopics.get_topic(visual_spec["topic"]).title(),
     )
     breadcrumb.append(make_current_page_breadcrumb_item(visual_spec["title"]))
     page_menu = PageMenu(breadcrumb=breadcrumb)
     self._extend_display_dropdown(page_menu, visual_spec["name"])
     make_header(html, visual_spec["title"], breadcrumb, page_menu)
     self.show_topology_content(topology_settings=topology_settings)
コード例 #8
0
    def page(self) -> None:
        title = _("Replicate user profile")
        breadcrumb = make_simple_page_breadcrumb(
            mega_menu_registry.menu_user(), title)
        make_header(html, title, breadcrumb, self._page_menu(breadcrumb))

        for message in get_flashed_messages():
            html.show_message(message)

        # Now, if in distributed environment where users can login to remote sites, set the trigger for
        # pushing the new user profile to the remote sites asynchronously
        user_profile_async_replication_page(
            back_url=request.get_url_input("back", "user_profile.py"))
コード例 #9
0
ファイル: werks.py プロジェクト: LinuxHaus/checkmk
def page_werk():
    load_werks()
    werk_id = request.get_integer_input_mandatory("werk")
    if werk_id not in g_werks:
        raise MKUserError("werk", _("This werk does not exist."))
    werk = g_werks[werk_id]

    title = ("%s %s - %s") % (_("Werk"), render_werk_id(
        werk, with_link=False), werk["title"])

    breadcrumb = make_main_menu_breadcrumb(mega_menu_registry["help_links"])
    breadcrumb.append(
        BreadcrumbItem(
            title=_("Change log (Werks)"),
            url="change_log.py",
        ))
    breadcrumb.append(make_current_page_breadcrumb_item(title))
    make_header(html, title, breadcrumb, _page_menu_werk(breadcrumb, werk))

    html.open_table(class_=["data", "headerleft", "werks"])

    def werk_table_row(caption, content, css=None):
        html.open_tr()
        html.th(caption)
        html.td(content, class_=css)
        html.close_tr()

    translator = cmk.utils.werks.WerkTranslator()
    werk_table_row(_("ID"), render_werk_id(werk, with_link=False))
    werk_table_row(_("Title"), HTMLWriter.render_b(render_werk_title(werk)))
    werk_table_row(_("Component"), translator.component_of(werk))
    werk_table_row(_("Date"), render_werk_date(werk))
    werk_table_row(_("Checkmk Version"), werk["version"])
    werk_table_row(_("Level"),
                   translator.level_of(werk),
                   css="werklevel werklevel%d" % werk["level"])
    werk_table_row(_("Class"),
                   translator.class_of(werk),
                   css="werkclass werkclass%s" % werk["class"])
    werk_table_row(
        _("Compatibility"),
        translator.compatibility_of(werk),
        css="werkcomp werkcomp%s" % werk["compatible"],
    )
    werk_table_row(_("Description"),
                   render_werk_description(werk),
                   css="nowiki")

    html.close_table()

    html.footer()
コード例 #10
0
def show_create_view_dialog(next_url=None):
    vs_ds = DatasourceSelection()

    ds = "services"  # Default selection

    title = _("Create view")
    breadcrumb = visuals.visual_page_breadcrumb("views", title, "create")
    make_header(
        html,
        title,
        breadcrumb,
        make_simple_form_page_menu(
            _("View"),
            breadcrumb,
            form_name="create_view",
            button_name="_save",
            save_title=_("Continue"),
        ),
    )

    if request.var("_save") and transactions.check_transaction():
        try:
            ds = vs_ds.from_html_vars("ds")
            vs_ds.validate_value(ds, "ds")

            if not next_url:
                next_url = makeuri(
                    request,
                    [("datasource", ds)],
                    filename="create_view_infos.py",
                )
            else:
                next_url = next_url + "&datasource=%s" % ds
            raise HTTPRedirect(next_url)
        except MKUserError as e:
            html.user_error(e)

    html.begin_form("create_view")
    html.hidden_field("mode", "create")

    forms.header(_("Select Datasource"))
    forms.section(vs_ds.title())
    vs_ds.render_input("ds", ds)
    html.help(vs_ds.help())
    forms.end()

    html.hidden_fields()
    html.end_form()
    html.footer()
コード例 #11
0
ファイル: logwatch.py プロジェクト: LinuxHaus/checkmk
def show_host_log_list(site, host_name):
    title = _("Logfiles of host %s") % host_name
    breadcrumb = _host_log_list_breadcrumb(host_name, title)
    make_header(html, title, breadcrumb,
                _host_log_list_page_menu(breadcrumb, site, host_name))

    if request.has_var("_ack") and not request.var("_do_actions") == _("No"):
        do_log_ack(site, host_name, file_name=None)
        return

    html.open_table(class_=["data"])
    list_logs(site, host_name, logfiles_of_host(site, host_name))
    html.close_table()

    html.footer()
コード例 #12
0
ファイル: checkmk.py プロジェクト: LinuxHaus/checkmk
def _render_exception(e: Exception, title: str) -> Response:
    if plain_error():
        return Response(
            response=[
                "%s%s\n" % (("%s: " % title) if title else "", e),
            ],
            mimetype="text/plain",
        )

    if not fail_silently():
        make_header(html, title, Breadcrumb())
        html.show_error(str(e))
        html.footer()

    return response
コード例 #13
0
def _bi_map() -> None:
    aggr_name = request.var("aggr_name")
    layout_id = request.var("layout_id")
    title = _("BI visualization")
    breadcrumb = make_simple_page_breadcrumb(mega_menu_registry.menu_monitoring(), title)
    make_header(html, title, breadcrumb)
    div_id = "node_visualization"
    html.div("", id=div_id)
    html.javascript(
        "node_instance = new cmk.node_visualization.BIVisualization(%s);" % json.dumps(div_id)
    )

    html.javascript(
        "node_instance.show_aggregations(%s, %s)" % (json.dumps([aggr_name]), json.dumps(layout_id))
    )
コード例 #14
0
ファイル: abstract_page.py プロジェクト: LinuxHaus/checkmk
    def page(self) -> None:
        title = self._page_title()
        breadcrumb = self._breadcrumb()
        make_header(html, title, breadcrumb, self._page_menu(breadcrumb))

        if transactions.check_transaction():
            try:
                self._action()
            except MKUserError as e:
                user_errors.add(e)

        for message in get_flashed_messages():
            html.show_message(message)

        html.show_user_errors()

        self._show_form()
コード例 #15
0
ファイル: crash_reporting.py プロジェクト: LinuxHaus/checkmk
    def page(self) -> None:
        row = self._get_crash_row()
        crash_info = self._get_crash_info(row)

        title = _("Crash report: %s") % self._crash_id
        breadcrumb = self._breadcrumb(title)
        make_header(html, title, breadcrumb,
                    self._page_menu(breadcrumb, crash_info))

        # Do not reveal crash context information to unauthenticated users or not permitted
        # users to prevent disclosure of internal information
        if not user.may("general.see_crash_reports"):
            html.show_error("<b>%s:</b> %s" %
                            (_("Internal error"), crash_info["exc_value"]))
            html.p(
                _("An internal error occurred while processing your request. "
                  "You can report this issue to your Checkmk administrator. "
                  "Detailed information can be found on the crash report page "
                  "or in <tt>var/log/web.log</tt>."))
            html.footer()
            return

        if request.has_var("_report") and transactions.check_transaction():
            details = self._handle_report_form(crash_info)
        else:
            details = ReportSubmitDetails(name="", mail="")

        if crash_info["crash_type"] == "gui":
            html.show_error("<b>%s:</b> %s" %
                            (_("Internal error"), crash_info["exc_value"]))
            html.p(
                _("An internal error occured while processing your request. "
                  "You can report this issue to the Checkmk team to help "
                  "fixing this issue. Please use the form below for reporting."
                  ))

        self._warn_about_local_files(crash_info)
        self._show_report_form(crash_info, details)
        self._show_crash_report(crash_info)
        self._show_crash_report_details(crash_info, row)

        html.footer()
コード例 #16
0
ファイル: notifications.py プロジェクト: LinuxHaus/checkmk
    def page(self) -> None:
        acktime = request.get_float_input_mandatory("acktime", time.time())
        if request.var("_confirm"):
            _acknowledge_failed_notifications(acktime, time.time())

            if user.authorized_login_sites():
                title = _("Replicate user profile")
                breadcrumb = make_simple_page_breadcrumb(
                    mega_menu_registry.menu_monitoring(), title
                )
                make_header(html, title, breadcrumb)

                for message in get_flashed_messages():
                    html.show_message(message)
                user_profile_async_replication_page(back_url="clear_failed_notifications.py")
                return

        failed_notifications = load_failed_notifications(before=acktime, after=acknowledged_time())
        self._show_page(acktime, failed_notifications)
        if request.var("_confirm"):
            html.reload_whole_page()
コード例 #17
0
    def page(self) -> None:
        assert user.id is not None

        html.render_headfoot = False
        html.add_body_css_class("login")
        html.add_body_css_class("two_factor")
        make_header(html,
                    _("Two-factor authentication"),
                    Breadcrumb(),
                    javascripts=[])

        html.open_div(id_="login")

        html.open_div(id_="login_window")

        html.open_a(href="https://checkmk.com")
        html.img(
            src=theme.detect_icon_path(icon_name="logo", prefix="mk-"),
            id_="logo",
            class_="custom" if theme.has_custom_logo() else None,
        )
        html.close_a()

        if not is_two_factor_login_enabled(user.id):
            raise MKGeneralException(
                _("Two-factor authentication not enabled"))

        html.begin_form("two_factor_login",
                        method="POST",
                        add_transid=False,
                        action="user_login_two_factor.py")
        html.prevent_password_auto_completion()
        html.hidden_field(
            "_origtarget", origtarget :=
            request.get_url_input("_origtarget", "index.py"))

        if backup_code := request.get_ascii_input("_backup_code"):
            if is_two_factor_backup_code_valid(user.id, backup_code):
                set_two_factor_completed()
                raise HTTPRedirect(origtarget)
コード例 #18
0
ファイル: werks.py プロジェクト: LinuxHaus/checkmk
    def page(self) -> cmk.gui.pages.PageResult:
        breadcrumb = make_simple_page_breadcrumb(
            mega_menu_registry["help_links"], self._title())

        load_werks()
        werk_table_options = _werk_table_options_from_request()

        make_header(
            html,
            self._title(),
            breadcrumb,
            self._page_menu(breadcrumb, werk_table_options),
        )

        for message in get_flashed_messages():
            html.show_message(message)

        handle_acknowledgement()

        html.open_div(class_="wato")
        render_werks_table(werk_table_options)
        html.close_div()

        html.footer()
コード例 #19
0
ファイル: logwatch.py プロジェクト: LinuxHaus/checkmk
def show_file(site, host_name, file_name):
    int_filename = form_file_to_int(file_name)

    title = _("Logfiles of Host %s: %s") % (host_name, int_filename)
    breadcrumb = _show_file_breadcrumb(host_name, title)
    make_header(
        html, title, breadcrumb,
        _show_file_page_menu(breadcrumb, site, host_name, int_filename))

    if request.has_var("_ack") and not request.var("_do_actions") == _("No"):
        do_log_ack(site, host_name, file_name)
        return

    try:
        log_chunks = parse_file(site,
                                host_name,
                                int_filename,
                                hidecontext=request.var("_hidecontext",
                                                        "no") == "yes")
    except Exception as e:
        if active_config.debug:
            raise
        html.show_error(_("Unable to show logfile: <b>%s</b>") % e)
        html.footer()
        return

    if log_chunks is None:
        html.show_error(_("The logfile does not exist on site."))
        html.footer()
        return

    if log_chunks == []:
        html.show_message(
            _("This logfile contains no unacknowledged messages."))
        html.footer()
        return

    html.open_div(id_="logwatch")
    for log in log_chunks:
        html.open_table(class_="groupheader")
        html.open_tr()
        html.td(form_level(log["level"]), class_=form_level(log["level"]))
        html.td(form_datetime(log["datetime"]), class_="date")
        html.close_tr()
        html.close_table()

        html.open_table(class_=["section"])
        for line in log["lines"]:
            html.open_tr(class_=line["class"])
            html.open_td(class_="lines")
            html.icon_button(
                analyse_url(site, host_name, int_filename, line["line"]),
                _("Analyze this line"),
                "analyze",
            )
            html.write_text(line["line"].replace(" ", "&nbsp;").replace(
                "\1", "<br>"))
            html.close_td()
            html.close_tr()

        html.close_table()

    html.close_div()
    html.footer()
コード例 #20
0
 def page(self) -> None:
     breadcrumb = make_simple_page_breadcrumb(mega_menu_registry.menu_user(), _("Messages"))
     make_header(html, self.title(), breadcrumb, self.page_menu(breadcrumb))
     render_user_message_table("gui_hint")
コード例 #21
0
    def page(self) -> cmk.gui.pages.PageResult:
        """Renders an iframe to view the content of the RobotMK log file"""
        site_id, host_name, service_description = _get_mandatory_request_vars()

        breadcrumb: Breadcrumb = make_service_breadcrumb(
            HostName(host_name), service_description)
        title = self._title() + _(" of service %s on host %s") % (
            service_description, host_name)
        try:
            content = _get_html_from_livestatus(self._report_type(), site_id,
                                                host_name, service_description)
        except MKLivestatusNotFoundError:
            make_header(
                html,
                title=title,
                breadcrumb=breadcrumb,
            )
            html.user_error(
                MKUserError(None,
                            _("You are not permitted to view this page")))
            return

        if not content[0]:
            make_header(
                html,
                title=title,
                breadcrumb=breadcrumb,
            )
            html.user_error(MKUserError(None, _("No logs could be found.")))
            return

        # Only render page menu with download option if content is not empty
        # and user is permitted
        make_header(
            html,
            title=title,
            breadcrumb=breadcrumb,
            page_menu=self._page_menu(breadcrumb, site_id, host_name,
                                      service_description),
        )

        iframe: str = self._report_type()
        html.iframe(
            content="",
            src=makeuri_contextless(
                request,
                [
                    ("report_type", self._report_type()),
                    ("site", site_id),
                    ("host", host_name),
                    ("service", service_description),
                ],
                filename="robotmk_report.py",
            ),
            name="%s_report" % self._report_type(),
            id_=iframe,
        )

        html.javascript('cmk.utils.content_scrollbar("main_page_content");')
        html.javascript(
            "cmk.utils.add_height_to_simple_bar_content_of_iframe(%s);" %
            json.dumps(iframe))
コード例 #22
0
def page_graph():
    host_name = HostName(request.get_str_input_mandatory("host"))
    service = request.get_str_input_mandatory("service")
    dsname = request.get_str_input_mandatory("dsname")

    breadcrumb = make_service_breadcrumb(host_name, service)
    make_header(
        html,
        _("Prediction for %s - %s - %s") % (host_name, service, dsname),
        breadcrumb,
    )

    # Get current value from perf_data via Livestatus
    current_value = get_current_perfdata(host_name, service, dsname)

    prediction_store = prediction.PredictionStore(host_name, service, dsname)

    timegroup, choices = _load_prediction_information(
        tg_name=request.var("timegroup"),
        prediction_store=prediction_store,
    )

    html.begin_form("prediction")
    html.write_text(_("Show prediction for "))
    html.dropdown("timegroup",
                  choices,
                  deflt=timegroup.name,
                  onchange="document.prediction.submit();")
    html.hidden_fields()
    html.end_form()

    # Get prediction data
    tg_data = prediction_store.get_data(timegroup.name)
    if tg_data is None:
        raise MKGeneralException(_("Missing prediction data."))

    swapped = swap_and_compute_levels(tg_data, timegroup.params)
    vertical_range = compute_vertical_range(swapped)
    legend = [
        ("#000000", _("Reference")),
        ("#ffffff", _("OK area")),
        ("#ffff00", _("Warning area")),
        ("#ff0000", _("Critical area")),
    ]
    if current_value is not None:
        legend.append(("#0000ff", _("Current value: %.2f") % current_value))

    create_graph(timegroup.name, graph_size, timegroup.range, vertical_range,
                 legend)

    if "levels_upper" in timegroup.params:
        render_dual_area(swapped["upper_warn"], swapped["upper_crit"],
                         "#fff000", 0.4)
        render_area_reverse(swapped["upper_crit"], "#ff0000", 0.1)

    if "levels_lower" in timegroup.params:
        render_dual_area(swapped["lower_crit"], swapped["lower_warn"],
                         "#fff000", 0.4)
        render_area(swapped["lower_crit"], "#ff0000", 0.1)

    vscala_low = vertical_range[0]
    vscala_high = vertical_range[1]
    vert_scala = compute_vertical_scala(vscala_low, vscala_high)
    time_scala = [[timegroup.range[0] + i * 3600,
                   "%02d:00" % i] for i in range(0, 25, 2)]
    render_coordinates(vert_scala, time_scala)

    if "levels_lower" in timegroup.params:
        render_dual_area(swapped["average"], swapped["lower_warn"], "#ffffff",
                         0.5)
        render_curve(swapped["lower_warn"], "#e0e000", square=True)
        render_curve(swapped["lower_crit"], "#f0b0a0", square=True)

    if "levels_upper" in timegroup.params:
        render_dual_area(swapped["upper_warn"], swapped["average"], "#ffffff",
                         0.5)
        render_curve(swapped["upper_warn"], "#e0e000", square=True)
        render_curve(swapped["upper_crit"], "#f0b0b0", square=True)
    render_curve(swapped["average"], "#000000")
    render_curve(swapped["average"], "#000000")  # repetition makes line bolder

    # Try to get current RRD data and render it also
    from_time, until_time = timegroup.range
    now = time.time()
    if from_time <= now <= until_time:
        timeseries = prediction.get_rrd_data(host_name, service, dsname, "MAX",
                                             from_time, until_time)
        rrd_data = timeseries.values

        render_curve(rrd_data, "#0000ff", 2)
        if current_value is not None:
            rel_time = (now - prediction.timezone_at(now)) % timegroup.slice
            render_point(timegroup.range[0] + rel_time, current_value,
                         "#0000ff")

    html.footer()