feat(settings): label provider order with account modes
This commit is contained in:
+21
-10
@@ -975,16 +975,27 @@ const KNOWN_HOSTERS: { id: string; label: string }[] = [
|
|||||||
{ id: "isra", label: "Isra.cloud" }
|
{ id: "isra", label: "Isra.cloud" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function buildProviderOrderEntry(provider: DebridProvider, settings: RendererSettings): {
|
||||||
|
id: DebridProvider;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
} {
|
||||||
|
const legacyMegaService = settings.megaDebridPreferApi ? "megadebrid-api" : "megadebrid-web";
|
||||||
|
const service = provider === "megadebrid" ? legacyMegaService : provider as AccountService;
|
||||||
|
const kind = getConfiguredAccountKind(settings, service);
|
||||||
|
const option = kind ? ACCOUNT_OPTIONS.find((entry) => entry.kind === kind) : null;
|
||||||
|
const modeLabel = option?.modeLabel || (provider === "megadebrid" ? (settings.megaDebridPreferApi ? "API" : "Web-Login") : "");
|
||||||
|
const serviceLabel = option?.serviceLabel || providerLabels[provider];
|
||||||
|
return {
|
||||||
|
id: provider,
|
||||||
|
label: modeLabel ? `${serviceLabel} (${modeLabel})` : serviceLabel,
|
||||||
|
icon: ACCOUNT_SERVICE_ICONS[option?.service || service]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function providerLabelWithMode(provider: DebridProvider, settings: RendererSettings): string {
|
function providerLabelWithMode(provider: DebridProvider, settings: RendererSettings): string {
|
||||||
const base = providerLabels[provider];
|
return buildProviderOrderEntry(provider, settings).label;
|
||||||
if (provider === "megadebrid" || provider === "megadebrid-api" || provider === "megadebrid-web") {
|
}
|
||||||
return base;
|
|
||||||
}
|
|
||||||
const kind = getConfiguredAccountKind(settings, provider as AccountService);
|
|
||||||
if (!kind) return base;
|
|
||||||
const opt = ACCOUNT_OPTIONS.find((o) => o.kind === kind);
|
|
||||||
return opt?.modeLabel ? `${base} (${opt.modeLabel})` : base;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatAllDebridSourceLabel(source: AllDebridHostInfo["source"]): string {
|
function formatAllDebridSourceLabel(source: AllDebridHostInfo["source"]): string {
|
||||||
return source === "web" ? "Web-Login" : "API-Key";
|
return source === "web" ? "Web-Login" : "API-Key";
|
||||||
@@ -5586,7 +5597,7 @@ export function App(): ReactElement {
|
|||||||
statusSort: accountStatusSort,
|
statusSort: accountStatusSort,
|
||||||
runtime: accountRuntimeModel,
|
runtime: accountRuntimeModel,
|
||||||
rules: {
|
rules: {
|
||||||
providerOrder: activeProviderOrder.map((provider) => providerLabelWithMode(provider, settingsDraft)),
|
providerOrder: activeProviderOrder.map((provider) => buildProviderOrderEntry(provider, settingsDraft)),
|
||||||
routing: routingEntries.map(([hosterId, provider]) => `${KNOWN_HOSTERS.find((hoster) => hoster.id === hosterId)?.label || hosterId} → ${providerLabelWithMode(provider, settingsDraft)}`),
|
routing: routingEntries.map(([hosterId, provider]) => `${KNOWN_HOSTERS.find((hoster) => hoster.id === hosterId)?.label || hosterId} → ${providerLabelWithMode(provider, settingsDraft)}`),
|
||||||
autoFallback: settingsDraft.autoProviderFallback,
|
autoFallback: settingsDraft.autoProviderFallback,
|
||||||
rememberCredentials: settingsDraft.rememberToken,
|
rememberCredentials: settingsDraft.rememberToken,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ function getAccountPanelNavigationIndex(currentIndex: number, key: string): numb
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AccountRulesViewModel {
|
export interface AccountRulesViewModel {
|
||||||
providerOrder: readonly string[];
|
providerOrder: readonly { id: string; label: string; icon: string }[];
|
||||||
routing: readonly string[];
|
routing: readonly string[];
|
||||||
autoFallback: boolean;
|
autoFallback: boolean;
|
||||||
rememberCredentials?: boolean;
|
rememberCredentials?: boolean;
|
||||||
@@ -505,21 +505,24 @@ function AccountRules({ model, actions }: AccountWorkspaceProps): ReactElement {
|
|||||||
<span className="settings-rule-empty">Keine Provider konfiguriert.</span>
|
<span className="settings-rule-empty">Keine Provider konfiguriert.</span>
|
||||||
) : (
|
) : (
|
||||||
<ol className="settings-provider-order">
|
<ol className="settings-provider-order">
|
||||||
{model.rules.providerOrder.map((provider, index) => (
|
{model.rules.providerOrder.map((provider, index) => (
|
||||||
<li
|
<li
|
||||||
draggable={Boolean(actions.onProviderDragStart)}
|
draggable={Boolean(actions.onProviderDragStart)}
|
||||||
key={`${provider}-${index}`}
|
key={provider.id}
|
||||||
onDragEnd={actions.onProviderDragEnd}
|
onDragEnd={actions.onProviderDragEnd}
|
||||||
onDragOver={(event) => actions.onProviderDragOver?.(event, index)}
|
onDragOver={(event) => actions.onProviderDragOver?.(event, index)}
|
||||||
onDragStart={(event) => actions.onProviderDragStart?.(event, index)}
|
onDragStart={(event) => actions.onProviderDragStart?.(event, index)}
|
||||||
onDrop={(event) => actions.onProviderDrop?.(event, index)}
|
onDrop={(event) => actions.onProviderDrop?.(event, index)}
|
||||||
>
|
>
|
||||||
<span>{provider}</span>
|
<span className="settings-provider-order-provider">
|
||||||
{actions.onMoveProvider ? (
|
<img alt="" aria-hidden="true" draggable={false} height="20" src={provider.icon} width="20" />
|
||||||
<span className="settings-provider-order-actions">
|
<span>{provider.label}</span>
|
||||||
<button aria-label={`${provider} nach oben`} disabled={index === 0} onClick={() => actions.onMoveProvider?.(index, -1)} type="button">↑</button>
|
</span>
|
||||||
<button aria-label={`${provider} nach unten`} disabled={index === model.rules.providerOrder.length - 1} onClick={() => actions.onMoveProvider?.(index, 1)} type="button">↓</button>
|
{actions.onMoveProvider ? (
|
||||||
</span>
|
<span className="settings-provider-order-actions">
|
||||||
|
<button aria-label={`${provider.label} nach oben`} disabled={index === 0} onClick={() => actions.onMoveProvider?.(index, -1)} type="button">↑</button>
|
||||||
|
<button aria-label={`${provider.label} nach unten`} disabled={index === model.rules.providerOrder.length - 1} onClick={() => actions.onMoveProvider?.(index, 1)} type="button">↓</button>
|
||||||
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1067,6 +1067,19 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-provider-order-provider {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 9px;
|
||||||
|
color: var(--ui-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-provider-order-provider img {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-provider-order-actions button {
|
.settings-provider-order-actions button {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { renderToStaticMarkup } from "react-dom/server";
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { defaultSettings } from "../src/main/constants";
|
import { defaultSettings } from "../src/main/constants";
|
||||||
import { createRendererSettings, createRendererState } from "../src/main/renderer-state";
|
import { createRendererSettings, createRendererState } from "../src/main/renderer-state";
|
||||||
import { buildAccountAddFields, buildAccountCreateProviderOrderUpdate, createAccountDialogState, createDiscardedSettingsState, createSettingsDraft, resolveSettingsSaveCompletion } from "../src/renderer/App";
|
import { buildAccountAddFields, buildAccountCreateProviderOrderUpdate, buildProviderOrderEntry, createAccountDialogState, createDiscardedSettingsState, createSettingsDraft, resolveSettingsSaveCompletion } from "../src/renderer/App";
|
||||||
import { buildAccountReplaceCommand, createAccountEditState, type AccountEditTarget } from "../src/renderer/account-edit";
|
import { buildAccountReplaceCommand, createAccountEditState, type AccountEditTarget } from "../src/renderer/account-edit";
|
||||||
import {
|
import {
|
||||||
buildScopedAccountEnabledState,
|
buildScopedAccountEnabledState,
|
||||||
@@ -292,7 +292,10 @@ function workspaceModel(): AccountWorkspaceViewModel {
|
|||||||
selectedIds: [buildAccountRowId("megadebrid-api", "API", "mega-premium")],
|
selectedIds: [buildAccountRowId("megadebrid-api", "API", "mega-premium")],
|
||||||
busy: false,
|
busy: false,
|
||||||
rules: {
|
rules: {
|
||||||
providerOrder: ["Debrid-Link", "Real-Debrid"],
|
providerOrder: [
|
||||||
|
{ id: "debridlink", label: "Debrid-Link (API)", icon: "./provider-icons/debrid-link.ico" },
|
||||||
|
{ id: "realdebrid", label: "Real-Debrid (Web-Login)", icon: "./provider-icons/real-debrid.png" }
|
||||||
|
],
|
||||||
routing: ["rapidgator.net → Debrid-Link"],
|
routing: ["rapidgator.net → Debrid-Link"],
|
||||||
autoFallback: true
|
autoFallback: true
|
||||||
},
|
},
|
||||||
@@ -1146,6 +1149,10 @@ describe("account workspace", () => {
|
|||||||
expect(html).toContain("Hoster-Routing");
|
expect(html).toContain("Hoster-Routing");
|
||||||
expect(html).toContain("Automatischer Fallback");
|
expect(html).toContain("Automatischer Fallback");
|
||||||
expect(html).toContain("Provider-Laufzeit");
|
expect(html).toContain("Provider-Laufzeit");
|
||||||
|
expect(html).toContain("Debrid-Link (API)");
|
||||||
|
expect(html).toContain("Real-Debrid (Web-Login)");
|
||||||
|
expect(html).toContain("./provider-icons/debrid-link.ico");
|
||||||
|
expect(html).toContain("./provider-icons/real-debrid.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps add and edit dialogs separate and every secret field protected", () => {
|
it("keeps add and edit dialogs separate and every secret field protected", () => {
|
||||||
@@ -1347,6 +1354,27 @@ describe("account workspace", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("settings App integration", () => {
|
describe("settings App integration", () => {
|
||||||
|
it("projects provider order entries with logos and explicit login modes", () => {
|
||||||
|
const settings = createRendererSettings({
|
||||||
|
...defaultSettings(),
|
||||||
|
megaDebridWebEnabled: true,
|
||||||
|
megaDebridWebCredentials: "member:password",
|
||||||
|
deepbridApiKey: "test-key",
|
||||||
|
providerOrder: ["megadebrid-web", "deepbrid"]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(buildProviderOrderEntry("megadebrid-web", settings)).toEqual({
|
||||||
|
id: "megadebrid-web",
|
||||||
|
label: "Mega-Debrid (Web-Login)",
|
||||||
|
icon: "./provider-icons/mega-debrid.png"
|
||||||
|
});
|
||||||
|
expect(buildProviderOrderEntry("deepbrid", settings)).toEqual({
|
||||||
|
id: "deepbrid",
|
||||||
|
label: "Deepbrid (API)",
|
||||||
|
icon: "./provider-icons/deepbrid.png"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("preserves the write-only webhook during live snapshots and clears it for backup reseeding", () => {
|
it("preserves the write-only webhook during live snapshots and clears it for backup reseeding", () => {
|
||||||
const safe = createRendererSettings({ ...defaultSettings(), notifyUrl: "https://private.example.test/hook" });
|
const safe = createRendererSettings({ ...defaultSettings(), notifyUrl: "https://private.example.test/hook" });
|
||||||
const current = { ...safe, archivePasswordList: "loaded-password", notifyUrl: "https://private.example.test/hook" };
|
const current = { ...safe, archivePasswordList: "loaded-password", notifyUrl: "https://private.example.test/hook" };
|
||||||
|
|||||||
Reference in New Issue
Block a user