diff --git a/roles/homarr/defaults/main.yml b/roles/homarr/defaults/main.yml index e981b1f..2ef6df3 100644 --- a/roles/homarr/defaults/main.yml +++ b/roles/homarr/defaults/main.yml @@ -54,4 +54,32 @@ homarr_use_ssl: true # Local admin homarr_admin_username: "admin" homarr_admin_email: "admin@digitalboard.ch" -homarr_admin_password: "ChangeMe123!" \ No newline at end of file +homarr_admin_password: "ChangeMe123!" + +# Applications shown on the default board. +# Each app needs id, name, description, icon, href and a width (1-10). +# Height defaults to 1, can be increased for taller tiles. +# Apps are automatically packed left-to-right into the desktop grid (10 cols), +# scaled to tablet (6 cols) and mobile (2 cols). +homarr_apps: + - id: nextcloud + name: Nextcloud + description: Cloud Storage & Collaboration + icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png + href: https://cloud.digitalboard.ch + width: 2 + height: 1 + - id: keycloak + name: Keycloak + description: Identity & Access Management + icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/keycloak.png + href: https://auth.digitalboard.ch + width: 2 + height: 1 + - id: mailman + name: Mailman + description: Mailing List Manager + icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/mailman.png + href: https://lists.digitalboard.ch + width: 2 + height: 1 \ No newline at end of file diff --git a/roles/homarr/tasks/main.yml b/roles/homarr/tasks/main.yml index a3ff991..06488f4 100644 --- a/roles/homarr/tasks/main.yml +++ b/roles/homarr/tasks/main.yml @@ -26,6 +26,18 @@ Set via OpenBao or remove 'oidc' from homarr_auth_providers. when: "'oidc' in homarr_auth_providers" +- name: Validate homarr_apps have unique ids + ansible.builtin.assert: + that: + - homarr_apps | map(attribute='id') | list | length == + homarr_apps | map(attribute='id') | unique | list | length + fail_msg: >- + homarr_apps contains duplicate ids. + Each app must have a unique 'id'. Got: + {{ homarr_apps | map(attribute='id') | list }} + success_msg: All app ids are unique + when: homarr_apps | length > 0 + # ===================================================================== # 1. PREPARATION: packages and directories before container start # ===================================================================== diff --git a/roles/homarr/templates/homarr_seed.sql.j2 b/roles/homarr/templates/homarr_seed.sql.j2 index 0490c04..fdb1a2f 100644 --- a/roles/homarr/templates/homarr_seed.sql.j2 +++ b/roles/homarr/templates/homarr_seed.sql.j2 @@ -1,3 +1,35 @@ +{#- + Auto-layout packing macro. + + Greedy left-to-right packing of apps into a grid with `cols` columns. + Returns the list of apps with computed x/y/w/h fields. + + Width is clamped to cols (so an app wider than the grid is downsized + rather than overflowing). Height is taken as-is. +-#} +{%- macro pack(apps, cols) -%} + {%- set ns = namespace(x=0, y=0, row_h=0, out=[]) -%} + {%- for app in apps -%} + {%- set w = [app.width, cols] | min -%} + {%- set h = app.height | default(1) -%} + {%- if ns.x + w > cols -%} + {%- set ns.x = 0 -%} + {%- set ns.y = ns.y + ns.row_h -%} + {%- set ns.row_h = 0 -%} + {%- endif -%} + {%- set _ = ns.out.append({'id': app.id, 'x': ns.x, 'y': ns.y, 'w': w, 'h': h}) -%} + {%- set ns.x = ns.x + w -%} + {%- if h > ns.row_h -%} + {%- set ns.row_h = h -%} + {%- endif -%} + {%- endfor -%} + {{- ns.out | to_json -}} +{%- endmacro -%} + +{%- set desktop_layout = pack(homarr_apps, 10) | from_json -%} +{%- set tablet_layout = pack(homarr_apps, 6) | from_json -%} +{%- set mobile_layout = pack(homarr_apps, 2) | from_json -%} + BEGIN TRANSACTION; -- ===================================================================== @@ -129,82 +161,51 @@ VALUES ('board-default', 'group-credentials-admin', 'full-access'); -- ===================================================================== --- APPS +-- APPS (auto-generated from homarr_apps variable) -- ===================================================================== --- Nextcloud +{% if homarr_apps | length > 0 %} +{% for app in homarr_apps %} INSERT OR IGNORE INTO app (id, name, description, icon_url, href) VALUES ( - 'app-nextcloud', - 'Nextcloud', - 'Cloud Storage & Collaboration', - 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png', - 'https://cloud.digitalboard.ch' + 'app-{{ app.id }}', + '{{ app.name }}', + '{{ app.description | default("") }}', + '{{ app.icon }}', + '{{ app.href }}' ); INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options) VALUES ( - 'item-nextcloud', + 'item-{{ app.id }}', 'board-default', 'app', - '{"json": {"appId": "app-nextcloud"}}', + '{"json": {"appId": "app-{{ app.id }}"}}', '{"json": {}}' ); -INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) -VALUES - ('item-nextcloud', 'section-apps', 'layout-desktop', 0, 0, 2, 1), - ('item-nextcloud', 'section-apps', 'layout-tablet', 0, 0, 2, 1), - ('item-nextcloud', 'section-apps', 'layout-mobile', 0, 0, 1, 1); - --- Keycloak -INSERT OR IGNORE INTO app (id, name, description, icon_url, href) -VALUES ( - 'app-keycloak', - 'Keycloak', - 'Identity & Access Management', - 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/keycloak.png', - 'https://auth.digitalboard.ch' -); - -INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options) -VALUES ( - 'item-keycloak', - 'board-default', - 'app', - '{"json": {"appId": "app-keycloak"}}', - '{"json": {}}' -); +{% endfor %} INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) VALUES - ('item-keycloak', 'section-apps', 'layout-desktop', 2, 0, 2, 1), - ('item-keycloak', 'section-apps', 'layout-tablet', 2, 0, 2, 1), - ('item-keycloak', 'section-apps', 'layout-mobile', 1, 0, 1, 1); - --- Mailman -INSERT OR IGNORE INTO app (id, name, description, icon_url, href) -VALUES ( - 'app-mailman', - 'Mailman', - 'Mailing List Manager', - 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/mailman.png', - 'https://lists.digitalboard.ch' -); - -INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options) -VALUES ( - 'item-mailman', - 'board-default', - 'app', - '{"json": {"appId": "app-mailman"}}', - '{"json": {}}' -); +{% for entry in desktop_layout %} + ('item-{{ entry.id }}', 'section-apps', 'layout-desktop', {{ entry.x }}, {{ entry.y }}, {{ entry.w }}, {{ entry.h }}){% if not loop.last %},{% endif %} +{% endfor %} +; INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) VALUES - ('item-mailman', 'section-apps', 'layout-desktop', 4, 0, 2, 1), - ('item-mailman', 'section-apps', 'layout-tablet', 4, 0, 2, 1), - ('item-mailman', 'section-apps', 'layout-mobile', 0, 1, 1, 1); +{% for entry in tablet_layout %} + ('item-{{ entry.id }}', 'section-apps', 'layout-tablet', {{ entry.x }}, {{ entry.y }}, {{ entry.w }}, {{ entry.h }}){% if not loop.last %},{% endif %} +{% endfor %} +; -COMMIT; +INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) +VALUES +{% for entry in mobile_layout %} + ('item-{{ entry.id }}', 'section-apps', 'layout-mobile', {{ entry.x }}, {{ entry.y }}, {{ entry.w }}, {{ entry.h }}){% if not loop.last %},{% endif %} +{% endfor %} +; +{% endif %} + +COMMIT; \ No newline at end of file