Managed mode: keep the admin settings and addons ratchets green

- The phone's OIDC card moves into MAdminOidcCard, so the settings section
  is back within its complexity limit after the managed-mode conditions.
- enabledPhotoProviderIds reads the enabled ids straight from the
  repository and has a test of its own, which brings the addons coverage
  back to its threshold.
This commit is contained in:
Maurice
2026-11-02 22:12:12 +00:00
committed by Maurice
parent f99b40cd81
commit 91bc1944dc
4 changed files with 78 additions and 51 deletions
@@ -0,0 +1,66 @@
import type { useAdmin } from '../../../pages/admin/useAdmin';
import type { TranslationFn } from '../../../types';
import { MAdminButton, MAdminCard, MAdminCardHead, MAdminField, MAdminInput } from './MAdminUi';
interface MAdminOidcCardProps {
admin: ReturnType<typeof useAdmin>;
t: TranslationFn;
onSave: () => void;
}
/** OIDC / SSO, as on the desktop settings tab. The settings section leaves it out on a managed install. */
export default function MAdminOidcCard({ admin, t, onSave }: MAdminOidcCardProps) {
const { oidcConfig, setOidcConfig, savingOidc } = admin;
return (
<MAdminCard>
<MAdminCardHead title={t('admin.oidcTitle')} hint={t('admin.oidcSubtitle')} />
<div className="space-y-3">
<MAdminField label={t('admin.oidcDisplayName')}>
<MAdminInput
type="text"
value={oidcConfig.display_name}
onChange={(e) => setOidcConfig((c) => ({ ...c, display_name: e.target.value }))}
placeholder={t('admin.oidcDisplayNamePlaceholder')}
/>
</MAdminField>
<MAdminField label={t('admin.oidcIssuer')} hint={t('admin.oidcIssuerHint')}>
<MAdminInput
type="url"
value={oidcConfig.issuer}
onChange={(e) => setOidcConfig((c) => ({ ...c, issuer: e.target.value }))}
placeholder="https://accounts.google.com"
/>
</MAdminField>
<MAdminField
label={t('admin.oidcDiscoveryUrl')}
hint={t('admin.oidcDiscoveryUrlHint', { path: '<issuer>/.well-known/openid-configuration' })}
>
<MAdminInput
type="url"
value={oidcConfig.discovery_url}
onChange={(e) => setOidcConfig((c) => ({ ...c, discovery_url: e.target.value }))}
placeholder="https://auth.example.com/application/o/trek/.well-known/openid-configuration"
/>
</MAdminField>
<MAdminField label={t('settings.oauth.clientId')}>
<MAdminInput
type="text"
value={oidcConfig.client_id}
onChange={(e) => setOidcConfig((c) => ({ ...c, client_id: e.target.value }))}
/>
</MAdminField>
<MAdminField label={t('settings.oauth.clientSecret')}>
<MAdminInput
type="password"
value={oidcConfig.client_secret}
onChange={(e) => setOidcConfig((c) => ({ ...c, client_secret: e.target.value }))}
placeholder={oidcConfig.client_secret_set ? '••••••••' : ''}
/>
</MAdminField>
<MAdminButton busy={savingOidc} onClick={onSave}>
{t('common.save')}
</MAdminButton>
</div>
</MAdminCard>
);
}
@@ -6,6 +6,7 @@ import type { TranslationFn } from '../../../types'
import type { useAdmin } from '../../../pages/admin/useAdmin'
import MToggle from '../../components/MToggle'
import { MBlockDisclosure, MProviderBlock, MTrekApiBlock } from './MApiProviderBlocks'
import MAdminOidcCard from './MAdminOidcCard'
import MSetPickerSheet from '../settings/MSetPickerSheet'
import {
MAdminButton,
@@ -31,7 +32,6 @@ export default function MAdminSettingsSection({ admin, t }: MAdminSettingsSectio
placesPhotosEnabled, placesAutocompleteEnabled, placesDetailsEnabled, placesEnrichEnabled,
placesGoogleOnly, handleTogglePlacesGoogleOnly,
placeShadowEnabled,
oidcConfig, setOidcConfig, savingOidc,
passwordLogin, setPasswordLogin, passwordRegistration, setPasswordRegistration,
oidcLogin, setOidcLogin, oidcRegistration, setOidcRegistration,
envOverrideOidcOnly, oidcConfigured, requireMfa,
@@ -388,55 +388,7 @@ export default function MAdminSettingsSection({ admin, t }: MAdminSettingsSectio
)}
{/* OIDC / SSO: the operator's on a managed install, as on the desktop tab. */}
{!managed && (
<MAdminCard>
<MAdminCardHead title={t('admin.oidcTitle')} hint={t('admin.oidcSubtitle')} />
<div className="space-y-3">
<MAdminField label={t('admin.oidcDisplayName')}>
<MAdminInput
type="text"
value={oidcConfig.display_name}
onChange={(e) => setOidcConfig((c) => ({ ...c, display_name: e.target.value }))}
placeholder={t('admin.oidcDisplayNamePlaceholder')}
/>
</MAdminField>
<MAdminField label={t('admin.oidcIssuer')} hint={t('admin.oidcIssuerHint')}>
<MAdminInput
type="url"
value={oidcConfig.issuer}
onChange={(e) => setOidcConfig((c) => ({ ...c, issuer: e.target.value }))}
placeholder="https://accounts.google.com"
/>
</MAdminField>
<MAdminField label={t('admin.oidcDiscoveryUrl')} hint={t('admin.oidcDiscoveryUrlHint', { path: '<issuer>/.well-known/openid-configuration' })}>
<MAdminInput
type="url"
value={oidcConfig.discovery_url}
onChange={(e) => setOidcConfig((c) => ({ ...c, discovery_url: e.target.value }))}
placeholder="https://auth.example.com/application/o/trek/.well-known/openid-configuration"
/>
</MAdminField>
<MAdminField label={t('settings.oauth.clientId')}>
<MAdminInput
type="text"
value={oidcConfig.client_id}
onChange={(e) => setOidcConfig((c) => ({ ...c, client_id: e.target.value }))}
/>
</MAdminField>
<MAdminField label={t('settings.oauth.clientSecret')}>
<MAdminInput
type="password"
value={oidcConfig.client_secret}
onChange={(e) => setOidcConfig((c) => ({ ...c, client_secret: e.target.value }))}
placeholder={oidcConfig.client_secret_set ? '••••••••' : ''}
/>
</MAdminField>
<MAdminButton busy={savingOidc} onClick={saveOidc}>
{t('common.save')}
</MAdminButton>
</div>
</MAdminCard>
)}
{!managed && <MAdminOidcCard admin={admin} t={t} onSave={saveOidc} />}
{/* Danger zone */}
<MAdminCard className="border-[color:color-mix(in_srgb,var(--m-st-danger)_28%,transparent)]">
+1 -1
View File
@@ -117,7 +117,7 @@ export class AddonsService {
/** The photo providers users can connect: none while the journey addon is off. */
async enabledPhotoProviderIds(): Promise<string[]> {
if (!(await this.isAddonEnabled(ADDON_IDS.JOURNEY))) return [];
return (await this.photoProviders.listEnabled()).flatMap((row) => (row.id ? [row.id] : []));
return this.photoProviders.listEnabledIds();
}
/** wanderer may be used: Tours is on and the admin allowed it under Tours. */
@@ -375,6 +375,15 @@ describe('AddonsService.list', () => {
expect(listSpy).not.toHaveBeenCalled();
listSpy.mockRestore();
});
it('names the photo providers users can connect: the enabled ones, none while the journey addon is off', async () => {
await insertProvider({ id: 'immich', name: 'Immich', icon: 'image', enabled: 1 });
await insertProvider({ id: 'off-provider', name: 'Off', icon: 'image', enabled: 0 });
expect(await svc.enabledPhotoProviderIds()).toEqual([]);
stubJourneyEnabled();
expect(await svc.enabledPhotoProviderIds()).toEqual(['immich']);
});
});
// Direct coverage of the enablement reads/writers relocated from