Skip to content

Commit

Permalink
Add custom interface type
Browse files Browse the repository at this point in the history
To be able to integrate interface that not be build with the GeoMapGish
build chain.

Also be able to configure some static files: favicon.ico, robot.txt,
api.js, api.js.map, api.css, apihelp.html.
  • Loading branch information
sbrunner committed Aug 31, 2024
1 parent d17e40f commit 939645f
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 9 deletions.
1 change: 1 addition & 0 deletions doc/integrator/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This chapter describes advanced configuration settings.

build
runtime
interface
ngeo
backend
caching
Expand Down
79 changes: 79 additions & 0 deletions doc/integrator/interface.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.. _integrator_interface:

Introduction
------------

This chapter describes how to integrate a new ``custom`` interface in a c2cgeoportal application.

``custom`` means that the interface can be based on other frontend than ngeo event if at the end of this
document we will speak about how to integrate a ngeo as custom interface with a simple build chain.

Review
------

In the c2cgeoportal application we have by default 3 interfaces: desktop, mobile and iframe.

c2cgeoportal provide different things around the interface but they are not hard linked:

- The interface in the admin interface is a way to define the layers visible in the interface.
- The ``interfaces_config`` in the ``vars.yaml`` is a way to define the interface configuration.
- The ``interface`` in the ``vars.yaml`` is a way to configure the interfaces route in c2cgeoportal: ``/``,
``/theme/<theme>``, ``/<interface>`` and ``/<interface>/theme/<theme_name>``.

Configuration
-------------

Here we will describe how to add a new ``custom`` interface in a c2cgeoportal application,
for that we should add a new entry in the ``interface_config`` with the ``type`` set to ``custom``.

.. code:: yaml
vars:
interfaces_config:
custom:
type: custom
name: my_interface
We can also add an optional ``file`` attribute in the config to specify the file that should be used for the interface,
with relative (from ``/etc/static-frontend``) or absolute file name.

Interface integration
---------------------

To publish the interfaces que should provide the interfaces files (HTML, CSS, JavaScript, images, ...) in
the ``/etc/static-frontend/`` directory.

The interface files should be in a directory named with the interface name suffixed by ``.html``.

Note that this folder is also available on the ``/static-frontend/`` endpoint with cache headers without
any cash bustering, then the files (other than the interfaces HTML files) should contains an hash.

If you need cache bustering you should put your files in the ``geoportal/geomapfish_geoportal/static/``
directory (``/etc/geomapfish/static`` in the container).

The interface HTML file is considered as mako template and he can use the following variables:
- ``request``: the Pyramid request object.
- ``dynamicUrl``: the URL to get the interface configuration.
- ``interface``: the interface name.
- ``staticFrontend``: the URL to the static frontend directory.
- ``staticCashBuster``: the URL to the static cash buster directory.

For that you should create a Docker image that provide a ``/etc/static-frontend/`` volume (``VOLUME /etc/static-frontend``).

And include it in the ``docker-compose.yaml`` file with something like:

.. code:: yaml
services:
my_interface:
image: my_interface
user: www-data
geoportal:
volumes_from:
- my_interface:ro
Ngeo integration
----------------

TODO
65 changes: 59 additions & 6 deletions geoportal/c2cgeoportal_geoportal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
TotalPythonObjectMemoryCollector,
)
from c2cgeoportal_geoportal.lib.xsd import XSD
from c2cgeoportal_geoportal.views.entry import Entry, canvas_view
from c2cgeoportal_geoportal.views.entry import Entry, canvas_view, custom_view

if TYPE_CHECKING:
from c2cgeoportal_commons.models import static # pylint: disable=ungrouped-imports,useless-suppression
Expand Down Expand Up @@ -99,6 +99,7 @@ def __call__(self, value: Any, system: dict[str, str]) -> bytes:

INTERFACE_TYPE_NGEO = "ngeo"
INTERFACE_TYPE_CANVAS = "canvas"
INTERFACE_TYPE_CUSTOM = "custom"


def add_interface_config(config: pyramid.config.Configurator, interface_config: dict[str, Any]) -> None:
Expand Down Expand Up @@ -139,6 +140,15 @@ def add_interface(
interface_config=interface_config,
**kwargs,
)
elif interface_type == INTERFACE_TYPE_CUSTOM:
assert interface_config is not None
add_interface_custom(
config,
route_name=interface_name,
route=route,
interface_config=interface_config,
**kwargs,
)
else:
_LOG.error(
"Unknown interface type '%s', should be '%s' or '%s'.",
Expand Down Expand Up @@ -209,6 +219,39 @@ def add_interface_canvas(
)


def add_interface_custom(
config: pyramid.config.Configurator,
route_name: str,
route: str,
interface_config: dict[str, Any],
permission: Optional[str] = None,
) -> None:
"""Add the ngeo interfaces views and routes."""

renderer = os.path.join("/etc/static-frontend", interface_config.get("file", f"{route_name}.html"))
config.add_route(route_name, route, request_method="GET")
# Permalink theme: recover the theme for generating custom viewer.js URL
config.add_route(
f"{route_name}theme",
f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
request_method="GET",
)
view = partial(custom_view, interface_config=interface_config)
view.__module__ = custom_view.__module__
config.add_view(
view,
route_name=route_name,
renderer=renderer,
permission=permission,
)
config.add_view(
view,
route_name=f"{route_name}theme",
renderer=renderer,
permission=permission,
)


def add_admin_interface(config: pyramid.config.Configurator) -> None:
"""Add the administration interface views and routes."""

Expand Down Expand Up @@ -607,12 +650,17 @@ def add_static_route(name: str, attr: str, path: str, renderer: str) -> None:
config.add_route(name, path, request_method="GET")
config.add_view(Entry, attr=attr, route_name=name, renderer=renderer)

add_static_route("favicon", "favicon", "/favicon.ico", "/etc/geomapfish/static/images/favicon.ico")
add_static_route("robot.txt", "robot_txt", "/robot.txt", "/etc/geomapfish/static/robot.txt")
static_files = config.get_settings().get("static_files", {})
for name, attr, path in [
("favicon.ico", "favicon", "/favicon.ico"),
("robot.txt", "robot_txt", "/robot.txt"),
("api.js.map", "apijsmap", "/api.js.map"),
("api.css", "apicss", "/api.css"),
("apihelp.html", "apihelp", "/apihelp/index.html"),
]:
if static_files.get(name):
add_static_route(name, attr, path, static_files[name])
config.add_route("apijs", "/api.js", request_method="GET")
add_static_route("apijsmap", "apijsmap", "/api.js.map", "/etc/static-ngeo/api.js.map")
add_static_route("apicss", "apicss", "/api.css", "/etc/static-ngeo/api.css")
add_static_route("apihelp", "apihelp", "/apihelp/index.html", "/etc/geomapfish/static/apihelp/index.html")
c2cgeoportal_geoportal.views.add_redirect(config, "apihelp_redirect", "/apihelp.html", "apihelp")

config.add_route("themes", "/themes", request_method="GET", pregenerator=C2CPregenerator(role=True))
Expand Down Expand Up @@ -747,6 +795,11 @@ def add_static_route(name: str, attr: str, path: str, renderer: str) -> None:
path="/etc/static-ngeo",
cache_max_age=int(config.get_settings()["default_max_age"]),
)
config.add_static_view(
name="static-frontend",
path="/etc/static-frontend",
cache_max_age=int(config.get_settings()["default_max_age"]),
)

# Add the c2cgeoportal static view with cache buster
config.add_static_view(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ vars:
escapeValue: false
backend: {}

static_files:
favicon.ico: /etc/geomapfish/static/images/favicon.ico
robot.txt: /etc/geomapfish/static/robot.txt
api.js: /etc/static-ngeo/api.js
api.js.map: /etc/static-ngeo/api.js.map
api.css: /etc/static-ngeo/api.css
apihelp.html: /etc/geomapfish/static/apihelp/index.html

interfaces_config:
default:
constants:
Expand Down
24 changes: 21 additions & 3 deletions geoportal/c2cgeoportal_geoportal/views/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ def get_ngeo_index_vars(self) -> dict[str, Any]:

@staticmethod
@_CACHE_REGION.cache_on_arguments()
def get_apijs(api_name: Optional[str]) -> str:
with open("/etc/static-ngeo/api.js", encoding="utf-8") as api_file:
def get_apijs(api_filename: str, api_name: Optional[str]) -> str:
with open(api_filename, encoding="utf-8") as api_file:
api = api_file.read().split("\n")
sourcemap = api.pop(-1)
if api_name:
Expand All @@ -77,7 +77,10 @@ def get_apijs(api_name: Optional[str]) -> str:

@view_config(route_name="apijs") # type: ignore
def apijs(self) -> pyramid.response.Response:
self.request.response.text = self.get_apijs(self.request.registry.settings["api"].get("name"))
self.request.response.text = self.get_apijs(
self.request.registry.settings["static_files"]["api.js"],
self.request.registry.settings["api"].get("name"),
)
set_common_headers(self.request, "api", Cache.PUBLIC, content_type="application/javascript")
return self.request.response

Expand Down Expand Up @@ -138,3 +141,18 @@ def canvas_view(request: pyramid.request.Request, interface_config: dict[str, An
),
"spinner": spinner,
}


def custom_view(request: pyramid.request.Request, interface_config: dict[str, Any]) -> dict[str, Any]:
"""Get view used as entry point of a canvas interface."""

set_common_headers(request, "index", Cache.PUBLIC_NO, content_type="text/html")

dynamic_url = request.route_url("dynamic")
return {
"request": request,
"dynamicUrl": dynamic_url,
"interface": interface_config["name"],
"staticFrontend": request.static_url("/etc/static-frontend"),
"staticCashBuster": request.static_url("/etc/geomapfish/static"),
}

0 comments on commit 939645f

Please sign in to comment.