Repository: privacyidea/privacyidea

Issue:
prepare for an alternative or new UI
We can easily prepare the current privacyIDEA to be able to call a new UI.

We "simply" need to replave the index.html. (This is also mentioned in #605)

The [index.html](https://github.com/privacyidea/privacyidea/blob/master/privacyidea/webui/login.py#L186) is the one and only initial html template, that pull all CSS and pulls the javascript framework and thus the complete SPA (single page application).

We can make the index.html configurable in ``pi.cfg`` in ``PI_INDEX_HTML`` and revert to the default one.
This way everyone can work on a new UI with the existing version 3.6 simply by modifying the ``pi.cfg``.

Later we could also provide several different UIs. They could be installed separately and chosen by configuring ``pi.cfg``.

 


Discussion:

Resolving patch:
diff --git a/privacyidea/app.py b/privacyidea/app.py
--- a/privacyidea/app.py
+++ b/privacyidea/app.py
@@ -136,6 +136,10 @@ def create_app(config_name="development",
     # If this file does not exist, we create an error!
     app.config.from_envvar(ENV_KEY, silent=True)
 
+    # We allow to set different static folders
+    app.static_folder = app.config.get("PI_STATIC_FOLDER", "static/")
+    app.template_folder = app.config.get("PI_TEMPLATE_FOLDER", "static/templates/")
+
     app.register_blueprint(validate_blueprint, url_prefix='/validate')
     app.register_blueprint(token_blueprint, url_prefix='/token')
     app.register_blueprint(system_blueprint, url_prefix='/system')
@@ -222,4 +226,7 @@ def get_locale():
 
     queue.register_app(app)
 
+    logging.debug(u"Reading application from the static folder {0!s} and "
+                  u"the template folder {1!s}".format(app.static_folder, app.template_folder))
+
     return app
diff --git a/privacyidea/config.py b/privacyidea/config.py
--- a/privacyidea/config.py
+++ b/privacyidea/config.py
@@ -113,6 +113,12 @@ class TestingConfig(Config):
                        "resolver": "resolverX"}]
 
 
+class AltUIConfig(TestingConfig):
+    PI_INDEX_HTML = "testui.html"
+    PI_STATIC_FOLDER = "../tests/testdata/altstatic"
+    PI_TEMPLATE_FOLDER = "../tests/testdata/altstatic/templates"
+
+
 class ProductionConfig(Config):
     SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
         'sqlite:///' + os.path.join(basedir, 'data.sqlite')
@@ -154,5 +160,6 @@ class HerokuConfig(Config):
     'testing': TestingConfig,
     'production': ProductionConfig,
     'default': DevelopmentConfig,
-    'heroku': HerokuConfig
+    'heroku': HerokuConfig,
+    'altUI': AltUIConfig
 }
diff --git a/privacyidea/webui/login.py b/privacyidea/webui/login.py
--- a/privacyidea/webui/login.py
+++ b/privacyidea/webui/login.py
@@ -183,4 +183,5 @@ def single_page_application():
         'page_title': page_title
     }
 
-    return send_html(render_template("index.html", **render_context))
+    index_page = current_app.config.get("PI_INDEX_HTML") or "index.html"
+    return send_html(render_template(index_page, **render_context))


Test patch:
diff --git a/tests/test_ui_login.py b/tests/test_ui_login.py
--- a/tests/test_ui_login.py
+++ b/tests/test_ui_login.py
@@ -9,6 +9,31 @@
 from privacyidea.lib.policy import set_policy, SCOPE, ACTION, PolicyClass, delete_all_policies
 from privacyidea.lib.utils import to_unicode
 import re
+from privacyidea.app import create_app
+from privacyidea.models import db, save_config_timestamp
+
+
+class AlternativeWebUI(MyTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.app = create_app('altUI', "")
+        cls.app_context = cls.app.app_context()
+        cls.app_context.push()
+        db.create_all()
+        # save the current timestamp to the database to avoid hanging cached
+        # data
+        save_config_timestamp()
+        db.session.commit()
+
+    def test_01_normal_login(self):
+        # We just test, if the alterrnative page is called
+        with self.app.test_request_context('/',
+                                           method='GET'):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+            self.assertEqual(res.mimetype, 'text/html', res)
+            self.assertIn(b"This is an alternative UI", res.data)
 
 
 class LoginUITestCase(MyTestCase):
diff --git a/tests/testdata/altstatic/templates/testui.html b/tests/testdata/altstatic/templates/testui.html
new file mode 100644
--- /dev/null
+++ b/tests/testdata/altstatic/templates/testui.html
@@ -0,0 +1 @@
+<h1>This is an alternative UI!</h1>