Revert to v1.5.49 base + fix "Ausgewählte Downloads starten"
- Restore all source files from v1.5.49 (proven stable on both servers) - Add startPackages() IPC method that starts only specified packages - Fix context menu "Ausgewählte Downloads starten" to use startPackages() instead of start() which was starting ALL enabled packages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ca4392fa8b
commit
2ef3983049
+14
-134
@@ -10,7 +10,6 @@ import type {
|
||||
DuplicatePolicy,
|
||||
HistoryEntry,
|
||||
PackageEntry,
|
||||
ProviderAccountInfo,
|
||||
StartConflictEntry,
|
||||
UiSnapshot,
|
||||
UpdateCheckResult,
|
||||
@@ -93,15 +92,6 @@ const providerLabels: Record<DebridProvider, string> = {
|
||||
realdebrid: "Real-Debrid", megadebrid: "Mega-Debrid", bestdebrid: "BestDebrid", alldebrid: "AllDebrid"
|
||||
};
|
||||
|
||||
const allProviders: DebridProvider[] = ["realdebrid", "megadebrid", "bestdebrid", "alldebrid"];
|
||||
|
||||
const providerCredentialFields: Record<DebridProvider, { key: keyof AppSettings; label: string; type: string }[]> = {
|
||||
realdebrid: [{ key: "token", label: "API Token", type: "password" }],
|
||||
megadebrid: [{ key: "megaLogin", label: "Login", type: "text" }, { key: "megaPassword", label: "Passwort", type: "password" }],
|
||||
bestdebrid: [{ key: "bestToken", label: "API Token", type: "password" }],
|
||||
alldebrid: [{ key: "allDebridToken", label: "API Key", type: "password" }]
|
||||
};
|
||||
|
||||
function extractHoster(url: string): string {
|
||||
try {
|
||||
const host = new URL(url).hostname.replace(/^www\./, "");
|
||||
@@ -452,11 +442,6 @@ export function App(): ReactElement {
|
||||
const latestStateRef = useRef<UiSnapshot | null>(null);
|
||||
const stateFlushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const toastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [accountInfoMap, setAccountInfoMap] = useState<Record<string, ProviderAccountInfo>>({});
|
||||
const [accountCheckLoading, setAccountCheckLoading] = useState<Set<DebridProvider>>(new Set());
|
||||
const [showAddAccountModal, setShowAddAccountModal] = useState(false);
|
||||
const [addAccountProvider, setAddAccountProvider] = useState<DebridProvider | "">("");
|
||||
const [addAccountFields, setAddAccountFields] = useState<Record<string, string>>({});
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
const [editingPackageId, setEditingPackageId] = useState<string | null>(null);
|
||||
const [editingName, setEditingName] = useState("");
|
||||
@@ -844,8 +829,6 @@ export function App(): ReactElement {
|
||||
return list;
|
||||
}, [settingsDraft.token, settingsDraft.megaLogin, settingsDraft.megaPassword, settingsDraft.bestToken, settingsDraft.allDebridToken]);
|
||||
|
||||
const unconfiguredProviders = useMemo(() => allProviders.filter((p) => !configuredProviders.includes(p)), [configuredProviders]);
|
||||
|
||||
const primaryProviderValue: DebridProvider = useMemo(() => {
|
||||
if (configuredProviders.includes(settingsDraft.providerPrimary)) {
|
||||
return settingsDraft.providerPrimary;
|
||||
@@ -1240,42 +1223,6 @@ export function App(): ReactElement {
|
||||
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: Math.floor(mbps * 1024) }));
|
||||
};
|
||||
|
||||
const checkSingleAccount = (provider: DebridProvider): void => {
|
||||
setAccountCheckLoading((prev) => new Set(prev).add(provider));
|
||||
window.rd.checkAccount(provider).then((info) => {
|
||||
setAccountInfoMap((prev) => ({ ...prev, [provider]: info }));
|
||||
}).catch(() => {
|
||||
setAccountInfoMap((prev) => ({ ...prev, [provider]: { provider, username: "", accountType: "", daysRemaining: null, loyaltyPoints: null, error: "Verbindungsfehler" } }));
|
||||
}).finally(() => {
|
||||
setAccountCheckLoading((prev) => { const next = new Set(prev); next.delete(provider); return next; });
|
||||
});
|
||||
};
|
||||
|
||||
const checkAllAccounts = (): void => {
|
||||
for (const provider of configuredProviders) {
|
||||
checkSingleAccount(provider);
|
||||
}
|
||||
};
|
||||
|
||||
const removeAccount = (provider: DebridProvider): void => {
|
||||
for (const field of providerCredentialFields[provider]) {
|
||||
setText(field.key, "");
|
||||
}
|
||||
setAccountInfoMap((prev) => { const next = { ...prev }; delete next[provider]; return next; });
|
||||
};
|
||||
|
||||
const saveAddAccountModal = (): void => {
|
||||
if (!addAccountProvider) {
|
||||
return;
|
||||
}
|
||||
for (const field of providerCredentialFields[addAccountProvider]) {
|
||||
setText(field.key, addAccountFields[field.key] || "");
|
||||
}
|
||||
setShowAddAccountModal(false);
|
||||
setAddAccountProvider("");
|
||||
setAddAccountFields({});
|
||||
};
|
||||
|
||||
const performQuickAction = async (
|
||||
action: () => Promise<unknown>,
|
||||
onError?: (error: unknown) => void
|
||||
@@ -2437,62 +2384,19 @@ export function App(): ReactElement {
|
||||
{settingsSubTab === "accounts" && (
|
||||
<div className="settings-section card">
|
||||
<h3>Accounts</h3>
|
||||
{configuredProviders.length === 0 ? (
|
||||
<div className="empty">Keine Accounts konfiguriert</div>
|
||||
) : (
|
||||
<table className="account-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hoster</th>
|
||||
<th>Status</th>
|
||||
<th>Benutzername</th>
|
||||
<th>Verfallsdatum</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{configuredProviders.map((provider) => {
|
||||
const info = accountInfoMap[provider];
|
||||
const loading = accountCheckLoading.has(provider);
|
||||
let statusClass = "account-status-unknown";
|
||||
let statusText = "Nicht geprüft";
|
||||
if (loading) {
|
||||
statusClass = "account-status-loading";
|
||||
statusText = "Prüfe…";
|
||||
} else if (info?.error) {
|
||||
statusClass = "account-status-error";
|
||||
statusText = "Fehler";
|
||||
} else if (info && (info.accountType === "Premium" || info.accountType === "premium")) {
|
||||
statusClass = "account-status-ok";
|
||||
statusText = "Premium";
|
||||
} else if (info && info.accountType) {
|
||||
statusClass = "account-status-configured";
|
||||
statusText = info.accountType;
|
||||
}
|
||||
return (
|
||||
<tr key={provider}>
|
||||
<td className="account-col-hoster">{providerLabels[provider]}</td>
|
||||
<td><span className={`account-status ${statusClass}`}>{statusText}</span>{info?.error ? <span style={{ marginLeft: 6, fontSize: 12, color: "var(--danger)" }}>{info.error}</span> : null}</td>
|
||||
<td>{info?.username || "—"}</td>
|
||||
<td>{info?.daysRemaining !== null && info?.daysRemaining !== undefined ? `${info.daysRemaining} Tage` : "—"}</td>
|
||||
<td className="account-actions-cell">
|
||||
<button className="btn" disabled={loading} onClick={() => checkSingleAccount(provider)}>Prüfen</button>
|
||||
<button className="btn danger" onClick={() => removeAccount(provider)}>Entfernen</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<label>Real-Debrid API Token</label>
|
||||
<input type="password" value={settingsDraft.token} onChange={(e) => setText("token", e.target.value)} />
|
||||
<label>Mega-Debrid Login</label>
|
||||
<input value={settingsDraft.megaLogin} onChange={(e) => setText("megaLogin", e.target.value)} />
|
||||
<label>Mega-Debrid Passwort</label>
|
||||
<input type="password" value={settingsDraft.megaPassword} onChange={(e) => setText("megaPassword", e.target.value)} />
|
||||
<label>BestDebrid API Token</label>
|
||||
<input type="password" value={settingsDraft.bestToken} onChange={(e) => setText("bestToken", e.target.value)} />
|
||||
<label>AllDebrid API Key</label>
|
||||
<input type="password" value={settingsDraft.allDebridToken} onChange={(e) => setText("allDebridToken", e.target.value)} />
|
||||
{configuredProviders.length === 0 && (
|
||||
<div className="hint">Füge mindestens einen Account hinzu, dann erscheint die Hoster-Auswahl.</div>
|
||||
)}
|
||||
<div className="account-toolbar">
|
||||
<button className="btn accent" disabled={unconfiguredProviders.length === 0} onClick={() => {
|
||||
setAddAccountProvider(unconfiguredProviders[0] || "");
|
||||
setAddAccountFields({});
|
||||
setShowAddAccountModal(true);
|
||||
}}>Hinzufügen</button>
|
||||
<button className="btn" disabled={configuredProviders.length === 0 || accountCheckLoading.size > 0} onClick={checkAllAccounts}>Alle aktualisieren</button>
|
||||
</div>
|
||||
{configuredProviders.length >= 1 && (
|
||||
<div><label>Hauptaccount</label><select value={primaryProviderValue} onChange={(e) => setText("providerPrimary", e.target.value)}>
|
||||
{configuredProviders.map((provider) => (<option key={provider} value={provider}>{providerLabels[provider]}</option>))}
|
||||
@@ -2615,30 +2519,6 @@ export function App(): ReactElement {
|
||||
)}
|
||||
</main>
|
||||
|
||||
{showAddAccountModal && (
|
||||
<div className="modal-backdrop" onClick={() => setShowAddAccountModal(false)}>
|
||||
<div className="modal-card" onClick={(event) => event.stopPropagation()}>
|
||||
<h3>Account hinzufügen</h3>
|
||||
<div>
|
||||
<label>Provider</label>
|
||||
<select value={addAccountProvider} onChange={(e) => { setAddAccountProvider(e.target.value as DebridProvider); setAddAccountFields({}); }}>
|
||||
{unconfiguredProviders.map((p) => (<option key={p} value={p}>{providerLabels[p]}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
{addAccountProvider && providerCredentialFields[addAccountProvider].map((field) => (
|
||||
<div key={field.key}>
|
||||
<label>{field.label}</label>
|
||||
<input type={field.type} value={addAccountFields[field.key] || ""} onChange={(e) => setAddAccountFields((prev) => ({ ...prev, [field.key]: e.target.value }))} />
|
||||
</div>
|
||||
))}
|
||||
<div className="modal-actions">
|
||||
<button className="btn" onClick={() => setShowAddAccountModal(false)}>Abbrechen</button>
|
||||
<button className="btn accent" disabled={!addAccountProvider || providerCredentialFields[addAccountProvider as DebridProvider]?.some((f) => !(addAccountFields[f.key] || "").trim())} onClick={saveAddAccountModal}>Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{confirmPrompt && (
|
||||
<div className="modal-backdrop" onClick={() => closeConfirmPrompt(false)}>
|
||||
<div className="modal-card" onClick={(event) => event.stopPropagation()}>
|
||||
@@ -2764,8 +2644,8 @@ export function App(): ReactElement {
|
||||
<div className="ctx-menu" style={{ left: contextMenu.x, top: contextMenu.y }} onClick={(e) => e.stopPropagation()}>
|
||||
{(!contextMenu.itemId || multi) && hasPackages && (
|
||||
<button className="ctx-menu-item" onClick={() => {
|
||||
for (const id of selectedIds) { const pkg = snapshot.session.packages[id]; if (pkg && !pkg.enabled) { void window.rd.togglePackage(id); } }
|
||||
void window.rd.start(); setContextMenu(null);
|
||||
const pkgIds = [...selectedIds].filter((id) => snapshot.session.packages[id]);
|
||||
void window.rd.startPackages(pkgIds); setContextMenu(null);
|
||||
}}>Ausgewählte Downloads starten{multi ? ` (${selectedIds.size})` : ""}</button>
|
||||
)}
|
||||
<button className="ctx-menu-item" onClick={() => { void window.rd.start(); setContextMenu(null); }}>Alle Downloads starten</button>
|
||||
|
||||
Reference in New Issue
Block a user