Module: check_mk
Branch: master
Commit: 567ba5b9dd6c07175b690f62bf2ff1c87315ae7d
URL:
http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=567ba5b9dd6c07…
Author: Lars Michelsen <lm(a)mathias-kettner.de>
Date: Thu Feb 7 20:46:47 2019 +0100
Improve support for GUI themes
The available themes are now found dynamically
Each directory below "web/htdocs/themes/" that contains
a valid theme.json file which contains at least the following
structure is shown as theme choice:
{
"title": "Title of theme"
}
The same applies to the directory
<tt>local/share/check_mk/web/htdocs/themes/</tt>.
Each site may deploy custom themes or override existing ones using this
directory.
CMK-1567
Change-Id: If192c28119b82787613632c509746850564ed817
---
cmk/gui/config.py | 30 ++++++++++--
cmk/gui/htmllib.py | 8 ++-
tests/conftest.py | 1 +
tests/unit/cmk/gui/test_gui_config.py | 91 +++++++++++++++++++++++++++++++++++
web/htdocs/themes/facelift/theme.json | 3 ++
5 files changed, 128 insertions(+), 5 deletions(-)
diff --git a/cmk/gui/config.py b/cmk/gui/config.py
index e96a543..c5b1368 100644
--- a/cmk/gui/config.py
+++ b/cmk/gui/config.py
@@ -28,8 +28,10 @@ import sys
import errno
import os
import copy
+import json
from typing import Callable, Union, Tuple, Dict # pylint: disable=unused-import
import six
+from pathlib2 import Path
import cmk.gui.utils as utils
import cmk.gui.i18n
@@ -1197,10 +1199,30 @@ def load_plugins(force):
def theme_choices():
- return [
- ("classic", _("Classic")),
- ("facelift", _("Modern")),
- ]
+ themes = {
+ "classic": _("Classic"),
+ }
+
+ for base_dir in [Path(cmk.utils.paths.web_dir),
Path(cmk.utils.paths.local_web_dir)]:
+ if not base_dir.exists():
+ continue
+
+ for theme_dir in (base_dir / "htdocs" / "themes").iterdir():
# pylint: disable=no-member
+ meta_file = theme_dir / "theme.json"
+ if not meta_file.exists():
+ continue
+
+ try:
+ theme_meta =
json.loads(meta_file.open(encoding="utf-8").read())
+ except ValueError:
+ # Ignore broken meta files and show the directory name as title
+ theme_meta = {
+ "title": theme_dir.name,
+ }
+
+ themes[theme_dir.name] = theme_meta["title"]
+
+ return sorted(themes.items())
def get_page_heading():
diff --git a/cmk/gui/htmllib.py b/cmk/gui/htmllib.py
index 119eaae..082bd1d 100644
--- a/cmk/gui/htmllib.py
+++ b/cmk/gui/htmllib.py
@@ -1033,7 +1033,13 @@ class html(HTMLGenerator):
def set_theme(self, theme_id):
# type: (str) -> None
- self._theme = theme_id or config.ui_theme
+ if not theme_id:
+ theme_id = config.ui_theme
+
+ if theme_id not in dict(config.theme_choices()):
+ theme_id = "facelift"
+
+ self._theme = theme_id
def get_theme(self):
# type: () -> str
diff --git a/tests/conftest.py b/tests/conftest.py
index 14a357a..d8f47e5 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -114,6 +114,7 @@ def fake_version_and_paths():
monkeypatch.setattr("cmk.utils.paths.notifications_dir",
"%s/notifications" % cmk_path())
monkeypatch.setattr("cmk.utils.paths.inventory_dir",
"%s/inventory" % cmk_path())
monkeypatch.setattr("cmk.utils.paths.check_manpages_dir",
"%s/checkman" % cmk_path())
+ monkeypatch.setattr("cmk.utils.paths.web_dir", "%s/web" %
cmk_path())
monkeypatch.setattr("cmk.utils.paths.tmp_dir", tmp_dir)
monkeypatch.setattr("cmk.utils.paths.precompiled_checks_dir",
os.path.join(tmp_dir,
"var/check_mk/precompiled_checks"))
diff --git a/tests/unit/cmk/gui/test_gui_config.py
b/tests/unit/cmk/gui/test_gui_config.py
index 7e8ab57..a9d16d1 100644
--- a/tests/unit/cmk/gui/test_gui_config.py
+++ b/tests/unit/cmk/gui/test_gui_config.py
@@ -1,10 +1,15 @@
+# encoding: utf-8
+
+import json
import six
import pytest # type: ignore
from pathlib2 import Path
+import cmk.utils.paths
import cmk.gui.modules as modules
import cmk.gui.config as config
import cmk.gui.permissions as permissions
+from cmk.gui.globals import html
from cmk.gui.permissions import (
permission_section_registry,
permission_registry,
@@ -718,3 +723,89 @@ def test_permission_sorting(do_sort, result):
])
def test_migrate_old_site_config(site, result):
assert config.migrate_old_site_config({"mysite": site}) ==
{"mysite": result}
+
+
+(a)pytest.fixture()
+def theme_dirs(tmp_path, monkeypatch):
+ theme_path = tmp_path / "htdocs" / "themes"
+ theme_path.mkdir(parents=True)
+
+ local_theme_path = tmp_path / "local" / "htdocs" /
"themes"
+ local_theme_path.mkdir(parents=True)
+
+ monkeypatch.setattr(cmk.utils.paths, "web_dir", str(tmp_path))
+ monkeypatch.setattr(cmk.utils.paths, "local_web_dir", str(tmp_path /
"local"))
+
+ return theme_path, local_theme_path
+
+
+(a)pytest.fixture()
+def my_theme(theme_dirs):
+ theme_path = theme_dirs[0]
+ my_dir = theme_path / "my_theme"
+ my_dir.mkdir()
+ my_dir.joinpath("theme.json").open(
+ mode="w",
encoding="utf-8").write(unicode(json.dumps({"title": "Määh Theme
:-)"})))
+ return my_dir
+
+
+# TODO: Enable with next commit
+#def test_theme_choices_empty(theme_dirs):
+# assert config.theme_choices() == []
+#
+#def test_theme_choices_normal(my_theme):
+# assert config.theme_choices() == [("my_theme", u"Määh Theme
:-)")]
+
+
+def test_theme_choices_local_theme(theme_dirs, my_theme):
+ local_theme_path = theme_dirs[1]
+
+ my_dir = local_theme_path / "my_improved_theme"
+ my_dir.mkdir()
+ my_dir.joinpath("theme.json").open(
+ mode="w",
encoding="utf-8").write(unicode(json.dumps({"title": "Määh Bettr
Theme :-D"})))
+
+ assert config.theme_choices() == sorted([
+ # TODO: Remove classic with next commit
+ ('classic', u'Classic'),
+ ("my_theme", u"Määh Theme :-)"),
+ ("my_improved_theme", u"Määh Bettr Theme :-D"),
+ ])
+
+
+def test_theme_choices_override(theme_dirs, my_theme):
+ local_theme_path = theme_dirs[1]
+
+ my_dir = local_theme_path / "my_theme"
+ my_dir.mkdir()
+ my_dir.joinpath("theme.json").open(
+ mode="w",
encoding="utf-8").write(unicode(json.dumps({"title": "Fixed
theme"})))
+
+ assert config.theme_choices() == sorted([
+ # TODO: Remove classic with next commit
+ ('classic', u'Classic'),
+ ("my_theme", u"Fixed theme"),
+ ])
+
+
+def test_theme_broken_meta(my_theme):
+ my_theme.joinpath("theme.json").open(
+ mode="w",
encoding="utf-8").write(unicode("{\"titlewrong\":
xyz\"bla\"}"))
+
+ assert config.theme_choices() == sorted([
+ # TODO: Remove classic with next commit
+ ('classic', u'Classic'),
+ ("my_theme", u"my_theme"),
+ ])
+
+
+def test_html_set_theme(my_theme, register_builtin_html):
+ html.set_theme(None)
+ # TODO: Cleanup with next commit
+ assert html.get_theme() == "classic"
+
+ html.set_theme("not_existing")
+ assert html.get_theme() == "facelift"
+
+ html.set_theme("my_theme")
+ assert html.get_theme() == "my_theme"
diff --git a/web/htdocs/themes/facelift/theme.json
b/web/htdocs/themes/facelift/theme.json
new file mode 100644
index 0000000..5039953
--- /dev/null
+++ b/web/htdocs/themes/facelift/theme.json
@@ -0,0 +1,3 @@
+{
+ "title": "Modern"
+}