feat(settings): server section for self-hosted storage

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-12 00:16:55 +02:00
1 parent cec1ad83a2
commit 07b942a832
14 files changed
+553 -5

No files matched your search

@@ -2,6 +2,7 @@ import { PLAYBACK_SETTINGS_SEARCH_ENTRIES } from './settings-search-entries-play
import { SettingsSearchEntry } from './settings-search.types';
const DASHBOARD_KEYWORDS = ['dashboard', 'home', 'rail', 'row'];
const SERVER_KEYWORDS = ['server', 'self-hosted', 'nas', 'sync', 'docker'];
function dashboardRail(
id: string,
@@ -244,6 +245,39 @@ export const SETTINGS_SEARCH_ENTRIES: readonly SettingsSearchEntry[] = [
keywords: ['parental', 'lock categories', 'groups', 'right-click'],
fallbackId: 'parental-lock-enable',
},
{
id: 'server-connection',
section: 'server',
labelKey: 'SETTINGS.SERVER_CONNECTION',
descriptionKey: 'SETTINGS.SERVER_CONNECTION_DESCRIPTION',
keywords: SERVER_KEYWORDS,
requires: ['server-storage'],
},
{
id: 'server-storage',
section: 'server',
labelKey: 'SETTINGS.SERVER_STORAGE',
descriptionKey: 'SETTINGS.SERVER_STORAGE_DESCRIPTION',
keywords: [...SERVER_KEYWORDS, 'database', 'backup', 'size'],
requires: ['server-storage'],
},
{
id: 'server-import-browser',
section: 'server',
labelKey: 'SETTINGS.SERVER_IMPORT_BROWSER',
descriptionKey: 'SETTINGS.SERVER_IMPORT_BROWSER_DESCRIPTION',
keywords: [...SERVER_KEYWORDS, 'import', 'migrate', 'browser'],
requires: ['server-storage'],
fallbackId: 'server-storage',
},
{
id: 'server-access',
section: 'server',
labelKey: 'SETTINGS.SERVER_ACCESS',
descriptionKey: 'SETTINGS.SERVER_ACCESS_DESCRIPTION',
keywords: [...SERVER_KEYWORDS, 'token', 'sign out', 'password'],
requires: ['server-storage'],
},
{
id: 'import-export-data',
section: 'backup',
@@ -65,6 +65,13 @@ export const SETTINGS_SECTION_DEFINITIONS: readonly SettingsSectionDefinition[]
requires: ['remote-control'],
group: 'devices',
},
{
id: 'server',
navLabelKey: 'SETTINGS.NAV_SERVER',
icon: 'dns',
requires: ['server-storage'],
group: 'data',
},
{
id: 'backup',
navLabelKey: 'SETTINGS.NAV_BACKUP',
@@ -2,7 +2,10 @@ import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { VodSourceDiscoveryService } from '@iptvnator/portal/shared/data-access';
import { RuntimeCapabilitiesService } from '@iptvnator/services';
import {
RuntimeCapabilitiesService,
ServerStorageStatusService,
} from '@iptvnator/services';
import { EMBEDDED_MPV_SUPPORT_RECHECK_MS } from '@iptvnator/shared/interfaces';
import { SETTINGS_SEARCH_ENTRIES } from './settings-search-entries';
import { SETTINGS_SECTION_DEFINITIONS } from './settings-search-sections';
@@ -31,10 +34,14 @@ interface RuntimeStub {
supportsEmbeddedMpv: boolean;
}
function setup(runtime: Partial<RuntimeStub> = {}) {
function setup(runtime: Partial<RuntimeStub> = {}, serverStorage = false) {
const router = { navigate: jest.fn().mockResolvedValue(true) };
TestBed.configureTestingModule({
providers: [
{
provide: ServerStorageStatusService,
useValue: { isActive: () => serverStorage },
},
{
provide: RuntimeCapabilitiesService,
useValue: {
@@ -131,7 +138,25 @@ describe('SettingsSearchService', () => {
// Embedded MPV rows wait for the support probe.
expect(entries).not.toContain('embedded-mpv-extra-options');
expect(entries).not.toContain('embedded-mpv-frame-copy');
expect(entries.length).toBe(SETTINGS_SEARCH_ENTRIES.length - 4);
// Server rows need the self-hosted PWA's server storage.
expect(entries).not.toContain('server-connection');
expect(entries.length).toBe(SETTINGS_SEARCH_ENTRIES.length - 8);
});
it('offers the server section and rows with server storage', () => {
const { service } = setup({}, true);
expect(service.visibleSections().map(({ id }) => id)).toContain(
'server'
);
expect(service.visibleEntries().map(({ id }) => id)).toEqual(
expect.arrayContaining([
'server-connection',
'server-storage',
'server-import-browser',
'server-access',
])
);
});
describe('embedded MPV probe', () => {
@@ -2,7 +2,10 @@ import { inject, Injectable, signal } from '@angular/core';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { VodSourceDiscoveryService } from '@iptvnator/portal/shared/data-access';
import { RuntimeCapabilitiesService } from '@iptvnator/services';
import {
RuntimeCapabilitiesService,
ServerStorageStatusService,
} from '@iptvnator/services';
import {
EmbeddedMpvSupport,
watchEmbeddedMpvSupport,
@@ -30,6 +33,7 @@ import {
@Injectable({ providedIn: 'root' })
export class SettingsSearchService {
private readonly runtime = inject(RuntimeCapabilitiesService);
private readonly serverStorageStatus = inject(ServerStorageStatusService);
private readonly vodSourceDiscovery = inject(VodSourceDiscoveryService);
private readonly router = inject(Router);
private readonly translate = inject(TranslateService);
@@ -61,6 +65,7 @@ export class SettingsSearchService {
'embedded-mpv': !!this.embeddedMpvSupport()?.supported,
'embedded-mpv-frame-copy':
!!this.embeddedMpvSupport()?.frameCopyAvailable,
'server-storage': this.serverStorageStatus.isActive(),
};
}
@@ -14,7 +14,9 @@ export type SettingsSearchRequirement =
| 'vod-multi-source'
/** Probed lazily; false until `ensureEmbeddedMpvSupportLoaded` resolves. */
| 'embedded-mpv'
| 'embedded-mpv-frame-copy';
| 'embedded-mpv-frame-copy'
/** The PWA runs against self-hosted server storage. */
| 'server-storage';
export type SettingsSearchCapabilities = Readonly<
Record<SettingsSearchRequirement, boolean>