feat(homarr): make apps list configurable with auto-layout

This commit is contained in:
Tobias Wüst 2026-05-13 15:02:32 +02:00
parent d4eaa5f12c
commit c1c1a84591
Signed by: Tobias-Wuest
GPG key ID: 2D8992B0F4CA97E8
3 changed files with 101 additions and 60 deletions

View file

@ -54,4 +54,32 @@ homarr_use_ssl: true
# Local admin # Local admin
homarr_admin_username: "admin" homarr_admin_username: "admin"
homarr_admin_email: "admin@digitalboard.ch" homarr_admin_email: "admin@digitalboard.ch"
homarr_admin_password: "ChangeMe123!" 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

View file

@ -26,6 +26,18 @@
Set via OpenBao or remove 'oidc' from homarr_auth_providers. Set via OpenBao or remove 'oidc' from homarr_auth_providers.
when: "'oidc' in 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 # 1. PREPARATION: packages and directories before container start
# ===================================================================== # =====================================================================

View file

@ -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; BEGIN TRANSACTION;
-- ===================================================================== -- =====================================================================
@ -129,82 +161,51 @@ VALUES
('board-default', 'group-credentials-admin', 'full-access'); ('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) INSERT OR IGNORE INTO app (id, name, description, icon_url, href)
VALUES ( VALUES (
'app-nextcloud', 'app-{{ app.id }}',
'Nextcloud', '{{ app.name }}',
'Cloud Storage & Collaboration', '{{ app.description | default("") }}',
'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png', '{{ app.icon }}',
'https://cloud.digitalboard.ch' '{{ app.href }}'
); );
INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options) INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options)
VALUES ( VALUES (
'item-nextcloud', 'item-{{ app.id }}',
'board-default', 'board-default',
'app', 'app',
'{"json": {"appId": "app-nextcloud"}}', '{"json": {"appId": "app-{{ app.id }}"}}',
'{"json": {}}' '{"json": {}}'
); );
INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) {% endfor %}
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": {}}'
);
INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height)
VALUES VALUES
('item-keycloak', 'section-apps', 'layout-desktop', 2, 0, 2, 1), {% for entry in desktop_layout %}
('item-keycloak', 'section-apps', 'layout-tablet', 2, 0, 2, 1), ('item-{{ entry.id }}', 'section-apps', 'layout-desktop', {{ entry.x }}, {{ entry.y }}, {{ entry.w }}, {{ entry.h }}){% if not loop.last %},{% endif %}
('item-keycloak', 'section-apps', 'layout-mobile', 1, 0, 1, 1); {% endfor %}
;
-- 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": {}}'
);
INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height)
VALUES VALUES
('item-mailman', 'section-apps', 'layout-desktop', 4, 0, 2, 1), {% for entry in tablet_layout %}
('item-mailman', 'section-apps', 'layout-tablet', 4, 0, 2, 1), ('item-{{ entry.id }}', 'section-apps', 'layout-tablet', {{ entry.x }}, {{ entry.y }}, {{ entry.w }}, {{ entry.h }}){% if not loop.last %},{% endif %}
('item-mailman', 'section-apps', 'layout-mobile', 0, 1, 1, 1); {% 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;