fix(settings): the integrations tab is always there on desktop so api keys stay reachable

API keys were split off from MCP to work without any addon, but the tab
that holds them still hid unless a photo provider, MCP, AirTrail, AI
Parsing or Dawarich was enabled. On a fresh install all of those are off,
so there was no way to create a key. The desktop tab now always shows;
the addon backed sections inside it keep their own gates. The phone layout
has no API Keys section and keeps its gate.

The wiki now says where keys are created and that no addon is needed.

Refs #2561
This commit is contained in:
Maurice
2026-10-12 12:46:12 +02:00
parent cec5ff1e2e
commit 2f0b867685
5 changed files with 38 additions and 12 deletions
@@ -4,9 +4,11 @@ import { useAuthStore } from '../../store/authStore';
/**
* Which integrations the settings shells offer: the addon flags behind each
* integration section, whether any of them is on (the Integrations tab only shows
* then), and whether this is a managed install. With `reloadAddons` the addon list
* is fetched again on mount, as the integrations tab on both shells does.
* integration section, whether any of them is on, and whether this is a managed
* install. The desktop Integrations tab shows regardless, since its API keys need
* no addon; the mobile one has no API keys and only shows when `hasIntegrations`.
* With `reloadAddons` the addon list is fetched again on mount, as the
* integrations tab on both shells does.
*/
export function useIntegrationGates({ reloadAddons }: { reloadAddons: boolean }) {
const { isEnabled: addonEnabled, loadAddons } = useAddonStore();
+24
View File
@@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
import { resetAllStores, seedStore } from '../../tests/helpers/store';
import { buildUser } from '../../tests/helpers/factories';
import { useAuthStore } from '../store/authStore';
import { useAddonStore } from '../store/addonStore';
import SettingsPage from './SettingsPage';
// Mock heavy settings sub-tabs to focus on page-level concerns
@@ -152,4 +153,27 @@ describe('SettingsPage', () => {
});
});
});
describe('FE-PAGE-SETTINGS-007: Integrations tab without any integration addon', () => {
// API keys live in the Integrations tab and need no addon, so the tab has to
// be there on an instance where MCP, AirTrail, AI Parsing, Dawarich and the
// photo providers are all switched off.
it('still offers the Integrations tab and opens it', async () => {
seedStore(useAddonStore, { isEnabled: () => false, loadAddons: vi.fn().mockResolvedValue(undefined) });
const user = userEvent.setup();
render(<SettingsPage />);
await user.click(await screen.findByRole('button', { name: /integrations/i }));
expect(await screen.findByTestId('integrations-tab')).toBeInTheDocument();
});
it('keeps it on a managed install too', async () => {
seedStore(useAddonStore, { isEnabled: () => false, loadAddons: vi.fn().mockResolvedValue(undefined) });
seedStore(useAuthStore, { managed: true });
render(<SettingsPage />, { initialEntries: ['/settings?tab=integrations'] });
expect(await screen.findByTestId('integrations-tab')).toBeInTheDocument();
});
});
});
+5 -5
View File
@@ -26,7 +26,7 @@ export default function SettingsPage(): React.ReactElement {
function SettingsPageDesktop(): React.ReactElement {
const { t } = useTranslation()
// Page = wiring container: addon/version loading + active-tab state in the hook.
const { hasIntegrations, hasPlugins, appVersion, activeTab, setActiveTab, managed } = useSettings()
const { hasPlugins, appVersion, activeTab, setActiveTab, managed } = useSettings()
// Every tab is its own help screen; a tab without one falls back to the settings overview.
const helpId = getHelpContext(`settings-${activeTab}`) ? `settings-${activeTab}` : 'settings'
@@ -35,9 +35,9 @@ function SettingsPageDesktop(): React.ReactElement {
{ id: 'appearance', label: t('settings.tabs.appearance'), icon: Paintbrush },
{ id: 'map', label: t('settings.tabs.map'), icon: Map },
{ id: 'notifications', label: t('settings.tabs.notifications'), icon: Bell },
...(hasIntegrations
? [{ id: 'integrations', label: t('settings.tabs.integrations'), icon: Plug }]
: []),
// Always there: API keys sit in this tab and need no addon. The addon backed
// sections inside it gate themselves.
{ id: 'integrations', label: t('settings.tabs.integrations'), icon: Plug },
...(hasPlugins
? [{ id: 'plugins', label: t('settings.tabs.plugins'), icon: Blocks }]
: []),
@@ -86,7 +86,7 @@ function SettingsPageDesktop(): React.ReactElement {
{activeTab === 'appearance' && <AppearanceSettingsTab />}
{activeTab === 'map' && <MapSettingsTab />}
{activeTab === 'notifications' && <NotificationsTab />}
{activeTab === 'integrations' && hasIntegrations && <IntegrationsTab />}
{activeTab === 'integrations' && <IntegrationsTab />}
{activeTab === 'plugins' && hasPlugins && <PluginSettingsTab />}
{activeTab === 'offline' && <OfflineTab />}
{activeTab === 'account' && <AccountTab />}
+1 -1
View File
@@ -17,7 +17,7 @@ It cannot write anything. An integration that reads your itinerary needs no abil
## Getting a key
1. Open **Settings → Integrations**
1. Open **Settings → Integrations** in the desktop layout. The tab is always there and needs no addon: an API key works with MCP and every other integration switched off. The phone layout has no API Keys section yet, so create the key on a wider screen.
2. Under **API Keys**, click **Create key** and give it a name you will recognise later (the name is only for you — "Dawarich", "home assistant", "laptop script")
3. Choose **what the key may read.** Everything is selected by default. Anything you switch off is refused for that key.
4. **Copy the key immediately.** It is shown once. TREK stores only a hash of it, so it cannot be shown again — if you lose it, delete the key and make a new one.
+3 -3
View File
@@ -20,7 +20,7 @@ If your account requires MFA setup, TREK redirects you directly to the **Account
| Appearance | Color mode, color scheme / accent, readability (transparency, reduce motion, density, text size), and which widgets appear on your dashboard | Always |
| Map | Map provider (Leaflet, Mapbox GL, or MapLibre GL), tile presets, map style and Mapbox token, 3D buildings, high-quality mode | Always |
| Notifications | Email, webhook, ntfy, push and in-app notification preferences, plus switching push on or off for this device (every device is switched on separately) | Always |
| Integrations | Photo providers (Immich, Synology, etc.), the AirTrail connection, the Dawarich connection, your own AI parsing model, public API keys, and MCP OAuth clients / API tokens | Only when a photo provider (Immich or Synology Photos), MCP, AirTrail, AI Parsing or Dawarich is enabled |
| Integrations | Photo providers (Immich, Synology, etc.), the AirTrail connection, the Dawarich connection, your own AI parsing model, public API keys, and MCP OAuth clients / API tokens | Always on desktop, because API keys need no addon. In the phone layout only when a photo provider (Immich or Synology Photos), MCP, AirTrail, AI Parsing or Dawarich is enabled |
| Plugins | Per-user settings for installed plugins | Only when at least one plugin is installed |
| Offline | Cached trips, pending changes, re-sync and clear cache | Always |
| Account | Username, email, password, MFA (TOTP + backup codes), passkeys, avatar, delete account | Always |
@@ -100,13 +100,13 @@ If your account was linked via SSO, an **SSO** badge appears next to your role a
## Integrations tab
The Integrations tab is only visible when a **photo provider** (Immich or Synology Photos), **MCP**, **AirTrail**, **AI Parsing** or **Dawarich** is enabled. Each section then appears with its own addon, so the tab holds only what your instance actually runs:
On desktop the Integrations tab is always there, because **API Keys** need no addon. Every other section appears with its own addon, so the tab holds only what your instance actually runs. The phone layout has no API Keys section and shows the tab only when a **photo provider** (Immich or Synology Photos), **MCP**, **AirTrail**, **AI Parsing** or **Dawarich** is enabled:
- **Photo Providers** (only when a photo provider is enabled) — Configure Immich, Synology Photos, and other photo integrations. One card appears per enabled provider.
- **AirTrail** (only when the AirTrail addon is enabled) — Instance URL and API key for your self-hosted AirTrail, plus a self-signed-certificate switch and a **Write changes back to AirTrail** toggle (off by default: AirTrail is the source of truth and TREK only reads from it). **Test connection** reports how many flights it can see. The key is stored encrypted and never prefilled — leaving the field blank keeps the stored one.
- **Dawarich** (only when the Dawarich addon is enabled): the **Instance address** and **API key** of your own Dawarich, a self-signed-certificate switch, **Test connection** and **Disconnect**. The key is stored encrypted and never handed back. See [Dawarich](Dawarich).
- **AI parsing** (only when the AI Parsing addon is enabled): the provider (OpenAI or Anthropic), model and API key used to extract bookings from your uploaded files, plus **Model reads images**. Turn that on for a model that reads images: a photo of a ticket can then be imported as a booking, and a receipt scanned in Costs. This is the per-user fallback: it only takes effect when your administrator has not configured a model for the whole instance. A local Ollama endpoint is not offered here; that is set up once, instance-wide, in the admin settings (#1772). See [AI-Booking-Import](AI-Booking-Import#photos) and [Budget-Tracking](Budget-Tracking#scanning-a-receipt).
- **API Keys** (whenever the tab is shown): read-only keys for the public API, so other software can read your trips. **Create key** names the key and lets you narrow **what this key may read** to single areas; the key is shown once. See [Public-API](Public-API).
- **API Keys** (always, desktop layout only): read-only keys for the public API, so other software can read your trips. **Create key** names the key and lets you narrow **what this key may read** to single areas; the key is shown once. See [Public-API](Public-API).
- **MCP section** (only when MCP addon is enabled):
- Shows the MCP server endpoint URL.
- **OAuth Clients** sub-tab — Create and manage OAuth 2.1 clients (with redirect URIs and scopes). Quick-fill presets are provided for Claude.ai, Claude Desktop, Cursor, VS Code, Windsurf, and Zed. Active OAuth sessions can be viewed and revoked here.