mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 09:08:54 -04:00
fix(ui): keep hub entries off the Apps route in the sidebar
The Build hub still owned /app/addons through its legacy Add-ons tab, so the sidebar lit both Apps and Build on the Apps page. A tab whose gate is closed now owns no route when the sidebar asks, so only the Apps entry is active there. The hubs keep their own routes, and the legacy navigation still gives /app/addons to the Build hub. Assisted-by: Claude Code:claude-sonnet-5-5 node playwright Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
3 files changed
+40
-3
No files matched your search
@@ -253,7 +253,7 @@ export default function Sidebar({ isOpen, onClose }) {
|
||||
item={item}
|
||||
onClose={onClose}
|
||||
collapsed={collapsed}
|
||||
active={item.hub ? hubOwnsPath(item.hub, location.pathname) : undefined}
|
||||
active={item.hub ? hubOwnsPath(item.hub, location.pathname, auth) : undefined}
|
||||
badge={item.hub?.id === 'operate' && activeOps > 0 ? (
|
||||
<span className={`nav-badge${failedOps > 0 ? ' nav-badge--error' : ''}`}>
|
||||
{failedOps > 0 ? failedOps : activeOps}
|
||||
|
||||
+5
-2
@@ -262,8 +262,11 @@ export function resolveTabs(hub, auth, pathname) {
|
||||
return { tabs, current }
|
||||
}
|
||||
|
||||
export function hubOwnsPath(hub, pathname) {
|
||||
return hub.tabs.some(tab => tabOwnsPath(tab, pathname))
|
||||
// With `auth`, a tab whose gate is closed owns nothing: the Add-ons tab of the
|
||||
// Build hub only exists in the legacy navigation, so in the four-item sidebar
|
||||
// /app/addons belongs to the Apps entry alone and must not light a hub entry.
|
||||
export function hubOwnsPath(hub, pathname, auth) {
|
||||
return hub.tabs.some(tab => (!auth || tabGateOpen(tab, auth)) && tabOwnsPath(tab, pathname))
|
||||
}
|
||||
|
||||
// The page the hub's single sidebar entry links to: its overview, when the
|
||||
|
||||
@@ -151,3 +151,37 @@ test('the first-run card shows only to an installer when nothing is installed',
|
||||
assert.equal(showFirstRun({ apps: [], isAdmin: false }), false)
|
||||
assert.equal(showFirstRun({ apps: [], isAdmin: true, canList: false }), false)
|
||||
})
|
||||
|
||||
test('sidebar entries own one route each: Apps on /app/addons, never a hub', async () => {
|
||||
const { hubs, hubOwnsPath } = await import('../../src/components/hub/hubConfig.js')
|
||||
const [build, operate] = hubs
|
||||
// /api/features has answered and the legacy switch is off: four-item sidebar.
|
||||
const auth = { isAdmin: true, authEnabled: true, hasFeature: () => true, features: { addon_navigation: true } }
|
||||
for (const p of ['/app/addons', '/app/addons/voice/voice', '/app/addons/notes/home']) {
|
||||
assert.equal(hubOwnsPath(build, p, auth), false, `Build owns ${p}`)
|
||||
assert.equal(hubOwnsPath(operate, p, auth), false, `Operate owns ${p}`)
|
||||
}
|
||||
// Before /api/features answers the Add-ons tab is also closed.
|
||||
assert.equal(hubOwnsPath(build, '/app/addons', { ...auth, features: {} }), false)
|
||||
})
|
||||
|
||||
test('sidebar hubs keep their own routes, and Apps does not claim them', async () => {
|
||||
const { hubs, hubOwnsPath } = await import('../../src/components/hub/hubConfig.js')
|
||||
const [build, operate] = hubs
|
||||
const auth = { isAdmin: true, authEnabled: true, hasFeature: () => true, features: { addon_navigation: true } }
|
||||
for (const p of ['/app/operate', '/app/settings', '/app/backends', '/app/traces', '/app/nodes']) {
|
||||
assert.equal(hubOwnsPath(operate, p, auth), true, `Operate misses ${p}`)
|
||||
assert.equal(hubOwnsPath(build, p, auth), false, `Build owns ${p}`)
|
||||
}
|
||||
assert.equal(hubOwnsPath(build, '/app/build', auth), true)
|
||||
assert.equal(hubOwnsPath(build, '/app/agents', auth), true)
|
||||
assert.equal(hubOwnsPath(operate, '/app/build', auth), false)
|
||||
// The primary Apps entry matches /app/addons and its subpaths only.
|
||||
const apps = '/app/addons'
|
||||
const appsActive = p => p === apps || p.startsWith(apps + '/')
|
||||
assert.equal(appsActive('/app/addons/voice/voice'), true)
|
||||
assert.equal(appsActive('/app/operate'), false)
|
||||
// Legacy navigation: the Build hub keeps the Add-ons tab.
|
||||
const legacy = { ...auth, features: { addon_navigation: false } }
|
||||
assert.equal(hubOwnsPath(build, '/app/addons', legacy), true)
|
||||
})
|
||||
Reference in new issue
Block a user