diff --git a/cli/fix/move_unnecessary_dependencies.py b/cli/fix/move_unnecessary_dependencies.py
index cbfaefcb..93d8843c 100644
--- a/cli/fix/move_unnecessary_dependencies.py
+++ b/cli/fix/move_unnecessary_dependencies.py
@@ -228,7 +228,7 @@ def parse_meta_dependencies(role_dir: str) -> List[str]:
def sanitize_run_once_var(role_name: str) -> str:
"""
Generate run_once variable name from role name.
- Example: 'sys-srv-web-inj-logout' -> 'run_once_sys_srv_web_inj_logout'
+ Example: 'sys-front-inj-logout' -> 'run_once_sys_front_inj_logout'
"""
return "run_once_" + role_name.replace("-", "_")
diff --git a/lookup_plugins/local_mtime_qs.py b/lookup_plugins/local_mtime_qs.py
new file mode 100644
index 00000000..6e8f4aa2
--- /dev/null
+++ b/lookup_plugins/local_mtime_qs.py
@@ -0,0 +1,53 @@
+from __future__ import annotations
+from ansible.plugins.lookup import LookupBase
+from ansible.errors import AnsibleError
+import os
+
+class LookupModule(LookupBase):
+ """
+ Return a cache-busting string based on the LOCAL file's mtime.
+
+ Usage (single path → string via Jinja):
+ {{ lookup('local_mtime_qs', '/path/to/file.css') }}
+ -> "?version=1712323456"
+
+ Options:
+ param (str): query parameter name (default: "version")
+ mode (str): "qs" (default) → returns "?="
+ "epoch" → returns ""
+
+ Multiple paths (returns list, one result per term):
+ {{ lookup('local_mtime_qs', '/a.js', '/b.js', param='v') }}
+ """
+
+ def run(self, terms, variables=None, **kwargs):
+ if not terms:
+ return []
+
+ param = kwargs.get('param', 'version')
+ mode = kwargs.get('mode', 'qs')
+
+ if mode not in ('qs', 'epoch'):
+ raise AnsibleError("local_mtime_qs: 'mode' must be 'qs' or 'epoch'")
+
+ results = []
+ for term in terms:
+ path = os.path.abspath(os.path.expanduser(str(term)))
+
+ # Fail fast if path is missing or not a regular file
+ if not os.path.exists(path):
+ raise AnsibleError(f"local_mtime_qs: file does not exist: {path}")
+ if not os.path.isfile(path):
+ raise AnsibleError(f"local_mtime_qs: not a regular file: {path}")
+
+ try:
+ mtime = int(os.stat(path).st_mtime)
+ except OSError as e:
+ raise AnsibleError(f"local_mtime_qs: cannot stat '{path}': {e}")
+
+ if mode == 'qs':
+ results.append(f"?{param}={mtime}")
+ else: # mode == 'epoch'
+ results.append(str(mtime))
+
+ return results
diff --git a/roles/categories.yml b/roles/categories.yml
index 2f8cba1a..ce4c5ecf 100644
--- a/roles/categories.yml
+++ b/roles/categories.yml
@@ -56,6 +56,16 @@ roles:
description: "Stack levels to setup the server"
icon: "fas fa-bars-staggered"
invokable: false
+ front:
+ title: "System Frontend Helpers"
+ description: "Frontend helpers for reverse-proxied apps (injection, shared assets, CDN plumbing)."
+ icon: "fas fa-wand-magic-sparkles"
+ invokable: false
+ inj:
+ title: "Injection"
+ description: "Composable HTML injection roles (CSS, JS, logout interceptor, analytics, desktop iframe) for Nginx/OpenResty via sub_filter/Lua with CDN-backed assets."
+ icon: "fas fa-filter"
+ invokable: false
update:
title: "Updates & Package Management"
description: "OS & package updates"
@@ -106,11 +116,6 @@ roles:
description: "General server roles for provisioning and managing server infrastructure—covering web servers, proxy servers, network services, and other backend components."
icon: "fas fa-server"
invokable: false
- web:
- title: "Webserver"
- description: "Web-server roles for installing and configuring Nginx (core, TLS, injection filters, composer modules)."
- icon: "fas fa-server"
- invokable: false
proxy:
title: "Proxy Server"
description: "Proxy-server roles for virtual-host orchestration and reverse-proxy setups."
diff --git a/roles/dev-locales/tasks/main.yml b/roles/dev-locales/tasks/main.yml
index 0c6ffc10..09c90c56 100644
--- a/roles/dev-locales/tasks/main.yml
+++ b/roles/dev-locales/tasks/main.yml
@@ -1,8 +1,14 @@
---
- name: Setup locale.gen
- template: src=locale.gen dest=/etc/locale.gen
+ template:
+ src: locale.gen.j2
+ dest: /etc/locale.gen
+
- name: Setup locale.conf
- template: src=locale.conf dest=/etc/locale.conf
+ template:
+ src: locale.conf.j2
+ dest: /etc/locale.conf
+
- name: Generate locales
shell: locale-gen
become: true
diff --git a/roles/dev-locales/templates/locale.conf b/roles/dev-locales/templates/locale.conf
deleted file mode 100644
index 456b20a7..00000000
--- a/roles/dev-locales/templates/locale.conf
+++ /dev/null
@@ -1,2 +0,0 @@
-LANG=en_US.UTF-8
-LANGUAGE=en_US.UTF-8
diff --git a/roles/dev-locales/templates/locale.conf.j2 b/roles/dev-locales/templates/locale.conf.j2
new file mode 100644
index 00000000..d5f9520d
--- /dev/null
+++ b/roles/dev-locales/templates/locale.conf.j2
@@ -0,0 +1,2 @@
+LANG={{ HOST_LL_CC }}.UTF-8
+LANGUAGE={{ HOST_LL_CC }}.UTF-8
diff --git a/roles/dev-locales/templates/locale.gen b/roles/dev-locales/templates/locale.gen.j2
similarity index 100%
rename from roles/dev-locales/templates/locale.gen
rename to roles/dev-locales/templates/locale.gen.j2
diff --git a/roles/srv-composer/README.md b/roles/srv-composer/README.md
index 637c7557..6cc85e03 100644
--- a/roles/srv-composer/README.md
+++ b/roles/srv-composer/README.md
@@ -2,7 +2,7 @@
This Ansible role composes and orchestrates all necessary HTTPS-layer tasks and HTML-content injections for your webserver domains. It integrates two key sub-roles into a unified workflow:
-1. **`sys-srv-web-inj-compose`**
+1. **`sys-front-inj-all`**
Injects global HTML snippets (CSS, Matomo tracking, iFrame notifier, custom JavaScript) into responses using Nginx `sub_filter`.
2. **`sys-svc-certs`**
Handles issuing, renewing, and managing TLS certificates via ACME/Certbot.
diff --git a/roles/srv-composer/tasks/main.yml b/roles/srv-composer/tasks/main.yml
index c5447e78..9bd3e06f 100644
--- a/roles/srv-composer/tasks/main.yml
+++ b/roles/srv-composer/tasks/main.yml
@@ -1,8 +1,8 @@
# run_once_srv_composer: deactivated
-- name: "include role sys-srv-web-inj-compose for '{{ domain }}'"
+- name: "include role sys-front-inj-all for '{{ domain }}'"
include_role:
- name: sys-srv-web-inj-compose
+ name: sys-front-inj-all
- name: "include role sys-svc-certs for '{{ domain }}'"
include_role:
diff --git a/roles/srv-proxy-core/templates/location/html.conf.j2 b/roles/srv-proxy-core/templates/location/html.conf.j2
index 73e96fec..1b02f204 100644
--- a/roles/srv-proxy-core/templates/location/html.conf.j2
+++ b/roles/srv-proxy-core/templates/location/html.conf.j2
@@ -35,6 +35,6 @@ location {{location}}
{% if proxy_lua_enabled %}
proxy_set_header Accept-Encoding "";
- {% include 'roles/sys-srv-web-inj-compose/templates/location.lua.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/location.lua.j2'%}
{% endif %}
}
\ No newline at end of file
diff --git a/roles/srv-proxy-core/templates/vhost/basic.conf.j2 b/roles/srv-proxy-core/templates/vhost/basic.conf.j2
index 2d4841c2..56372e97 100644
--- a/roles/srv-proxy-core/templates/vhost/basic.conf.j2
+++ b/roles/srv-proxy-core/templates/vhost/basic.conf.j2
@@ -7,7 +7,7 @@ server
{% include 'roles/web-app-oauth2-proxy/templates/endpoint.conf.j2'%}
{% endif %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% if proxy_extra_configuration is defined %}
{# Additional Domain Specific Configuration #}
diff --git a/roles/srv-proxy-core/templates/vhost/ws_generic.conf.j2 b/roles/srv-proxy-core/templates/vhost/ws_generic.conf.j2
index 5d819bf8..82b5ba86 100644
--- a/roles/srv-proxy-core/templates/vhost/ws_generic.conf.j2
+++ b/roles/srv-proxy-core/templates/vhost/ws_generic.conf.j2
@@ -8,7 +8,7 @@ server {
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2' %}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2' %}
client_max_body_size {{ client_max_body_size | default('100m') }};
keepalive_timeout 70;
diff --git a/roles/sys-srv-web-inj-compose/README.md b/roles/sys-front-inj-all/README.md
similarity index 100%
rename from roles/sys-srv-web-inj-compose/README.md
rename to roles/sys-front-inj-all/README.md
diff --git a/roles/sys-srv-web-inj-compose/__init__.py b/roles/sys-front-inj-all/__init__.py
similarity index 100%
rename from roles/sys-srv-web-inj-compose/__init__.py
rename to roles/sys-front-inj-all/__init__.py
diff --git a/roles/sys-srv-web-inj-compose/filter_plugins/__init__.py b/roles/sys-front-inj-all/filter_plugins/__init__.py
similarity index 100%
rename from roles/sys-srv-web-inj-compose/filter_plugins/__init__.py
rename to roles/sys-front-inj-all/filter_plugins/__init__.py
diff --git a/roles/sys-srv-web-inj-compose/filter_plugins/inj_enabled.py b/roles/sys-front-inj-all/filter_plugins/inj_enabled.py
similarity index 94%
rename from roles/sys-srv-web-inj-compose/filter_plugins/inj_enabled.py
rename to roles/sys-front-inj-all/filter_plugins/inj_enabled.py
index 5b1b031a..7e3c87ff 100644
--- a/roles/sys-srv-web-inj-compose/filter_plugins/inj_enabled.py
+++ b/roles/sys-front-inj-all/filter_plugins/inj_enabled.py
@@ -1,4 +1,3 @@
-# roles/sys-srv-web-inj-compose/filter_plugins/inj_enabled.py
#
# Usage in tasks:
# - set_fact:
diff --git a/roles/sys-srv-web-inj-compose/filter_plugins/inj_snippets.py b/roles/sys-front-inj-all/filter_plugins/inj_snippets.py
similarity index 79%
rename from roles/sys-srv-web-inj-compose/filter_plugins/inj_snippets.py
rename to roles/sys-front-inj-all/filter_plugins/inj_snippets.py
index 424cdf8f..285cc769 100644
--- a/roles/sys-srv-web-inj-compose/filter_plugins/inj_snippets.py
+++ b/roles/sys-front-inj-all/filter_plugins/inj_snippets.py
@@ -2,10 +2,10 @@
Jinja filter: `inj_features(kind)` filters a list of features to only those
that actually provide the corresponding snippet template file.
-- kind='head' -> roles/sys-srv-web-inj-/templates/head_sub.j2
-- kind='body' -> roles/sys-srv-web-inj-/templates/body_sub.j2
+- kind='head' -> roles/sys-front-inj-/templates/head_sub.j2
+- kind='body' -> roles/sys-front-inj-/templates/body_sub.j2
-If the feature's role directory (roles/sys-srv-web-inj-) does not
+If the feature's role directory (roles/sys-front-inj-) does not
exist, this filter raises FileNotFoundError.
Usage in a template:
@@ -15,13 +15,13 @@ Usage in a template:
import os
-# This file lives at: roles/sys-srv-web-inj-compose/filter_plugins/inj_snippets.py
+# This file lives at: roles/sys-front-inj-all/filter_plugins/inj_snippets.py
_THIS_DIR = os.path.dirname(__file__)
-_ROLE_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..")) # roles/sys-srv-web-inj-compose
+_ROLE_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..")) # roles/sys-front-inj-all
_ROLES_DIR = os.path.abspath(os.path.join(_ROLE_DIR, "..")) # roles
def _feature_role_dir(feature: str) -> str:
- return os.path.join(_ROLES_DIR, f"sys-srv-web-inj-{feature}")
+ return os.path.join(_ROLES_DIR, f"sys-front-inj-{feature}")
def _has_snippet(feature: str, kind: str) -> bool:
if kind not in ("head", "body"):
diff --git a/roles/sys-srv-web-inj-compose/meta/main.yml b/roles/sys-front-inj-all/meta/main.yml
similarity index 96%
rename from roles/sys-srv-web-inj-compose/meta/main.yml
rename to roles/sys-front-inj-all/meta/main.yml
index 3839faea..18f3f843 100644
--- a/roles/sys-srv-web-inj-compose/meta/main.yml
+++ b/roles/sys-front-inj-all/meta/main.yml
@@ -14,7 +14,7 @@ galaxy_info:
- theming
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
- documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-srv-web-inj-compose"
+ documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-front-inj-all"
min_ansible_version: "2.9"
platforms:
- name: Any
diff --git a/roles/sys-srv-web-inj-compose/tasks/main.yml b/roles/sys-front-inj-all/tasks/main.yml
similarity index 58%
rename from roles/sys-srv-web-inj-compose/tasks/main.yml
rename to roles/sys-front-inj-all/tasks/main.yml
index 91518243..72e2df03 100644
--- a/roles/sys-srv-web-inj-compose/tasks/main.yml
+++ b/roles/sys-front-inj-all/tasks/main.yml
@@ -2,39 +2,10 @@
set_fact:
inj_enabled: "{{ applications | inj_enabled(application_id, SRV_WEB_INJ_COMP_FEATURES_ALL) }}"
-- block:
- - name: Include dependency 'srv-core'
- include_role:
- name: srv-core
- when: run_once_srv_core is not defined
- - include_tasks: utils/run_once.yml
- when: run_once_sys_srv_web_inj_compose is not defined
-
-- name: "Activate Portfolio iFrame notifier for '{{ domain }}'"
+- name: "Load CDN Service for '{{ domain }}'"
include_role:
- name: sys-srv-web-inj-desktop
- public: true # Vars used in templates
- when: inj_enabled.desktop
-
-- name: "Load CDN for '{{ domain }}'"
- include_role:
- name: web-svc-cdn
- public: false
- when:
- - inj_enabled.logout
- - inj_enabled.desktop
- - application_id != 'web-svc-cdn'
- - run_once_web_svc_cdn is not defined
-
-- name: Overwritte CDN handlers with neutral handlers
- ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/utils/load_handlers.yml"
- loop:
- - svc-prx-openresty
- - docker-compose
- loop_control:
- label: "{{ item }}"
- vars:
- handler_role_name: "{{ item }}"
+ name: sys-svc-cdn
+ public: true # Expose variables so that they can be used in all injection roles
- name: Reinitialize 'inj_enabled' for '{{ domain }}', after modification by CDN
set_fact:
@@ -42,25 +13,37 @@
inj_head_features: "{{ SRV_WEB_INJ_COMP_FEATURES_ALL | inj_features('head') }}"
inj_body_features: "{{ SRV_WEB_INJ_COMP_FEATURES_ALL | inj_features('body') }}"
+- name: "Activate Desktop iFrame notifier for '{{ domain }}'"
+ include_role:
+ name: sys-front-inj-desktop
+ public: true # Vars used in templates
+ when: inj_enabled.desktop
+
- name: "Activate Corporate CSS for '{{ domain }}'"
include_role:
- name: sys-srv-web-inj-css
- when:
- - inj_enabled.css
- - run_once_sys_srv_web_inj_css is not defined
+ name: sys-front-inj-css
+ when: inj_enabled.css
- name: "Activate Matomo Tracking for '{{ domain }}'"
include_role:
- name: sys-srv-web-inj-matomo
+ name: sys-front-inj-matomo
when: inj_enabled.matomo
- name: "Activate Javascript for '{{ domain }}'"
include_role:
- name: sys-srv-web-inj-javascript
+ name: sys-front-inj-javascript
when: inj_enabled.javascript
- name: "Activate logout proxy for '{{ domain }}'"
include_role:
- name: sys-srv-web-inj-logout
+ name: sys-front-inj-logout
public: true # Vars used in templates
when: inj_enabled.logout
+
+- block:
+ - name: Include dependency 'srv-core'
+ include_role:
+ name: srv-core
+ when: run_once_srv_core is not defined
+ - include_tasks: utils/run_once.yml
+ when: run_once_sys_front_inj_all is not defined
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-compose/templates/location.lua.j2 b/roles/sys-front-inj-all/templates/location.lua.j2
similarity index 97%
rename from roles/sys-srv-web-inj-compose/templates/location.lua.j2
rename to roles/sys-front-inj-all/templates/location.lua.j2
index d0dcfa5c..304c9b87 100644
--- a/roles/sys-srv-web-inj-compose/templates/location.lua.j2
+++ b/roles/sys-front-inj-all/templates/location.lua.j2
@@ -3,7 +3,7 @@
{% set kind = list_name | regex_replace('_snippets$','') %}
{% for f in features if inj_enabled.get(f) -%}
{{ list_name }}[#{{ list_name }} + 1] = [=[
- {%- include 'roles/sys-srv-web-inj-' ~ f ~ '/templates/' ~ kind ~ '_sub.j2' -%}
+ {%- include 'roles/sys-front-inj-' ~ f ~ '/templates/' ~ kind ~ '_sub.j2' -%}
]=]
{% endfor -%}
{%- endmacro %}
diff --git a/roles/sys-srv-web-inj-compose/templates/server.conf.j2 b/roles/sys-front-inj-all/templates/server.conf.j2
similarity index 50%
rename from roles/sys-srv-web-inj-compose/templates/server.conf.j2
rename to roles/sys-front-inj-all/templates/server.conf.j2
index 97fae282..e1bec5e5 100644
--- a/roles/sys-srv-web-inj-compose/templates/server.conf.j2
+++ b/roles/sys-front-inj-all/templates/server.conf.j2
@@ -1,7 +1,3 @@
-{% if inj_enabled.css %}
-{% include 'roles/sys-srv-web-inj-css/templates/location.conf.j2' %}
-{% endif %}
-
{% if inj_enabled.logout %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-compose/vars/main.yml b/roles/sys-front-inj-all/vars/main.yml
similarity index 100%
rename from roles/sys-srv-web-inj-compose/vars/main.yml
rename to roles/sys-front-inj-all/vars/main.yml
diff --git a/roles/sys-srv-web-inj-css/README.md b/roles/sys-front-inj-css/README.md
similarity index 77%
rename from roles/sys-srv-web-inj-css/README.md
rename to roles/sys-front-inj-css/README.md
index e0332311..d1901d4e 100644
--- a/roles/sys-srv-web-inj-css/README.md
+++ b/roles/sys-front-inj-css/README.md
@@ -2,12 +2,12 @@
## Description
-This Ansible role ensures **consistent global theming** across all Nginx-served applications by injecting a unified `global.css` file.
+This Ansible role ensures **consistent global theming** across all Nginx-served applications by injecting CSS files.
The role leverages [`colorscheme-generator`](https://github.com/kevinveenbirkenbach/colorscheme-generator/) to generate a dynamic, customizable color palette for light and dark mode, compatible with popular web tools like **Bootstrap**, **Keycloak**, **Nextcloud**, **Taiga**, **Mastodon**, and many more.
## Overview
-This role deploys a centralized global stylesheet (`global.css`) that overrides the default theming of web applications served via Nginx. It's optimized to run only once per deployment and generates a **cache-busting version number** based on file modification timestamps.
+This role deploys a centralized global stylesheet that overrides the default theming of web applications served via Nginx. It's optimized to run only once per deployment and generates a **cache-busting version number** based on file modification timestamps.
It includes support for **dark mode**, **custom fonts**, and **extensive Bootstrap and UI component overrides**.
## Purpose
@@ -18,7 +18,7 @@ It makes all applications feel like part of the same ecosystem — visually and
## Features
- 🎨 **Dynamic Theming** via [`colorscheme-generator`](https://github.com/kevinveenbirkenbach/colorscheme-generator/)
-- 📁 **Unified global.css** deployment for all Nginx applications
+- 📁 **Unified CSS Base Configuration** deployment for all Nginx applications
- 🌒 **Dark mode support** out of the box
- 🚫 **No duplication** – tasks run once per deployment
- ⏱️ **Versioning logic** to bust browser cache
diff --git a/roles/sys-srv-web-inj-css/meta/main.yml b/roles/sys-front-inj-css/meta/main.yml
similarity index 100%
rename from roles/sys-srv-web-inj-css/meta/main.yml
rename to roles/sys-front-inj-css/meta/main.yml
diff --git a/roles/sys-front-inj-css/tasks/01_core.yml b/roles/sys-front-inj-css/tasks/01_core.yml
new file mode 100644
index 00000000..1bacec75
--- /dev/null
+++ b/roles/sys-front-inj-css/tasks/01_core.yml
@@ -0,0 +1,21 @@
+- name: Include dependency 'srv-core'
+ include_role:
+ name: srv-core
+ when: run_once_srv_core is not defined
+
+- name: Generate color palette with colorscheme-generator
+ set_fact:
+ color_palette: "{{ lookup('colorscheme', CSS_BASE_COLOR, count=CSS_COUNT, shades=CSS_SHADES) }}"
+
+- name: Generate inverted color palette with colorscheme-generator
+ set_fact:
+ inverted_color_palette: "{{ lookup('colorscheme', CSS_BASE_COLOR, count=CSS_COUNT, shades=CSS_SHADES, invert_lightness=True) }}"
+
+- name: Deploy default CSS files
+ template:
+ src: "{{ ['css', item ~ '.j2'] | path_join }}"
+ dest: "{{ [cdn.shared.css, item] | path_join }}"
+ owner: "{{ NGINX.USER }}"
+ group: "{{ NGINX.USER }}"
+ mode: '0644'
+ loop: "{{ CSS_FILES }}"
diff --git a/roles/sys-front-inj-css/tasks/main.yml b/roles/sys-front-inj-css/tasks/main.yml
new file mode 100644
index 00000000..5529a772
--- /dev/null
+++ b/roles/sys-front-inj-css/tasks/main.yml
@@ -0,0 +1,25 @@
+- block:
+ - include_tasks: 01_core.yml
+ - include_tasks: utils/run_once.yml
+ when: run_once_sys_front_inj_css is not defined
+
+- name: "Resolve optional app style.css source for '{{ application_id }}'"
+ vars:
+ app_role_dir: "{{ playbook_dir }}/roles/{{ application_id }}"
+ _app_style_src: >-
+ {{ lookup('first_found', {
+ 'files': ['templates/style.css.j2','files/style.css'],
+ 'paths': [app_role_dir]
+ }, errors='ignore') | default('', true) }}
+ set_fact:
+ app_style_src: "{{ _app_style_src }}"
+ app_style_present: "{{ _app_style_src | length > 0 }}"
+
+- name: "Deploy per-app '{{ app_style_src }}' to '{{ css_app_dst }}'"
+ when: app_style_present
+ copy:
+ content: "{{ lookup('template', app_style_src) }}"
+ dest: "{{ css_app_dst }}"
+ owner: "{{ NGINX.USER }}"
+ group: "{{ NGINX.USER }}"
+ mode: '0644'
\ No newline at end of file
diff --git a/roles/sys-front-inj-css/templates/css/bootstrap.css.j2 b/roles/sys-front-inj-css/templates/css/bootstrap.css.j2
new file mode 100644
index 00000000..70b01ba7
--- /dev/null
+++ b/roles/sys-front-inj-css/templates/css/bootstrap.css.j2
@@ -0,0 +1,69 @@
+
+/* Buttons (Background, Text, Border, and Shadow)
+ Now using a button background that is only slightly darker than the overall background */
+html[native-dark-active] .btn, .btn {
+ background-color: var(--color-01-87);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-91), var(--color-01-95), var(--color-01-95));
+ color: var(--color-01-50);
+ border-color: var(--color-01-80);
+ cursor: pointer;
+}
+
+/* Navigation (Background and Text Colors) */
+.navbar, .navbar-light, .navbar-dark, .navbar.bg-light {
+ background-color: var(--color-01-90);
+ /* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
+ color: var(--color-01-50);
+ border-color: var(--color-01-85);
+}
+
+.navbar a {
+ color: var(--color-01-40);
+}
+
+.navbar a.dropdown-item {
+ color: var(--color-01-43);
+}
+
+/* Cards / Containers (Background, Border, and Shadow)
+ Cards now use a slightly lighter background and a bold, clear shadow */
+.card {
+ background-color: var(--color-01-90);
+ /* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
+ border-color: var(--color-01-85);
+ color: var(--color-01-12);
+}
+
+.card-body {
+ color: var(--color-01-40);
+}
+
+/* Dropdown Menu and Submenu (Background, Text, and Shadow) */
+.navbar .dropdown-menu,
+.nav-item .dropdown-menu {
+ background-color: var(--color-01-80);
+ /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
+ color: var(--color-01-40);
+}
+
+.navbar-nav {
+ --bs-nav-link-hover-color: var(--color-01-17);
+}
+
+.dropdown-item {
+ color: var(--color-01-40);
+ background-color: var(--color-01-80);
+ /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
+}
+
+.dropdown-item:hover,
+.dropdown-item:focus {
+ background-color: var(--color-01-65);
+ /* New Gradient based on original background (65 -5, 65, 65 +1, 65 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-60), var(--color-01-65), var(--color-01-66), var(--color-01-70));
+ color: var(--color-01-40);
+}
diff --git a/roles/sys-front-inj-css/templates/css/default.css.j2 b/roles/sys-front-inj-css/templates/css/default.css.j2
new file mode 100644
index 00000000..667af01d
--- /dev/null
+++ b/roles/sys-front-inj-css/templates/css/default.css.j2
@@ -0,0 +1,297 @@
+/***
+
+Global Theming Styles – Color and Shadow Variables
+
+HINT:
+- Better overwritte CSS variables instead of individual elements.
+- Don't use !important. If possible use a specific selector.
+
+*/
+
+{% if design.font.import_url %}
+@import url('{{ design.font.import_url }}');
+{% endif %}
+
+/* Auto-generated by colorscheme-generator */
+
+:root {
+ {% for var_name, color in color_palette.items() %}
+ {{ var_name }}: {{ color }};
+ {% endfor %}
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ {% for var_name, color in inverted_color_palette.items() %}
+ {{ var_name }}: {{ color }};
+ {% endfor %}
+ }
+}
+
+:root, ::after, ::before, ::backdrop {
+ /* For Dark Mode Plugin
+ * @See https://chromewebstore.google.com/detail/dark-mode/dmghijelimhndkbmpgbldicpogfkceaj
+ */
+ --native-dark-accent-color: var(--color-01-60); /* was #a9a9a9 */
+ --native-dark-bg-color: var(--color-01-10); /* was #292929 */
+ --native-dark-bg-image-color: rgba(var(--color-01-rgb-01), 0.10); /* remains the same, or adjust if needed */
+ --native-dark-border-color: var(--color-01-40); /* was #555555 */
+ --native-dark-box-shadow: 0 0 0 1px rgb(var(--color-01-rgb-99), / 10%);
+ --native-dark-cite-color: var(--color-01-70); /* was #92de92 – you might adjust if a green tone is needed */
+ --native-dark-fill-color: var(--color-01-50); /* was #7d7d7d */
+ --native-dark-font-color: var(--color-01-95); /* was #dcdcdc */
+ --native-dark-link-color: var(--color-01-80); /* was #8db2e5 */
+ --native-dark-visited-link-color: var(--color-01-85); /* was #c76ed7 */
+}
+
+/* Bootstrap Overrides (Color/Shadow Variables Only) */
+:root {
+ --bs-black: var(--color-01-01); /* Original tone: Black (#000) */
+ --bs-white: var(--color-01-99); /* Original tone: White (#fff) */
+ --bs-gray: var(--color-01-50); /* Original tone: Gray (#6c757d) */
+ --bs-gray-dark: var(--color-01-20); /* Original tone: Dark Gray (#343a40) */
+{% for i in range(1, 10) %}
+{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
+ {% set gray = i * 100 %}
+ {% set color = 100 - i * 10 %}
+ --bs-gray-{{ gray }}: var(--color-01-{{ "%02d" % color }});
+{% endfor %}
+ --bs-primary: var(--color-01-65); /* Original tone: Blue (#0d6efd) */
+ --bs-light: var(--color-01-95); /* Original tone: Light (#f8f9fa) */
+ --bs-dark: var(--color-01-10); /* Original tone: Dark (#212529) */
+ --bs-primary-rgb: var(--color-01-rgb-65); /* Original tone: Blue (13, 110, 253) */
+ --bs-secondary-rgb: var(--color-01-rgb-50); /* Original tone: Grayish (#6c757d / 108, 117, 125) */
+ --bs-light-rgb: var(--color-01-rgb-95); /* Original tone: Light (248, 249, 250) */
+ --bs-dark-rgb: var(--color-01-rgb-10); /* Original tone: Dark (33, 37, 41) */
+ --bs-white-rgb: var(--color-01-rgb-99); /* Original tone: White (255, 255, 255) */
+ --bs-black-rgb: var(--color-01-rgb-01); /* Original tone: Black (0, 0, 0) */
+ --bs-body-color-rgb: var(--color-01-rgb-10); /* Original tone: Dark (#212529 / 33, 37, 41) */
+ --bs-body-bg-rgb: var(--color-01-rgb-99); /* Original tone: White (#fff / 255, 255, 255) */
+ --bs-body-color: var(--color-01-10); /* Original tone: Dark (#212529) */
+ --bs-body-bg: var(--color-01-99); /* Original tone: White (#fff) */
+ --bs-border-color: var(--color-01-85); /* Original tone: Gray (#dee2e6) */
+ --bs-link-color: var(--color-01-65); /* Original tone: Blue (#0d6efd) */
+ --bs-link-hover-color: var(--color-01-60); /* Original tone: Darker Blue (#0a58ca) */
+ --bs-code-color: var(--color-01-55); /* Original tone: Pink (#d63384) */
+ --bs-highlight-bg: var(--color-01-93); /* Original tone: Light Yellow (#fff3cd) */
+ --bs-list-group-bg: var(--color-01-40);
+ --bs-emphasis-color: var(--color-01-01); /* Gemappt von #000 */
+ --bs-emphasis-color-rgb: var(--color-01-rgb-01); /* Gemappt von 0, 0, 0 */
+ --bs-secondary-color: rgba(var(--color-01-rgb-10), 0.75); /* Gemappt von rgba(33, 37, 41, 0.75) */
+ --bs-secondary-color-rgb: var(--color-01-rgb-10); /* Gemappt von 33, 37, 41 */
+ --bs-secondary-bg: var(--color-01-90); /* Gemappt von #e9ecef */
+ --bs-secondary-bg-rgb: var(--color-01-rgb-90); /* Gemappt von 233, 236, 239 */
+ --bs-tertiary-color: rgba(var(--color-01-rgb-10), 0.5); /* Gemappt von rgba(33, 37, 41, 0.5) */
+ --bs-tertiary-color-rgb: var(--color-01-rgb-10); /* Gemappt von 33, 37, 41 */
+ --bs-tertiary-bg: var(--color-01-95); /* Gemappt von #f8f9fa */
+ --bs-tertiary-bg-rgb: var(--color-01-rgb-95); /* Gemappt von 248, 249, 250 */
+ --bs-link-color-rgb: var(--color-01-rgb-65); /* Gemappt von 13, 110, 253 */
+ --bs-link-hover-color-rgb: var(--color-01-rgb-60); /* Gemappt von 10, 88, 202 */
+ --bs-highlight-color: var(--color-01-10); /* Gemappt von #212529 */
+ --bs-border-color-translucent: rgba(var(--color-01-rgb-01), 0.175); /* Gemappt von rgba(0, 0, 0, 0.175) */
+ --bs-focus-ring-color: rgba(var(--color-01-rgb-65), 0.25); /* Gemappt von rgba(13, 110, 253, 0.25) */
+
+ --bs-table-color: var(--bs-emphasis-color);
+ --bs-table-bg: var(--color-01-99); /* White (#fff) */
+ --bs-table-border-color: var(--color-01-99); /* White (#fff) */
+ --bs-table-striped-bg: var(--color-01-85); /* Light Gray (entspricht ca. #dee2e6) */
+ --bs-table-hover-color: var(--color-01-01); /* Black (#000) */
+ --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075);
+}
+
+/* Global Defaults (Colors Only) */
+body, html[native-dark-active] {
+ background-color: var(--color-01-93);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-93), var(--color-01-91), var(--color-01-95), var(--color-01-93));
+ background-attachment: fixed;
+ color: var(--color-01-40);
+ font-family: {{design.font.type}};
+}
+
+{# All links (applies to all anchor elements regardless of state) #}
+a {
+ color: var(--color-01-50);
+}
+
+{# Unvisited links (applies only to links that have not been visited) #}
+a:link {
+ color: var(--color-01-55);
+}
+
+{# Visited links (applies only to links that have been visited) #}
+a:visited {
+ color: var(--color-01-45);
+}
+
+{# Hover state (applies when the mouse pointer is over the link) #}
+a:hover {
+ color: var(--color-01-60);
+}
+
+{# Active state (applies during the time the link is being activated, e.g., on click) #}
+a:active {
+ color: var(--color-01-65);
+}
+
+/** Set default buttons transparent **/
+html[native-dark-active] button, button{
+ background-color: var(--color-01-87);
+}
+
+button:hover, .btn:hover {
+ filter: brightness(0.9);
+}
+
+/* {# Invalid state: when the input value fails validation criteria. Use danger color for error indication. #} */
+input:invalid,
+textarea:invalid,
+select:invalid {
+ background-color: var(--color-01-01);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-01), var(--color-01-10));
+ /* Use Bootstrap danger color for error messages */
+ color: var(--bs-danger);
+ border-color: var(--color-01-20);
+}
+
+/* {# Valid state: when the input value meets all validation criteria. Use success color for confirmation. #} */
+input:valid,
+textarea:valid,
+select:valid {
+ background-color: var(--color-01-80);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-80), var(--color-01-90));
+ /* Use Bootstrap success color for confirmation messages */
+ color: var(--bs-success);
+ border-color: var(--color-01-70);
+}
+
+/* {# Required field: applied to elements that must be filled out by the user. Use warning color for emphasis. #} */
+input:required,
+textarea:required,
+select:required {
+ background-color: var(--color-01-50);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-50), var(--color-01-60));
+ /* Use Bootstrap warning color to indicate a required field */
+ color: var(--bs-warning);
+ border-color: var(--color-01-70);
+}
+
+/* {# Optional field: applied to elements that are not mandatory. Use info color to denote additional information. #} */
+input:optional,
+textarea:optional,
+select:optional {
+ background-color: var(--color-01-60);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-60), var(--color-01-70));
+ /* Use Bootstrap info color to indicate optional information */
+ color: var(--bs-info);
+ border-color: var(--color-01-70);
+}
+
+/* {# Read-only state: when an element is not editable by the user. #} */
+input:read-only,
+textarea:read-only,
+select:read-only {
+ background-color: var(--color-01-80);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-90), var(--color-01-70));
+ color: var(--color-01-20);
+ border-color: var(--color-01-50);
+}
+
+/* {# Read-write state: when an element is editable by the user. #} */
+input:read-write,
+textarea:read-write,
+select:read-write {
+ background-color: var(--color-01-70);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-80));
+ color: var(--color-01-40);
+ border-color: var(--color-01-70);
+}
+
+/* {# In-range: for inputs with a defined range, when the value is within the allowed limits. #} */
+input:in-range,
+textarea:in-range,
+select:in-range {
+ background-color: var(--color-01-70);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-85));
+ color: var(--color-01-40);
+ border-color: var(--color-01-70);
+}
+
+/* {# Out-of-range: for inputs with a defined range, when the value falls outside the allowed limits. #} */
+input:out-of-range,
+textarea:out-of-range,
+select:out-of-range {
+ background-color: var(--color-01-10);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-10), var(--color-01-30));
+ color: var(--color-01-10);
+ border-color: var(--color-01-50);
+}
+
+/* {# Placeholder-shown: when the input field is displaying its placeholder text. #} */
+input:placeholder-shown,
+textarea:placeholder-shown,
+select:placeholder-shown {
+ background-color: var(--color-01-82);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-82), var(--color-01-90));
+ color: var(--color-01-40);
+ border-color: var(--color-01-70);
+}
+
+/* {# Focus state: when the element is focused by the user. #} */
+input:focus,
+textarea:focus,
+select:focus {
+ background-color: var(--color-01-75);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-85));
+ color: var(--color-01-40);
+ border-color: var(--color-01-50);
+}
+
+/* {# Hover state: when the mouse pointer is over the element. #} */
+input:hover,
+textarea:hover,
+select:hover {
+ background-color: var(--color-01-78);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-78), var(--color-01-88));
+ color: var(--color-01-40);
+ border-color: var(--color-01-65);
+}
+
+/* {# Active state: when the element is being activated (e.g., clicked). #} */
+input:active,
+textarea:active,
+select:active {
+ background-color: var(--color-01-68);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-68), var(--color-01-78));
+ color: var(--color-01-40);
+ border-color: var(--color-01-60);
+}
+
+/* {# Checked state: specifically for radio buttons and checkboxes when selected. #} */
+input:checked {
+ background-color: var(--color-01-90);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-90), var(--color-01-99));
+ color: var(--color-01-40);
+ border-color: var(--color-01-70);
+}
+
+option {
+ background-color: var(--color-01-82);
+ color: var(--color-01-07);
+}
+
+/* Tables (Borders and Header Colors) */
+th, td {
+ border-color: var(--color-01-70);
+}
+
+thead {
+ background-color: var(--color-01-80);
+ /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
+ color: var(--color-01-40);
+}
+
+/* Headings (Text Color) */
+h1, h2, h3, h4, h5, h6, p{
+ color: var(--color-01-10);
+}
diff --git a/roles/sys-front-inj-css/templates/head_sub.j2 b/roles/sys-front-inj-css/templates/head_sub.j2
new file mode 100644
index 00000000..740ce22a
--- /dev/null
+++ b/roles/sys-front-inj-css/templates/head_sub.j2
@@ -0,0 +1,8 @@
+{% set __css_tpl_dir = [playbook_dir, 'roles', 'sys-front-inj-css', 'templates', 'css'] | path_join %}
+
+{% for css_file in ['default.css','bootstrap.css'] %}
+
+{% endfor %}
+{% if app_style_present | bool %}
+
+{% endif %}
\ No newline at end of file
diff --git a/roles/sys-front-inj-css/vars/main.yml b/roles/sys-front-inj-css/vars/main.yml
new file mode 100644
index 00000000..9e0f84f2
--- /dev/null
+++ b/roles/sys-front-inj-css/vars/main.yml
@@ -0,0 +1,8 @@
+# Constants
+CSS_FILES: ['default.css','bootstrap.css']
+CSS_BASE_COLOR: "{{ design.css.colors.base }}"
+CSS_COUNT: 7
+CSS_SHADES: 100
+
+# Variables
+css_app_dst: "{{ [cdn.role.release.css, 'style.css'] | path_join }}"
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-desktop/README.md b/roles/sys-front-inj-desktop/README.md
similarity index 100%
rename from roles/sys-srv-web-inj-desktop/README.md
rename to roles/sys-front-inj-desktop/README.md
diff --git a/roles/sys-srv-web-inj-desktop/meta/main.yml b/roles/sys-front-inj-desktop/meta/main.yml
similarity index 100%
rename from roles/sys-srv-web-inj-desktop/meta/main.yml
rename to roles/sys-front-inj-desktop/meta/main.yml
diff --git a/roles/sys-front-inj-desktop/tasks/01_deploy.yml b/roles/sys-front-inj-desktop/tasks/01_deploy.yml
new file mode 100644
index 00000000..ca1dd08d
--- /dev/null
+++ b/roles/sys-front-inj-desktop/tasks/01_deploy.yml
@@ -0,0 +1,7 @@
+- name: Deploy {{ INJ_DESKTOP_JS_FILE_NAME }}
+ template:
+ src: "{{ INJ_DESKTOP_JS_FILE_NAME }}.j2"
+ dest: "{{ INJ_DESKTOP_JS_FILE_DESTINATION }}"
+ owner: "{{ NGINX.USER }}"
+ group: "{{ NGINX.USER }}"
+ mode: '0644'
diff --git a/roles/sys-srv-web-inj-desktop/tasks/main.yml b/roles/sys-front-inj-desktop/tasks/main.yml
similarity index 93%
rename from roles/sys-srv-web-inj-desktop/tasks/main.yml
rename to roles/sys-front-inj-desktop/tasks/main.yml
index a96b19e0..30ee17c1 100644
--- a/roles/sys-srv-web-inj-desktop/tasks/main.yml
+++ b/roles/sys-front-inj-desktop/tasks/main.yml
@@ -5,7 +5,7 @@
when: run_once_srv_core is not defined
- include_tasks: 01_deploy.yml
- include_tasks: utils/run_once.yml
- when: run_once_sys_srv_web_inj_desktop is not defined
+ when: run_once_sys_front_inj_desktop is not defined
# --- Build tiny inline initializer (CSP-hashed) ---
- name: "Load iFrame init code for '{{ application_id }}'"
diff --git a/roles/sys-srv-web-inj-desktop/templates/body_sub.j2 b/roles/sys-front-inj-desktop/templates/body_sub.j2
similarity index 100%
rename from roles/sys-srv-web-inj-desktop/templates/body_sub.j2
rename to roles/sys-front-inj-desktop/templates/body_sub.j2
diff --git a/roles/sys-front-inj-desktop/templates/head_sub.j2 b/roles/sys-front-inj-desktop/templates/head_sub.j2
new file mode 100644
index 00000000..e01e2d1e
--- /dev/null
+++ b/roles/sys-front-inj-desktop/templates/head_sub.j2
@@ -0,0 +1 @@
+
diff --git a/roles/sys-srv-web-inj-desktop/templates/iframe-handler.js.j2 b/roles/sys-front-inj-desktop/templates/iframe-handler.js.j2
similarity index 100%
rename from roles/sys-srv-web-inj-desktop/templates/iframe-handler.js.j2
rename to roles/sys-front-inj-desktop/templates/iframe-handler.js.j2
diff --git a/roles/sys-srv-web-inj-desktop/templates/iframe-init_one_liner.js.j2 b/roles/sys-front-inj-desktop/templates/iframe-init_one_liner.js.j2
similarity index 100%
rename from roles/sys-srv-web-inj-desktop/templates/iframe-init_one_liner.js.j2
rename to roles/sys-front-inj-desktop/templates/iframe-init_one_liner.js.j2
diff --git a/roles/sys-front-inj-desktop/vars/main.yml b/roles/sys-front-inj-desktop/vars/main.yml
new file mode 100644
index 00000000..3db900b5
--- /dev/null
+++ b/roles/sys-front-inj-desktop/vars/main.yml
@@ -0,0 +1,2 @@
+INJ_DESKTOP_JS_FILE_NAME: "iframe-handler.js"
+INJ_DESKTOP_JS_FILE_DESTINATION: "{{ [cdn.shared.js, INJ_DESKTOP_JS_FILE_NAME] | path_join }}"
diff --git a/roles/sys-srv-web-inj-javascript/README.md b/roles/sys-front-inj-javascript/README.md
similarity index 100%
rename from roles/sys-srv-web-inj-javascript/README.md
rename to roles/sys-front-inj-javascript/README.md
diff --git a/roles/sys-srv-web-inj-javascript/meta/main.yml b/roles/sys-front-inj-javascript/meta/main.yml
similarity index 100%
rename from roles/sys-srv-web-inj-javascript/meta/main.yml
rename to roles/sys-front-inj-javascript/meta/main.yml
diff --git a/roles/sys-srv-web-inj-javascript/tasks/main.yml b/roles/sys-front-inj-javascript/tasks/main.yml
similarity index 92%
rename from roles/sys-srv-web-inj-javascript/tasks/main.yml
rename to roles/sys-front-inj-javascript/tasks/main.yml
index 4bf9e9be..63eca016 100644
--- a/roles/sys-srv-web-inj-javascript/tasks/main.yml
+++ b/roles/sys-front-inj-javascript/tasks/main.yml
@@ -5,7 +5,7 @@
name: srv-core
when: run_once_srv_core is not defined
- include_tasks: utils/run_once.yml
- when: run_once_sys_srv_web_inj_javascript is not defined
+ when: run_once_sys_front_inj_javascript is not defined
- name: "Load JavaScript code for '{{ application_id }}'"
set_fact:
diff --git a/roles/sys-srv-web-inj-javascript/templates/head_sub.j2 b/roles/sys-front-inj-javascript/templates/head_sub.j2
similarity index 100%
rename from roles/sys-srv-web-inj-javascript/templates/head_sub.j2
rename to roles/sys-front-inj-javascript/templates/head_sub.j2
diff --git a/roles/sys-srv-web-inj-javascript/vars/main.yml b/roles/sys-front-inj-javascript/vars/main.yml
similarity index 100%
rename from roles/sys-srv-web-inj-javascript/vars/main.yml
rename to roles/sys-front-inj-javascript/vars/main.yml
diff --git a/roles/sys-srv-web-inj-logout/README.md b/roles/sys-front-inj-logout/README.md
similarity index 78%
rename from roles/sys-srv-web-inj-logout/README.md
rename to roles/sys-front-inj-logout/README.md
index 5698569f..74fa5400 100644
--- a/roles/sys-srv-web-inj-logout/README.md
+++ b/roles/sys-front-inj-logout/README.md
@@ -1,10 +1,10 @@
-# sys-srv-web-inj-logout
+# sys-front-inj-logout
This role injects a catcher that intercepts all logout elements in HTML pages served by Nginx and redirects them to a centralized logout endpoint via JavaScript.
## Description
-The `sys-srv-web-inj-logout` Ansible role automatically embeds a lightweight JavaScript snippet into your web application's HTML responses. This script identifies logout links, buttons, forms, and other elements, overrides their target URLs, and ensures users are redirected to a central OIDC logout endpoint, providing a consistent single sign‑out experience.
+The `sys-front-inj-logout` Ansible role automatically embeds a lightweight JavaScript snippet into your web application's HTML responses. This script identifies logout links, buttons, forms, and other elements, overrides their target URLs, and ensures users are redirected to a central OIDC logout endpoint, providing a consistent single sign‑out experience.
## Overview
diff --git a/roles/sys-srv-web-inj-logout/meta/main.yml b/roles/sys-front-inj-logout/meta/main.yml
similarity index 92%
rename from roles/sys-srv-web-inj-logout/meta/main.yml
rename to roles/sys-front-inj-logout/meta/main.yml
index dea58256..87004aa5 100644
--- a/roles/sys-srv-web-inj-logout/meta/main.yml
+++ b/roles/sys-front-inj-logout/meta/main.yml
@@ -1,6 +1,6 @@
galaxy_info:
author: "Kevin Veen‑Birkenbach"
- role_name: "sys-srv-web-inj-logout"
+ role_name: "sys-front-inj-logout"
description: >
Injects a JavaScript snippet via Nginx sub_filter that intercepts all logout actions
(links, buttons, forms) and redirects users to a centralized OIDC logout endpoint.
@@ -21,4 +21,4 @@ galaxy_info:
Kevin Veen‑Birkenbach Consulting & Coaching Solutions https://www.veen.world
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
- documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-srv-web-inj-logout"
+ documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-front-inj-logout"
diff --git a/roles/sys-srv-web-inj-logout/tasks/01_core.yml b/roles/sys-front-inj-logout/tasks/01_core.yml
similarity index 100%
rename from roles/sys-srv-web-inj-logout/tasks/01_core.yml
rename to roles/sys-front-inj-logout/tasks/01_core.yml
diff --git a/roles/sys-srv-web-inj-logout/tasks/02_deploy.yml b/roles/sys-front-inj-logout/tasks/02_deploy.yml
similarity index 100%
rename from roles/sys-srv-web-inj-logout/tasks/02_deploy.yml
rename to roles/sys-front-inj-logout/tasks/02_deploy.yml
diff --git a/roles/sys-srv-web-inj-logout/tasks/main.yml b/roles/sys-front-inj-logout/tasks/main.yml
similarity index 85%
rename from roles/sys-srv-web-inj-logout/tasks/main.yml
rename to roles/sys-front-inj-logout/tasks/main.yml
index 2285a67f..c7773761 100644
--- a/roles/sys-srv-web-inj-logout/tasks/main.yml
+++ b/roles/sys-front-inj-logout/tasks/main.yml
@@ -1,8 +1,8 @@
- block:
- include_tasks: 01_core.yml
- set_fact:
- run_once_sys_srv_web_inj_logout: true
- when: run_once_sys_srv_web_inj_logout is not defined
+ run_once_sys_front_inj_logout: true
+ when: run_once_sys_front_inj_logout is not defined
- name: "Load logout code for '{{ application_id }}'"
set_fact:
diff --git a/roles/sys-srv-web-inj-logout/templates/body_sub.j2 b/roles/sys-front-inj-logout/templates/body_sub.j2
similarity index 100%
rename from roles/sys-srv-web-inj-logout/templates/body_sub.j2
rename to roles/sys-front-inj-logout/templates/body_sub.j2
diff --git a/roles/sys-front-inj-logout/templates/head_sub.j2 b/roles/sys-front-inj-logout/templates/head_sub.j2
new file mode 100644
index 00000000..88f81729
--- /dev/null
+++ b/roles/sys-front-inj-logout/templates/head_sub.j2
@@ -0,0 +1 @@
+
diff --git a/roles/sys-srv-web-inj-logout/templates/logout.js.j2 b/roles/sys-front-inj-logout/templates/logout.js.j2
similarity index 100%
rename from roles/sys-srv-web-inj-logout/templates/logout.js.j2
rename to roles/sys-front-inj-logout/templates/logout.js.j2
diff --git a/roles/sys-srv-web-inj-logout/templates/logout_one_liner.js.j2 b/roles/sys-front-inj-logout/templates/logout_one_liner.js.j2
similarity index 100%
rename from roles/sys-srv-web-inj-logout/templates/logout_one_liner.js.j2
rename to roles/sys-front-inj-logout/templates/logout_one_liner.js.j2
diff --git a/roles/sys-front-inj-logout/vars/main.yml b/roles/sys-front-inj-logout/vars/main.yml
new file mode 100644
index 00000000..ceb03f1d
--- /dev/null
+++ b/roles/sys-front-inj-logout/vars/main.yml
@@ -0,0 +1,2 @@
+INJ_LOGOUT_JS_FILE_NAME: "logout.js"
+INJ_LOGOUT_JS_DESTINATION: "{{ [cdn.shared.js, INJ_LOGOUT_JS_FILE_NAME] | path_join }}"
diff --git a/roles/sys-srv-web-inj-matomo/README.md b/roles/sys-front-inj-matomo/README.md
similarity index 100%
rename from roles/sys-srv-web-inj-matomo/README.md
rename to roles/sys-front-inj-matomo/README.md
diff --git a/roles/sys-srv-web-inj-matomo/meta/main.yml b/roles/sys-front-inj-matomo/meta/main.yml
similarity index 96%
rename from roles/sys-srv-web-inj-matomo/meta/main.yml
rename to roles/sys-front-inj-matomo/meta/main.yml
index 25e543fd..bcb151fa 100644
--- a/roles/sys-srv-web-inj-matomo/meta/main.yml
+++ b/roles/sys-front-inj-matomo/meta/main.yml
@@ -13,7 +13,7 @@ galaxy_info:
- analytics
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
- documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-srv-web-inj-matomo"
+ documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-front-inj-matomo"
min_ansible_version: "2.9"
platforms:
- name: Any
diff --git a/roles/sys-srv-web-inj-matomo/tasks/main.yml b/roles/sys-front-inj-matomo/tasks/main.yml
similarity index 97%
rename from roles/sys-srv-web-inj-matomo/tasks/main.yml
rename to roles/sys-front-inj-matomo/tasks/main.yml
index 5996ba81..0cce22f7 100644
--- a/roles/sys-srv-web-inj-matomo/tasks/main.yml
+++ b/roles/sys-front-inj-matomo/tasks/main.yml
@@ -4,7 +4,7 @@
name: srv-core
when: run_once_srv_core is not defined
- include_tasks: utils/run_once.yml
- when: run_once_sys_srv_web_inj_matomo is not defined
+ when: run_once_sys_front_inj_matomo is not defined
- name: "Relevant variables for role: {{ role_path | basename }}"
debug:
diff --git a/roles/sys-srv-web-inj-matomo/templates/body_sub.j2 b/roles/sys-front-inj-matomo/templates/body_sub.j2
similarity index 100%
rename from roles/sys-srv-web-inj-matomo/templates/body_sub.j2
rename to roles/sys-front-inj-matomo/templates/body_sub.j2
diff --git a/roles/sys-srv-web-inj-matomo/templates/head_sub.j2 b/roles/sys-front-inj-matomo/templates/head_sub.j2
similarity index 100%
rename from roles/sys-srv-web-inj-matomo/templates/head_sub.j2
rename to roles/sys-front-inj-matomo/templates/head_sub.j2
diff --git a/roles/sys-srv-web-inj-matomo/templates/matomo-tracking.js.j2 b/roles/sys-front-inj-matomo/templates/matomo-tracking.js.j2
similarity index 100%
rename from roles/sys-srv-web-inj-matomo/templates/matomo-tracking.js.j2
rename to roles/sys-front-inj-matomo/templates/matomo-tracking.js.j2
diff --git a/roles/sys-srv-web-inj-matomo/vars/main.yml b/roles/sys-front-inj-matomo/vars/main.yml
similarity index 100%
rename from roles/sys-srv-web-inj-matomo/vars/main.yml
rename to roles/sys-front-inj-matomo/vars/main.yml
diff --git a/roles/sys-service/defaults/main.yml b/roles/sys-service/defaults/main.yml
index b1729647..d9d4a5d3 100644
--- a/roles/sys-service/defaults/main.yml
+++ b/roles/sys-service/defaults/main.yml
@@ -1,4 +1,4 @@
-SYS_SERVICE_ALL_ENABLED: "{{ MODE_DEBUG }}"
+SYS_SERVICE_ALL_ENABLED: "{{ MODE_DEBUG | bool }}"
SYS_SERVICE_DEFAULT_STATE: "{{ 'restarted' if MODE_DEBUG else omit }}"
SYS_SERVICE_DEFAULT_RUNTIME: "86400s" # Maximum total runtime a service is allowed to run before being stopped
SYS_SERVICE_SUPPRESS_FLUSH: [] # Services where the flush should be suppressed
\ No newline at end of file
diff --git a/roles/sys-service/tasks/05_service.yml b/roles/sys-service/tasks/05_service.yml
index 6c5b4296..be6ad891 100644
--- a/roles/sys-service/tasks/05_service.yml
+++ b/roles/sys-service/tasks/05_service.yml
@@ -46,5 +46,5 @@
command: /bin/true
notify: refresh systemctl service
when: not system_service_uses_at
- when: (SYS_SERVICE_ALL_ENABLED | bool or system_force_flush | bool)
+ when: system_force_flush | bool
diff --git a/roles/sys-service/vars/main.yml b/roles/sys-service/vars/main.yml
index bb8711d3..bfc1ecfc 100644
--- a/roles/sys-service/vars/main.yml
+++ b/roles/sys-service/vars/main.yml
@@ -6,7 +6,7 @@ system_service_role_dir: "{{ [ playbook_dir, 'roles', system_service_role_
system_service_script_dir: "{{ [ PATH_SYSTEMCTL_SCRIPTS, system_service_id ] | path_join }}"
## Settings
-system_force_flush: false # When set to true it activates the flushing of services :)
+system_force_flush: "{{ SYS_SERVICE_ALL_ENABLED | bool }}" # When set to true it activates the flushing of services. defaults to SYS_SERVICE_ALL_ENABLED
system_service_suppress_flush: "{{ (system_service_id in SYS_SERVICE_SUPPRESS_FLUSH) | bool }}" # When set to true it suppresses the flushing of services
system_service_copy_files: true # When set to false file copying will be skipped
system_service_timer_enabled: false # When set to true timer will be loaded
diff --git a/roles/sys-srv-web-inj-css/tasks/01_core.yml b/roles/sys-srv-web-inj-css/tasks/01_core.yml
deleted file mode 100644
index 862688ab..00000000
--- a/roles/sys-srv-web-inj-css/tasks/01_core.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-- name: Include dependency 'srv-core'
- include_role:
- name: srv-core
- when: run_once_srv_core is not defined
-
-- name: Generate color palette with colorscheme-generator
- set_fact:
- color_palette: "{{ lookup('colorscheme', global_css_base_color, count=global_css_count, shades=global_css_shades) }}"
-
-- name: Generate inverted color palette with colorscheme-generator
- set_fact:
- inverted_color_palette: "{{ lookup('colorscheme', global_css_base_color, count=global_css_count, shades=global_css_shades, invert_lightness=True) }}"
-
-- name: Deploy global.css
- template:
- src: global.css.j2
- dest: "{{ global_css_destination }}"
- owner: "{{ NGINX.USER }}"
- group: "{{ NGINX.USER }}"
- mode: '0644'
-
-- name: Get stat for global.css
- stat:
- path: "{{ global_css_destination }}"
- register: global_css_stat
-
-- name: Set global_css_version
- set_fact:
- global_css_version: "{{ global_css_stat.stat.mtime }}"
diff --git a/roles/sys-srv-web-inj-css/tasks/main.yml b/roles/sys-srv-web-inj-css/tasks/main.yml
deleted file mode 100644
index 350826b7..00000000
--- a/roles/sys-srv-web-inj-css/tasks/main.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-- block:
- - include_tasks: 01_core.yml
- - include_tasks: utils/run_once.yml
- when: run_once_sys_srv_web_inj_css is not defined
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-css/templates/global.css.j2 b/roles/sys-srv-web-inj-css/templates/global.css.j2
deleted file mode 100644
index 040a0625..00000000
--- a/roles/sys-srv-web-inj-css/templates/global.css.j2
+++ /dev/null
@@ -1,1204 +0,0 @@
-/***
-
-Global Theming Styles – Color and Shadow Variables
-
-HINT:
-- Better overwritte CSS variables instead of individual elements.
-- Don't use !important. If possible use a specific selector.
-
-*/
-
-{% if design.font.import_url %}
-@import url('{{design.font.import_url}}');
-{% endif %}
-
-/* Auto-generated by colorscheme-generator */
-
-:root {
- {% for var_name, color in color_palette.items() %}
- {{ var_name }}: {{ color }};
- {% endfor %}
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- {% for var_name, color in inverted_color_palette.items() %}
- {{ var_name }}: {{ color }};
- {% endfor %}
- }
-}
-
-:root, ::after, ::before, ::backdrop {
- /* For Dark Mode Plugin
- * @See https://chromewebstore.google.com/detail/dark-mode/dmghijelimhndkbmpgbldicpogfkceaj
- */
- --native-dark-accent-color: var(--color-01-60); /* was #a9a9a9 */
- --native-dark-bg-color: var(--color-01-10); /* was #292929 */
- --native-dark-bg-image-color: rgba(var(--color-01-rgb-01), 0.10); /* remains the same, or adjust if needed */
- --native-dark-border-color: var(--color-01-40); /* was #555555 */
- --native-dark-box-shadow: 0 0 0 1px rgb(var(--color-01-rgb-99), / 10%);
- --native-dark-cite-color: var(--color-01-70); /* was #92de92 – you might adjust if a green tone is needed */
- --native-dark-fill-color: var(--color-01-50); /* was #7d7d7d */
- --native-dark-font-color: var(--color-01-95); /* was #dcdcdc */
- --native-dark-link-color: var(--color-01-80); /* was #8db2e5 */
- --native-dark-visited-link-color: var(--color-01-85); /* was #c76ed7 */
-}
-
-/* Bootstrap Overrides (Color/Shadow Variables Only) */
-:root {
- --bs-black: var(--color-01-01); /* Original tone: Black (#000) */
- --bs-white: var(--color-01-99); /* Original tone: White (#fff) */
- --bs-gray: var(--color-01-50); /* Original tone: Gray (#6c757d) */
- --bs-gray-dark: var(--color-01-20); /* Original tone: Dark Gray (#343a40) */
-{% for i in range(1, 10) %}
-{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
- {% set gray = i * 100 %}
- {% set color = 100 - i * 10 %}
- --bs-gray-{{ gray }}: var(--color-01-{{ "%02d" % color }});
-{% endfor %}
- --bs-primary: var(--color-01-65); /* Original tone: Blue (#0d6efd) */
- --bs-light: var(--color-01-95); /* Original tone: Light (#f8f9fa) */
- --bs-dark: var(--color-01-10); /* Original tone: Dark (#212529) */
- --bs-primary-rgb: var(--color-01-rgb-65); /* Original tone: Blue (13, 110, 253) */
- --bs-secondary-rgb: var(--color-01-rgb-50); /* Original tone: Grayish (#6c757d / 108, 117, 125) */
- --bs-light-rgb: var(--color-01-rgb-95); /* Original tone: Light (248, 249, 250) */
- --bs-dark-rgb: var(--color-01-rgb-10); /* Original tone: Dark (33, 37, 41) */
- --bs-white-rgb: var(--color-01-rgb-99); /* Original tone: White (255, 255, 255) */
- --bs-black-rgb: var(--color-01-rgb-01); /* Original tone: Black (0, 0, 0) */
- --bs-body-color-rgb: var(--color-01-rgb-10); /* Original tone: Dark (#212529 / 33, 37, 41) */
- --bs-body-bg-rgb: var(--color-01-rgb-99); /* Original tone: White (#fff / 255, 255, 255) */
- --bs-body-color: var(--color-01-10); /* Original tone: Dark (#212529) */
- --bs-body-bg: var(--color-01-99); /* Original tone: White (#fff) */
- --bs-border-color: var(--color-01-85); /* Original tone: Gray (#dee2e6) */
- --bs-link-color: var(--color-01-65); /* Original tone: Blue (#0d6efd) */
- --bs-link-hover-color: var(--color-01-60); /* Original tone: Darker Blue (#0a58ca) */
- --bs-code-color: var(--color-01-55); /* Original tone: Pink (#d63384) */
- --bs-highlight-bg: var(--color-01-93); /* Original tone: Light Yellow (#fff3cd) */
- --bs-list-group-bg: var(--color-01-40);
- --bs-emphasis-color: var(--color-01-01); /* Gemappt von #000 */
- --bs-emphasis-color-rgb: var(--color-01-rgb-01); /* Gemappt von 0, 0, 0 */
- --bs-secondary-color: rgba(var(--color-01-rgb-10), 0.75); /* Gemappt von rgba(33, 37, 41, 0.75) */
- --bs-secondary-color-rgb: var(--color-01-rgb-10); /* Gemappt von 33, 37, 41 */
- --bs-secondary-bg: var(--color-01-90); /* Gemappt von #e9ecef */
- --bs-secondary-bg-rgb: var(--color-01-rgb-90); /* Gemappt von 233, 236, 239 */
- --bs-tertiary-color: rgba(var(--color-01-rgb-10), 0.5); /* Gemappt von rgba(33, 37, 41, 0.5) */
- --bs-tertiary-color-rgb: var(--color-01-rgb-10); /* Gemappt von 33, 37, 41 */
- --bs-tertiary-bg: var(--color-01-95); /* Gemappt von #f8f9fa */
- --bs-tertiary-bg-rgb: var(--color-01-rgb-95); /* Gemappt von 248, 249, 250 */
- --bs-link-color-rgb: var(--color-01-rgb-65); /* Gemappt von 13, 110, 253 */
- --bs-link-hover-color-rgb: var(--color-01-rgb-60); /* Gemappt von 10, 88, 202 */
- --bs-highlight-color: var(--color-01-10); /* Gemappt von #212529 */
- --bs-border-color-translucent: rgba(var(--color-01-rgb-01), 0.175); /* Gemappt von rgba(0, 0, 0, 0.175) */
- --bs-focus-ring-color: rgba(var(--color-01-rgb-65), 0.25); /* Gemappt von rgba(13, 110, 253, 0.25) */
-
- --bs-table-color: var(--bs-emphasis-color);
- --bs-table-bg: var(--color-01-99); /* White (#fff) */
- --bs-table-border-color: var(--color-01-99); /* White (#fff) */
- --bs-table-striped-bg: var(--color-01-85); /* Light Gray (entspricht ca. #dee2e6) */
- --bs-table-hover-color: var(--color-01-01); /* Black (#000) */
- --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075);
-}
-
-
-/* Discourse */
-:root section#main{
- /* Base Colors */
- --primary: var(--color-01-20); /* originally #203243 */
- --secondary: var(--color-01-95); /* originally #eef4f7 */
- --tertiary: var(--color-01-40); /* originally #416376 */
- --quaternary: var(--color-01-50); /* originally #5e99b9 */
-
- /* Header & Highlight */
- --header_background: var(--color-01-86); /* originally #86bddb */
- --header_primary: var(--color-01-20); /* same as --primary */
- --highlight: var(--color-01-70); /* same as header_background */
- --d-selected: var(--color-01-85); /* originally #bee0f2 */
- --d-hover: var(--color-01-90); /* originally #d2efff */
-
- /* Normally refers to secondary. Somehow this reference does not work.*/
- --d-sidebar-background: var(--color-01-92);
- --d-sidebar-footer-fade: var(--color-01-92);
-
-
- /* RGB values */
- --always-black-rgb: var(--color-01-rgb-01);
- --primary-rgb: var(--color-01-rgb-20);
- --primary-low-rgb: var(--color-01-rgb-95);
- --primary-very-low-rgb: var(--color-01-rgb-99);
- --secondary-rgb: var(--color-01-rgb-95);
- --header_background-rgb: var(--color-01-rgb-70);
- --tertiary-rgb: var(--color-01-rgb-40);
- --highlight-rgb: var(--color-01-rgb-70);
- --success-rgb: var(--color-01-rgb-50);
-
-
- /* Primary Scale */
- --primary-very-low: var(--color-01-99); /* originally #f7f9fb */
- --primary-low: var(--color-01-95); /* originally #e3ebf2 */
- --primary-low-mid: var(--color-01-75); /* originally #acc2d7 */
- --primary-medium: var(--color-01-60); /* originally #7499bd */
- --primary-high: var(--color-01-40); /* originally #487096 */
- --primary-very-high: var(--color-01-20); /* originally #34516d */
- --primary-50: var(--color-01-99); /* originally #f7f9fb */
-{% for i in range(1, 10) %}
-{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
- {% set primary = i * 100 %}
- {% set color = 100 - i * 8 %}
- --primary-{{ primary }}: var(--color-01-{{ "%02d" % color }});
-{% endfor %}
-
- /* Header Primary Scale */
- --header_primary-low: rgb(var(--color-01-rgb-75)); /* rgb(128, 180, 209) */
- --header_primary-low-mid: rgb(var(--color-01-rgb-70)); /* rgb(110, 155, 181) */
- --header_primary-medium: rgb(var(--color-01-rgb-60)); /* rgb(93, 132, 155) */
- --header_primary-high: rgb(var(--color-01-rgb-50)); /* rgb(78, 112, 132) */
- --header_primary-very-high: rgb(var(--color-01-rgb-20)); /* rgb(52, 76, 94) */
-
- /* Secondary Scale */
- --secondary-low: var(--color-01-20); /* originally #2f5163 */
- --secondary-medium: var(--color-01-40); /* originally #4e88a5 */
- --secondary-high: var(--color-01-60); /* originally #7ba9c1 */
- --secondary-very-high: var(--color-01-90); /* originally #d7e5ec */
-
- /* Tertiary Scale */
- --tertiary-very-low: var(--color-01-99); /* originally #eaf0f3 */
- --tertiary-low: var(--color-01-95); /* originally #dfe8ee */
- --tertiary-medium: var(--color-01-60); /* originally #96b4c5 */
- --tertiary-high: var(--color-01-40); /* originally #5886a0 */
- --tertiary-hover: var(--color-01-20); /* originally #314a59 */
- --tertiary-50: var(--color-01-99); /* originally #eaf0f3 */
-{% for i in range(1, 10) %}
-{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
- {% set tertiary = i * 100 %}
- {% set color = 100 - i * 5 %}
- --tertiary-{{ tertiary }}: var(--color-01-{{ "%02d" % color }});
-{% endfor %}
-
- /* Quaternary */
- --quaternary-low: var(--color-01-80); /* originally #cfe0ea */
-
- /* Highlight */
- --highlight-bg: var(--color-01-90); /* originally #dbebf4 */
- --highlight-low: var(--color-01-90); /* originally #dbebf4 */
- --highlight-medium: var(--color-01-80); /* originally #c3deed */
- --highlight-high: var(--color-01-30); /* originally #286688 */
-
- /* Combination Variables */
- --blend-primary-secondary-5: rgb(var(--color-01-rgb-95)); /* originally rgb(232, 238, 241) */
- --primary-med-or-secondary-med: var(--color-01-60); /* originally #7499bd */
- --primary-med-or-secondary-high: var(--color-01-60); /* originally #7499bd */
- --primary-high-or-secondary-low: var(--color-01-40); /* originally #487096 */
- --primary-low-mid-or-secondary-high: var(--color-01-75); /* originally #acc2d7 */
- --primary-low-mid-or-secondary-low: var(--color-01-75); /* originally #acc2d7 */
- --primary-or-primary-low-mid: var(--color-01-20); /* originally #203243 */
- --highlight-low-or-medium: var(--color-01-90); /* originally #dbebf4 */
- --tertiary-or-tertiary-low: var(--color-01-40); /* originally #416376 */
- --tertiary-low-or-tertiary-high: var(--color-01-95); /* originally #dfe8ee */
- --tertiary-med-or-tertiary: var(--color-01-60); /* originally #96b4c5 */
- --secondary-or-primary: var(--color-01-95); /* originally #eef4f7 */
- --tertiary-or-white: var(--color-01-40); /* originally #416376 */
-
- /* Float Kit */
- --float-kit-arrow-stroke-color: var(--primary-low); /* already mapped above */
- --float-kit-arrow-fill-color: var(--secondary); /* already mapped above */
-}
-
-/* gitea */
-:root {
- /* Base and derived colors are now referenced from the computed scale */
- --color-01-primary: var(--color-01-50);
- --color-01-primary-contrast: var(--color-01-99);
- --color-01-primary-dark-1: var(--color-01-48);
- --color-01-primary-dark-2: var(--color-01-47);
- --color-01-primary-dark-3: var(--color-01-46);
- --color-01-primary-dark-4: var(--color-01-45);
- --color-01-primary-dark-5: var(--color-01-44);
- --color-01-primary-dark-6: var(--color-01-43);
- --color-01-primary-dark-7: var(--color-01-42);
- --color-01-primary-light-1: var(--color-01-52);
- --color-01-primary-light-2: var(--color-01-53);
- --color-01-primary-light-3: var(--color-01-54);
- --color-01-primary-light-4: var(--color-01-55);
- --color-01-primary-light-5: var(--color-01-57);
- --color-01-primary-light-6: var(--color-01-59);
- --color-01-primary-light-7: var(--color-01-61);
-
- /* Alpha variants reference the base RGB variable */
-{% for i in range(1, 10) %}
-{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
- {% set alpha = i * 10 %}
- --color-01-primary-alpha-{{ alpha }}: rgba(var(--color-01-rgb-50), 0.{{ alpha }});
-{% endfor %}
-
- --color-01-primary-hover: var(--color-01-primary-dark-1);
- --color-01-primary-active: var(--color-01-primary-dark-2);
-
- /* Secondary colors */
- --color-01-secondary: var(--color-01-80);
- --color-01-secondary-dark-1: var(--color-01-78);
- --color-01-secondary-dark-2: var(--color-01-76);
- --color-01-secondary-dark-3: var(--color-01-74);
- --color-01-secondary-dark-4: var(--color-01-72);
- --color-01-secondary-dark-5: var(--color-01-70);
- --color-01-secondary-dark-6: var(--color-01-68);
- --color-01-secondary-dark-7: var(--color-01-66);
- --color-01-secondary-dark-8: var(--color-01-64);
- --color-01-secondary-dark-9: var(--color-01-62);
- --color-01-secondary-dark-10: var(--color-01-60);
- --color-01-secondary-dark-11: var(--color-01-58);
- --color-01-secondary-dark-12: var(--color-01-56);
- --color-01-secondary-dark-13: var(--color-01-54);
- --color-01-secondary-light-1: var(--color-01-92);
- --color-01-secondary-light-2: var(--color-01-93);
- --color-01-secondary-light-3: var(--color-01-94);
- --color-01-secondary-light-4: var(--color-01-95);
-
-{% for i in range(1, 10) %}
-{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
- {% set alpha = i * 10 %}
- --color-01-secondary-alpha-{{ alpha }}: rgba(var(--color-01-rgb-80), 0.{{ alpha }});
-{% endfor %}
-
- --color-01-secondary-button: var(--color-01-secondary-dark-4);
- --color-01-secondary-hover: var(--color-01-secondary-dark-5);
- --color-01-secondary-active: var(--color-01-secondary-dark-6);
-
- /* Console Colors */
- --color-01-console-fg: var(--color-01-98);
- --color-01-console-fg-subtle: var(--color-01-85);
- --color-01-console-bg: var(--color-01-10);
- --color-01-console-border: var(--color-01-40);
- --color-01-console-hover-bg: var(--color-01-42);
- --color-01-console-active-bg: var(--color-01-40);
- --color-01-console-menu-bg: var(--color-01-38);
- --color-01-console-menu-border:var(--color-01-45);
-
- /* Body, Text, and Miscellaneous Colors */
- --color-01-white: var(--color-01-99);
- --color-01-grey: var(--color-01-60);
- --color-01-grey-light: var(--color-01-65);
-
- --color-01-body: var(--color-01-white);
- --color-01-text-dark: var(--color-01-10);
- --color-01-text: var(--color-01-40);
- --color-01-text-light: var(--color-01-60);
- --color-01-text-light-1: var(--color-01-65);
- --color-01-text-light-2: var(--color-01-70);
- --color-01-text-light-3: var(--color-01-75);
-
- --color-01-footer: var(--color-01-nav-bg);
- --color-01-timeline: var(--color-01-80);
-
- /* Input Colors */
- --color-01-input-text: var(--color-01-10);
- --color-01-input-background: var(--color-01-white);
- --color-01-input-toggle-background: var(--color-01-80);
- --color-01-input-border: var(--color-01-secondary);
- --color-01-input-border-hover: var(--color-01-secondary-dark-1);
-
- /* Effects */
- --color-01-light: var(--color-01-05);
- --color-01-light-mimic-enabled: rgba(var(--color-01-rgb-05), calc(6 / 255 * 222 / 255 / 0.55));
- --color-01-light-border: var(--color-01-05);
- --color-01-hover: var(--color-01-05);
- --color-01-hover-opaque: var(--color-01-95);
- --color-01-active: var(--color-01-05);
-
- /* Menu, Card, and Markup Colors */
- --color-01-menu: var(--color-01-99);
- --color-01-card: var(--color-01-99);
- --color-01-markup-table-row: var(--color-01-01);
- --color-01-markup-code-block: var(--color-01-01);
- --color-01-markup-code-inline: var(--color-01-01);
- --color-01-button: var(--color-01-99);
- --color-01-code-bg: var(--color-01-99);
- --color-01-shadow: var(--color-01-05);
- --color-01-shadow-opaque: var(--color-01-85);
- --color-01-secondary-bg: var(--color-01-95);
- --color-01-expand-button: var(--color-01-98);
- --color-01-placeholder-text: var(--color-01-text-light-3);
- --color-01-editor-line-highlight: var(--color-01-primary-light-6);
- --color-01-project-column-bg: var(--color-01-secondary-light-4);
- --color-01-caret: var(--color-01-10);
-
- /* Reaction and Tooltip Colors */
- --color-01-reaction-bg: var(--color-01-05);
- --color-01-reaction-hover-bg: var(--color-01-primary-light-5);
- --color-01-reaction-active-bg: var(--color-01-primary-light-6);
- --color-01-tooltip-text: var(--color-01-99);
- --color-01-tooltip-bg: var(--color-01-05);
-
- /* Navigation Colors */
- --color-01-nav-bg: var(--color-01-99);
- --color-01-nav-hover-bg: var(--color-01-secondary-light-1);
- --color-01-nav-text: var(--color-01-40);
- --color-01-secondary-nav-bg: var(--color-01-99);
-
- /* Label and Accent Colors */
- --color-01-label-text: var(--color-01-40);
- --color-01-label-bg: var(--color-01-50);
- --color-01-label-hover-bg: var(--color-01-60);
- --color-01-label-active-bg: var(--color-01-70);
- --color-01-accent: var(--color-01-primary-light-1);
- --color-01-small-accent: var(--color-01-primary-light-6);
- --color-01-highlight-fg: var(--color-01-10);
- --color-01-highlight-bg: var(--color-01-99);
- --color-01-overlay-backdrop: var(--color-01-05);
-}
-
-/* Keycloak */
-:root{
- /* --- Palette Black (Graustufen) --- */
-{% for i in range(1, 21) %}
-{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
- {% set black = i * 50 %}
- {% set color = 100 - i * 5 %}
- --pf-v5-global--palette--black-{{ black }}: var(--color-01-{{ "%02d" % color }});
-{% endfor %}
-
- /* --- White --- */
- --pf-v5-global--palette--white: var(--color-01-99);
-
- /* --- Background Colors --- */
- --pf-v5-global--BackgroundColor--100: var(--color-01-99);
- --pf-v5-global--BackgroundColor--150: var(--color-01-95);
- --pf-v5-global--BackgroundColor--200: var(--color-01-85);
- --pf-v5-global--BackgroundColor--300: var(--color-01-75);
- --pf-v5-global--BackgroundColor--400: var(--color-01-65);
- --pf-v5-global--BackgroundColor--light-100: var(--color-01-99);
- --pf-v5-global--BackgroundColor--light-200: var(--color-01-95);
- --pf-v5-global--BackgroundColor--light-300: var(--color-01-85);
- --pf-v5-global--BackgroundColor--dark-100: var(--color-01-10);
- --pf-v5-global--BackgroundColor--dark-200: var(--color-01-25);
- --pf-v5-global--BackgroundColor--dark-300: var(--color-01-20);
- --pf-v5-global--BackgroundColor--dark-400: var(--color-01-30);
- --pf-v5-global--BackgroundColor--dark-transparent-100: rgba(var(--color-01-rgb-10),0.7);
- --pf-v5-global--BackgroundColor--dark-transparent-200: rgba(var(--color-01-rgb-20),0.8);
-
- /* --- Color Variables --- */
- --pf-v5-global--color-01--100: var(--color-01-10);
- --pf-v5-global--color-01--200: var(--color-01-40);
- --pf-v5-global--color-01--300: var(--color-01-25);
- --pf-v5-global--color-01--400: var(--color-01-50);
- --pf-v5-global--color-01--light-100: var(--color-01-99);
- --pf-v5-global--color-01--light-200: var(--color-01-85);
- --pf-v5-global--color-01--light-300: var(--color-01-75);
- --pf-v5-global--color-01--dark-100: var(--color-01-10);
- --pf-v5-global--color-01--dark-200: var(--color-01-40);
-
- /* --- Active Colors --- */
- --pf-v5-global--active-color--100: var(--color-01-65);
- --pf-v5-global--active-color--200: var(--color-01-95);
- --pf-v5-global--active-color--300: var(--color-01-75);
- --pf-v5-global--active-color--400: var(--color-01-85);
-
- /* --- Disabled Colors --- */
- --pf-v5-global--disabled-color--100: var(--color-01-40);
- --pf-v5-global--disabled-color--200: var(--color-01-75);
- --pf-v5-global--disabled-color--300: var(--color-01-85);
-
- /* --- Primary Colors --- */
- --pf-v5-global--primary-color--100: var(--color-01-65);
- --pf-v5-global--primary-color--200: var(--color-01-40);
- --pf-v5-global--primary-color--light-100: var(--color-01-75);
- --pf-v5-global--primary-color--dark-100: var(--color-01-65);
-
- /* --- Secondary Colors --- */
- --pf-v5-global--secondary-color--100: var(--color-01-40);
-
- /* --- Custom Colors --- */
- --pf-v5-global--custom-color--100: var(--color-01-65);
- --pf-v5-global--custom-color--200: var(--color-01-65);
- --pf-v5-global--custom-color--300: var(--color-01-30);
-
- /* --- Link Colors --- */
- --pf-v5-global--link--Color: var(--color-01-65);
- --pf-v5-global--link--color-01--hover: var(--color-01-40);
- --pf-v5-global--link--color-01--light: var(--color-01-75);
- --pf-v5-global--link--color-01--light--hover: var(--color-01-85);
- --pf-v5-global--link--color-01--dark: var(--color-01-65);
- --pf-v5-global--link--color-01--dark--hover: var(--color-01-40);
- --pf-v5-global--link--color-01--visited: var(--color-01-40);
-
- /* --- Border Colors --- */
- --pf-v5-global--BorderColor--100: var(--color-01-75);
- --pf-v5-global--BorderColor--200: var(--color-01-50);
- --pf-v5-global--BorderColor--300: var(--color-01-85);
- --pf-v5-global--BorderColor--400: var(--color-01-65);
- --pf-v5-global--BorderColor--dark-100: var(--color-01-75);
- --pf-v5-global--BorderColor--light-100: var(--color-01-65);
-
- /* --- Icon Colors --- */
- --pf-v5-global--icon--color-01--light: var(--color-01-40);
- --pf-v5-global--icon--color-01--dark: var(--color-01-10);
- --pf-v5-global--icon--color-01--light--light: var(--color-01-85);
- --pf-v5-global--icon--color-01--dark--light: var(--color-01-99);
- --pf-v5-global--icon--color-01--light--dark: var(--color-01-40);
- --pf-v5-global--icon--color-01--dark--dark: var(--color-01-10);
-
-}
-
-.pf-v5-c-button.pf-m-tertiary {
- --pf-v5-c-button--m-tertiary--BackgroundColor: var(--color-01-70);
- {# Assume that the following line is necessary due to load order #}
- background-color: var(--pf-v5-c-button--m-tertiary--BackgroundColor);
-}
-
-/* Additional Keykloak Configuration */
-a.pf-v5-c-nav__link{
- --pf-v5-c-nav__link--BackgroundColor: rgba(var(--color-01-rgb-56), 0.4);
-}
-
-/* LAM */
-:root {
---lam-background-color-default: var(--color-01-99); {# from #FFFFFF (very bright white) #}
---lam-input-bg-color: var(--color-01-98); {# from #fcfcfc (almost white) #}
---lam-text-color-default: var(--color-01-01); {# from #000000 (pure black) #}
---lam-border-color: var(--color-01-90); {# from #e8e8e8 (light grey) #}
---lam-border-color-primary: var(--color-01-15); {# from #01689e (dark blue) #}
---lam-border-color-secondary: var(--color-01-85); {# from #ffcb1d (bright yellow) #}
---lam-background-color-primary: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
---lam-background-color-secondary: var(--color-01-90); {# from #ffe233 (bright yellow) #}
---lam-text-color-primary: var(--color-01-99); {# from #ffffff (pure white) #}
---lam-text-color-secondary: var(--color-01-01); {# from #000000 (pure black) #}
---lam-text-color-ok: var(--color-01-10); {# from #237d0c (dark green) #}
---lam-table-background-color-bright: var(--color-01-98); {# from #fbfbfb (very light grey) #}
---lam-table-background-color-dark: var(--color-01-92); {# from #e8f3ff (light blue) #}
---lam-table-background-color-hover: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
---lam-table-text-color-hover: var(--color-01-99); {# from #ffffff (pure white) #}
---lam-table-border-color: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
-}
-
-/** Mastodon Overrides **/
-div#mastodon, div#admin-wrapper {
- /* Dropdown */
- --dropdown-border-color: var(--color-01-35);
- --dropdown-background-color: rgba(var(--color-01-rgb-03), 0.9);
- --dropdown-shadow: 0 20px 25px -5px rgba(var(--color-01-rgb-01), 0.25),
- 0 8px 10px -6px rgba(var(--color-01-rgb-01), 0.25);
-
- /* Modal */
- --modal-background-color: rgba(var(--color-01-rgb-03), 0.7);
- --modal-background-variant-color: rgba(var(--color-01-rgb-05), 0.7);
- --modal-border-color: var(--color-01-35);
-
- /* Background */
- --background-border-color: var(--color-01-82);
- --background-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
- --background-color: var(--color-01-93);
- --background-color-tint: rgba(var(--color-01-rgb-03), 0.9);
-
- /* Surface */
- --surface-background-color: var(--color-01-90);
- --surface-variant-background-color: var(--color-01-89);
- --surface-variant-active-background-color: var(--color-01-35);
- --on-surface-color: rgba(var(--color-01-rgb-05), 0.5);
-
- /* Media & Overlay */
- --media-outline-color: rgba(var(--color-01-rgb-99), 0.15);
- --overlay-icon-shadow: drop-shadow(0 0 8px rgba(var(--color-01-rgb-01), 0.25));
-}
-
-.swal2-popup {
- color: #000;
-}
-
-/* Modal Overwrittes */
-div.modal div.modal-content {
- /* Colors – adjusted to the existing scheme */
- --bs-modal-color: var(--color-01-21); /* Text color: dark contrast against the light modal background */
- --bs-modal-bg: var(--color-01-82); /* Background color, as desired */
- --bs-modal-border-color: var(--color-01-82); /* A slightly darker border than the background */
- --bs-modal-header-border-color: var(--color-01-87); /* Same shade as the modal border */
- --bs-modal-footer-bg: var(--color-01-87); /* A slightly offset footer background (a bit darker than the main area) */
- --bs-modal-footer-border-color: var(--color-01-87);
-}
-
-/** Nextcloud Specific**/
-:root{
- --color-01-main-background: var(--color-01-84);
- --color-01-main-background-rgb: rgba(var(--color-01-rgb-84),0.83);
- --color-01-primary-element: var(--color-01-80);
- --color-01-main-text: var(--color-01-40);
- --color-01-background-hover: var(--color-01-65);
-
- /** Calendar **/
- --color-01-background-dark: var(--color-01-73); /** Days which aren't in the current month **/
- --color-01-primary-element-light: var(--color-01-65);
-}
-
-/** Peertube **/
-body#custom-css {
- --mainColor: var(--color-01-60); /* Original tone: hsl(24, 90%, 50%) – vibrant orange */
- --mainColorLighter: var(--color-01-70); /* Original tone: #f5873d – lighter orange */
- --mainColorLightest: var(--color-01-90); /* Original tone: #fce1cf – very light orange/beige */
- --mainColorVeryLight: var(--color-01-95); /* Original tone: #fff5eb – almost white */
- --mainHoverColor: var(--color-01-64); /* Original tone: #f47825 – hover orange */
- --mainBackgroundHoverColor: var(--color-01-92); /* Original tone: #e9ecef – light gray */
- --mainBackgroundColor: var(--color-01-99); /* Original tone: #fff – white */
- --mainForegroundColor: var(--color-01-10); /* Original tone: #212529 – dark gray/black */
- --greyForegroundColor: var(--color-01-50); /* Original tone: #585858 – medium gray */
- --greyBackgroundColor: var(--color-01-90); /* Original tone: #E5E5E5 – light gray */
- --greySecondaryBackgroundColor: var(--color-01-91); /* Original tone: #EFEFEF – very light gray */
- --menuBackgroundColor: var(--color-01-82); /* Original tone: #000 – black */
- --menuForegroundColor: var(--color-01-99); /* Original tone: #fff – white */
- --submenuBackgroundColor: var(--color-01-95); /* Original tone: #F7F7F7 – off-white/light gray */
- --channelBackgroundColor: var(--color-01-93); /* Original tone: #f6ede8 – warm light beige */
- --inputForegroundColor: var(--color-01-10); /* Original tone: #212529 – dark gray */
- --inputBackgroundColor: var(--color-01-99); /* Original tone: #fff – white */
- --inputPlaceholderColor: var(--color-01-55); /* Original tone: #797676 – medium gray */
- --inputBorderColor: var(--color-01-80); /* Original tone: #C6C6C6 – light gray */
- --textareaForegroundColor: var(--color-01-10); /* Original tone: #212529 – dark gray */
- --textareaBackgroundColor: var(--color-01-99); /* Original tone: #fff – white */
- --markdownTextareaBackgroundColor: var(--color-01-91); /* Original tone: #EFEFEF – very light gray */
- --actionButtonColor: var(--color-01-50); /* Original tone: #585858 – medium gray */
- --supportButtonColor: var(--actionButtonColor); /* Original tone: same as actionButtonColor (#585858) */
- --activatedActionButtonColor: var(--color-01-10); /* Original tone: #212529 – dark gray */
- color: var(--mainForegroundColor);
- background-color: var(--mainBackgroundColor);
-}
-
-div.searchbox input.autocomplete-input{
- background-position: 12px;
- background-repeat: no-repeat;
-}
-
-html[native-dark-active] my-app button, button {
- background-color: transparent;
-}
-
-/** Pixelfed **/
-
-:root {
- /* Base Colors */
- --light: var(--color-01-05); /* Very dark (was #000) */
- --dark: var(--color-01-99); /* Very light (was #fff) */
-
- /* Backgrounds */
- --body-bg: var(--color-01-05); /* Main background: very dark */
- --nav-bg: var(--color-01-05); /* Navigation background: very dark */
-
- /* Text Colors */
- --body-color: var(--color-01-70); /* Main text – mid brightness */
- --text-lighter: var(--color-01-60); /* Lighter text for less prominent elements */
-
- /* Section Backgrounds and Cards */
- --bg-light: var(--color-01-95); /* Lighter background areas */
- --card-bg: var(--color-01-90); /* Card background */
- --light-gray: var(--color-01-75); /* For less dominant elements */
- --light-hover-bg: var(--color-01-85); /* Slightly lighter hover background */
-
- /* Borders and Input Fields */
- --btn-light-border: var(--color-01-10); /* Dark border for buttons */
- --input-border: var(--color-01-10); /* Border color for inputs */
- --border-color: var(--color-01-85); /* General border: slightly lighter than background */
-
- /* Other Areas */
- --comment-bg: var(--color-01-85); /* Background for comments */
- --card-header-accent: var(--color-01-85); /* Accent color in card headers */
-
- /* Dropdown Menus */
- --dropdown-item-hover-bg: var(--color-01-05); /* Hover background: very dark */
- --dropdown-item-hover-color: var(--color-01-60); /* Hover text: a bit lighter */
- --dropdown-item-color: var(--color-01-70); /* Regular dropdown item text */
- --dropdown-item-active-color: var(--color-01-99); /* Active state: very light (white) */
-}
-
-/** Sphinx **/
-.bg-background\/95 {
- background-color: var(--color-01-96);
-}
-
-.border-border {
- border-color: var(--color-01-85);
-}
-
-{# Hide Toogle Button #}
-nav.flex.items-center.space-x-1{
- display:none;
-}
-
-/** Taiga **/
-:host, :root {
- --color-01-solid-primary: var(--color-01-83);
- --color-01-link-primary: var(--color-01-44);
- --color-01-link-tertiary: var(--color-01-45);
- --color-01-gray100: var(--color-01-97);
- --color-01-gray200: var(--color-01-93);
- --color-01-gray300: var(--color-01-90);
- --color-01-gray400: var(--color-01-86);
- --color-01-black600: var(--color-01-34);
- --color-01-black700: var(--color-01-30);
- --color-01-black800: var(--color-01-26);
- --color-01-black900: var(--color-01-21);
- --color-01-black: var(--color-01-01);
- --color-01-white: var(--color-01-99);
-}
-
-/* Global Defaults (Colors Only) */
-body, html[native-dark-active] {
- background-color: var(--color-01-93);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-93), var(--color-01-91), var(--color-01-95), var(--color-01-93));
- background-attachment: fixed;
- color: var(--color-01-40);
- font-family: {{design.font.type}};
-}
-
-{# All links (applies to all anchor elements regardless of state) #}
-a {
- color: var(--color-01-50);
-}
-
-{# Unvisited links (applies only to links that have not been visited) #}
-a:link {
- color: var(--color-01-55);
-}
-
-{# Visited links (applies only to links that have been visited) #}
-a:visited {
- color: var(--color-01-45);
-}
-
-{# Hover state (applies when the mouse pointer is over the link) #}
-a:hover {
- color: var(--color-01-60);
-}
-
-{# Active state (applies during the time the link is being activated, e.g., on click) #}
-a:active {
- color: var(--color-01-65);
-}
-
-/** Set default buttons transparent **/
-html[native-dark-active] button, button{
- background-color: var(--color-01-87);
-}
-
-button:hover, .btn:hover {
- filter: brightness(0.9);
-}
-
-/* {# Invalid state: when the input value fails validation criteria. Use danger color for error indication. #} */
-input:invalid,
-textarea:invalid,
-select:invalid {
- background-color: var(--color-01-01);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-01), var(--color-01-10));
- /* Use Bootstrap danger color for error messages */
- color: var(--bs-danger);
- border-color: var(--color-01-20);
-}
-
-/* {# Valid state: when the input value meets all validation criteria. Use success color for confirmation. #} */
-input:valid,
-textarea:valid,
-select:valid {
- background-color: var(--color-01-80);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-80), var(--color-01-90));
- /* Use Bootstrap success color for confirmation messages */
- color: var(--bs-success);
- border-color: var(--color-01-70);
-}
-
-/* {# Required field: applied to elements that must be filled out by the user. Use warning color for emphasis. #} */
-input:required,
-textarea:required,
-select:required {
- background-color: var(--color-01-50);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-50), var(--color-01-60));
- /* Use Bootstrap warning color to indicate a required field */
- color: var(--bs-warning);
- border-color: var(--color-01-70);
-}
-
-/* {# Optional field: applied to elements that are not mandatory. Use info color to denote additional information. #} */
-input:optional,
-textarea:optional,
-select:optional {
- background-color: var(--color-01-60);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-60), var(--color-01-70));
- /* Use Bootstrap info color to indicate optional information */
- color: var(--bs-info);
- border-color: var(--color-01-70);
-}
-
-/* {# Read-only state: when an element is not editable by the user. #} */
-input:read-only,
-textarea:read-only,
-select:read-only {
- background-color: var(--color-01-80);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-90), var(--color-01-70));
- color: var(--color-01-20);
- border-color: var(--color-01-50);
-}
-
-/* {# Read-write state: when an element is editable by the user. #} */
-input:read-write,
-textarea:read-write,
-select:read-write {
- background-color: var(--color-01-70);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-80));
- color: var(--color-01-40);
- border-color: var(--color-01-70);
-}
-
-/* {# In-range: for inputs with a defined range, when the value is within the allowed limits. #} */
-input:in-range,
-textarea:in-range,
-select:in-range {
- background-color: var(--color-01-70);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-85));
- color: var(--color-01-40);
- border-color: var(--color-01-70);
-}
-
-/* {# Out-of-range: for inputs with a defined range, when the value falls outside the allowed limits. #} */
-input:out-of-range,
-textarea:out-of-range,
-select:out-of-range {
- background-color: var(--color-01-10);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-10), var(--color-01-30));
- color: var(--color-01-10);
- border-color: var(--color-01-50);
-}
-
-/* {# Placeholder-shown: when the input field is displaying its placeholder text. #} */
-input:placeholder-shown,
-textarea:placeholder-shown,
-select:placeholder-shown {
- background-color: var(--color-01-82);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-82), var(--color-01-90));
- color: var(--color-01-40);
- border-color: var(--color-01-70);
-}
-
-/* {# Focus state: when the element is focused by the user. #} */
-input:focus,
-textarea:focus,
-select:focus {
- background-color: var(--color-01-75);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-85));
- color: var(--color-01-40);
- border-color: var(--color-01-50);
-}
-
-/* {# Hover state: when the mouse pointer is over the element. #} */
-input:hover,
-textarea:hover,
-select:hover {
- background-color: var(--color-01-78);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-78), var(--color-01-88));
- color: var(--color-01-40);
- border-color: var(--color-01-65);
-}
-
-/* {# Active state: when the element is being activated (e.g., clicked). #} */
-input:active,
-textarea:active,
-select:active {
- background-color: var(--color-01-68);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-68), var(--color-01-78));
- color: var(--color-01-40);
- border-color: var(--color-01-60);
-}
-
-/* {# Checked state: specifically for radio buttons and checkboxes when selected. #} */
-input:checked {
- background-color: var(--color-01-90);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-90), var(--color-01-99));
- color: var(--color-01-40);
- border-color: var(--color-01-70);
-}
-
-option {
- background-color: var(--color-01-82);
- color: var(--color-01-07);
-}
-
-/* Tables (Borders and Header Colors) */
-th, td {
- border-color: var(--color-01-70);
-}
-
-thead {
- background-color: var(--color-01-80);
- /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
- color: var(--color-01-40);
-}
-
-/* Headings (Text Color) */
-h1, h2, h3, h4, h5, h6, p{
- color: var(--color-01-10);
-}
-
-/* Buttons (Background, Text, Border, and Shadow)
- Now using a button background that is only slightly darker than the overall background */
-html[native-dark-active] .btn, .btn {
- background-color: var(--color-01-87);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-91), var(--color-01-95), var(--color-01-95));
- color: var(--color-01-50);
- border-color: var(--color-01-80);
- cursor: pointer;
-}
-
-/* Navigation (Background and Text Colors) */
-.navbar, .navbar-light, .navbar-dark, .navbar.bg-light {
- background-color: var(--color-01-90);
- /* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
- color: var(--color-01-50);
- border-color: var(--color-01-85);
-}
-
-.navbar a {
- color: var(--color-01-40);
-}
-
-.navbar a.dropdown-item {
- color: var(--color-01-43);
-}
-
-/* Cards / Containers (Background, Border, and Shadow)
- Cards now use a slightly lighter background and a bold, clear shadow */
-.card {
- background-color: var(--color-01-90);
- /* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
- border-color: var(--color-01-85);
- color: var(--color-01-12);
-}
-
-.card-body {
- color: var(--color-01-40);
-}
-
-/* Dropdown Menu and Submenu (Background, Text, and Shadow) */
-.navbar .dropdown-menu,
-.nav-item .dropdown-menu {
- background-color: var(--color-01-80);
- /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
- color: var(--color-01-40);
-}
-
-.navbar-nav {
- --bs-nav-link-hover-color: var(--color-01-17);
-}
-
-.dropdown-item {
- color: var(--color-01-40);
- background-color: var(--color-01-80);
- /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
-}
-
-.dropdown-item:hover,
-.dropdown-item:focus {
- background-color: var(--color-01-65);
- /* New Gradient based on original background (65 -5, 65, 65 +1, 65 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-60), var(--color-01-65), var(--color-01-66), var(--color-01-70));
- color: var(--color-01-40);
-}
-
-/* Keycloak */
-div#app header{
- background-color: var(--color-01-60);
- /* New Gradient based on original background (60 -5, 60, 60 +1, 60 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-55), var(--color-01-60), var(--color-01-61), var(--color-01-65));
- color: var(--color-01-98);
-}
-
-/** LAM Specific **/
-.lam-vertical-tabs-navigation li, .lam-vertical-tabs-navigation{
- background-color: transparent !important;
- border-color: transparent;
-}
-
-ul.lam-tab-navigation {
- background: rgba(var(--color-01-rgb-90), 0.1);
- border-color: transparent;
-}
-
-/* Not changable due to inline css */
-.roundedShadowBox {
- color: #000000;
-}
-
-.titleBar {
- background-image: linear-gradient(var(--color-01-83), var(--color-01-92));
- /* New Gradient based on original background (83 -5, 83, 83 +1, 83 +5) */
- background-image: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-78), var(--color-01-83), var(--color-01-84), var(--color-01-88));
- border-top-color: var(--color-01-78);
- border-left-color: var(--color-01-87);
- border-right-color: var(--color-01-87);
-}
-
-div.statusInfo {
- background-color: var(--color-01-81);
- /* New Gradient based on original background (81 -5, 81, 81 +1, 81 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-76), var(--color-01-81), var(--color-01-82), var(--color-01-86));
- color: var(--color-01-23);
-}
-
-/** Mailu **/
-[class*=sidebar-dark-], .bg-mailu-logo {
- background-color: var(--color-01-90);
- /* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
-}
-
-div.statusError {
- background-color: var(--color-01-60);
- /* New Gradient based on original background (60 -5, 60, 60 +1, 60 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-55), var(--color-01-60), var(--color-01-61), var(--color-01-65));
-}
-
-div.wrapper footer.main-footer, div.wrapper div.content-wrapper{
- background-color: var(--color-01-85);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-89), var(--color-01-85), var(--color-01-80), var(--color-01-79));
- color: var(--color-01-39);
-}
-
-html.dark-mode #layout-menu .special-buttons a:not(:focus) {
- background: none;
-}
-
-html.dark-mode #taskmenu a.selected, html.dark-mode .menu.toolbar a.selected {
- background-color: rgba(var(--color-01-rgb-82), 0.5);
-}
-
-html.dark-mode .listing li.selected, html.dark-mode .listing li.selected>a, html.dark-mode .listing li.selected>div>a, html.dark-mode .listing tr.selected td {
- color: var(--color-01-30);
- background-color: rgba(var(--color-01-rgb-82), 0.5);
-}
-
-html.dark-mode .message-htmlpart {
- background-color: rgba(var(--color-01-rgb-99), 0.08);
- color: var(--color-01-15);
-}
-
-/** Nextcloud specific **/
-html.ng-csp header#header{
- background-color: var(--color-01-80);
- /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
- color: var(--color-01-17);
-}
-
-.files-list__row-name button, button.button-vue{
- background: transparent;
-}
-
-html.ng-csp div#postsetupchecks ul.info{
- background-color: transparent;
-}
-
-div#mastodon .column-link{
- color: var(--color-01-55);
-}
-
-div#mastodon .column-back-button {
- color: var(--color-01-58);
-}
-
-div#mastodon textarea, div#mastodon input, div#mastodon .compose-form__highlightable {
- background-color: var(--color-01-89);
- /* New Gradient based on original background (89 -5, 89, 89 +1, 89 +5) */
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-84), var(--color-01-89), var(--color-01-90), var(--color-01-94));
- color: var(--color-01-19);
-}
-
-div#mastodon .status-card__title, div#mastodon .display-name strong{
- color: var(--color-01-33);
-}
-
-div#mastodon a.unhandled-link, div#mastodon .dropdown-button, div#mastodon .status__content a, div#mastodon .status-card__author{
- color: var(--color-01-29);
-}
-div#mastodon .dropdown-button{
- border: 1px solid #8c8dff;
-}
-
-div#mastodon .button, div#mastodon .button:active, div#mastodon .button:focus, div#mastodon .button:hover{
- background-color: var(--color-01-71);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-66), var(--color-01-71), var(--color-01-72), var(--color-01-76));
-}
-
-.compose-form__actions .icon-button {
- color: var(--color-01-28);
-}
-
-/** OpenProject **/
-header.op-app-header{
- background-color: var(--color-01-40);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-35), var(--color-01-40), var(--color-01-41), var(--color-01-45));
- color: var(--color-01-40);
-}
-
-div#wrapper button, div#wrapper input, button.top-menu-search-button, div.menu-sidebar a{
- background-color: transparent;
-}
-
-/* OAuth2 Proxy */
-{# The variables look like they are bootstrap variables. @todo Verify and generalize if possible #}
-.box {
- background-color: var(--color-01-92);
- color: var(--color-01-10);
-}
-
-.subtitle {
- color: inherit;
-}
-
-.has-background-light {
- background-color: var(--color-01-96) !important;
-}
-
-/* Pixelfed */
-div.page-wrapper{
- background: none;
- background-color: none;
-}
-
-.card-header-title {
- color: var(--color-01-37);
-}
-
-/* PHP MyAdmin */
-#pma_navigation {
- background: linear-gradient(to right, var(--color-01-95), var(--color-01-85));
- color: var(--color-01-05);
-}
-
-#pma_navigation_tree a {
- color: var(--color-01-05);
-}
-
-#pma_navigation_tree li.activePointer, #pma_navigation_tree li.selected {
- color: var(--color-01-05);
- background-color: var(--color-01-70);
-}
-
-.breadcrumb-navbar {
- background-color: var(--color-01-86);
-}
-
-.navbar-nav .nav-item {
- background: linear-gradient(var(--color-01-99), var(--color-01-85));
- border-right-color: var(--color-01-99);
- border-left-color: var(--color-01-80);
- border-bottom-color: var(--color-01-80);
-}
-
-.result_query div.sqlOuter {
- background: var(--color-01-50);
-}
-
-.table {
- --bs-table-bg: var(--color-01-99); /* #fff → white */
- --bs-table-border-color: var(--color-01-99); /* #fff → white */
- --bs-table-striped-bg: var(--color-01-90); /* #dfdfdf → light gray */
- --bs-table-hover-color: var(--color-01-01); /* #000 → black */
-}
-
-.table thead th {
- background-image: linear-gradient(var(--color-01-90), var(--color-01-80));
- border-color: var(--color-01-99);
-}
-
-.table th, .table td {
- text-shadow: 0 1px 0 var(--color-01-60);
-}
-
-div.tools, .tblFooters {
- color: var(--color-01-01);
- background: var(--color-01-62);
-}
-
-
-.navigation {
- background: linear-gradient(var(--color-01-87), var(--color-01-69));
-}
-
-.pma-fieldset {
- border-color: var(--color-01-17);
- background: var(--color-01-80);
-}
-
-/** Taiga specific configuration **/
-
-section.main.kanban{
- background-color: transparent;
-}
-
-div.master, div.kanban-header, div.kanban-table-inner, section.kanban button,a.dropdown-project-list-projects{
- background-color: var(--color-01-92);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-87), var(--color-01-92), var(--color-01-93), var(--color-01-97));
- color: var(--color-01-40);
-}
-
-section.kanban h1, section.kanban h2{
- color: var(--color-01-40);
-}
-
-.home-project {
- background: var(--color-01-88);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-83), var(--color-01-88), var(--color-01-89), var(--color-01-93));
- border-color: var(--color-01-60);
- color: var(--color-01-12);
-}
-
-.home-wrapper .title-bar {
- background: var(--color-01-75);
- background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-75), var(--color-01-76), var(--color-01-80));
-}
-
-.kanban.swimlane .kanban-header {
- background: none;
-}
-
-.kanban-table-header .task-colum-name {
- background-color: var(--color-01-70);
- color: var(--color-01-10);
-}
-
-input.ng-empty::placeholder,.ng-empty::placeholder {
- color: rgba(var(--color-01-rgb-03),0.6);
-}
-
-.lightbox {
- background: rgba(var(--color-01-rgb-97), .93);
- color: var(--color-01-03);
-}
-
-.kanban-filter tg-filter {
- border-color: var(--color-01-70);
-}
-
-.discover-header {
- background: none;
-}
-
-/* Portfolio */
-.card-img-top i {
- filter: drop-shadow(4px 4px 4px rgba(var(--color-01-rgb-23), 0.6));
-}
-
-.kanban-table-body .kanban-uses-box {
- background-color: var(--color-01-81);
-}
-
-.kanban-swimlane-title {
- border-bottom: none;
-}
-
-.navbar-toggler {
- background-color: rgba(var(--color-rgb-01-75), 0.9);
- border-color: var(--color-01-67)
-}
-
-.sys-ctl-alm-info {
- --bs-sys-ctl-alm-color: var(--color-03-14);
- --bs-sys-ctl-alm-bg: var(--color-01-86);
-}
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-css/templates/head_sub.j2 b/roles/sys-srv-web-inj-css/templates/head_sub.j2
deleted file mode 100644
index fa1120bd..00000000
--- a/roles/sys-srv-web-inj-css/templates/head_sub.j2
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-css/templates/location.conf.j2 b/roles/sys-srv-web-inj-css/templates/location.conf.j2
deleted file mode 100644
index d204c4d4..00000000
--- a/roles/sys-srv-web-inj-css/templates/location.conf.j2
+++ /dev/null
@@ -1,3 +0,0 @@
-location = /global.css {
- root {{ NGINX.DIRECTORIES.DATA.CDN }};
-}
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-css/vars/main.yml b/roles/sys-srv-web-inj-css/vars/main.yml
deleted file mode 100644
index 10d51cb4..00000000
--- a/roles/sys-srv-web-inj-css/vars/main.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-global_css_destination: "{{ NGINX.DIRECTORIES.DATA.CDN }}global.css"
-global_css_base_color: "{{ design.css.colors.base }}"
-global_css_count: 7
-global_css_shades: 100
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-desktop/tasks/01_deploy.yml b/roles/sys-srv-web-inj-desktop/tasks/01_deploy.yml
deleted file mode 100644
index 82ae7684..00000000
--- a/roles/sys-srv-web-inj-desktop/tasks/01_deploy.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-- name: Deploy iframe-handler.js
- template:
- src: iframe-handler.js.j2
- dest: "{{ INJ_DESKTOP_JS_FILE_DESTINATION }}"
- owner: "{{ NGINX.USER }}"
- group: "{{ NGINX.USER }}"
- mode: '0644'
-
-- name: Get stat for iframe-handler.js
- stat:
- path: "{{ INJ_DESKTOP_JS_FILE_DESTINATION }}"
- register: inj_port_ui_js_stat
-
-- name: Set inj_port_ui_js_version
- set_fact:
- inj_port_ui_js_version: "{{ inj_port_ui_js_stat.stat.mtime }}"
diff --git a/roles/sys-srv-web-inj-desktop/templates/head_sub.j2 b/roles/sys-srv-web-inj-desktop/templates/head_sub.j2
deleted file mode 100644
index f4a3f642..00000000
--- a/roles/sys-srv-web-inj-desktop/templates/head_sub.j2
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-desktop/vars/main.yml b/roles/sys-srv-web-inj-desktop/vars/main.yml
deleted file mode 100644
index ca67d07b..00000000
--- a/roles/sys-srv-web-inj-desktop/vars/main.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-INJ_DESKTOP_JS_FILE_NAME: "iframe-handler.js"
-INJ_DESKTOP_JS_FILE_DESTINATION: "{{ [ NGINX.DIRECTORIES.DATA.CDN, INJ_DESKTOP_JS_FILE_NAME ] | path_join }}"
diff --git a/roles/sys-srv-web-inj-logout/templates/head_sub.j2 b/roles/sys-srv-web-inj-logout/templates/head_sub.j2
deleted file mode 100644
index 317ac0a7..00000000
--- a/roles/sys-srv-web-inj-logout/templates/head_sub.j2
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/roles/sys-srv-web-inj-logout/vars/main.yml b/roles/sys-srv-web-inj-logout/vars/main.yml
deleted file mode 100644
index 385f6e8f..00000000
--- a/roles/sys-srv-web-inj-logout/vars/main.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-INJ_LOGOUT_JS_FILE_NAME: "logout.js"
-INJ_LOGOUT_JS_DESTINATION: "{{ [ NGINX.DIRECTORIES.DATA.CDN, INJ_LOGOUT_JS_FILE_NAME ] | path_join }}"
\ No newline at end of file
diff --git a/roles/sys-stk-front-proxy/README.md b/roles/sys-stk-front-proxy/README.md
index 5effc606..5580c522 100644
--- a/roles/sys-stk-front-proxy/README.md
+++ b/roles/sys-stk-front-proxy/README.md
@@ -8,7 +8,7 @@ This role bootstraps **per-domain Nginx configuration**: it requests TLS certifi
A higher-level orchestration wrapper, *sys-stk-front-proxy* ties together several lower-level roles:
-1. **`sys-srv-web-inj-compose`** – applies global tweaks and includes.
+1. **`sys-front-inj-all`** – applies global tweaks and includes.
2. **`sys-svc-certs`** – obtains Let’s Encrypt certificates.
3. **Domain template deployment** – copies a Jinja2 vHost from *srv-proxy-core*.
4. **`web-app-oauth2-proxy`** *(optional)* – protects the site with OAuth2.
diff --git a/roles/sys-svc-cdn/README.md b/roles/sys-svc-cdn/README.md
new file mode 100644
index 00000000..d37f9a3b
--- /dev/null
+++ b/roles/sys-svc-cdn/README.md
@@ -0,0 +1,19 @@
+# sys-svc-cdn
+
+CDN helper role for building a consistent asset tree, URLs, and on-disk layout.
+
+## Description
+
+Provides compact filters and defaults to define CDN paths, turn them into public URLs, collect required directories, and prepare the filesystem (including a `latest` release link).
+
+## Overview
+
+Defines a per-role CDN structure under `roles//` plus shared and vendor areas. Exposes ready-to-use variables (`cdn`, `cdn_dirs`, `cdn_urls`) and ensures directories exist. Optionally links the current release to `latest`.
+
+## Features
+
+* Jinja filters: `cdn_paths`, `cdn_urls`, `cdn_dirs`
+* Variables: `CDN_ROOT`, `CDN_VERSION`, `CDN_BASE_URL`, `cdn`, `cdn_dirs`, `cdn_urls`
+* Creates shared/vendor/release directories
+* Maintains `roles//latest` symlink (when version ≠ `latest`)
+* Plays nicely with `web-svc-cdn` without circular inclusion
diff --git a/tests/unit/roles/sys-srv-web-inj-compose/__init__.py b/roles/sys-svc-cdn/__init__.py
similarity index 100%
rename from tests/unit/roles/sys-srv-web-inj-compose/__init__.py
rename to roles/sys-svc-cdn/__init__.py
diff --git a/tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/__init__.py b/roles/sys-svc-cdn/filter_plugins/__init__.py
similarity index 100%
rename from tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/__init__.py
rename to roles/sys-svc-cdn/filter_plugins/__init__.py
diff --git a/roles/sys-svc-cdn/filter_plugins/cdn_dirs.py b/roles/sys-svc-cdn/filter_plugins/cdn_dirs.py
new file mode 100644
index 00000000..d2490dd6
--- /dev/null
+++ b/roles/sys-svc-cdn/filter_plugins/cdn_dirs.py
@@ -0,0 +1,17 @@
+import os
+
+def cdn_dirs(tree):
+ out = set()
+ def walk(v):
+ if isinstance(v, dict):
+ for x in v.values(): walk(x)
+ elif isinstance(v, list):
+ for x in v: walk(x)
+ elif isinstance(v, str) and os.path.isabs(v):
+ out.add(v)
+ walk(tree)
+ return sorted(out)
+
+class FilterModule(object):
+ def filters(self):
+ return {"cdn_dirs": cdn_dirs}
diff --git a/roles/sys-svc-cdn/filter_plugins/cdn_paths.py b/roles/sys-svc-cdn/filter_plugins/cdn_paths.py
new file mode 100644
index 00000000..a0e10445
--- /dev/null
+++ b/roles/sys-svc-cdn/filter_plugins/cdn_paths.py
@@ -0,0 +1,46 @@
+import datetime
+import os
+
+def cdn_paths(cdn_root, application_id, version):
+ """
+ Build a structured dictionary of all CDN paths for a given application.
+
+ Args:
+ cdn_root (str): Base CDN root, e.g. /var/www/cdn
+ application_id (str): Role/application identifier
+ version (str): Release version string (default: current UTC timestamp)
+
+ Returns:
+ dict: Hierarchical CDN path structure
+ """
+ cdn_root = os.path.abspath(cdn_root)
+
+ return {
+ "root": cdn_root,
+ "shared": {
+ "root": os.path.join(cdn_root, "_shared"),
+ "css": os.path.join(cdn_root, "_shared", "css"),
+ "js": os.path.join(cdn_root, "_shared", "js"),
+ "img": os.path.join(cdn_root, "_shared", "img"),
+ "fonts": os.path.join(cdn_root, "_shared", "fonts"),
+ },
+ "vendor": os.path.join(cdn_root, "vendor"),
+ "role": {
+ "id": application_id,
+ "root": os.path.join(cdn_root, "roles", application_id),
+ "version": version,
+ "release": {
+ "root": os.path.join(cdn_root, "roles", application_id, version),
+ "css": os.path.join(cdn_root, "roles", application_id, version, "css"),
+ "js": os.path.join(cdn_root, "roles", application_id, version, "js"),
+ "img": os.path.join(cdn_root, "roles", application_id, version, "img"),
+ "fonts": os.path.join(cdn_root, "roles", application_id, version, "fonts"),
+ },
+ },
+ }
+
+class FilterModule(object):
+ def filters(self):
+ return {
+ "cdn_paths": cdn_paths,
+ }
diff --git a/roles/sys-svc-cdn/filter_plugins/cdn_urls.py b/roles/sys-svc-cdn/filter_plugins/cdn_urls.py
new file mode 100644
index 00000000..c3e38e7c
--- /dev/null
+++ b/roles/sys-svc-cdn/filter_plugins/cdn_urls.py
@@ -0,0 +1,60 @@
+# filter_plugins/cdn_urls.py
+import os
+
+def _to_url_tree(obj, cdn_root, base_url):
+ """
+ Recursively walk a nested dict and replace any string paths under cdn_root
+ with URLs based on base_url. Non-path strings (e.g. role.id, role.version)
+ are left untouched.
+ """
+ if isinstance(obj, dict):
+ return {k: _to_url_tree(v, cdn_root, base_url) for k, v in obj.items()}
+
+ if isinstance(obj, list):
+ return [_to_url_tree(v, cdn_root, base_url) for v in obj]
+
+ if isinstance(obj, str):
+ # Normalize inputs
+ norm_root = os.path.abspath(cdn_root)
+ norm_val = os.path.abspath(obj)
+
+ if norm_val.startswith(norm_root):
+ # Compute path relative to CDN root and map to URL
+ rel = os.path.relpath(norm_val, norm_root)
+ # Handle root itself ('.') → empty path
+ if rel == ".":
+ rel = ""
+ # Always forward slashes for URLs
+ rel_url = rel.replace(os.sep, "/")
+ base = base_url.rstrip("/")
+ return f"{base}/{rel_url}" if rel_url else f"{base}/"
+ # Non-CDN string → leave as-is (e.g., role.id / role.version)
+ return obj
+
+ # Any other type → return as-is
+ return obj
+
+
+def cdn_urls(cdn_dict, base_url):
+ """
+ Create a URL-structured dict from a CDN path dict.
+
+ Args:
+ cdn_dict (dict): output of cdn_paths(...), containing absolute paths
+ base_url (str): CDN base URL, e.g. https://cdn.example.com
+
+ Returns:
+ dict: same shape as cdn_dict, but with URLs instead of filesystem paths
+ for any strings pointing under cdn_dict['root'].
+ Keys like role.id and role.version remain strings as-is.
+ """
+ if not isinstance(cdn_dict, dict) or "root" not in cdn_dict:
+ raise ValueError("cdn_urls expects a dict from cdn_paths with a 'root' key")
+ return _to_url_tree(cdn_dict, cdn_dict["root"], base_url)
+
+
+class FilterModule(object):
+ def filters(self):
+ return {
+ "cdn_urls": cdn_urls,
+ }
diff --git a/roles/sys-svc-cdn/meta/main.yml b/roles/sys-svc-cdn/meta/main.yml
new file mode 100644
index 00000000..99732ef7
--- /dev/null
+++ b/roles/sys-svc-cdn/meta/main.yml
@@ -0,0 +1,24 @@
+---
+galaxy_info:
+ author: "Kevin Veen-Birkenbach"
+ description: "Prepares and manages the CDN folder structure with shared, vendor, and per-role release directories."
+ license: "Infinito.Nexus NonCommercial License"
+ license_url: "https://s.infinito.nexus/license"
+ company: |
+ Kevin Veen-Birkenbach
+ Consulting & Coaching Solutions
+ https://www.veen.world
+ min_ansible_version: "2.9"
+ platforms:
+ - name: Any
+ versions:
+ - all
+ galaxy_tags:
+ - cdn
+ - nginx
+ - assets
+ - roles
+ - versioning
+ repository: "https://s.infinito.nexus/code"
+ issue_tracker_url: "https://s.infinito.nexus/issues"
+ documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-svc-cdn"
diff --git a/roles/sys-svc-cdn/tasks/main.yml b/roles/sys-svc-cdn/tasks/main.yml
new file mode 100644
index 00000000..38defcd5
--- /dev/null
+++ b/roles/sys-svc-cdn/tasks/main.yml
@@ -0,0 +1,41 @@
+---
+- block:
+ - name: "Load CDN for '{{ domain }}'"
+ include_role:
+ name: web-svc-cdn
+ public: false
+ when:
+ #- inj_enabled.logout
+ #- inj_enabled.desktop
+ - application_id != 'web-svc-cdn'
+ - run_once_web_svc_cdn is not defined
+
+ - name: Overwritte CDN handlers with neutral handlers
+ ansible.builtin.include_tasks: "{{ [ playbook_dir, 'tasks/utils/load_handlers.yml'] | path_join }}"
+ loop:
+ - svc-prx-openresty
+ - docker-compose
+ loop_control:
+ label: "{{ item }}"
+ vars:
+ handler_role_name: "{{ item }}"
+ - include_tasks: utils/run_once.yml
+ when:
+ - run_once_sys_svc_cdn is not defined
+
+- name: Ensure CDN directories exist
+ file:
+ path: "{{ item }}"
+ state: directory
+ owner: "{{ NGINX.USER }}"
+ group: "{{ NGINX.USER }}"
+ mode: "0755"
+ loop: "{{ cdn_dirs }}"
+
+- name: Ensure 'latest' symlink points to current release
+ file:
+ src: "{{ cdn.role.release.root }}"
+ dest: "{{ [cdn.role.root, 'latest'] | path_join }}"
+ state: link
+ force: true
+ when: CDN_VERSION != 'latest'
diff --git a/roles/sys-svc-cdn/vars/main.yml b/roles/sys-svc-cdn/vars/main.yml
new file mode 100644
index 00000000..6ee87117
--- /dev/null
+++ b/roles/sys-svc-cdn/vars/main.yml
@@ -0,0 +1,19 @@
+# Base CDN root (shared across all roles)
+CDN_ROOT: "{{ NGINX.DIRECTORIES.DATA.CDN }}"
+
+# Default version identifier: UTC timestamp
+CDN_VERSION: "latest" # Latest is used atm because timestamp based would just lead to an unneccessary overhead
+
+# Base Url to deliver the files
+CDN_BASE_URL: "{{ domains | get_url('web-svc-cdn', WEB_PROTOCOL) }}"
+
+# Role specific CDN paths
+
+## Build CDN path structure (via filter)
+cdn: "{{ CDN_ROOT | cdn_paths(application_id, CDN_VERSION) }}"
+
+## Flatten CDN dict to list of all string paths
+cdn_dirs: "{{ cdn | cdn_dirs }}"
+
+# Dictionary with all urls
+cdn_urls: "{{ cdn | cdn_urls(CDN_BASE_URL) }}"
diff --git a/roles/update-docker/templates/script.py.j2 b/roles/update-docker/templates/script.py.j2
index eea21548..facfdec9 100644
--- a/roles/update-docker/templates/script.py.j2
+++ b/roles/update-docker/templates/script.py.j2
@@ -144,14 +144,6 @@ def update_discourse(directory):
else:
print("Discourse update skipped. No changes in git repository.")
-def update_mastodon():
- """
- Runs the database migration for Mastodon to ensure all required tables are up to date.
- """
- print("Starting Mastodon database migration.")
- run_command("docker compose exec -T web bash -c 'RAILS_ENV={{ ENVIRONMENT | lower }} bin/rails db:migrate'")
- print("Mastodon database migration complete.")
-
def upgrade_listmonk():
"""
Runs the upgrade for Listmonk
@@ -220,8 +212,6 @@ if __name__ == "__main__":
update_discourse(dir_path)
elif os.path.basename(dir_path) == "listmonk":
upgrade_listmonk()
- elif os.path.basename(dir_path) == "mastodon":
- update_mastodon()
# @todo implement dedicated procedure for bluesky
# @todo implement dedicated procedure for taiga
diff --git a/roles/web-app-desktop/config/main.yml b/roles/web-app-desktop/config/main.yml
index 68788d80..c8f5671d 100644
--- a/roles/web-app-desktop/config/main.yml
+++ b/roles/web-app-desktop/config/main.yml
@@ -13,7 +13,7 @@ server:
- https://cdn.jsdelivr.net
- https://kit.fontawesome.com
- https://code.jquery.com/
- style-src:
+ style-src-elem:
- https://cdn.jsdelivr.net
font-src:
- https://ka-f.fontawesome.com
@@ -24,12 +24,8 @@ server:
frame-src:
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}"
flags:
- style-src:
- unsafe-inline: true
script-src:
unsafe-inline: true
- script-src-elem:
- unsafe-inline: true
domains:
canonical:
- "{{ PRIMARY_DOMAIN }}"
diff --git a/roles/web-app-desktop/templates/style.css.j2 b/roles/web-app-desktop/templates/style.css.j2
new file mode 100644
index 00000000..928289fd
--- /dev/null
+++ b/roles/web-app-desktop/templates/style.css.j2
@@ -0,0 +1,21 @@
+.card-img-top i {
+ filter: drop-shadow(4px 4px 4px rgba(var(--color-01-rgb-23), 0.6));
+}
+
+.kanban-table-body .kanban-uses-box {
+ background-color: var(--color-01-81);
+}
+
+.kanban-swimlane-title {
+ border-bottom: none;
+}
+
+.navbar-toggler {
+ background-color: rgba(var(--color-01-rgb-75), 0.9);
+ border-color: var(--color-01-67)
+}
+
+.sys-ctl-alm-info {
+ --bs-sys-ctl-alm-color: var(--color-03-14);
+ --bs-sys-ctl-alm-bg: var(--color-01-86);
+}
\ No newline at end of file
diff --git a/roles/web-app-discourse/templates/style.css.j2 b/roles/web-app-discourse/templates/style.css.j2
new file mode 100644
index 00000000..41821e5f
--- /dev/null
+++ b/roles/web-app-discourse/templates/style.css.j2
@@ -0,0 +1,102 @@
+/* Discourse */
+:root section#main{
+ /* Base Colors */
+ --primary: var(--color-01-20); /* originally #203243 */
+ --secondary: var(--color-01-95); /* originally #eef4f7 */
+ --tertiary: var(--color-01-40); /* originally #416376 */
+ --quaternary: var(--color-01-50); /* originally #5e99b9 */
+
+ /* Header & Highlight */
+ --header_background: var(--color-01-86); /* originally #86bddb */
+ --header_primary: var(--color-01-20); /* same as --primary */
+ --highlight: var(--color-01-70); /* same as header_background */
+ --d-selected: var(--color-01-85); /* originally #bee0f2 */
+ --d-hover: var(--color-01-90); /* originally #d2efff */
+
+ /* Normally refers to secondary. Somehow this reference does not work.*/
+ --d-sidebar-background: var(--color-01-92);
+ --d-sidebar-footer-fade: var(--color-01-92);
+
+
+ /* RGB values */
+ --always-black-rgb: var(--color-01-rgb-01);
+ --primary-rgb: var(--color-01-rgb-20);
+ --primary-low-rgb: var(--color-01-rgb-95);
+ --primary-very-low-rgb: var(--color-01-rgb-99);
+ --secondary-rgb: var(--color-01-rgb-95);
+ --header_background-rgb: var(--color-01-rgb-70);
+ --tertiary-rgb: var(--color-01-rgb-40);
+ --highlight-rgb: var(--color-01-rgb-70);
+ --success-rgb: var(--color-01-rgb-50);
+
+
+ /* Primary Scale */
+ --primary-very-low: var(--color-01-99); /* originally #f7f9fb */
+ --primary-low: var(--color-01-95); /* originally #e3ebf2 */
+ --primary-low-mid: var(--color-01-75); /* originally #acc2d7 */
+ --primary-medium: var(--color-01-60); /* originally #7499bd */
+ --primary-high: var(--color-01-40); /* originally #487096 */
+ --primary-very-high: var(--color-01-20); /* originally #34516d */
+ --primary-50: var(--color-01-99); /* originally #f7f9fb */
+{% for i in range(1, 10) %}
+{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
+ {% set primary = i * 100 %}
+ {% set color = 100 - i * 8 %}
+ --primary-{{ primary }}: var(--color-01-{{ "%02d" % color }});
+{% endfor %}
+
+ /* Header Primary Scale */
+ --header_primary-low: rgb(var(--color-01-rgb-75)); /* rgb(128, 180, 209) */
+ --header_primary-low-mid: rgb(var(--color-01-rgb-70)); /* rgb(110, 155, 181) */
+ --header_primary-medium: rgb(var(--color-01-rgb-60)); /* rgb(93, 132, 155) */
+ --header_primary-high: rgb(var(--color-01-rgb-50)); /* rgb(78, 112, 132) */
+ --header_primary-very-high: rgb(var(--color-01-rgb-20)); /* rgb(52, 76, 94) */
+
+ /* Secondary Scale */
+ --secondary-low: var(--color-01-20); /* originally #2f5163 */
+ --secondary-medium: var(--color-01-40); /* originally #4e88a5 */
+ --secondary-high: var(--color-01-60); /* originally #7ba9c1 */
+ --secondary-very-high: var(--color-01-90); /* originally #d7e5ec */
+
+ /* Tertiary Scale */
+ --tertiary-very-low: var(--color-01-99); /* originally #eaf0f3 */
+ --tertiary-low: var(--color-01-95); /* originally #dfe8ee */
+ --tertiary-medium: var(--color-01-60); /* originally #96b4c5 */
+ --tertiary-high: var(--color-01-40); /* originally #5886a0 */
+ --tertiary-hover: var(--color-01-20); /* originally #314a59 */
+ --tertiary-50: var(--color-01-99); /* originally #eaf0f3 */
+{% for i in range(1, 10) %}
+{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
+ {% set tertiary = i * 100 %}
+ {% set color = 100 - i * 5 %}
+ --tertiary-{{ tertiary }}: var(--color-01-{{ "%02d" % color }});
+{% endfor %}
+
+ /* Quaternary */
+ --quaternary-low: var(--color-01-80); /* originally #cfe0ea */
+
+ /* Highlight */
+ --highlight-bg: var(--color-01-90); /* originally #dbebf4 */
+ --highlight-low: var(--color-01-90); /* originally #dbebf4 */
+ --highlight-medium: var(--color-01-80); /* originally #c3deed */
+ --highlight-high: var(--color-01-30); /* originally #286688 */
+
+ /* Combination Variables */
+ --blend-primary-secondary-5: rgb(var(--color-01-rgb-95)); /* originally rgb(232, 238, 241) */
+ --primary-med-or-secondary-med: var(--color-01-60); /* originally #7499bd */
+ --primary-med-or-secondary-high: var(--color-01-60); /* originally #7499bd */
+ --primary-high-or-secondary-low: var(--color-01-40); /* originally #487096 */
+ --primary-low-mid-or-secondary-high: var(--color-01-75); /* originally #acc2d7 */
+ --primary-low-mid-or-secondary-low: var(--color-01-75); /* originally #acc2d7 */
+ --primary-or-primary-low-mid: var(--color-01-20); /* originally #203243 */
+ --highlight-low-or-medium: var(--color-01-90); /* originally #dbebf4 */
+ --tertiary-or-tertiary-low: var(--color-01-40); /* originally #416376 */
+ --tertiary-low-or-tertiary-high: var(--color-01-95); /* originally #dfe8ee */
+ --tertiary-med-or-tertiary: var(--color-01-60); /* originally #96b4c5 */
+ --secondary-or-primary: var(--color-01-95); /* originally #eef4f7 */
+ --tertiary-or-white: var(--color-01-40); /* originally #416376 */
+
+ /* Float Kit */
+ --float-kit-arrow-stroke-color: var(--primary-low); /* already mapped above */
+ --float-kit-arrow-fill-color: var(--secondary); /* already mapped above */
+}
diff --git a/roles/web-app-gitea/templates/style.css.j2 b/roles/web-app-gitea/templates/style.css.j2
new file mode 100644
index 00000000..eb38808b
--- /dev/null
+++ b/roles/web-app-gitea/templates/style.css.j2
@@ -0,0 +1,141 @@
+:root {
+ /* Base and derived colors are now referenced from the computed scale */
+ --color-01-primary: var(--color-01-50);
+ --color-01-primary-contrast: var(--color-01-99);
+ --color-01-primary-dark-1: var(--color-01-48);
+ --color-01-primary-dark-2: var(--color-01-47);
+ --color-01-primary-dark-3: var(--color-01-46);
+ --color-01-primary-dark-4: var(--color-01-45);
+ --color-01-primary-dark-5: var(--color-01-44);
+ --color-01-primary-dark-6: var(--color-01-43);
+ --color-01-primary-dark-7: var(--color-01-42);
+ --color-01-primary-light-1: var(--color-01-52);
+ --color-01-primary-light-2: var(--color-01-53);
+ --color-01-primary-light-3: var(--color-01-54);
+ --color-01-primary-light-4: var(--color-01-55);
+ --color-01-primary-light-5: var(--color-01-57);
+ --color-01-primary-light-6: var(--color-01-59);
+ --color-01-primary-light-7: var(--color-01-61);
+
+ /* Alpha variants reference the base RGB variable */
+{% for i in range(1, 10) %}
+{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
+ {% set alpha = i * 10 %}
+ --color-01-primary-alpha-{{ alpha }}: rgba(var(--color-01-rgb-50), 0.{{ alpha }});
+{% endfor %}
+
+ --color-01-primary-hover: var(--color-01-primary-dark-1);
+ --color-01-primary-active: var(--color-01-primary-dark-2);
+
+ /* Secondary colors */
+ --color-01-secondary: var(--color-01-80);
+ --color-01-secondary-dark-1: var(--color-01-78);
+ --color-01-secondary-dark-2: var(--color-01-76);
+ --color-01-secondary-dark-3: var(--color-01-74);
+ --color-01-secondary-dark-4: var(--color-01-72);
+ --color-01-secondary-dark-5: var(--color-01-70);
+ --color-01-secondary-dark-6: var(--color-01-68);
+ --color-01-secondary-dark-7: var(--color-01-66);
+ --color-01-secondary-dark-8: var(--color-01-64);
+ --color-01-secondary-dark-9: var(--color-01-62);
+ --color-01-secondary-dark-10: var(--color-01-60);
+ --color-01-secondary-dark-11: var(--color-01-58);
+ --color-01-secondary-dark-12: var(--color-01-56);
+ --color-01-secondary-dark-13: var(--color-01-54);
+ --color-01-secondary-light-1: var(--color-01-92);
+ --color-01-secondary-light-2: var(--color-01-93);
+ --color-01-secondary-light-3: var(--color-01-94);
+ --color-01-secondary-light-4: var(--color-01-95);
+
+{% for i in range(1, 10) %}
+{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
+ {% set alpha = i * 10 %}
+ --color-01-secondary-alpha-{{ alpha }}: rgba(var(--color-01-rgb-80), 0.{{ alpha }});
+{% endfor %}
+
+ --color-01-secondary-button: var(--color-01-secondary-dark-4);
+ --color-01-secondary-hover: var(--color-01-secondary-dark-5);
+ --color-01-secondary-active: var(--color-01-secondary-dark-6);
+
+ /* Console Colors */
+ --color-01-console-fg: var(--color-01-98);
+ --color-01-console-fg-subtle: var(--color-01-85);
+ --color-01-console-bg: var(--color-01-10);
+ --color-01-console-border: var(--color-01-40);
+ --color-01-console-hover-bg: var(--color-01-42);
+ --color-01-console-active-bg: var(--color-01-40);
+ --color-01-console-menu-bg: var(--color-01-38);
+ --color-01-console-menu-border:var(--color-01-45);
+
+ /* Body, Text, and Miscellaneous Colors */
+ --color-01-white: var(--color-01-99);
+ --color-01-grey: var(--color-01-60);
+ --color-01-grey-light: var(--color-01-65);
+
+ --color-01-body: var(--color-01-white);
+ --color-01-text-dark: var(--color-01-10);
+ --color-01-text: var(--color-01-40);
+ --color-01-text-light: var(--color-01-60);
+ --color-01-text-light-1: var(--color-01-65);
+ --color-01-text-light-2: var(--color-01-70);
+ --color-01-text-light-3: var(--color-01-75);
+
+ --color-01-footer: var(--color-01-nav-bg);
+ --color-01-timeline: var(--color-01-80);
+
+ /* Input Colors */
+ --color-01-input-text: var(--color-01-10);
+ --color-01-input-background: var(--color-01-white);
+ --color-01-input-toggle-background: var(--color-01-80);
+ --color-01-input-border: var(--color-01-secondary);
+ --color-01-input-border-hover: var(--color-01-secondary-dark-1);
+
+ /* Effects */
+ --color-01-light: var(--color-01-05);
+ --color-01-light-mimic-enabled: rgba(var(--color-01-rgb-05), calc(6 / 255 * 222 / 255 / 0.55));
+ --color-01-light-border: var(--color-01-05);
+ --color-01-hover: var(--color-01-05);
+ --color-01-hover-opaque: var(--color-01-95);
+ --color-01-active: var(--color-01-05);
+
+ /* Menu, Card, and Markup Colors */
+ --color-01-menu: var(--color-01-99);
+ --color-01-card: var(--color-01-99);
+ --color-01-markup-table-row: var(--color-01-01);
+ --color-01-markup-code-block: var(--color-01-01);
+ --color-01-markup-code-inline: var(--color-01-01);
+ --color-01-button: var(--color-01-99);
+ --color-01-code-bg: var(--color-01-99);
+ --color-01-shadow: var(--color-01-05);
+ --color-01-shadow-opaque: var(--color-01-85);
+ --color-01-secondary-bg: var(--color-01-95);
+ --color-01-expand-button: var(--color-01-98);
+ --color-01-placeholder-text: var(--color-01-text-light-3);
+ --color-01-editor-line-highlight: var(--color-01-primary-light-6);
+ --color-01-project-column-bg: var(--color-01-secondary-light-4);
+ --color-01-caret: var(--color-01-10);
+
+ /* Reaction and Tooltip Colors */
+ --color-01-reaction-bg: var(--color-01-05);
+ --color-01-reaction-hover-bg: var(--color-01-primary-light-5);
+ --color-01-reaction-active-bg: var(--color-01-primary-light-6);
+ --color-01-tooltip-text: var(--color-01-99);
+ --color-01-tooltip-bg: var(--color-01-05);
+
+ /* Navigation Colors */
+ --color-01-nav-bg: var(--color-01-99);
+ --color-01-nav-hover-bg: var(--color-01-secondary-light-1);
+ --color-01-nav-text: var(--color-01-40);
+ --color-01-secondary-nav-bg: var(--color-01-99);
+
+ /* Label and Accent Colors */
+ --color-01-label-text: var(--color-01-40);
+ --color-01-label-bg: var(--color-01-50);
+ --color-01-label-hover-bg: var(--color-01-60);
+ --color-01-label-active-bg: var(--color-01-70);
+ --color-01-accent: var(--color-01-primary-light-1);
+ --color-01-small-accent: var(--color-01-primary-light-6);
+ --color-01-highlight-fg: var(--color-01-10);
+ --color-01-highlight-bg: var(--color-01-99);
+ --color-01-overlay-backdrop: var(--color-01-05);
+}
diff --git a/roles/web-app-keycloak/templates/style.css.j2 b/roles/web-app-keycloak/templates/style.css.j2
new file mode 100644
index 00000000..e1de6a67
--- /dev/null
+++ b/roles/web-app-keycloak/templates/style.css.j2
@@ -0,0 +1,109 @@
+:root{
+ /* --- Palette Black (Gray Scale) --- */
+{% for i in range(1, 21) %}
+{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
+ {% set black = i * 50 %}
+ {% set color = 100 - i * 5 %}
+ --pf-v5-global--palette--black-{{ black }}: var(--color-01-{{ "%02d" % color }});
+{% endfor %}
+
+ /* --- White --- */
+ --pf-v5-global--palette--white: var(--color-01-99);
+
+ /* --- Background Colors --- */
+ --pf-v5-global--BackgroundColor--100: var(--color-01-99);
+ --pf-v5-global--BackgroundColor--150: var(--color-01-95);
+ --pf-v5-global--BackgroundColor--200: var(--color-01-85);
+ --pf-v5-global--BackgroundColor--300: var(--color-01-75);
+ --pf-v5-global--BackgroundColor--400: var(--color-01-65);
+ --pf-v5-global--BackgroundColor--light-100: var(--color-01-99);
+ --pf-v5-global--BackgroundColor--light-200: var(--color-01-95);
+ --pf-v5-global--BackgroundColor--light-300: var(--color-01-85);
+ --pf-v5-global--BackgroundColor--dark-100: var(--color-01-10);
+ --pf-v5-global--BackgroundColor--dark-200: var(--color-01-25);
+ --pf-v5-global--BackgroundColor--dark-300: var(--color-01-20);
+ --pf-v5-global--BackgroundColor--dark-400: var(--color-01-30);
+ --pf-v5-global--BackgroundColor--dark-transparent-100: rgba(var(--color-01-rgb-10),0.7);
+ --pf-v5-global--BackgroundColor--dark-transparent-200: rgba(var(--color-01-rgb-20),0.8);
+
+ /* --- Color Variables --- */
+ --pf-v5-global--color-01--100: var(--color-01-10);
+ --pf-v5-global--color-01--200: var(--color-01-40);
+ --pf-v5-global--color-01--300: var(--color-01-25);
+ --pf-v5-global--color-01--400: var(--color-01-50);
+ --pf-v5-global--color-01--light-100: var(--color-01-99);
+ --pf-v5-global--color-01--light-200: var(--color-01-85);
+ --pf-v5-global--color-01--light-300: var(--color-01-75);
+ --pf-v5-global--color-01--dark-100: var(--color-01-10);
+ --pf-v5-global--color-01--dark-200: var(--color-01-40);
+
+ /* --- Active Colors --- */
+ --pf-v5-global--active-color--100: var(--color-01-65);
+ --pf-v5-global--active-color--200: var(--color-01-95);
+ --pf-v5-global--active-color--300: var(--color-01-75);
+ --pf-v5-global--active-color--400: var(--color-01-85);
+
+ /* --- Disabled Colors --- */
+ --pf-v5-global--disabled-color--100: var(--color-01-40);
+ --pf-v5-global--disabled-color--200: var(--color-01-75);
+ --pf-v5-global--disabled-color--300: var(--color-01-85);
+
+ /* --- Primary Colors --- */
+ --pf-v5-global--primary-color--100: var(--color-01-65);
+ --pf-v5-global--primary-color--200: var(--color-01-40);
+ --pf-v5-global--primary-color--light-100: var(--color-01-75);
+ --pf-v5-global--primary-color--dark-100: var(--color-01-65);
+
+ /* --- Secondary Colors --- */
+ --pf-v5-global--secondary-color--100: var(--color-01-40);
+
+ /* --- Custom Colors --- */
+ --pf-v5-global--custom-color--100: var(--color-01-65);
+ --pf-v5-global--custom-color--200: var(--color-01-65);
+ --pf-v5-global--custom-color--300: var(--color-01-30);
+
+ /* --- Link Colors --- */
+ --pf-v5-global--link--Color: var(--color-01-65);
+ --pf-v5-global--link--color-01--hover: var(--color-01-40);
+ --pf-v5-global--link--color-01--light: var(--color-01-75);
+ --pf-v5-global--link--color-01--light--hover: var(--color-01-85);
+ --pf-v5-global--link--color-01--dark: var(--color-01-65);
+ --pf-v5-global--link--color-01--dark--hover: var(--color-01-40);
+ --pf-v5-global--link--color-01--visited: var(--color-01-40);
+
+ /* --- Border Colors --- */
+ --pf-v5-global--BorderColor--100: var(--color-01-75);
+ --pf-v5-global--BorderColor--200: var(--color-01-50);
+ --pf-v5-global--BorderColor--300: var(--color-01-85);
+ --pf-v5-global--BorderColor--400: var(--color-01-65);
+ --pf-v5-global--BorderColor--dark-100: var(--color-01-75);
+ --pf-v5-global--BorderColor--light-100: var(--color-01-65);
+
+ /* --- Icon Colors --- */
+ --pf-v5-global--icon--color-01--light: var(--color-01-40);
+ --pf-v5-global--icon--color-01--dark: var(--color-01-10);
+ --pf-v5-global--icon--color-01--light--light: var(--color-01-85);
+ --pf-v5-global--icon--color-01--dark--light: var(--color-01-99);
+ --pf-v5-global--icon--color-01--light--dark: var(--color-01-40);
+ --pf-v5-global--icon--color-01--dark--dark: var(--color-01-10);
+
+}
+
+.pf-v5-c-button.pf-m-tertiary {
+ --pf-v5-c-button--m-tertiary--BackgroundColor: var(--color-01-70);
+ {# Assume that the following line is necessary due to load order #}
+ background-color: var(--pf-v5-c-button--m-tertiary--BackgroundColor);
+}
+
+/* Additional Keykloak Configuration */
+a.pf-v5-c-nav__link{
+ --pf-v5-c-nav__link--BackgroundColor: rgba(var(--color-01-rgb-56), 0.4);
+}
+
+/* Keycloak */
+div#app header{
+ background-color: var(--color-01-60);
+ /* New Gradient based on original background (60 -5, 60, 60 +1, 60 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-55), var(--color-01-60), var(--color-01-61), var(--color-01-65));
+ color: var(--color-01-98);
+}
diff --git a/roles/web-app-lam/templates/style.css.j2 b/roles/web-app-lam/templates/style.css.j2
new file mode 100644
index 00000000..f79b242a
--- /dev/null
+++ b/roles/web-app-lam/templates/style.css.j2
@@ -0,0 +1,51 @@
+:root {
+--lam-background-color-default: var(--color-01-99); {# from #FFFFFF (very bright white) #}
+--lam-input-bg-color: var(--color-01-98); {# from #fcfcfc (almost white) #}
+--lam-text-color-default: var(--color-01-01); {# from #000000 (pure black) #}
+--lam-border-color: var(--color-01-90); {# from #e8e8e8 (light grey) #}
+--lam-border-color-primary: var(--color-01-15); {# from #01689e (dark blue) #}
+--lam-border-color-secondary: var(--color-01-85); {# from #ffcb1d (bright yellow) #}
+--lam-background-color-primary: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
+--lam-background-color-secondary: var(--color-01-90); {# from #ffe233 (bright yellow) #}
+--lam-text-color-primary: var(--color-01-99); {# from #ffffff (pure white) #}
+--lam-text-color-secondary: var(--color-01-01); {# from #000000 (pure black) #}
+--lam-text-color-ok: var(--color-01-10); {# from #237d0c (dark green) #}
+--lam-table-background-color-bright: var(--color-01-98); {# from #fbfbfb (very light grey) #}
+--lam-table-background-color-dark: var(--color-01-92); {# from #e8f3ff (light blue) #}
+--lam-table-background-color-hover: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
+--lam-table-text-color-hover: var(--color-01-99); {# from #ffffff (pure white) #}
+--lam-table-border-color: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
+}
+
+
+/** LAM Specific **/
+.lam-vertical-tabs-navigation li, .lam-vertical-tabs-navigation{
+ background-color: transparent !important;
+ border-color: transparent;
+}
+
+ul.lam-tab-navigation {
+ background: rgba(var(--color-01-rgb-90), 0.1);
+ border-color: transparent;
+}
+
+/* Not changable due to inline css */
+.roundedShadowBox {
+ color: #000000;
+}
+
+.titleBar {
+ background-image: linear-gradient(var(--color-01-83), var(--color-01-92));
+ /* New Gradient based on original background (83 -5, 83, 83 +1, 83 +5) */
+ background-image: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-78), var(--color-01-83), var(--color-01-84), var(--color-01-88));
+ border-top-color: var(--color-01-78);
+ border-left-color: var(--color-01-87);
+ border-right-color: var(--color-01-87);
+}
+
+div.statusInfo {
+ background-color: var(--color-01-81);
+ /* New Gradient based on original background (81 -5, 81, 81 +1, 81 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-76), var(--color-01-81), var(--color-01-82), var(--color-01-86));
+ color: var(--color-01-23);
+}
diff --git a/roles/web-app-mailu/templates/style.css.j2 b/roles/web-app-mailu/templates/style.css.j2
new file mode 100644
index 00000000..b4474e27
--- /dev/null
+++ b/roles/web-app-mailu/templates/style.css.j2
@@ -0,0 +1,35 @@
+[class*=sidebar-dark-], .bg-mailu-logo {
+ background-color: var(--color-01-90);
+ /* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
+}
+
+div.statusError {
+ background-color: var(--color-01-60);
+ /* New Gradient based on original background (60 -5, 60, 60 +1, 60 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-55), var(--color-01-60), var(--color-01-61), var(--color-01-65));
+}
+
+div.wrapper footer.main-footer, div.wrapper div.content-wrapper{
+ background-color: var(--color-01-85);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-89), var(--color-01-85), var(--color-01-80), var(--color-01-79));
+ color: var(--color-01-39);
+}
+
+html.dark-mode #layout-menu .special-buttons a:not(:focus) {
+ background: none;
+}
+
+html.dark-mode #taskmenu a.selected, html.dark-mode .menu.toolbar a.selected {
+ background-color: rgba(var(--color-01-rgb-82), 0.5);
+}
+
+html.dark-mode .listing li.selected, html.dark-mode .listing li.selected>a, html.dark-mode .listing li.selected>div>a, html.dark-mode .listing tr.selected td {
+ color: var(--color-01-30);
+ background-color: rgba(var(--color-01-rgb-82), 0.5);
+}
+
+html.dark-mode .message-htmlpart {
+ background-color: rgba(var(--color-01-rgb-99), 0.08);
+ color: var(--color-01-15);
+}
diff --git a/roles/web-app-mastodon/templates/style.css.j2 b/roles/web-app-mastodon/templates/style.css.j2
new file mode 100644
index 00000000..4da574aa
--- /dev/null
+++ b/roles/web-app-mastodon/templates/style.css.j2
@@ -0,0 +1,43 @@
+div#mastodon, div#admin-wrapper {
+ /* Dropdown */
+ --dropdown-border-color: var(--color-01-35);
+ --dropdown-background-color: rgba(var(--color-01-rgb-03), 0.9);
+ --dropdown-shadow: 0 20px 25px -5px rgba(var(--color-01-rgb-01), 0.25),
+ 0 8px 10px -6px rgba(var(--color-01-rgb-01), 0.25);
+
+ /* Modal */
+ --modal-background-color: rgba(var(--color-01-rgb-03), 0.7);
+ --modal-background-variant-color: rgba(var(--color-01-rgb-05), 0.7);
+ --modal-border-color: var(--color-01-35);
+
+ /* Background */
+ --background-border-color: var(--color-01-82);
+ --background-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
+ --background-color: var(--color-01-93);
+ --background-color-tint: rgba(var(--color-01-rgb-03), 0.9);
+
+ /* Surface */
+ --surface-background-color: var(--color-01-90);
+ --surface-variant-background-color: var(--color-01-89);
+ --surface-variant-active-background-color: var(--color-01-35);
+ --on-surface-color: rgba(var(--color-01-rgb-05), 0.5);
+
+ /* Media & Overlay */
+ --media-outline-color: rgba(var(--color-01-rgb-99), 0.15);
+ --overlay-icon-shadow: drop-shadow(0 0 8px rgba(var(--color-01-rgb-01), 0.25));
+}
+
+.swal2-popup {
+ color: #000;
+}
+
+/* Modal Overwrittes */
+div.modal div.modal-content {
+ /* Colors – adjusted to the existing scheme */
+ --bs-modal-color: var(--color-01-21); /* Text color: dark contrast against the light modal background */
+ --bs-modal-bg: var(--color-01-82); /* Background color, as desired */
+ --bs-modal-border-color: var(--color-01-82); /* A slightly darker border than the background */
+ --bs-modal-header-border-color: var(--color-01-87); /* Same shade as the modal border */
+ --bs-modal-footer-bg: var(--color-01-87); /* A slightly offset footer background (a bit darker than the main area) */
+ --bs-modal-footer-border-color: var(--color-01-87);
+}
diff --git a/roles/web-app-nextcloud/templates/nginx/host.conf.j2 b/roles/web-app-nextcloud/templates/nginx/host.conf.j2
index bc7ec963..2326e6aa 100644
--- a/roles/web-app-nextcloud/templates/nginx/host.conf.j2
+++ b/roles/web-app-nextcloud/templates/nginx/host.conf.j2
@@ -4,7 +4,7 @@ server
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;
diff --git a/roles/web-app-nextcloud/templates/style.css.j2 b/roles/web-app-nextcloud/templates/style.css.j2
new file mode 100644
index 00000000..32edf0dc
--- /dev/null
+++ b/roles/web-app-nextcloud/templates/style.css.j2
@@ -0,0 +1,65 @@
+:root{
+ --color-01-main-background: var(--color-01-84);
+ --color-01-main-background-rgb: rgba(var(--color-01-rgb-84),0.83);
+ --color-01-primary-element: var(--color-01-80);
+ --color-01-main-text: var(--color-01-40);
+ --color-01-background-hover: var(--color-01-65);
+
+ /** Calendar **/
+ --color-01-background-dark: var(--color-01-73); /** Days which aren't in the current month **/
+ --color-01-primary-element-light: var(--color-01-65);
+}
+
+/** Nextcloud specific **/
+html.ng-csp header#header{
+ background-color: var(--color-01-80);
+ /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
+ color: var(--color-01-17);
+}
+
+.files-list__row-name button, button.button-vue{
+ background: transparent;
+}
+
+html.ng-csp div#postsetupchecks ul.info{
+ background-color: transparent;
+}
+
+
+/** I wounder if this is correct here or if it belongs to the mastodon role */
+
+div#mastodon .column-link{
+ color: var(--color-01-55);
+}
+
+div#mastodon .column-back-button {
+ color: var(--color-01-58);
+}
+
+div#mastodon textarea, div#mastodon input, div#mastodon .compose-form__highlightable {
+ background-color: var(--color-01-89);
+ /* New Gradient based on original background (89 -5, 89, 89 +1, 89 +5) */
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-84), var(--color-01-89), var(--color-01-90), var(--color-01-94));
+ color: var(--color-01-19);
+}
+
+div#mastodon .status-card__title, div#mastodon .display-name strong{
+ color: var(--color-01-33);
+}
+
+div#mastodon a.unhandled-link, div#mastodon .dropdown-button, div#mastodon .status__content a, div#mastodon .status-card__author{
+ color: var(--color-01-29);
+}
+div#mastodon .dropdown-button{
+ border: 1px solid #8c8dff;
+}
+
+div#mastodon .button, div#mastodon .button:active, div#mastodon .button:focus, div#mastodon .button:hover{
+ background-color: var(--color-01-71);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-66), var(--color-01-71), var(--color-01-72), var(--color-01-76));
+}
+
+.compose-form__actions .icon-button {
+ color: var(--color-01-28);
+}
\ No newline at end of file
diff --git a/roles/web-app-oauth2-proxy/templates/style.css.j2 b/roles/web-app-oauth2-proxy/templates/style.css.j2
new file mode 100644
index 00000000..aec9289f
--- /dev/null
+++ b/roles/web-app-oauth2-proxy/templates/style.css.j2
@@ -0,0 +1,12 @@
+.box {
+ background-color: var(--color-01-92);
+ color: var(--color-01-10);
+}
+
+.subtitle {
+ color: inherit;
+}
+
+.has-background-light {
+ background-color: var(--color-01-96) !important;
+}
diff --git a/roles/web-app-openproject/templates/style.css.j2 b/roles/web-app-openproject/templates/style.css.j2
new file mode 100644
index 00000000..33b56055
--- /dev/null
+++ b/roles/web-app-openproject/templates/style.css.j2
@@ -0,0 +1,9 @@
+header.op-app-header{
+ background-color: var(--color-01-40);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-35), var(--color-01-40), var(--color-01-41), var(--color-01-45));
+ color: var(--color-01-40);
+}
+
+div#wrapper button, div#wrapper input, button.top-menu-search-button, div.menu-sidebar a{
+ background-color: transparent;
+}
diff --git a/roles/web-app-peertube/templates/peertube.conf.j2 b/roles/web-app-peertube/templates/peertube.conf.j2
index 5296ba90..2875350a 100644
--- a/roles/web-app-peertube/templates/peertube.conf.j2
+++ b/roles/web-app-peertube/templates/peertube.conf.j2
@@ -3,7 +3,7 @@ server {
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-core/templates/headers/content_security_policy.conf.j2' %}
diff --git a/roles/web-app-peertube/templates/style.css.j2 b/roles/web-app-peertube/templates/style.css.j2
new file mode 100644
index 00000000..5a0ef2e3
--- /dev/null
+++ b/roles/web-app-peertube/templates/style.css.j2
@@ -0,0 +1,38 @@
+body#custom-css {
+ --mainColor: var(--color-01-60); /* Original tone: hsl(24, 90%, 50%) – vibrant orange */
+ --mainColorLighter: var(--color-01-70); /* Original tone: #f5873d – lighter orange */
+ --mainColorLightest: var(--color-01-90); /* Original tone: #fce1cf – very light orange/beige */
+ --mainColorVeryLight: var(--color-01-95); /* Original tone: #fff5eb – almost white */
+ --mainHoverColor: var(--color-01-64); /* Original tone: #f47825 – hover orange */
+ --mainBackgroundHoverColor: var(--color-01-92); /* Original tone: #e9ecef – light gray */
+ --mainBackgroundColor: var(--color-01-99); /* Original tone: #fff – white */
+ --mainForegroundColor: var(--color-01-10); /* Original tone: #212529 – dark gray/black */
+ --greyForegroundColor: var(--color-01-50); /* Original tone: #585858 – medium gray */
+ --greyBackgroundColor: var(--color-01-90); /* Original tone: #E5E5E5 – light gray */
+ --greySecondaryBackgroundColor: var(--color-01-91); /* Original tone: #EFEFEF – very light gray */
+ --menuBackgroundColor: var(--color-01-82); /* Original tone: #000 – black */
+ --menuForegroundColor: var(--color-01-99); /* Original tone: #fff – white */
+ --submenuBackgroundColor: var(--color-01-95); /* Original tone: #F7F7F7 – off-white/light gray */
+ --channelBackgroundColor: var(--color-01-93); /* Original tone: #f6ede8 – warm light beige */
+ --inputForegroundColor: var(--color-01-10); /* Original tone: #212529 – dark gray */
+ --inputBackgroundColor: var(--color-01-99); /* Original tone: #fff – white */
+ --inputPlaceholderColor: var(--color-01-55); /* Original tone: #797676 – medium gray */
+ --inputBorderColor: var(--color-01-80); /* Original tone: #C6C6C6 – light gray */
+ --textareaForegroundColor: var(--color-01-10); /* Original tone: #212529 – dark gray */
+ --textareaBackgroundColor: var(--color-01-99); /* Original tone: #fff – white */
+ --markdownTextareaBackgroundColor: var(--color-01-91); /* Original tone: #EFEFEF – very light gray */
+ --actionButtonColor: var(--color-01-50); /* Original tone: #585858 – medium gray */
+ --supportButtonColor: var(--actionButtonColor); /* Original tone: same as actionButtonColor (#585858) */
+ --activatedActionButtonColor: var(--color-01-10); /* Original tone: #212529 – dark gray */
+ color: var(--mainForegroundColor);
+ background-color: var(--mainBackgroundColor);
+}
+
+div.searchbox input.autocomplete-input{
+ background-position: 12px;
+ background-repeat: no-repeat;
+}
+
+html[native-dark-active] my-app button, button {
+ background-color: transparent;
+}
diff --git a/roles/web-app-phpmyadmin/templates/style.css.j2 b/roles/web-app-phpmyadmin/templates/style.css.j2
new file mode 100644
index 00000000..fa28d3f7
--- /dev/null
+++ b/roles/web-app-phpmyadmin/templates/style.css.j2
@@ -0,0 +1,60 @@
+
+#pma_navigation {
+ background: linear-gradient(to right, var(--color-01-95), var(--color-01-85));
+ color: var(--color-01-05);
+}
+
+#pma_navigation_tree a {
+ color: var(--color-01-05);
+}
+
+#pma_navigation_tree li.activePointer, #pma_navigation_tree li.selected {
+ color: var(--color-01-05);
+ background-color: var(--color-01-70);
+}
+
+.breadcrumb-navbar {
+ background-color: var(--color-01-86);
+}
+
+.navbar-nav .nav-item {
+ background: linear-gradient(var(--color-01-99), var(--color-01-85));
+ border-right-color: var(--color-01-99);
+ border-left-color: var(--color-01-80);
+ border-bottom-color: var(--color-01-80);
+}
+
+.result_query div.sqlOuter {
+ background: var(--color-01-50);
+}
+
+.table {
+ --bs-table-bg: var(--color-01-99); /* #fff → white */
+ --bs-table-border-color: var(--color-01-99); /* #fff → white */
+ --bs-table-striped-bg: var(--color-01-90); /* #dfdfdf → light gray */
+ --bs-table-hover-color: var(--color-01-01); /* #000 → black */
+}
+
+.table thead th {
+ background-image: linear-gradient(var(--color-01-90), var(--color-01-80));
+ border-color: var(--color-01-99);
+}
+
+.table th, .table td {
+ text-shadow: 0 1px 0 var(--color-01-60);
+}
+
+div.tools, .tblFooters {
+ color: var(--color-01-01);
+ background: var(--color-01-62);
+}
+
+
+.navigation {
+ background: linear-gradient(var(--color-01-87), var(--color-01-69));
+}
+
+.pma-fieldset {
+ border-color: var(--color-01-17);
+ background: var(--color-01-80);
+}
diff --git a/roles/web-app-pixelfed/templates/style.css.j2 b/roles/web-app-pixelfed/templates/style.css.j2
new file mode 100644
index 00000000..8423051e
--- /dev/null
+++ b/roles/web-app-pixelfed/templates/style.css.j2
@@ -0,0 +1,43 @@
+:root {
+ /* Base Colors */
+ --light: var(--color-01-05); /* Very dark (was #000) */
+ --dark: var(--color-01-99); /* Very light (was #fff) */
+
+ /* Backgrounds */
+ --body-bg: var(--color-01-05); /* Main background: very dark */
+ --nav-bg: var(--color-01-05); /* Navigation background: very dark */
+
+ /* Text Colors */
+ --body-color: var(--color-01-70); /* Main text – mid brightness */
+ --text-lighter: var(--color-01-60); /* Lighter text for less prominent elements */
+
+ /* Section Backgrounds and Cards */
+ --bg-light: var(--color-01-95); /* Lighter background areas */
+ --card-bg: var(--color-01-90); /* Card background */
+ --light-gray: var(--color-01-75); /* For less dominant elements */
+ --light-hover-bg: var(--color-01-85); /* Slightly lighter hover background */
+
+ /* Borders and Input Fields */
+ --btn-light-border: var(--color-01-10); /* Dark border for buttons */
+ --input-border: var(--color-01-10); /* Border color for inputs */
+ --border-color: var(--color-01-85); /* General border: slightly lighter than background */
+
+ /* Other Areas */
+ --comment-bg: var(--color-01-85); /* Background for comments */
+ --card-header-accent: var(--color-01-85); /* Accent color in card headers */
+
+ /* Dropdown Menus */
+ --dropdown-item-hover-bg: var(--color-01-05); /* Hover background: very dark */
+ --dropdown-item-hover-color: var(--color-01-60); /* Hover text: a bit lighter */
+ --dropdown-item-color: var(--color-01-70); /* Regular dropdown item text */
+ --dropdown-item-active-color: var(--color-01-99); /* Active state: very light (white) */
+}
+
+div.page-wrapper{
+ background: none;
+ background-color: none;
+}
+
+.card-header-title {
+ color: var(--color-01-37);
+}
diff --git a/roles/web-app-sphinx/templates/style.css.j2 b/roles/web-app-sphinx/templates/style.css.j2
new file mode 100644
index 00000000..4fcbc614
--- /dev/null
+++ b/roles/web-app-sphinx/templates/style.css.j2
@@ -0,0 +1,12 @@
+.bg-background\/95 {
+ background-color: var(--color-01-96);
+}
+
+.border-border {
+ border-color: var(--color-01-85);
+}
+
+{# Hide Toogle Button #}
+nav.flex.items-center.space-x-1{
+ display:none;
+}
diff --git a/roles/web-app-syncope/templates/proxy.conf b/roles/web-app-syncope/templates/proxy.conf
index cba2936e..aecbd020 100644
--- a/roles/web-app-syncope/templates/proxy.conf
+++ b/roles/web-app-syncope/templates/proxy.conf
@@ -8,7 +8,7 @@ server
{% include 'roles/web-app-oauth2-proxy/templates/endpoint.conf.j2'%}
{% endif %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% if proxy_extra_configuration is defined %}
{# Additional Domain Specific Configuration #}
diff --git a/roles/web-app-taiga/templates/style.css.j2 b/roles/web-app-taiga/templates/style.css.j2
new file mode 100644
index 00000000..3f24e1fd
--- /dev/null
+++ b/roles/web-app-taiga/templates/style.css.j2
@@ -0,0 +1,67 @@
+:host, :root {
+ --color-01-solid-primary: var(--color-01-83);
+ --color-01-link-primary: var(--color-01-44);
+ --color-01-link-tertiary: var(--color-01-45);
+ --color-01-gray100: var(--color-01-97);
+ --color-01-gray200: var(--color-01-93);
+ --color-01-gray300: var(--color-01-90);
+ --color-01-gray400: var(--color-01-86);
+ --color-01-black600: var(--color-01-34);
+ --color-01-black700: var(--color-01-30);
+ --color-01-black800: var(--color-01-26);
+ --color-01-black900: var(--color-01-21);
+ --color-01-black: var(--color-01-01);
+ --color-01-white: var(--color-01-99);
+}
+
+section.main.kanban{
+ background-color: transparent;
+}
+
+div.master, div.kanban-header, div.kanban-table-inner, section.kanban button,a.dropdown-project-list-projects{
+ background-color: var(--color-01-92);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-87), var(--color-01-92), var(--color-01-93), var(--color-01-97));
+ color: var(--color-01-40);
+}
+
+section.kanban h1, section.kanban h2{
+ color: var(--color-01-40);
+}
+
+.home-project {
+ background: var(--color-01-88);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-83), var(--color-01-88), var(--color-01-89), var(--color-01-93));
+ border-color: var(--color-01-60);
+ color: var(--color-01-12);
+}
+
+.home-wrapper .title-bar {
+ background: var(--color-01-75);
+ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-75), var(--color-01-76), var(--color-01-80));
+}
+
+.kanban.swimlane .kanban-header {
+ background: none;
+}
+
+.kanban-table-header .task-colum-name {
+ background-color: var(--color-01-70);
+ color: var(--color-01-10);
+}
+
+input.ng-empty::placeholder,.ng-empty::placeholder {
+ color: rgba(var(--color-01-rgb-03),0.6);
+}
+
+.lightbox {
+ background: rgba(var(--color-01-rgb-97), .93);
+ color: var(--color-01-03);
+}
+
+.kanban-filter tg-filter {
+ border-color: var(--color-01-70);
+}
+
+.discover-header {
+ background: none;
+}
diff --git a/roles/web-svc-cdn/config/main.yml b/roles/web-svc-cdn/config/main.yml
index 41f563bc..8c156c5d 100644
--- a/roles/web-svc-cdn/config/main.yml
+++ b/roles/web-svc-cdn/config/main.yml
@@ -1,7 +1,7 @@
features:
- matomo: true
- css: true
- desktop: false
+ matomo: true
+ css: true
+ desktop: false
server:
domains:
canonical:
diff --git a/roles/web-svc-cdn/tasks/01_core.yml b/roles/web-svc-cdn/tasks/01_core.yml
index 027ea0f8..b1193aa5 100644
--- a/roles/web-svc-cdn/tasks/01_core.yml
+++ b/roles/web-svc-cdn/tasks/01_core.yml
@@ -11,8 +11,8 @@
vars:
http_port: "{{ ports.localhost.http[application_id] }}"
-- name: "generate '{{ CDN_NGINX_FILE }}'"
+- name: "deploy '{{ CDN_NGINX_PATH }}'"
template:
src: "nginx.conf.j2"
- dest: "{{ NGINX.DIRECTORIES.HTTP.SERVERS }}{{ CDN_NGINX_FILE }}"
+ dest: "{{ CDN_NGINX_PATH }}"
notify: restart openresty
\ No newline at end of file
diff --git a/roles/web-svc-cdn/templates/nginx.conf.j2 b/roles/web-svc-cdn/templates/nginx.conf.j2
index 8464204f..0c6b597c 100644
--- a/roles/web-svc-cdn/templates/nginx.conf.j2
+++ b/roles/web-svc-cdn/templates/nginx.conf.j2
@@ -4,7 +4,7 @@ server
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-core/templates/headers/content_security_policy.conf.j2' %}
@@ -16,11 +16,11 @@ server
autoindex on; {# Enable directory listing #}
autoindex_exact_size off; {# Display sizes in a human-readable format #}
autoindex_localtime on; {# Show local time #}
- {% include 'roles/sys-srv-web-inj-compose/templates/location.lua.j2' %}
+ {% include 'roles/sys-front-inj-all/templates/location.lua.j2' %}
}
location /.well-known/ {
- alias {{NGINX.DIRECTORIES.DATA.WELL_KNOWN}};
+ alias {{ NGINX.DIRECTORIES.DATA.WELL_KNOWN }};
allow all;
default_type "text/plain";
autoindex on;
diff --git a/roles/web-svc-cdn/vars/main.yml b/roles/web-svc-cdn/vars/main.yml
index e919592b..f37c6205 100644
--- a/roles/web-svc-cdn/vars/main.yml
+++ b/roles/web-svc-cdn/vars/main.yml
@@ -4,3 +4,4 @@ domain: "{{ domains | get_domain(application_id) }}"
# CDN
CDN_NGINX_FILE: "{{ domain }}.conf"
+CDN_NGINX_PATH: "{{ [ NGINX.DIRECTORIES.HTTP.SERVERS, CDN_NGINX_FILE ] | path_join }}"
diff --git a/roles/web-svc-collabora/templates/nginx.conf.j2 b/roles/web-svc-collabora/templates/nginx.conf.j2
index 2867241f..05ca2498 100644
--- a/roles/web-svc-collabora/templates/nginx.conf.j2
+++ b/roles/web-svc-collabora/templates/nginx.conf.j2
@@ -2,7 +2,7 @@ server {
server_name {{ domain }};
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-core/templates/headers/content_security_policy.conf.j2' %}
diff --git a/roles/web-svc-file/templates/nginx.conf.j2 b/roles/web-svc-file/templates/nginx.conf.j2
index 8e42af95..54d83292 100644
--- a/roles/web-svc-file/templates/nginx.conf.j2
+++ b/roles/web-svc-file/templates/nginx.conf.j2
@@ -4,7 +4,7 @@ server
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-core/templates/headers/content_security_policy.conf.j2' %}
@@ -16,11 +16,11 @@ server
autoindex on; {# Enable directory listing #}
autoindex_exact_size off; {# Display sizes in a human-readable format #}
autoindex_localtime on; {# Show local time #}
- {% include 'roles/sys-srv-web-inj-compose/templates/location.lua.j2' %}
+ {% include 'roles/sys-front-inj-all/templates/location.lua.j2' %}
}
location /.well-known/ {
- alias {{NGINX.DIRECTORIES.DATA.WELL_KNOWN}};
+ alias {{ NGINX.DIRECTORIES.DATA.WELL_KNOWN }};
allow all;
default_type "text/plain";
autoindex on;
diff --git a/roles/web-svc-html/templates/nginx.conf.j2 b/roles/web-svc-html/templates/nginx.conf.j2
index 586a8ce6..435493ae 100644
--- a/roles/web-svc-html/templates/nginx.conf.j2
+++ b/roles/web-svc-html/templates/nginx.conf.j2
@@ -4,7 +4,7 @@ server
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
- {% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
+ {% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-core/templates/headers/content_security_policy.conf.j2' %}
@@ -14,11 +14,11 @@ server
{
root {{NGINX.DIRECTORIES.DATA.HTML}};
index index.html index.htm;
- {% include 'roles/sys-srv-web-inj-compose/templates/location.lua.j2' %}
+ {% include 'roles/sys-front-inj-all/templates/location.lua.j2' %}
}
location /.well-known/ {
- alias {{NGINX.DIRECTORIES.DATA.WELL_KNOWN}};
+ alias {{ NGINX.DIRECTORIES.DATA.WELL_KNOWN }};
allow all;
default_type "text/plain";
autoindex on;
diff --git a/templates/roles/web-app/templates/style.css.j2 b/templates/roles/web-app/templates/style.css.j2
new file mode 100644
index 00000000..357c1e5e
--- /dev/null
+++ b/templates/roles/web-app/templates/style.css.j2
@@ -0,0 +1 @@
+/** Custom CSS for 'sys-front-inj-css' if features.css is enabled **/
\ No newline at end of file
diff --git a/tests/integration/test_csp_configuration_consistency.py b/tests/integration/test_csp_configuration_consistency.py
index 1cd00594..804aaab3 100644
--- a/tests/integration/test_csp_configuration_consistency.py
+++ b/tests/integration/test_csp_configuration_consistency.py
@@ -12,6 +12,7 @@ class TestCspConfigurationConsistency(unittest.TestCase):
'script-src',
'script-src-elem',
'style-src',
+ 'style-src-elem',
'font-src',
'worker-src',
'manifest-src',
diff --git a/tests/integration/test_variable_definitions.py b/tests/integration/test_variable_definitions.py
index a37dfaa9..26700154 100644
--- a/tests/integration/test_variable_definitions.py
+++ b/tests/integration/test_variable_definitions.py
@@ -218,7 +218,7 @@ class TestVariableDefinitions(unittest.TestCase):
if var in (
'lookup', 'role_name', 'domains', 'item', 'host_type',
'inventory_hostname', 'role_path', 'playbook_dir',
- 'ansible_become_password', 'inventory_dir', 'ansible_memtotal_mb', 'omit', 'group_names'
+ 'ansible_become_password', 'inventory_dir', 'ansible_memtotal_mb', 'omit', 'group_names', 'ansible_processor_vcpus'
):
continue
diff --git a/tests/unit/lookup_plugins/test_local_mtime_qs.py b/tests/unit/lookup_plugins/test_local_mtime_qs.py
new file mode 100644
index 00000000..de750c0f
--- /dev/null
+++ b/tests/unit/lookup_plugins/test_local_mtime_qs.py
@@ -0,0 +1,50 @@
+import os
+import sys
+import tempfile
+import time
+import unittest
+
+# ensure repo root on path
+ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+sys.path.insert(0, ROOT)
+
+from ansible.errors import AnsibleError # type: ignore
+from lookup_plugins.local_mtime_qs import LookupModule
+
+
+class TestLocalMtimeQs(unittest.TestCase):
+ def setUp(self):
+ self.tmpdir = tempfile.TemporaryDirectory()
+ self.path = os.path.join(self.tmpdir.name, "file.css")
+ with open(self.path, "w", encoding="utf-8") as f:
+ f.write("body{}")
+ # set stable mtime
+ self.mtime = int(time.time()) - 123
+ os.utime(self.path, (self.mtime, self.mtime))
+
+ def tearDown(self):
+ self.tmpdir.cleanup()
+
+ def test_single_path_qs_default(self):
+ res = LookupModule().run([self.path])
+ self.assertEqual(res, [f"?version={self.mtime}"])
+
+ def test_single_path_epoch(self):
+ res = LookupModule().run([self.path], mode="epoch")
+ self.assertEqual(res, [str(self.mtime)])
+
+ def test_multiple_paths(self):
+ path2 = os.path.join(self.tmpdir.name, "a.js")
+ with open(path2, "w", encoding="utf-8") as f:
+ f.write("// js")
+ os.utime(path2, (self.mtime + 1, self.mtime + 1))
+ res = LookupModule().run([self.path, path2], param="v")
+ self.assertEqual(res, [f"?v={self.mtime}", f"?v={self.mtime + 1}"])
+
+ def test_missing_raises(self):
+ with self.assertRaises(AnsibleError):
+ LookupModule().run([os.path.join(self.tmpdir.name, "nope.css")])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/roles/sys-front-inj-all/__init__.py b/tests/unit/roles/sys-front-inj-all/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/roles/sys-front-inj-all/filter_plugins/__init__.py b/tests/unit/roles/sys-front-inj-all/filter_plugins/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_enabled.py b/tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_enabled.py
similarity index 92%
rename from tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_enabled.py
rename to tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_enabled.py
index 400d8ae0..51390a4c 100644
--- a/tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_enabled.py
+++ b/tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_enabled.py
@@ -1,4 +1,3 @@
-# tests/unit/roles/srv-web-inj-compose/filter_plugins/test_inj_enabled.py
import importlib.util
from importlib import import_module
from pathlib import Path
@@ -8,7 +7,7 @@ import unittest
THIS_FILE = Path(__file__)
def find_repo_root(start: Path) -> Path:
- target_rel = Path("roles") / "sys-srv-web-inj-compose" / "filter_plugins" / "inj_enabled.py"
+ target_rel = Path("roles") / "sys-front-inj-all" / "filter_plugins" / "inj_enabled.py"
cur = start
for _ in range(12):
if (cur / target_rel).is_file():
@@ -17,7 +16,7 @@ def find_repo_root(start: Path) -> Path:
return start.parents[6]
REPO_ROOT = find_repo_root(THIS_FILE)
-PLUGIN_PATH = REPO_ROOT / "roles" / "sys-srv-web-inj-compose" / "filter_plugins" / "inj_enabled.py"
+PLUGIN_PATH = REPO_ROOT / "roles" / "sys-front-inj-all" / "filter_plugins" / "inj_enabled.py"
# Ensure 'module_utils' is importable under its canonical package name
if str(REPO_ROOT) not in sys.path:
diff --git a/tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_snippets.py b/tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_snippets.py
similarity index 92%
rename from tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_snippets.py
rename to tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_snippets.py
index ec3f8660..9fb8e1c1 100644
--- a/tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_snippets.py
+++ b/tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_snippets.py
@@ -1,6 +1,6 @@
-# tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_snippets.py
+# tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_snippets.py
"""
-Unit tests for roles/sys-srv-web-inj-compose/filter_plugins/inj_snippets.py
+Unit tests for roles/sys-front-inj-all/filter_plugins/inj_snippets.py
- Uses tempfile.TemporaryDirectory for an isolated roles/ tree.
- Loads inj_snippets.py by absolute path (no sys.path issues).
@@ -22,7 +22,7 @@ class TestInjSnippets(unittest.TestCase):
cls.test_dir = os.path.dirname(__file__)
root = cls.test_dir
inj_rel = os.path.join(
- "roles", "sys-srv-web-inj-compose", "filter_plugins", "inj_snippets.py"
+ "roles", "sys-front-inj-all", "filter_plugins", "inj_snippets.py"
)
while True:
@@ -67,7 +67,7 @@ class TestInjSnippets(unittest.TestCase):
@classmethod
def _mkrole(cls, feature, head=False, body=False):
- role_dir = os.path.join(cls.roles_dir, f"sys-srv-web-inj-{feature}")
+ role_dir = os.path.join(cls.roles_dir, f"sys-front-inj-{feature}")
tmpl_dir = os.path.join(role_dir, "templates")
os.makedirs(tmpl_dir, exist_ok=True)
if head:
diff --git a/tests/unit/roles/sys-svc-cdn/__init__.py b/tests/unit/roles/sys-svc-cdn/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/roles/sys-svc-cdn/filter_plugins/__init__.py b/tests/unit/roles/sys-svc-cdn/filter_plugins/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/roles/sys-svc-cdn/filter_plugins/test_cdn_paths_urls_dirs.py b/tests/unit/roles/sys-svc-cdn/filter_plugins/test_cdn_paths_urls_dirs.py
new file mode 100644
index 00000000..386958b9
--- /dev/null
+++ b/tests/unit/roles/sys-svc-cdn/filter_plugins/test_cdn_paths_urls_dirs.py
@@ -0,0 +1,108 @@
+import os
+import tempfile
+import unittest
+import importlib.util
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+def _find_repo_root(start_dir: str, probe_parts: list[str]) -> str:
+ """
+ Walk upwards from start_dir until a path joined with probe_parts exists.
+ Returns the directory considered the repo root.
+ """
+ cur = os.path.abspath(start_dir)
+ for _ in range(15): # plenty of headroom
+ candidate = os.path.join(cur, *probe_parts)
+ if os.path.exists(candidate):
+ return cur
+ parent = os.path.dirname(cur)
+ if parent == cur:
+ break
+ cur = parent
+ raise RuntimeError(
+ f"Could not locate {'/'.join(probe_parts)} starting from {start_dir}"
+ )
+
+PROBE = ["roles", "sys-svc-cdn", "filter_plugins", "cdn_paths.py"]
+ROOT = _find_repo_root(HERE, PROBE)
+
+def _load_module(mod_name: str, rel_path_from_root: str):
+ """Load a python module from an absolute file path (hyphen-safe)."""
+ path = os.path.join(ROOT, rel_path_from_root)
+ if not os.path.isfile(path):
+ raise FileNotFoundError(path)
+ spec = importlib.util.spec_from_file_location(mod_name, path)
+ module = importlib.util.module_from_spec(spec)
+ assert spec and spec.loader, f"Cannot load spec for {path}"
+ spec.loader.exec_module(module) # type: ignore[attr-defined]
+ return module
+
+cdn_paths_mod = _load_module(
+ "cdn_paths_mod", os.path.join("roles", "sys-svc-cdn", "filter_plugins", "cdn_paths.py")
+)
+cdn_urls_mod = _load_module(
+ "cdn_urls_mod", os.path.join("roles", "sys-svc-cdn", "filter_plugins", "cdn_urls.py")
+)
+cdn_dirs_mod = _load_module(
+ "cdn_dirs_mod", os.path.join("roles", "sys-svc-cdn", "filter_plugins", "cdn_dirs.py")
+)
+
+class TestCdnPathsUrlsDirs(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.TemporaryDirectory()
+ self.root = self.tmp.name
+ self.app = "web-app-desktop"
+ self.ver = "20250101"
+
+ self.cdn_paths = cdn_paths_mod.cdn_paths
+ self.cdn_urls = cdn_urls_mod.cdn_urls
+ self.cdn_dirs = cdn_dirs_mod.cdn_dirs
+
+ self.tree = self.cdn_paths(self.root, self.app, self.ver)
+
+ def tearDown(self):
+ self.tmp.cleanup()
+
+ # ---- cdn_paths ----
+ def test_paths_shape_and_values(self):
+ t = self.tree
+ self.assertTrue(os.path.isabs(t["root"]))
+ self.assertEqual(t["role"]["id"], self.app)
+ self.assertEqual(t["role"]["version"], self.ver)
+ self.assertTrue(t["shared"]["css"].endswith(os.path.join("_shared", "css")))
+ self.assertTrue(
+ t["role"]["release"]["css"].endswith(os.path.join(self.app, self.ver, "css"))
+ )
+
+ # ---- cdn_urls ----
+ def test_urls_mapping_and_root_trailing_slash(self):
+ base = "https://cdn.example.com"
+ urls = self.cdn_urls(self.tree, base)
+
+ # Non-path strings remain untouched
+ self.assertEqual(urls["role"]["id"], self.app)
+ self.assertEqual(urls["role"]["version"], self.ver)
+
+ # Paths are mapped to URLs
+ self.assertTrue(urls["shared"]["js"].startswith(base + "/"))
+ self.assertTrue(urls["vendor"].startswith(base + "/vendor"))
+
+ # Root always ends with '/'
+ self.assertEqual(urls["root"], base.rstrip("/") + "/")
+
+ def test_urls_invalid_input_raises(self):
+ with self.assertRaises(ValueError):
+ self.cdn_urls({}, "https://cdn.example.com")
+ with self.assertRaises(ValueError):
+ self.cdn_urls("nope", "https://cdn.example.com") # type: ignore[arg-type]
+
+ # ---- cdn_dirs ----
+ def test_dirs_collects_all_abs_dirs_sorted_unique(self):
+ dirs = self.cdn_dirs(self.tree)
+ self.assertIn(os.path.join(self.root, "_shared", "css"), dirs)
+ self.assertIn(os.path.join(self.root, "roles", self.app, self.ver, "img"), dirs)
+ self.assertEqual(dirs, sorted(dirs))
+ self.assertEqual(len(dirs), len(set(dirs)))
+
+if __name__ == "__main__":
+ unittest.main()