diff --git a/client/src/pages/AdminPage.test.tsx b/client/src/pages/AdminPage.test.tsx
index 1cd09f0f4..bffee1972 100644
--- a/client/src/pages/AdminPage.test.tsx
+++ b/client/src/pages/AdminPage.test.tsx
@@ -69,6 +69,10 @@ vi.mock('../components/Admin/DefaultUserSettingsTab', () => ({
default: () =>
,
}));
+vi.mock('../components/Admin/storage/AdminStoragePanel', () => ({
+ default: () => ,
+}));
+
beforeEach(() => {
resetAllStores();
});
@@ -1631,4 +1635,19 @@ describe('AdminPage', () => {
expect(screen.queryByText('Files')).not.toBeInTheDocument();
});
});
+
+ it('FE-PAGE-ADMIN-STOR-001: the Storage tab renders the storage panel', async () => {
+ seedStore(useAuthStore, { isAuthenticated: true, user: buildAdmin() });
+ render();
+ const tab = await screen.findByRole('button', { name: /^storage$/i });
+ fireEvent.click(tab);
+ expect(screen.getByTestId('admin-storage-panel')).toBeInTheDocument();
+ });
+
+ it('FE-PAGE-ADMIN-STOR-002: managed mode hides the Storage tab (hoster-level config)', async () => {
+ seedStore(useAuthStore, { isAuthenticated: true, user: buildAdmin(), managed: true });
+ render();
+ await screen.findByRole('button', { name: /^users$/i });
+ expect(screen.queryByRole('button', { name: /^storage$/i })).not.toBeInTheDocument();
+ });
});
diff --git a/client/src/pages/AdminPage.tsx b/client/src/pages/AdminPage.tsx
index 9fc153137..16e31f8a6 100644
--- a/client/src/pages/AdminPage.tsx
+++ b/client/src/pages/AdminPage.tsx
@@ -12,7 +12,8 @@ import PackingTemplateManager from '../components/Admin/PackingTemplateManager'
import AuditLogPanel from '../components/Admin/AuditLogPanel'
import AdminMcpTokensPanel from '../components/Admin/AdminMcpTokensPanel'
import AdminPluginsPanel from '../components/Admin/AdminPluginsPanel'
-import { Users, Map, Briefcase, Shield, FileText, SlidersHorizontal, UserCog, Puzzle, Blocks, Settings as SettingsIcon, Bell, Database, ScrollText, KeyRound, GitBranch, Bug } from 'lucide-react'
+import AdminStoragePanel from '../components/Admin/storage/AdminStoragePanel'
+import { Users, Map, Briefcase, Shield, FileText, SlidersHorizontal, UserCog, Puzzle, Blocks, Settings as SettingsIcon, Bell, Database, ScrollText, KeyRound, GitBranch, Bug, HardDrive } from 'lucide-react'
import PageSidebar, { type PageSidebarTab } from '../components/Layout/PageSidebar'
import { useAdmin } from './admin/useAdmin'
import AdminUpdateBanner from './admin/AdminUpdateBanner'
@@ -55,6 +56,9 @@ function AdminPageDesktop(): React.ReactElement {
{ id: 'settings', label: t('admin.tabs.settings'), icon: SettingsIcon, group: gConfig },
{ id: 'addons', label: t('admin.tabs.addons'), icon: Puzzle, group: gConfig },
{ id: 'plugins', label: t('admin.tabs.plugins'), icon: Blocks, group: gConfig },
+ // Storage backends and their credentials are hoster-level configuration —
+ // the server refuses the whole surface in managed mode (MANAGED_FORBIDDEN).
+ ...(managed ? [] : [{ id: 'storage', label: t('admin.tabs.storage'), icon: HardDrive, group: gConfig }]),
{ id: 'notifications', label: t('admin.tabs.notifications'), icon: Bell, group: gIntegration },
...(mcpEnabled ? [{ id: 'mcp-tokens', label: t('admin.tabs.mcpTokens'), icon: KeyRound, group: gIntegration }] : []),
// Releases and update cadence belong to whoever operates the install.
@@ -180,6 +184,8 @@ function AdminPageDesktop(): React.ReactElement {
{activeTab === 'plugins' && }
+ {activeTab === 'storage' && }
+
{activeTab === 'github' && }
{activeTab === 'defaults' && }