Skip to content

feat(tiles): a tile opens its office network address at the office, and administrators set the office networks - #787

Open
rubenvdlinde wants to merge 45 commits into
developmentfrom
feat/launcher-tile-internal-address
Open

rubenvdlinde wants to merge 45 commits into
developmentfrom
feat/launcher-tile-internal-address

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What this does

Change launcher-tile-internal-address (archived in this PR at openspec/changes/archive/2026-09-30-launcher-tile-internal-address). Stacked on #786 (feat/launcher-bookmark-import): this branch contains every PR below it. Land the stack in order, or land the top PR with a merge commit and the ones below close with it.

  • Administrators list the office networks (IPv4 and IPv6 ranges) on a new "Office networks" admin tab; a malformed range is refused ("This is not a valid network range"). The tab names the administrator's own address and whether it is inside. GET/PUT /api/admin/office-networks, admin only.
  • A tile gets an optional "Address on the office network". The server validates it like the main address (http or https with a host, or a path on this Nextcloud) and refuses the whole update otherwise.
  • The server decides from the request address (IRequest::getRemoteAddress(), trusted proxies applied) and tells the page onOfficeNetwork; a tile with an office address opens it only then. The browser never probes internal hosts. With no ranges set nothing changes.
  • The tile editor says which address the author gets right now.

Rows moved to built: d-internal-url (built.change = openspec/changes/archive/2026-09-30-launcher-tile-internal-address). The spec delta is folded into the main spec; the design records what the build corrected.

Tests (red first)

  • tests/Unit/Service/OfficeNetworkServiceTest.php (IPv4, IPv6, empty list, malformed range stores nothing, unparsable remote address), with a CIDR double; tests/Unit/Database/OfficeNetworkLiveTest.php checks the same ranges with Nextcloud's own IP factory where CI boots Nextcloud
  • tests/Unit/Service/TileInternalAddressTest.php through the real PlacementService, PlacementUpdater and TileUpdater (valid addresses stored, invalid ones refuse the update with nothing written)
  • tests/Unit/Service/InitialStateBuilderTest.php, src/components/__tests__/TileWidget.internalAddress.spec.js, src/components/admin/tabs/__tests__/OfficeNetworksTab.spec.js

Playwright tasks are not written; the specs carry @e2e exclude naming these files.

Verified

  • The stack now contains origin/development 5288399 (merged into feat(people): find colleagues by expertise and show extra profile fields #743 and up the stack; l10n catalogues merged per key, info.xml 1.1.7-unstable.20261005190000 for the whole stack, one migration Version002012).
  • Full local mirror of the required CI jobs on the stack top (feat/launcher-tile-internal-address): npm lint, format, check:manifest, check:schema-l10n, l10n-parity and Vitest (963 of 963) all 0 on 7579477; the later source changes are three @spec comments (eslint 0 on those files). composer check:strict (lint, phpcs, phpmd, psalm, phpstan, PHPUnit 1948 tests, 14 skipped database tests that run in CI) and phpmetrics: 0 on 4a17769, after fixing six phpmd findings the stack introduced: BookmarkImportController coupling and static access (4e44727), PageController::index() length and TileUpdater complexity (4a17769).
  • Hydra gates at CI's version (hydra-gates@main 4de7693, full scope, --require-full-coverage) on b2005d8: 85 of 86 applicable gates pass. Gate 16 found four untagged hooks, fixed where they were introduced (tile sorting, shortcuts, bookmarks). Gate 23 fails with the one match development itself has (PermissionService, RoleFeaturePermissionService, rule consume-or-rbac-fleet-wide); inherited, not touched here, and waiting on a decision.
  • No local production build; CI's Frontend Build is the check.

Live check

Admin settings, LaunchPad, "Office networks": add the range your own address is in, save: the tab says your address is on the office network. Edit a tile with main address https://zaken.gemeente.nl, set "Address on the office network" to http://zaken.intern: the editor says this tile opens the internal address; the tile's link is http://zaken.intern. Remove the range: the tile opens the main address again. Entering 10.20.0.0/40 is refused.

…ly, by most or last used, or at random

Most and last used read counts kept in the viewer's own browser, never sent to the
server; a viewer can forget them. The order is applied when the page is viewed and
never written back, and random holds for the page load.

REQ-TSO-001..004, REQ-CONT-007 (launcher-tile-sorting)
…ends a query straight to a site

search_shortcuts holds up to 30 entries {prefix, name, urlTemplate}; a prefix is !
and 1 to 10 letters or digits, unique; the address passes the https {query} check
the quick-search fallback already uses. The list reaches every page through the
workspace initial state.

REQ-QSP-001, REQ-QSP-002 (search-ai-prefix-shortcuts)
…d ? lists the shortcuts

In the search widget a query whose first word is a shortcut prefix shows one
result, Search TOPdesk for printer, and Enter opens the site in a new tab; an
unknown prefix filters tiles as before; ! or ? lists the shortcuts. Administrators
edit the list on a new admin tab.

REQ-SPX-001..003 (search-ai-prefix-shortcuts)
…ming a group of tiles

The browser reads the file (Firefox, Chrome and Edge exports), shows folders and
loose bookmarks to pick from, and refuses files over 5 MB or 2,000 bookmarks. The
server keeps only http and https addresses and lists the rest as skipped, checks the
widget quota once for the whole import (409 names the room left), and creates a
container of link tiles per folder plus loose tiles at the bottom in one transaction.
The entry sits in the active dashboard's menu for people who may add widgets.

REQ-BMI-001..004 (launcher-bookmark-import)
…carry an address for inside the office

office_networks holds IPv4 and IPv6 ranges, validated with Nextcloud's IP factory;
the server decides from the request address and tells the page onOfficeNetwork. A
tile's content.internalUrl must be an http or https address or a path on this
Nextcloud, or the update is refused.

REQ-TIA-001, REQ-TIA-002 (launcher-tile-internal-address)
…nd administrators set the office networks

The tile editor gets "Address on the office network" and says which address the
author gets right now; the tile opens the internal address only when the server says
the request comes from an office network. A new admin tab lists the ranges and names
the administrator's own address.

REQ-TIA-001..003 (launcher-tile-internal-address)
…e tile wrapper is not a fake control (gate-32)
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/launchpad @ 8b38a63

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-manifest ✅
format ✅
check-schema-l10n ✅
composer ✅ ✅ 104/104
npm ✅ ✅ 636/636
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ✅
Newman ✅
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ❌

Quality workflow — 2026-10-05 18:13 UTC

Download the full PDF report from the workflow artifacts.

…tile-sorting

# Conflicts:
#	l10n/be.js
#	l10n/be.json
#	l10n/bg.js
#	l10n/bg.json
#	l10n/bs.js
#	l10n/bs.json
#	l10n/ca.js
#	l10n/ca.json
#	l10n/cs.js
#	l10n/cs.json
#	l10n/da.js
#	l10n/da.json
#	l10n/de.js
#	l10n/de.json
#	l10n/el.js
#	l10n/el.json
#	l10n/en.js
#	l10n/en.json
#	l10n/es.js
#	l10n/es.json
#	l10n/et.js
#	l10n/et.json
#	l10n/fi.js
#	l10n/fi.json
#	l10n/fr.js
#	l10n/fr.json
#	l10n/ga.js
#	l10n/ga.json
#	l10n/hr.js
#	l10n/hr.json
#	l10n/hu.js
#	l10n/hu.json
#	l10n/is.js
#	l10n/is.json
#	l10n/it.js
#	l10n/it.json
#	l10n/lb.js
#	l10n/lb.json
#	l10n/lt.js
#	l10n/lt.json
#	l10n/lv.js
#	l10n/lv.json
#	l10n/mk.js
#	l10n/mk.json
#	l10n/mt.js
#	l10n/mt.json
#	l10n/nb.js
#	l10n/nb.json
#	l10n/nl.js
#	l10n/nl.json
#	l10n/pl.js
#	l10n/pl.json
#	l10n/pt.js
#	l10n/pt.json
#	l10n/rm.js
#	l10n/rm.json
#	l10n/ro.js
#	l10n/ro.json
#	l10n/ru.js
#	l10n/ru.json
#	l10n/sk.js
#	l10n/sk.json
#	l10n/sl.js
#	l10n/sl.json
#	l10n/sq.js
#	l10n/sq.json
#	l10n/sr.js
#	l10n/sr.json
#	l10n/sv.js
#	l10n/sv.json
#	l10n/tr.js
#	l10n/tr.json
#	l10n/uk.js
#	l10n/uk.json
…shortcuts

# Conflicts:
#	l10n/be.js
#	l10n/be.json
#	l10n/bg.js
#	l10n/bg.json
#	l10n/bs.js
#	l10n/bs.json
#	l10n/ca.js
#	l10n/ca.json
#	l10n/cs.js
#	l10n/cs.json
#	l10n/da.js
#	l10n/da.json
#	l10n/de.js
#	l10n/de.json
#	l10n/el.js
#	l10n/el.json
#	l10n/en.js
#	l10n/en.json
#	l10n/es.js
#	l10n/es.json
#	l10n/et.js
#	l10n/et.json
#	l10n/fi.js
#	l10n/fi.json
#	l10n/fr.js
#	l10n/fr.json
#	l10n/ga.js
#	l10n/ga.json
#	l10n/hr.js
#	l10n/hr.json
#	l10n/hu.js
#	l10n/hu.json
#	l10n/is.js
#	l10n/is.json
#	l10n/it.js
#	l10n/it.json
#	l10n/lb.js
#	l10n/lb.json
#	l10n/lt.js
#	l10n/lt.json
#	l10n/lv.js
#	l10n/lv.json
#	l10n/mk.js
#	l10n/mk.json
#	l10n/mt.js
#	l10n/mt.json
#	l10n/nb.js
#	l10n/nb.json
#	l10n/nl.js
#	l10n/nl.json
#	l10n/pl.js
#	l10n/pl.json
#	l10n/pt.js
#	l10n/pt.json
#	l10n/rm.js
#	l10n/rm.json
#	l10n/ro.js
#	l10n/ro.json
#	l10n/ru.js
#	l10n/ru.json
#	l10n/sk.js
#	l10n/sk.json
#	l10n/sl.js
#	l10n/sl.json
#	l10n/sq.js
#	l10n/sq.json
#	l10n/sr.js
#	l10n/sr.json
#	l10n/sv.js
#	l10n/sv.json
#	l10n/tr.js
#	l10n/tr.json
#	l10n/uk.js
#	l10n/uk.json
#	lib/Controller/PageController.php
#	lib/Db/AdminSettingKey.php
#	lib/Service/InitialStateBuilder.php
#	src/components/admin/AdminSettings.vue
#	src/utils/loadInitialState.js
…kmark-import

# Conflicts:
#	l10n/be.js
#	l10n/be.json
#	l10n/bg.js
#	l10n/bg.json
#	l10n/bs.js
#	l10n/bs.json
#	l10n/ca.js
#	l10n/ca.json
#	l10n/cs.js
#	l10n/cs.json
#	l10n/da.js
#	l10n/da.json
#	l10n/de.js
#	l10n/de.json
#	l10n/el.js
#	l10n/el.json
#	l10n/en.js
#	l10n/en.json
#	l10n/es.js
#	l10n/es.json
#	l10n/et.js
#	l10n/et.json
#	l10n/fi.js
#	l10n/fi.json
#	l10n/fr.js
#	l10n/fr.json
#	l10n/ga.js
#	l10n/ga.json
#	l10n/hr.js
#	l10n/hr.json
#	l10n/hu.js
#	l10n/hu.json
#	l10n/is.js
#	l10n/is.json
#	l10n/it.js
#	l10n/it.json
#	l10n/lb.js
#	l10n/lb.json
#	l10n/lt.js
#	l10n/lt.json
#	l10n/lv.js
#	l10n/lv.json
#	l10n/mk.js
#	l10n/mk.json
#	l10n/mt.js
#	l10n/mt.json
#	l10n/nb.js
#	l10n/nb.json
#	l10n/nl.js
#	l10n/nl.json
#	l10n/pl.js
#	l10n/pl.json
#	l10n/pt.js
#	l10n/pt.json
#	l10n/rm.js
#	l10n/rm.json
#	l10n/ro.js
#	l10n/ro.json
#	l10n/ru.js
#	l10n/ru.json
#	l10n/sk.js
#	l10n/sk.json
#	l10n/sl.js
#	l10n/sl.json
#	l10n/sq.js
#	l10n/sq.json
#	l10n/sr.js
#	l10n/sr.json
#	l10n/sv.js
#	l10n/sv.json
#	l10n/tr.js
#	l10n/tr.json
#	l10n/uk.js
#	l10n/uk.json
#	src/views/Views.vue
…internal-address

# Conflicts:
#	l10n/be.js
#	l10n/be.json
#	l10n/bg.js
#	l10n/bg.json
#	l10n/bs.js
#	l10n/bs.json
#	l10n/ca.js
#	l10n/ca.json
#	l10n/cs.js
#	l10n/cs.json
#	l10n/da.js
#	l10n/da.json
#	l10n/de.js
#	l10n/de.json
#	l10n/el.js
#	l10n/el.json
#	l10n/en.js
#	l10n/en.json
#	l10n/es.js
#	l10n/es.json
#	l10n/et.js
#	l10n/et.json
#	l10n/fi.js
#	l10n/fi.json
#	l10n/fr.js
#	l10n/fr.json
#	l10n/ga.js
#	l10n/ga.json
#	l10n/hr.js
#	l10n/hr.json
#	l10n/hu.js
#	l10n/hu.json
#	l10n/is.js
#	l10n/is.json
#	l10n/it.js
#	l10n/it.json
#	l10n/lb.js
#	l10n/lb.json
#	l10n/lt.js
#	l10n/lt.json
#	l10n/lv.js
#	l10n/lv.json
#	l10n/mk.js
#	l10n/mk.json
#	l10n/mt.js
#	l10n/mt.json
#	l10n/nb.js
#	l10n/nb.json
#	l10n/nl.js
#	l10n/nl.json
#	l10n/pl.js
#	l10n/pl.json
#	l10n/pt.js
#	l10n/pt.json
#	l10n/rm.js
#	l10n/rm.json
#	l10n/ro.js
#	l10n/ro.json
#	l10n/ru.js
#	l10n/ru.json
#	l10n/sk.js
#	l10n/sk.json
#	l10n/sl.js
#	l10n/sl.json
#	l10n/sq.js
#	l10n/sq.json
#	l10n/sr.js
#	l10n/sr.json
#	l10n/sv.js
#	l10n/sv.json
#	l10n/tr.js
#	l10n/tr.json
#	l10n/uk.js
#	l10n/uk.json
#	lib/Controller/PageController.php
#	lib/Db/AdminSettingKey.php
#	lib/Service/InitialStateBuilder.php
#	src/components/admin/AdminSettings.vue
#	src/utils/loadInitialState.js
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/launchpad @ 3d2e1d8

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-manifest ✅
format ❌
check-schema-l10n ✅
composer ✅ ✅ 107/107
npm ❌ ✅ 636/636
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ❌
Newman ❌
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ❌

Quality workflow — 2026-10-23 05:24 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant