def render_mobile_dataset(rows, view, group_cells, cells, num_columns, show_checkboxes): if not is_mobile(request, response): html.show_error(_("This view can only be used in mobile mode.")) return painter_options = PainterOptions.get_instance() painter_options.set("ts_format", "both") for row in rows: html.open_table(class_="dataset") for cell in cells: _tdclass, content = cell.render(row) if not content: continue # Omit empty cells html.open_tr(class_="header") html.th(cell.title()) html.close_tr() html.open_tr(class_="data") cell.paint(row) html.close_tr() html.close_table() html.javascript( '$("table.dataset > tbody > tr.data > td").addClass("ui-shadow").not(".state").addClass("nonstatus");\n' '$("table.dataset > tbody > tr.data a").attr("data-ajax", "false");\n')
def js_dashlet(self, figure_type_name: str) -> None: fetch_url = "ajax_figure_dashlet_data.py" div_id = "%s_dashlet_%d" % (self.type_name(), self._dashlet_id) html.div("", id_=div_id) # TODO: Would be good to align this scheme with AjaxPage.webapi_request() # (a single HTTP variable "request=<json-body>". post_body = urlencode_vars(self._dashlet_http_variables()) html.javascript( """ let figure_%(dashlet_id)d = cmk.figures.figure_registry.get_figure(%(type_name)s); let %(instance_name)s = new figure_%(dashlet_id)d(%(div_selector)s); %(instance_name)s.set_post_url_and_body(%(url)s, %(body)s); %(instance_name)s.initialize(); %(instance_name)s.scheduler.set_update_interval(%(update)d); %(instance_name)s.scheduler.enable(); """ % { "type_name": json.dumps(figure_type_name), "dashlet_id": self._dashlet_id, "instance_name": self.instance_name, "div_selector": json.dumps("#%s" % div_id), "url": json.dumps(fetch_url), "body": json.dumps(post_body), "update": self.update_interval, })
def show(self): html.open_div(class_="speedometer") html.img(theme.url("images/speedometer.svg"), id_="speedometerbg") html.canvas("", width=str(snapin_width), height="146", id_="speedometer") html.close_div() html.javascript("cmk.sidebar.speedometer_show_speed(0, 0, 0);")
def inpage_search_form(mode: Optional[str] = None, default_value: str = "") -> None: form_name = "inpage_search_form" reset_button_id = "%s_reset" % form_name was_submitted = request.get_ascii_input("filled_in") == form_name html.begin_form(form_name, add_transid=False) html.text_input( "search", size=32, default_value=default_value, placeholder=_("Filter"), required=True, title="", ) html.hidden_fields() if mode: html.hidden_field("mode", mode, add_var=True) reset_url = request.get_ascii_input_mandatory("reset_url", requested_file_with_query(request)) html.hidden_field("reset_url", reset_url, add_var=True) html.button("submit", "", cssclass="submit", help_=_("Apply")) html.buttonlink(reset_url, "", obj_id=reset_button_id, title=_("Reset")) html.end_form() html.javascript( "cmk.page_menu.inpage_search_init(%s, %s)" % (json.dumps(reset_button_id), json.dumps(was_submitted)) )
def create_graph(name, size, bounds, v_range, legend): html.open_table(class_="prediction") html.open_tr() html.open_td() html.canvas( "", class_="prediction", id_="content_%s" % name, style="width: %dpx; height: %dpx;" % (int(size[0] / 2.0), int(size[1] / 2.0)), width=size[0], height=size[1], ) html.close_td() html.close_tr() html.open_tr() html.open_td(class_="legend") for color, title in legend: html.div("", class_="color", style="background-color: %s" % color) html.div(title, class_="entry") html.close_td() html.close_tr() html.close_table() html.javascript( 'cmk.prediction.create_graph("content_%s", %.4f, %.4f, %.4f, %.4f);' % (name, bounds[0], bounds[1], v_range[0], v_range[1]))
def _show_command_form(datasource: ABCDataSource, rows: Rows) -> None: what = datasource.infos[0] html.javascript( """ $(document).ready(function() { $('.command_group').has('x').trigger('expand'); $('x').children().css('background-color', '#f84'); }); """ ) one_shown = False html.open_div(**{"data-role": "collapsible-set"}) for command_class in command_registry.values(): command = command_class() if what in command.tables and user.may(command.permission.name): html.open_div(class_=["command_group"], **{"data-role": "collapsible"}) html.h3(command.title) html.open_p() html.begin_form("actions") html.hidden_field("_do_actions", "yes") html.hidden_field("actions", "yes") command.render(what) html.hidden_fields() html.end_form() html.close_p() html.close_div() one_shown = True html.close_div() if not one_shown: html.write_text(_("No commands are possible in this view"))
def init_rowselect(selection_key: str) -> None: selected = user.get_rowselection(weblib.selection_id(), selection_key) selection_properties = { "page_id": selection_key, "selection_id": weblib.selection_id(), "selected_rows": selected, } html.javascript("cmk.selection.init_rowselect(%s);" % (json.dumps(selection_properties)))
def render_input(self, varprefix: str, value: CascadingDropdownChoiceValue) -> None: super().render_input(varprefix, value) root_prefix = varprefix[: varprefix.find(self._vs_name)] metric_ref_prefix = root_prefix + self._metric_vs_name # This will load an event listener between the unit and the metric valuespec html.javascript( "cmk.valuespecs.update_unit_selector(%s, %s)" % (json.dumps(varprefix), json.dumps(metric_ref_prefix)) )
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)
def show_topology_content(self, topology_settings: TopologySettings) -> None: div_id = "node_visualization" html.div("", id_=div_id) html.javascript( "topology_instance = new cmk.node_visualization.TopologyVisualization(%s);" % json.dumps(div_id) ) html.javascript( "topology_instance.show_topology(%s)" % json.dumps(TopologySettingsJSON(**asdict(topology_settings)).to_json()) )
def _show_diagnose_output(self): if not request.var("_save"): html.show_message( _( "You can diagnose the connection to a specific host using this dialog. " "You can either test whether your current configuration is still working " "or investigate in which ways a host can be reached. Simply configure the " "connection options you like to try on the right side of the screen and " 'press the "Test" button. The results will be displayed here.' ) ) return if user_errors: html.show_user_errors() return # TODO: Insert any vs_host valuespec validation # These tests can be called with invalid valuespec settings... # TODO: Replace hard coded icon paths with dynamic ones to old or new theme for ident, title in ModeDiagHost.diag_host_tests(): html.h3(title) html.open_table(class_=["data", "test"]) html.open_tr(class_=["data", "odd0"]) html.open_td(class_="icons") html.open_div() html.icon("reload", id_="%s_img" % ident) html.open_a(href="") html.icon( "reload", title=_("Retry this test"), cssclass="retry", id_="%s_retry" % ident ) html.close_a() html.close_div() html.close_td() html.open_td() html.div("", class_="log", id="%s_log" % ident) html.close_td() html.close_tr() html.close_table() html.javascript( "cmk.host_diagnose.start_test(%s, %s, %s)" % ( json.dumps(ident), json.dumps(self._hostname), json.dumps(transactions.fresh_transid()), ) )
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)) )
def user_profile_async_replication_dialog(sites: Sequence[SiteId], back_url: str) -> None: html.p( _("In order to activate your changes available on all remote sites, your user profile needs " "to be replicated to the remote sites. This is done on this page now. Each site " "is being represented by a single image which is first shown gray and then fills " "to green during synchronisation.")) html.h3(_("Replication States")) html.open_div(id_="profile_repl") num_replsites = 0 for site_id in sites: site = active_config.sites[site_id] if "secret" not in site: status_txt = _("Not logged in.") start_sync = False icon = "repl_locked" else: status_txt = _("Waiting for replication to start") start_sync = True icon = "repl_pending" html.open_div(class_="site", id_="site-%s" % site_id) html.div("", title=status_txt, class_=["icon", "repl_status", icon]) if start_sync: changes_manager = ActivateChanges() changes_manager.load() estimated_duration = changes_manager.get_activation_time( site_id, ACTIVATION_TIME_PROFILE_SYNC, 2.0) html.javascript("cmk.profile_replication.start(%s, %d, %s);" % ( json.dumps(site_id), int(estimated_duration * 1000.0), json.dumps(_("Replication in progress")), )) num_replsites += 1 else: _add_profile_replication_change(site_id, status_txt) html.span(site.get("alias", site_id)) html.close_div() html.javascript("cmk.profile_replication.prepare(%d, %s);\n" % (num_replsites, json.dumps(back_url))) html.close_div()
def _show_snapin_bar(self, user_config: UserSidebarConfig) -> None: html.open_div( class_="scroll" if active_config.sidebar_show_scrollbar else None, id_="side_content") refresh_snapins, restart_snapins, static_snapins = self._show_snapins( user_config) self._show_add_snapin_button() html.close_div() html.javascript( "cmk.sidebar.initialize_sidebar(%0.2f, %s, %s, %s);\n" % ( active_config.sidebar_update_interval, json.dumps(refresh_snapins), json.dumps(restart_snapins), json.dumps(static_snapins), ))
def render_mobile_list(rows, view, group_cells, cells, num_columns, show_checkboxes): if not is_mobile(request, response): html.show_error(_("This view can only be used in mobile mode.")) return # Force relative timestamp always. This saves space. painter_options = PainterOptions.get_instance() painter_options.set("ts_format", "rel") html.open_ul(class_="mobilelist", **{"data-role": "listview"}) # Paint data rows for row in rows: html.open_li() rendered_cells = [cell.render(row) for cell in cells] if rendered_cells: # First cell (assumedly state) is left rendered_class, rendered_content = rendered_cells[0] html.p(rendered_content, class_=["ui-li-aside", "ui-li-desc", rendered_class]) if len(rendered_cells) > 1: content = HTML(" · ").join([ rendered_cell[1] for rendered_cell in rendered_cells[1:num_columns + 1] ]) html.h3(content) for rendered_cell, cell in zip( rendered_cells[num_columns + 1:], cells[num_columns + 1:]): rendered_class, rendered_content = rendered_cell html.open_p(class_="ui-li-desc") cell.paint_as_header() html.write_text(": ") html.span(rendered_content, class_=rendered_class) html.close_p() html.close_li() html.close_ul() html.javascript('$("ul.mobilelist a").attr("data-ajax", "false");')
def show(self, menu: MegaMenu) -> None: more_id = "main_menu_" + menu.name show_more = user.get_show_more_setting(more_id) html.open_div(id_=more_id, class_=["main_menu", "more" if show_more else "less"]) hide_entries_js = "cmk.popup_menu.mega_menu_hide_entries('%s')" % more_id html.open_div(class_="navigation_bar") html.open_div(class_="search_bar") if menu.search: menu.search.show_search_field() html.close_div() if menu.info_line: html.span(menu.info_line(), id_="info_line_%s" % menu.name, class_="info_line") topics = menu.topics() if any_show_more_items(topics): html.open_div() html.more_button(id_=more_id, dom_levels_up=3, additional_js=hide_entries_js, with_text=True) html.close_div() html.close_div() html.open_div(class_="content inner", id="content_inner_%s" % menu.name) for topic in topics: if not topic.items: continue self._show_topic(topic, menu.name) html.close_div() html.close_div() html.javascript(hide_entries_js) html.javascript("cmk.popup_menu.initialize_mega_menus();") html.open_div(class_="content inner", id="content_inner_%s_search" % menu.name) html.close_div()
def render_mobile_table(rows, view, group_cells, cells, num_columns, show_checkboxes): if not is_mobile(request, response): html.show_error(_("This view can only be used in mobile mode.")) return # Force relative timestamp always. This saves space. painter_options = PainterOptions.get_instance() painter_options.set("ts_format", "rel") odd = "odd" html.open_table(class_="mobile data") # Paint header if view.get("column_headers") != "off": html.open_tr() n = 0 for cell in cells: cell.paint_as_header() html.close_tr() # Paint data rows for row in rows: odd = "even" if odd == "odd" else "odd" html.open_tr(class_="%s0" % odd) for n, cell in enumerate(cells): if n > 0 and n % num_columns == 0: html.close_tr() html.open_tr(class_="%s0" % odd) if n == len(cells) - 1 and n % num_columns != (num_columns - 1): colspan = num_columns - (n % num_columns) else: colspan = None cell.paint(row, colspan=colspan) html.close_row() html.close_table() html.javascript('$("table.mobile a").attr("data-ajax", "false");')
def _show_initial_iframe_container(self) -> None: iframe_url = self._get_iframe_url() if not iframe_url: return # Fix of iPad >:-P html.open_div( style="width: 100%; height: 100%; -webkit-overflow-scrolling:touch;" ) html.iframe( "", src="about:blank" if self.reload_on_resize() else iframe_url, id_="dashlet_iframe_%d" % self._dashlet_id, allowTransparency="true", frameborder="0", width="100%", height="100%", ) html.close_div() if self.reload_on_resize(): html.javascript( "cmk.dashboard.set_reload_on_resize(%s, %s);" % (json.dumps(self._dashlet_id), json.dumps(iframe_url)))
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))
def render_coordinates(v_scala, t_scala): html.javascript("cmk.prediction.render_coordinates(%s, %s);" % (json.dumps(v_scala), json.dumps(t_scala)))
def _handle_report_form(self, crash_info: CrashInfo) -> ReportSubmitDetails: details: ReportSubmitDetails try: vs = self._vs_crash_report() details = vs.from_html_vars("_report") vs.validate_value(details, "_report") # Make the resulting page execute the crash report post request url_encoded_params = urlencode_vars([ ("name", details["name"]), ("mail", details["mail"]), ( "crashdump", base64.b64encode( _pack_crash_report( self._get_serialized_crash_report())).decode( "ascii"), ), ]) html.open_div(id_="pending_msg", style="display:none") html.show_message(_("Submitting crash report...")) html.close_div() html.open_div(id_="success_msg", style="display:none") html.show_message( _("Your crash report has been submitted (ID: ###ID###). Thanks for your participation, " "it is very important for the quality of Checkmk.<br><br>" "Please note:" "<ul>" "<li>In general we do <i>not</i> respond to crash reports, " "except we need further information from you.</li>" "<li>We read every feedback thoroughly, but this might happen " "not before a couple of weeks or even months have passed and is " "often aligned with our release cycle.</li>" "<li>If you are in need of a quick solution for your problem, then " "we can help you within the scope of professional support. If you " "already have a support contract, then please use your personal " "support email address to send us a mail refering to your crash " "report.<br>If you are interested in the details about support, " 'you find details on <a href="https://checkmk.com/' 'checkmk_support_contract.html" target="_blank">our website</a>.' )) html.close_div() html.open_div(id_="fail_msg", style="display:none") report_url = makeuri_contextless( request, [ ("subject", "Checkmk Crash Report - " + self._get_version()), ], filename="mailto:" + self._get_crash_report_target(), ) html.show_error( _("Failed to send the crash report. Please download it manually and send it " 'to <a href="%s">%s</a>') % (report_url, self._get_crash_report_target())) html.close_div() html.javascript("cmk.transfer.submit_crash_report(%s, %s);" % (json.dumps(active_config.crash_report_url), json.dumps(url_encoded_params))) except MKUserError as e: user_errors.add(e) return details
def render_point(t, v, color): html.javascript("cmk.prediction.render_point(%s, %s, %s);" % (json.dumps(t), json.dumps(v), json.dumps(color)))
def render_curve(points, color, width=1, square=False): html.javascript( "cmk.prediction.render_curve(%s, %s, %d, %d);" % (json.dumps(points), json.dumps(color), width, square and 1 or 0))
def render_area_reverse(points, color, alpha=1.0): html.javascript("cmk.prediction.render_area_reverse(%s, %s, %f);" % (json.dumps(points), json.dumps(color), alpha))
def show(self): html.div(_("Loading maps..."), class_="loading") html.javascript("cmk.sidebar.fetch_nagvis_snapin_contents()")
def _write_table( self, rows: TableRows, num_rows_unlimited: int, actions_enabled: bool, actions_visible: bool, search_term: Optional[str], ) -> None: if not self.options["omit_update_header"]: row_info = _("1 row") if len( rows) == 1 else _("%d rows") % num_rows_unlimited html.javascript("cmk.utils.update_row_info(%s);" % json.dumps(row_info)) table_id = self.id num_cols = self._get_num_cols(rows) empty_columns = self._get_empty_columns(rows, num_cols) if self.options["omit_empty_columns"]: num_cols -= len([v for v in empty_columns if v]) html.open_table(class_=["data", "oddeven"] + self.css) # If we have no group headers then paint the headers now if self.rows and not isinstance(self.rows[0], GroupHeader): self._render_headers( actions_enabled, actions_visible, empty_columns, ) if actions_enabled and actions_visible: html.open_tr(class_=["data", "even0", "actions"]) html.open_td(colspan=num_cols) if not html.in_form(): html.begin_form("%s_actions" % table_id) if request.has_var("_%s_sort" % table_id): html.open_div(class_=["sort"]) html.button("_%s_reset_sorting" % table_id, _("Reset sorting")) html.close_div() if not html.in_form(): html.begin_form("%s_actions" % table_id) html.hidden_fields() html.end_form() html.close_tr() for nr, row in enumerate(rows): # Intermediate header if isinstance(row, GroupHeader): # Show the header only, if at least one (non-header) row follows if nr < len(rows) - 1 and not isinstance( rows[nr + 1], GroupHeader): html.open_tr(class_="groupheader") html.open_td(colspan=num_cols) html.h3(row.title) html.close_td() html.close_tr() self._render_headers(actions_enabled, actions_visible, empty_columns) continue oddeven_name = "even" if nr % 2 == 0 else "odd" class_ = ["data", "%s%d" % (oddeven_name, row.state)] if isinstance(row.css, list): class_.extend([c for c in row.css if c is not None]) elif row.css is not None: class_.append(row.css) html.open_tr(class_=class_, id_=row.id_, onmouseover=row.onmouseover, onmouseout=row.onmouseout) for col_index, cell in enumerate(row.cells): if self.options["omit_empty_columns"] and empty_columns[ col_index]: continue html.td(cell.content, class_=cell.css, colspan=cell.colspan) html.close_tr() if not rows and search_term: html.open_tr(class_=["data", "odd0", "no_match"]) html.td( _("Found no matching rows. Please try another search term."), colspan=num_cols) html.close_tr() html.close_table()
def update(self): self._show_view_as_dashlet(self._get_view_spec()) html.javascript( 'cmk.utils.add_simplebar_scrollbar("dashlet_content_wrapper");')
def render_dual_area(lower_points, upper_points, color, alpha=1.0): html.javascript("cmk.prediction.render_dual_area(%s, %s, %s, %f);" % (json.dumps(lower_points), json.dumps(upper_points), json.dumps(color), alpha))
class UserLoginTwoFactor(Page): 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) html.label( _("Two-factor authentication"), for_="webauthn_message", id_="label_2fa", class_="legend", ) html.div("", id_="webauthn_message") with foldable_container( treename="webauthn_backup_codes", id_="backup_container", isopen=False, title=_("Use backup code"), indent=False, save_state=False, ): html.label( "%s:" % _("Backup code"), id_="label_pass", class_=["legend"], for_="_backup_code", ) html.br() html.password_input("_backup_code", id_="input_pass", size=None) html.open_div(id_="button_text") html.button("_use_backup_code", _("Use backup code"), cssclass="hot") html.close_div() html.close_div() if user_errors: html.open_div(id_="login_error") html.show_user_errors() html.close_div() html.javascript("cmk.webauthn.login()") html.hidden_fields() html.end_form() html.close_div() html.footer()
def _activation_status(self): with table_element( "site-status", title=_("Activation status"), searchable=False, sortable=False, css="activation", foldable=Foldable.FOLDABLE_STATELESS, ) as table: for site_id, site in sort_sites(activation_sites()): table.row() site_status, status = self._get_site_status(site_id, site) is_online = self._site_is_online(status) is_logged_in = self._site_is_logged_in(site_id, site) has_foreign = self._site_has_foreign_changes(site_id) can_activate_all = not has_foreign or user.may( "wato.activateforeign") # Disable actions for offline sites and not logged in sites if not is_online or not is_logged_in: can_activate_all = False need_restart = self._is_activate_needed(site_id) need_sync = self.is_sync_needed(site_id) need_action = need_restart or need_sync nr_changes = len(self._changes_of_site(site_id)) # Activation checkbox table.cell("", css=["buttons"]) if can_activate_all and nr_changes: html.checkbox("site_%s" % site_id, need_action, cssclass="site_checkbox") # Iconbuttons table.cell(_("Actions"), css=["buttons"]) if user.may("wato.sites"): edit_url = folder_preserving_link([("mode", "edit_site"), ("site", site_id)]) html.icon_button(edit_url, _("Edit the properties of this site"), "edit") # State if can_activate_all and need_sync: html.icon_button( url="javascript:void(0)", id_="activate_%s" % site_id, cssclass="activate_site", title= _("This site is not update and needs a replication. Start it now." ), icon="need_replicate", onclick='cmk.activation.activate_changes("site", "%s")' % site_id, ) if can_activate_all and need_restart: html.icon_button( url="javascript:void(0)", id_="activate_%s" % site_id, cssclass="activate_site", title= _("This site needs a restart for activating the changes. Start it now." ), icon="need_restart", onclick='cmk.activation.activate_changes("site", "%s")' % site_id, ) if can_activate_all and not need_action: html.icon("siteuptodate", _("This site is up-to-date.")) site_url = site.get("multisiteurl") if site_url: html.icon_button( site_url, _("Open this site's local web user interface"), "url", target="_blank", ) table.cell(_("Site"), site.get("alias", site_id), css=["narrow nobr"]) # Livestatus table.cell(_("Status"), css=["narrow nobr"]) html.status_label(content=status, status=status, title=_("This site is %s") % status) # Livestatus-/Checkmk-Version table.cell(_("Version"), site_status.get("livestatus_version", ""), css=["narrow nobr"]) table.cell(_("Changes"), "%d" % nr_changes, css=["number narrow nobr"]) table.cell(_("Progress"), css=["repprogress"]) html.open_div(id_="site_%s_status" % site_id, class_=["msg"]) html.close_div() html.open_div(id_="site_%s_progress" % site_id, class_=["progress"]) html.close_div() table.cell(_("Details"), css=["details"]) html.open_div(id_="site_%s_details" % site_id) last_state = self._last_activation_state(site_id) if not is_logged_in: html.write_text(_("Is not logged in.") + " ") if not last_state: html.write_text(_("Has never been activated")) elif need_action and last_state[ "_state"] == activate_changes.STATE_SUCCESS: html.write_text(_("Activation needed")) else: html.javascript( "cmk.activation.update_site_activation_state(%s);" % json.dumps(last_state)) html.close_div()