# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.12.10] – 2026-11-09 ### Fixed - 84 user interface texts were missing from the German translation and were shown in English, for example the notification channel settings, font colors, card background, resources category, table widget, layout lock and the "service offline" / "service back online" notifications. All texts are now translated. The other languages received the missing keys as English placeholders. ## [1.12.9] – 2026-11-09 ### Changed - Failed status checks now show the reason in the status indicator tooltip, e.g. "TLS certificate could not be verified (self-signed or expired?)", "Connection timed out", "Connection refused or host unreachable" or "Host name could not be resolved" (issue #22). For certificate errors the tooltip also says how to allow an exception. - Widgets that fail because of a certificate error now say "TLS certificate could not be verified" instead of only the cURL error number. - The service editor explains that TLS certificates are verified for `https://` services, and that an administrator must allow per-service exceptions before self-signed certificates can be accepted. Before, the per-service option was hidden without any hint. ### Fixed - German translations for the TLS certificate verification settings were missing. ## [1.12.8] – 2026-10-22 ### Fixed - LinkBoard's own "service offline" and "service back online" notifications no longer log a deprecation warning ("Notifier::prepare() threw \InvalidArgumentException which is deprecated") on Nextcloud 30 and newer (issue #21). The notification icon was set with a relative path, which Nextcloud rejects since version 30. Because of this, the notifications were shown without the app icon and without the link to the board. The icon is now passed as an absolute URL. ## [1.12.7] – 2026-10-05 ### Fixed - Read-only viewers of the Global Board now see the source user's card positions and sizes (issue #20). Their board received no grid layout, because it was removed together with the widget configuration, so the cards were arranged differently. Only the layout is sent to read-only viewers; widget credentials stay hidden. - With "Responsive layout" enabled, cards in narrow categories now wrap row by row in the same order as on the desktop layout. Before, the cards kept their desktop positions squeezed into fewer columns, so their order got mixed up. - Responsive categories narrower than 480 px (e.g. on phones) no longer fail to render the card grid. - Status updates no longer reset the cards of a narrow responsive category to their desktop positions. ### Changed - In edit mode, responsive categories always show the desktop layout, so moving or resizing a card always changes the stored layout. ## [1.12.6] – 2026-10-03 ### Fixed - Admins who edit the Global Board but are not its source user now export and import the Global Board. Before, export and import used their own board, which is hidden while the Global Board is active: the export did not contain the board they were looking at, and an import seemed to succeed but did not change it. - Notification channels created by such admins now trigger for the Global Board. Status alerts for Global Board services are sent through the source user's channels, but the settings page created and listed the admin's own channels, which never fired for these services. Global Board editors now see and manage the source user's channels. ### Security - Read-only viewers of the Global Board can still only reach their own notification channels and their own export, so the source user's channel configuration and the widget credentials in the export stay hidden from them. ## [1.12.5] – 2026-10-03 ### Fixed - The Global Board now looks the same for non-admin users as for its source user (issue #20). Several parts of LinkBoard still used the viewer's own data instead of the source user's: - External icon URLs were blocked by the Content Security Policy, because the allowed image domains were collected from the viewer's services and background. - Uploaded icons returned HTTP 404, because they were looked up in the viewer's icon folder. - The viewer's own display settings were applied, so card style, sizes, spacing and layout differed from the source user's board. - The layout migration on page load tried to save every service for read-only viewers, which produced a series of HTTP 403 errors in the browser console. Read-only viewers now only apply the migrated layout locally. - Admins who edit the Global Board but are not its source user now change the source user's settings and upload, list and delete icons in the source user's icon folder. Icons they uploaded to their own folder before are still shown. ### Changed - Read-only viewers of the Global Board no longer see the Settings button, and the settings API rejects their changes, because their own settings have no effect on the Global Board. ## [1.12.4] – 2026-10-02 ### Fixed - LinkBoard no longer fills `nextcloud.log` with "`OCA\LinkBoard\Notification\Notifier::prepare() threw \InvalidArgumentException which is deprecated`" warnings (issue #19). Nextcloud asks every app's notifier about every notification, on every poll of the notifications endpoint and in cron. LinkBoard rejected notifications of other apps with the deprecated `\InvalidArgumentException`, which logged one warning per foreign notification and request. It now throws `\OCP\Notification\UnknownNotificationException`, as Nextcloud has required since version 30. - A service whose hostname no longer resolves is now shown as offline. Before, the background status check failed before the request was sent: it only logged "Status check failed for service " and never saved a result, so the card kept its last state (often "online") indefinitely and no offline notification was sent. A failed DNS lookup is now recorded as offline ("Could not resolve host"), the same as other connection errors, and counts towards the offline notification threshold (issue #19). ## [1.12.3] – 2026-09-24 ### Fixed - The "Save" and "Delete" buttons at the bottom of the service editor were not visible, so changes to a service could not be saved (discussion #18). The bottom of the whole app was cut off by the height of the Nextcloud header. The Vue app was mounted inside Nextcloud's `#content` container instead of replacing it, so both containers applied the header offset, and `css/styles.css` also stretched the inner container to the full window height. LinkBoard now mounts on `#content` like other Nextcloud apps, and the height override is removed. The bottom of the dashboard is no longer cut off either. - The service and category editor sidebars are now positioned inside the app container in every browser. Before, their position depended on whether Nextcloud's background blur was active (Firefox, Safari and all browsers on macOS) or not, and with the blur they started 50 px too low. - The category selector in the service editor now lists sub-categories as "Parent › Child". A service inside a sub-category used to show the category's numeric ID, and services could not be moved into a sub-category from the editor. ## [1.12.2] – 2026-09-19 ### Fixed - `occ upgrade` on Nextcloud 35 no longer reports a database schema mismatch for LinkBoard ("column 'collapsed' differs in: nullable", likewise for `ping_enabled`, `show_scrollbar`, `ignore_tls` and `notified`). Installations created with releases before 1.4.4 or 1.6.7 still had these boolean columns as NOT NULL, because the earlier fix only changed the migrations for new installations. A new migration now makes them nullable, matching fresh installations; existing values are kept. ## [1.12.1] – 2026-09-19 ### Fixed - Updated the bundled frontend dependencies to resolve published security advisories: axios 1.13.6 → 1.20.0 (prototype pollution, denial of service, `maxBodyLength` bypass), DOMPurify 3.3.1 → 3.4.15 (XSS), fast-xml-parser 5.4.1 → 5.11.1 (via webdav), form-data 4.0.5 → 4.0.6, follow-redirects 1.15.11 → 1.16.0, plus postcss, nanoid and brace-expansion. This also updates @nextcloud/vue from 8.36.0 to 8.41.0. Only semver-compatible updates (`npm audit fix`); LinkBoard's own code is unchanged. The remaining low-severity advisories concern Vue 2 itself, which is end-of-life and can only be resolved by migrating to Vue 3. ## [1.12.0] – 2026-09-19 ### Added - Nextcloud 35 compatibility (`max-version` raised from 34 to 35 in `appinfo/info.xml`). Nextcloud 35 disables every app that does not declare support for it during the upgrade, so LinkBoard 1.11.x disappears after upgrading to Nextcloud 35. No code changes were needed: LinkBoard was checked against the Nextcloud 35 public API (the new `OCP\DB\Schema` migration wrappers, the `ColumnType` enum used by `Entity::addType()`, the typed `ICache` and `IQueryBuilder` signatures, and the removed front-end globals). Nextcloud 32–34 remain supported. ## [1.11.2] – 2026-09-08 ### Fixed - Editing a service now updates its card immediately after clicking "Save" instead of only after leaving edit mode (issue #17). The store replaced the updated service via `services[idx] = data`; Vue 2 cannot intercept an assignment by array index, so no dependency was notified and neither the card grid nor the compact list re-rendered. Leaving edit mode changed an unrelated reactive flag, which forced the re-render that finally showed the new values. The service is now replaced with `splice()`. - Saving a service that has no widget no longer discards its position in the card grid. The service editor sent `widgetType: ''` without a `widgetConfig`, and clearing the widget type also clears `widget_config` server-side — which is where the grid position `_layout` is stored. The editor now sends the stored `_layout` back explicitly; widget credentials are still cleared as before. ## [1.11.1] – 2026-08-06 ### Fixed - The dashboard now scrolls automatically while dragging when the cursor approaches the top or bottom edge of the visible area (issue #16). Previously a service in the compact list mode could not be dropped into a category outside the viewport, because nothing scrolled during the drag: SortableJS' auto-scroll listens for `dragover` on the document in the bubble phase, while Sortable itself stops that event's propagation as soon as the cursor is over one of its lists — which is precisely the case when a list is taller than the screen. The scrolling is now driven by LinkBoard itself from a capture-phase listener. It applies to all drags on the dashboard: service rows, categories between rows, and row reordering. ## [1.11.0] – 2026-07-31 ### Added - New "Link target" setting (issue #15): choose whether service links open in a new tab, in the same tab, or per service. The default is "New tab". With "Per service", each service's own "Link target" field in the service editor decides (defaulting to "New tab"). ### Changed - The "Link target" field in the service editor is only shown while the global setting is "Per service", and now displays labeled options ("New tab" / "Same tab") instead of the raw values `_blank` / `_self`. - Behavior change: the stored per-service link target was previously always honored. With the new global default "New tab", services configured to open in the same tab do so again only after selecting "Per service" in the settings. - The per-service link target is now validated server-side; values other than `_blank` / `_self` are stored as `_blank`. ## [1.10.1] – 2026-07-27 ### Fixed - The "Edit category" panel now scrolls when its content is taller than the window (issue #14). Previously the panel was cut off at the bottom on short viewports (e.g. small window with 120% zoom) and wheel scrolling moved the dashboard behind it instead. ## [1.10.0] – 2026-07-27 ### Added - Services can be dragged between categories in the compact list display mode: dropping a row into another category's list moves the service there at the exact drop position. The stored card-grid position is cleared on the move, so the service gets a fresh default spot when switching back to cards mode. ### Fixed - Sections can now be dropped vertically between two rows (issue #13). While dragging a category by its handle, drop zones appear above, between, and below the rows; dropping there makes the category its own row at that position. Previously a dragged section could only be placed side by side into an existing row. ## [1.9.0] – 2026-07-26 ### Added - Drag & drop reordering in the compact list display mode: in edit mode each row gets a drag handle to reorder services within their category. The order is persisted as the service sort order, so the cards grid layout stays completely untouched. This lifts the 1.8.0 limitation of switching to cards mode to reorder. ### Changed - The compact list now displays services in their stored sort order instead of mirroring the cards-grid position — a one-time change for 1.8.0 list-mode users; the cards layout itself is unaffected. While a search filter is active, reordering is disabled. ## [1.8.0] – 2026-07-26 ### Added - Compact list display mode (discussion #12): a new "Display mode" setting renders services as single-line rows instead of cards — icon, text, and inline status dot. A companion "List row content" setting selects what each row shows: title, URL, title and description, or title and URL. Widgets are not rendered in list mode. Grid layout data is preserved, so switching back to cards is lossless; to reorder services, switch to cards mode temporarily. ## [1.7.7] – 2026-07-26 ### Fixed - Fritz!Box widget: the digest handshake never reached the box (issue #11). FRITZ!OS validates the SOAP body **before** authentication, and libcurl's explicit-digest mode strips the body off its first POST while waiting for the 401 challenge — the box answered that bodyless request with `500 "XML error"` instead of the challenge, so the credentials were never sent. The proxy now uses `CURLAUTH_ANYSAFE`, which sends the first request complete and unauthenticated (the sequence `curl --anyauth` performs, proven against real hardware) and authenticates on the challenge. Basic auth stays excluded, so credentials cannot be downgraded to cleartext. ### Changed - The Fritz!Box mock now validates the SOAP body before authentication, in the order the real box does — a bodyless probe draws `500 "XML error"` even on the credentialed TR-064 endpoint, which is exactly the failure mode that broke the previous handshake. ## [1.7.6] – 2026-07-26 ### Fixed - Fritz!Box widget: a single non-UTF-8 byte silently discarded a whole SOAP reply (issue #11). FRITZ!OS emits its replies without an encoding declaration but with ISO-8859-1 bytes in them — the character lists in a `WANPPPConnection` `GetInfo` reply contain a section sign (`§`) — and libxml rejects the entire document over that one byte, leaving the tile empty with HTTP 200 and no error anywhere. A reply that is not valid UTF-8 now gets a second parse as Latin-1. - Fritz!Box widget: a 200 reply whose body defeats the parser is now named in the tile ("its reply could not be parsed") instead of being indistinguishable from an unconfigured box. ### Changed - The Fritz!Box mock serves its replies as ISO-8859-1 under `FRITZMOCK_LATIN1=1`, reproducing the FRITZ!OS encoding. ## [1.7.5] – 2026-07-26 ### Added - Fritz!Box widget: when the TR-064 probe fails, the tile now names the failure instead of silently reporting an unconfigured box (issue #11). An `HTTP 401` is called out as the box rejecting the configured credentials; other statuses and cURL errors are shown verbatim. An empty answer and a rejected request need different fixes, and the tile no longer hides which one happened. ### Changed - The Fritz!Box mock verifies the digest hash for real (MD5, `qop="auth"`) instead of accepting any `Authorization` header, so a broken digest implementation can no longer pass the mock and fail on real hardware. Credentials default to `lb`/`secret`, overridable via `FRITZMOCK_USER`/`FRITZMOCK_PASS`. ## [1.7.4] – 2026-07-26 ### Fixed - Fritz!Box widget: a PPPoE box's uptime and line rates stayed at the IGD tariff values (issue #11). A real box hands out the external IP on `WANIPConnection` even while calling that service `Unconfigured`, and the widget took the present IP as proof of a live connection — skipping the `WANPPPConnection` probes that carry the uptime and the actual sync rates. Only the reported status decides now; values without any status still count. ### Changed - The Fritz!Box mock's PPPoE mode matches the captured behavior of a real FRITZ!Box 7590: the external IP is served despite `Unconfigured`, and `WANCommonInterfaceConfig` reports an Ethernet WAN with the configured tariff rates instead of a dead DSL link. ## [1.7.3] – 2026-07-26 ### Fixed - Fritz!Box widget: configuring credentials broke the tile entirely with an HTTP 500 (issue #11). Digest authentication was attached to every request up front, which makes libcurl send the first POST with an **empty body** while it waits for a 401 challenge — the credential-free IGD endpoint never challenges, processed the empty request and answered `500 "XML error"`. The proxy now sends every request plainly first and repeats it with digest credentials only after the target has answered 401. - Fritz!Box widget: the TR-064 `WANPPPConnection` interface reports its bit rates in **kbit/s**, not bit/s — a 226 Mbit/s PPPoE line would have shown as "0.2 Mbps". Confirmed against a capture from real FRITZ!OS hardware (issue #11), which now serves as the test fixture. ### Changed - The Fritz!Box mock reproduces two more behaviors of a real box: control requests without a SOAP body draw `500 "XML error"`, and the unimplemented IGD `WANPPPConnection` service answers with a UPnPError instead of 404. ## [1.7.2] – 2026-07-26 ### Fixed - Fritz!Box widget: boxes whose internet connection runs over **PPPoE** stayed empty. UPnP splits the WAN across two services and a box fills in only the one matching its connection type — the widget asked `WANIPConnection` alone, which a PPPoE box answers with the status `Unconfigured` and no values. It now falls back to `WANPPPConnection`, on the IGD interface and, once box credentials are configured, on the TR-064 interface where FRITZ!OS actually serves it. - The 1.7.1 explanation for an empty tile named only IP client and bridge mode as the cause of `Unconfigured`, which is wrong for a PPPoE box. The tile now points at the credentials such a box needs, and says so differently once both interfaces have been asked. ### Changed - Documented what separates the two Fritz!Box cases in `WIDGETS.md`: an IP-routed line needs no credentials, a PPPoE line needs "Allow access for applications" plus a FRITZ!Box user permitted to read the box settings. - The widget proxy accepts follow-up requests marked optional: a probe for a service the upstream may not implement no longer fails the whole tile. - The Fritz!Box widget checks and the widget error-reporting checks now run in CI. ## [1.7.1] – 2026-07-26 ### Fixed - A Fritz!Box tile that stays empty now says why. Boxes that don't run the internet connection themselves — IP client mode, bridge mode, cascaded behind another router — report the WAN status `Unconfigured` and no uptime, which left four unexplained dashes on the tile; the reported status is now shown underneath them. ### Changed - Documented the two things that keep the Fritz!Box widget from filling in: the Nextcloud server must be allowed to open outgoing connections to tcp/49000 on the box (a firewall dropping them shows up as `cURL 28`), and a box without its own internet connection has no WAN values to report. ### Added - The widget proxy can consume SOAP/XML responses in addition to JSON, and can authenticate with HTTP digest where a service demands it. ### Fixed - The Fritz!Box widget returned placeholders instead of data. It now reads the external IP, uptime, and maximum line rates from the TR-064/IGD interface on port 49000, which it derives from the service URL — no credentials needed when the box transmits status information over UPnP. ## [1.6.16] – 2026-07-26 ### Fixed - Failing widgets name the reason again: the tile and the Nextcloud log now carry the upstream HTTP status or the cURL error number, so misconfigured credentials, wrong base URLs, and network failures can be told apart. Request URLs, headers, and upstream response details remain hidden. ### Changed - Documented what the Immich widget requires — an API key from an administrator account with the `server.statistics` permission, and Immich 1.118 or newer — plus a troubleshooting table decoding the widget error messages. ## [1.6.15] – 2026-07-16 ### Changed - User-controlled HTTP, WebSocket, and SMTP destinations are pinned to their validated DNS addresses; loopback, link-local, multicast, documentation, and reserved targets are rejected while RFC1918, ULA, and CGNAT homelab targets remain supported. - SMTP connections are restricted to common mail ports and now enforce bounded reads/writes, validated protocol responses, safe message headers, and correct STARTTLS or implicit TLS negotiation. - User settings are restricted to a typed allowlist; enum, boolean, color, URL, opacity, and length constraints are normalized before any values are persisted, and unknown imported settings are ignored. - Widget batch loading is paginated, time-bounded, rate-limited, deduplicated with atomic locks, and cached briefly to avoid repeated upstream requests. - Manual status checks are rate-limited and service-locked; bulk checks process the stalest services first within fixed request and runtime budgets while background checks remain comprehensive. ### Fixed - Widget request failures no longer expose credential-bearing URLs or upstream exception details to dashboard viewers and application logs. - Temporary widget session cookie files are removed even when a request or response mapping fails. - WebSocket handshakes now verify the server's Sec-WebSocket-Accept value. - Notification test failures no longer return provider exception details or log potentially sensitive exception messages. - Read-only global-board viewers can no longer trigger status checks for the designated source user's services. ## [1.6.14] – 2026-07-15 ### Added - Global TLS certificate verification policy with an optional per-service exception for status checks and widgets. ### Changed - Outbound requests are restricted to HTTP(S), reject loopback, link-local and reserved targets, do not follow redirects, and enforce response-size limits. - Notification channel secrets are no longer returned by the API; blank secret fields preserve stored credentials on update. - Imports are size-limited, validated and executed inside a database transaction. - Uploaded icons receive server-generated filenames; active SVG uploads are no longer accepted. ### Fixed - Manual status refresh can no longer trigger checks and notifications for other users. - TLS certificate verification is enabled by default for status checks, widgets, notifications and update checks. ## [1.6.13] – 2026-07-11 ### Added - PHP 8.5 support (`max-version` raised from 8.4 to 8.5 in `appinfo/info.xml`). Nextcloud 33 and 34 support PHP 8.5, so LinkBoard no longer shows as incompatible on those servers ([#9](https://github.com/tschuegy/linkboard-nextcloud/issues/9)). ## [1.6.12] – 2026-06-19 ### Fixed - Material Design Icon names entered in camelCase (e.g. `mdiCloud`, `mdiSwapHorizontal` — the exact form copied from the Pictogrammers/MDI site) now render for both services and categories. Previously only kebab-case (`mdi-cloud`) was recognized; the camelCase form fell through to the icon CDN and ultimately produced a 404, leaving services with a letter fallback and categories with a dark-red `?` ([#8](https://github.com/tschuegy/linkboard-nextcloud/issues/8)). ## [1.6.11] – 2026-05-14 ### Added - Inline format hint below widget config fields. When a widget defines a `pattern` (regex) and `validationMessage` for a config field, the editor now shows a red helper text under the field when the value doesn't match. Initially enabled for Proxmox VE and Proxmox Backup Server API tokens. Non-blocking — the backend remains authoritative. ### Fixed - Pi-hole widget rewritten for the v6 REST API (the legacy `/admin/api.php?summaryRaw` endpoint was removed in v6). The widget now performs the two-step session login (`POST /api/auth` → `GET /api/stats/summary?sid=...`) via the existing `buildFollowUpRequests` framework path. Config field renamed from `api_token` (optional) to `password` (required in v6); existing Pi-hole services must be reconfigured once. ## [1.6.10] – 2026-05-13 ### Fixed - Import dropped services whose `name` field was an empty string (e.g. icon/widget-only cards) — they are now imported as intended. - Import discarded the category `type` and `config` (grid settings) — categories with a custom `_gridSettings.colCount` now keep their layout instead of falling back to the 12-column default. - Import re-assigned `sortOrder` sequentially instead of preserving the exported values — category and service order now matches the source instance. - Import dropped the per-service `showScrollbar` flag — it is now restored from the export. ## [1.6.9] – 2026-05-11 ### Added - Nextcloud 34 compatibility (`max-version` raised from 33 to 34 in `appinfo/info.xml`) ## [1.6.8] – 2026-05-02 ### Fixed - Status check response time was inflated for HTTPS targets and slow backends because it timed the entire HTTP transaction (DNS + TCP + TLS handshake + server processing). It now records pure TCP-handshake RTT via `CURLINFO_CONNECT_TIME_T`, matching ICMP ping expectations. ### Changed - Status overview "Last 7 days" view now bucketed server-side (~30-min buckets, 336 points) instead of streaming every raw row to the browser. Eliminates the multi-second freeze and "stop script" dialog. The chart shows the per-bucket average plus a dashed max-line so spikes remain visible. - `historyAll` API uses a single batched DB query for all ping-enabled services (no more N+1 over services). ## [1.6.7] – 2026-04-22 ### Fixed - Installation on PostgreSQL failing with "type Bool and also NotNull, so it can not store 'false'" when migration `001011` added the `show_scrollbar` column ([#5](https://github.com/tschuegy/linkboard-nextcloud/issues/5)). Column is now added as nullable with default `false`, matching all other boolean columns in the schema. ## [1.6.6] – 2026-04-22 ### Added - Per-service toggle for scrollbar visibility on service cards (default: hidden; content stays scrollable via trackpad, touch, or keyboard) ### Fixed - Page-level wheel scrolling when viewport edges or fixed overlays would otherwise block the native scroll (programmatic smooth-scroll fallback in `App.vue`) - Dashboard background image intercepting mouse events (`pointer-events: none` on the background layer) ## [1.6.5] – 2026-04-13 ### Added - Admin settings page under Settings > Administration > LinkBoard - Global Board: admins can designate one user's board as a shared read-only dashboard for all users - Group Restriction: optionally restrict LinkBoard access to specific Nextcloud groups - Container-aware memory detection for System Resources widget (cgroup v2/v1 support) ### Fixed - System Resources widget showing wrong memory total in LXC/Docker containers (e.g. "1024.0 TiB" instead of actual RAM) - Response time chart timezone handling (UTC suffix for correct local time display) ### Changed - Admin settings (status check interval) moved from in-app settings to dedicated Nextcloud admin page - Edit mode restricted to users with write access when global board is active ## [1.6.4] – 2026-03-29 ### Added - Inline-editable Table widget: create and edit tables directly on service cards with add/remove columns and rows, editable headers, and auto-save ## [1.6.3] – 2026-03-22 ### Added - Per-category toggle for responsive column adjustment (default: off), fixing layout issues on smaller viewports ## [1.6.2] – 2026-03-21 ### Fixed - Grid resize not working in categories with fewer siblings per row (responsive column minimum was too aggressive) ## [1.6.1] – 2026-03-21 ### Fixed - PHPStan CI errors: expand `buildRequests()` return type to include all optional request spec keys (`auth`, `calls`, `_websocket_jsonrpc`, etc.) - Widget container overflow: allow widget blocks to wrap instead of being hidden ### Added - Two-stage widget support: `buildFollowUpRequests()` method for widgets that need sequential API calls - Arcane widget: reworked with environment auto-detection, shows containers/projects/images/volumes/networks - Widget container `itemsPerRow` prop for CSS grid layout control ## [1.6.0] – 2026-03-21 ### Added - Grafana-style grid layout: free drag, resize, and placement of Service Cards within categories - Configurable grid settings per category (column count, row height, auto-arrange, min-height) - Lock/unlock edit mode toggle replaces pencil icon ### Changed - Service Cards now use vue-grid-layout instead of CSS Grid with fixed column spans - Category columns setting replaced by grid settings (colCount: 6/12/24) - **Migration note:** Existing Service Cards are automatically converted to the new grid layout, but card sizes may not match the previous layout exactly. A one-time manual adjustment of card sizes and positions may be required after upgrading. ### Removed - Cross-category drag-and-drop (use category dropdown in Service Editor instead) - Fixed column-span resize handle on Service Cards (replaced by vue-grid-layout handles) ## [1.5.6] – 2026-03-18 ### Added - Font color customization: auto-detection from background luminance + manual color picker for title, category, service, description, widget values/labels, header buttons, and card background - TrueNAS widget: WebSocket JSON-RPC support for TrueNAS v25.04+ ### Changed - Font color settings moved from theme-only to dedicated "Font colors" section with auto/manual toggle - TrueNAS widget uses WebSocket JSON-RPC (`/api/current`) instead of REST API - Widget and resource display components accept `manualColors` prop for font color overrides ### Fixed - Header button font color not applying (CSS custom property approach instead of inherited `color`) - TrueNAS uptime calculation handling numeric and `$date` boottime formats ## [1.5.5] – 2026-03-17 ### Changed - UniFi widget: added Controller Type selector (UniFi OS vs Legacy Controller) with correct API paths and cookie-based session auth - Dropped support for Nextcloud 30/31 and PHP 8.1 (now requires Nextcloud 32+ and PHP 8.2+) ### Fixed - Missing translations for Status History modal and Status Overview page (12 new strings in all 57 languages, German fully translated) - Status label in StatusHistoryModal not using i18n (`Status` hardcoded instead of `t('linkboard', 'Status')`) - Status badge in StatusOverviewPage showing raw status key instead of translated label (Online/Offline/Unknown) - Period selector buttons missing focus-visible outline for keyboard accessibility ## [1.5.4] – 2026-03-16 ### Fixed - Migration `Version001009Date20260316000000` also calling `$result->free()` instead of `closeCursor()`, causing fresh installations to fail (same issue as v1.5.3 fix, but in the earlier migration) ## [1.5.3] – 2026-03-16 ### Fixed - Migration `Version001010Date20260317000000` calling `$result->free()` which does not exist on Nextcloud 33's `ResultAdapter`, causing `occ upgrade` to fail and leaving Nextcloud stuck in maintenance mode (replaced with `closeCursor()`) ## [1.5.2] – 2026-03-17 ### Fixed - Table names `linkboard_notif_channels` and `linkboard_status_history` exceeding Nextcloud 32 primary index name length limit (renamed to `linkboard_channels` and `linkboard_history`) ## 1.5.1 – 2026-03-16 ### Fixed - Table name `oc_linkboard_notification_channels` too long for Nextcloud 32 Oracle DB compatibility (renamed to `linkboard_notif_channels`) - Status check retry with GET on 5xx responses from HEAD requests ## 1.5.0 – 2026-03-15 ### Added - Status history tracking – background job records status checks with response times in new `linkboard_status_history` table - Status History Modal – click any service's status indicator to view response time charts - Status Overview Page (`/status`) – dedicated page showing all monitored services with uptime%, failure counts, and charts - Mini status history bars on service cards showing last ~10 status checks (configurable opacity in settings) - Period-based history views (1h, 3h, 24h, 7d) - Total failure counter per service in status cache - New settings: "Show status history bars" toggle and "Status bar opacity" slider - Widget warning display in widget containers ### Fixed - Boolean NotNull columns breaking installation on Nextcloud 32 (Oracle constraint compatibility) ## 1.4.2 – 2026-03-10 ### Fixed - Fix database.xml validation for Nextcloud App Store (merge duplicate `` blocks) ## 1.4.1 – 2026-03-09 ### Added - Per-service notification channel overrides: disable globally-enabled channels or enable globally-disabled channels on individual services ### Fixed - Clear status cache when disabling status checks to prevent stale data on re-enable ## 1.4.0 – 2026-03-09 ### Added - External notification channels with 19 providers: Webhook, Discord, Slack, Telegram, Matrix, Microsoft Teams, Nextcloud Talk, Google Chat, Signal, Threema, CallMeBot, Home Assistant, Gotify, Ntfy, Pushover, Brevo, SendGrid, Resend, and E-Mail (SMTP) - Provider registry system (following the widget pattern) for easy extensibility - Nextcloud notifications toggle (enable/disable built-in notifications independently) - Test button for each notification channel to verify configuration - Per-channel enable/disable toggle ## 1.2.0 – 2026-03-07 ### Added - System Resources widget displaying CPU, memory, disk usage, uptime, and CPU temperature with progress bars (new `resources` category type) - Category spacers with multiple decorative styles (solid, dashed, dotted, dots, stars, diamonds, arrows, fade, wave) - Card background options: Glass, Solid, Flat, Transparent - Version check in footer with optional update notifications from GitHub - MDI inline SVG rendering for Material Design Icons (replaces letter fallbacks) - GitHub Sponsors badge in README ### Changed - Consolidated database migrations (merged into single v1.4 migration adding `parent_id`, `type`, `config` columns) - Removed "ungroup" drop zone from dashboard (simplification) - Dialogs use `:open` prop pattern instead of `v-if` ## 1.1.0 – 2026-03-05 ### Added - Category nesting with drag-and-drop grouping ## 1.0.0 – 2026-03-01 ### Added - Personal service dashboard with category-based organization - Drag & drop sorting for categories and services - Service health checks with status indicators (dot/border styles) - Widget system with 134 built-in widgets for live data from external APIs - Custom icon upload (PNG, JPEG, SVG, WebP, GIF, ICO) - Material Design Icon support (mdi-* prefix) - URL-based icon support - Dark/Light/Auto theme with customizable backgrounds and blur - Configurable layout (columns, card styles, search bar visibility) - YAML and JSON import/export (Gethomepage services.yaml compatible) - Background job for periodic status checks - Full internationalization (i18n) with 57 languages - Keyboard shortcuts (/, E, R, Esc) - Tab-based category grouping - Collapsible category sections - Quick search across all services