diff --git a/src/common/tools/object_factory/Service.py b/src/common/tools/object_factory/Service.py
index 32b99a31f22072874ab894de2a87ce2b7d56ba85..b05821c7814ce250abca1819b111376af7c0430f 100644
--- a/src/common/tools/object_factory/Service.py
+++ b/src/common/tools/object_factory/Service.py
@@ -42,6 +42,16 @@ def json_service(
         'service_config'      : {'config_rules': copy.deepcopy(config_rules)},
     }
 
+def json_service_qkd_planned(
+        service_uuid : str, endpoint_ids : List[Dict] = [], constraints : List[Dict] = [],
+        config_rules : List[Dict] = [], context_uuid : str = DEFAULT_CONTEXT_NAME
+    ):
+
+    return json_service(
+        service_uuid, ServiceTypeEnum.SERVICETYPE_QKD, context_id=json_context_id(context_uuid),
+        status=ServiceStatusEnum.SERVICESTATUS_PLANNED, endpoint_ids=endpoint_ids, constraints=constraints,
+        config_rules=config_rules)
+
 def json_service_l2nm_planned(
         service_uuid : str, endpoint_ids : List[Dict] = [], constraints : List[Dict] = [],
         config_rules : List[Dict] = [], context_uuid : str = DEFAULT_CONTEXT_NAME
diff --git a/src/webui/service/app/__init__.py b/src/webui/service/app/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02
--- /dev/null
+++ b/src/webui/service/app/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/src/webui/service/app/routes.py b/src/webui/service/app/routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..752fc7e1530229608314383cbfc21567d9cd581c
--- /dev/null
+++ b/src/webui/service/app/routes.py
@@ -0,0 +1,113 @@
+# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import grpc, json, logging
+
+from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for
+from common.proto.context_pb2 import Empty, Link, LinkId, LinkList
+from common.proto.app_pb2 import App, QKDAppStatusEnum, QKDAppTypesEnum
+from common.tools.context_queries.Context import get_context
+from common.tools.context_queries.Device import get_device
+from common.tools.context_queries.Topology import get_topology
+from context.client.ContextClient import ContextClient
+from app.client.AppClient import AppClient
+
+
+LOGGER = logging.getLogger(__name__)
+app = Blueprint('app', __name__, url_prefix='/app')
+
+app_client = AppClient()
+context_client = ContextClient()
+
+@app.get('/')
+def home():
+    if 'context_uuid' not in session or 'topology_uuid' not in session:
+        flash("Please select a context!", "warning")
+        return redirect(url_for("main.home"))
+    context_uuid = session['context_uuid']
+    topology_uuid = session['topology_uuid']
+
+    context_client.connect()
+    device_names = dict()
+
+    context_obj = get_context(context_client, context_uuid, rw_copy=False)
+    if context_obj is None:
+        flash('Context({:s}) not found'.format(str(context_uuid)), 'danger')
+        apps = list()
+    else:
+        try:
+            apps = app_client.ListApps(context_obj.context_id)
+            apps = apps.apps
+        except grpc.RpcError as e:
+            if e.code() != grpc.StatusCode.NOT_FOUND: raise
+            if e.details() != 'Context({:s}) not found'.format(context_uuid): raise
+            apps = list()
+        else:
+            # Too many requests to context_client if it has too many apps (update in the future)
+            for app in apps:
+                if app.local_device_id.device_uuid.uuid not in device_names:
+                    device = get_device(context_client, app.local_device_id.device_uuid.uuid)
+                    if device is not None:
+                        device_names[app.local_device_id.device_uuid.uuid] = device.name
+                
+                if app.remote_device_id.device_uuid.uuid and app.remote_device_id.device_uuid.uuid not in device_names:
+                    device = get_device(context_client, app.remote_device_id.device_uuid.uuid)
+                    if device is not None:
+                        device_names[app.remote_device_id.device_uuid.uuid] = device.name
+
+    context_client.close()
+    return render_template(
+        'app/home.html', apps=apps, device_names=device_names, ate=QKDAppTypesEnum, ase=QKDAppStatusEnum)
+
+
+@app.route('detail/<path:app_uuid>', methods=('GET', 'POST'))
+def detail(app_uuid: str):
+    '''
+    context_client.connect()
+    link_obj = get_link(context_client, link_uuid, rw_copy=False)
+    if link_obj is None:
+        flash('Link({:s}) not found'.format(str(link_uuid)), 'danger')
+        link_obj = Link()
+        device_names, endpoints_data = dict(), dict()
+    else:
+        device_names, endpoints_data = get_endpoint_names(context_client, link_obj.link_endpoint_ids)
+    context_client.close()
+    return render_template('link/detail.html',link=link_obj, device_names=device_names, endpoints_data=endpoints_data)
+    '''
+    pass
+
+@app.get('<path:app_uuid>/delete')
+def delete(app_uuid):
+    '''
+    try:
+
+        # first, check if link exists!
+        # request: LinkId = LinkId()
+        # request.link_uuid.uuid = link_uuid
+        # response: Link = client.GetLink(request)
+        # TODO: finalize implementation
+
+        request = LinkId()
+        request.link_uuid.uuid = link_uuid # pylint: disable=no-member
+        context_client.connect()
+        context_client.RemoveLink(request)
+        context_client.close()
+
+        flash(f'Link "{link_uuid}" deleted successfully!', 'success')
+    except Exception as e: # pylint: disable=broad-except
+        flash(f'Problem deleting link "{link_uuid}": {e.details()}', 'danger')
+        current_app.logger.exception(e)
+    return redirect(url_for('link.home'))
+    '''
+    pass