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
homarr_admin_username: "admin"
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.
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
# =====================================================================

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;
-- =====================================================================
@ -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;