Full port: v1 renderer shim + folder monitor + remote server + updater + upload log fallback
Major additions:
Frontend — v1 renderer/app.js and renderer/index.html copied 1:1. A
new tauri-shim.js reconstructs window.api with all 54 methods v1 used,
each mapped to a matching Rust #[tauri::command] so the existing
renderer works unchanged. Drag-drop paths are bridged via the Tauri
tauri://drag-drop event.
Backend modules:
- folder_monitor.rs: notify-debouncer-full based recursive/
non-recursive watch, include/exclude extension filter, skip-dupes,
initial baseline scan, emits 'folder-monitor-new-files' to frontend.
Auto-restarts on launch if persisted settings have enabled=true.
- remote_server.rs: axum HTTP server with bearer-token auth exposing
/api/status, /api/control/cancel, /api/control/finish-after.
Auto-restarts on launch if enabled + token set.
- updater.rs: Gitea releases polling + semver compare, returns
download URL for current platform. install_update opens the URL
externally (true in-app update needs signing cert later).
- upload_log.rs: full fallback ladder (primary → Desktop → AppData),
daily-log suffix handling, auto-persists working fallback path into
globalSettings.logFilePath so next session writes there directly,
emits 'upload-log-fallback' to the renderer once per session.
Commands added (all wired into tauri::generate_handler!):
get_hoster_settings, save_hoster_settings, get_global_settings,
save_global_settings, set_always_on_top, get_always_on_top,
set_shutdown_after_finish, get_shutdown_after_finish, cancel_shutdown,
resolve_folder_files, copy_to_clipboard (Windows clipboard via
PowerShell pipe), start_upload, add_jobs_to_batch,
finish_after_active, run_health_check, export_backup, import_backup,
import_backup_saved (legacy-password path), read_own_upload_log,
import_upload_log, save_text_file, open_log_folder,
read_rotation_log, get_version, check_for_update, install_update,
folder_monitor_start/stop/status, remote_get_settings,
remote_save_settings, remote_generate_token, remote_status,
show_drop_target, hide_drop_target, debug_log.
Release build: exe 7.5 MB, NSIS 2.7 MB, MSI 3.7 MB.
This commit is contained in:
+4016
-423
File diff suppressed because it is too large
Load Diff
+299
-102
@@ -1,122 +1,319 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Multi-Hoster-Upload 2.0</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' ipc: http://ipc.localhost; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self' ipc: http://ipc.localhost; img-src 'self' data: blob:;">
|
||||
<title>Multi-Hoster-Upload</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="app-header">
|
||||
<div class="app-title">Multi-Hoster-Upload <span class="ver">v2.0</span></div>
|
||||
<nav class="tabs">
|
||||
<button class="tab active" data-view="upload">Upload</button>
|
||||
<button class="tab" data-view="accounts">Accounts</button>
|
||||
<button class="tab" data-view="settings">Einstellungen</button>
|
||||
<button class="tab" data-view="log">Rotation-Log</button>
|
||||
</nav>
|
||||
</header>
|
||||
<nav class="tab-bar">
|
||||
<button class="tab active" data-view="upload">Upload</button>
|
||||
<button class="tab" data-view="accounts">Accounts</button>
|
||||
<button class="tab" data-view="settings">Einstellungen</button>
|
||||
<button class="tab" data-view="history">Verlauf</button>
|
||||
<span class="version-label" id="versionLabel"></span>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<section class="view active" id="upload-view">
|
||||
<div id="updateBanner" class="update-banner" style="display:none">
|
||||
<span id="updateMessage"></span>
|
||||
<button class="btn btn-sm btn-primary" id="installUpdateBtn">Update installieren</button>
|
||||
<button class="btn btn-sm btn-secondary" id="dismissUpdateBtn">×</button>
|
||||
</div>
|
||||
|
||||
<div id="upload-view" class="view active">
|
||||
<div class="upload-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="hoster-summary" id="hosterSummary" style="display:none"></span>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<button class="btn btn-xs btn-primary" id="addFilesBtn">+ Dateien</button>
|
||||
<button class="btn btn-xs btn-secondary" id="addFolderBtn">+ Ordner</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="upload-workspace">
|
||||
<div class="drop-zone" id="dropZone">
|
||||
<div class="drop-hint">Dateien hierher ziehen oder Button klicken</div>
|
||||
<button class="btn btn-primary" id="pickFilesBtn">+ Dateien wählen</button>
|
||||
<div class="drop-icon">📁</div>
|
||||
<p>Dateien hierher ziehen oder klicken</p>
|
||||
</div>
|
||||
|
||||
<div class="hoster-picker">
|
||||
<label>Upload zu:</label>
|
||||
<div id="hosterCheckboxes"></div>
|
||||
</div>
|
||||
|
||||
<div class="queue-shell">
|
||||
<div class="queue-toolbar">
|
||||
<button class="btn btn-primary" id="startBtn" disabled>▶ Upload starten</button>
|
||||
<button class="btn" id="cancelBtn" disabled>✕ Abbrechen</button>
|
||||
<span class="queue-stats" id="queueStats">—</span>
|
||||
<div class="queue-shell" id="queueShell" style="display:none">
|
||||
<div class="queue-command-bar" id="queueCommandBar">
|
||||
<button class="toolbar-btn" id="startUploadBtn" title="Start all" disabled>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M4 2l10 6-10 6z" fill="#4caf50"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" id="startSelectedBtn" title="Start selected" disabled>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M6 3l8 5-8 5z" fill="#4caf50"/><rect x="1" y="3" width="3" height="10" rx="0.5" fill="#4caf50"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" id="reuploadSelectedBtn" title="Reupload selected file">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M8 1a7 7 0 0 0-5 2.1V1H2v4h4V4H3.7A5.5 5.5 0 1 1 2.5 8H1a7 7 0 1 0 7-7z" fill="#4caf50"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" id="abortSelectedBtn" title="Abort selected file">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><rect x="3" y="3" width="10" height="10" rx="1" fill="#e53935"/><path d="M5.5 5.5l5 5M10.5 5.5l-5 5" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" id="finishStopBtn" title="Finish Uploads in Progress and Stop">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M2 8l4 4 8-8" stroke="#4caf50" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="11" y="9" width="5" height="5" rx="1" fill="#e53935"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn toolbar-btn-danger" id="abortAllBtn" title="Abort all Downloads">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><rect x="1" y="1" width="14" height="14" rx="2" fill="#e53935"/><path d="M4.5 4.5l7 7M11.5 4.5l-7 7" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<span class="toolbar-sep"></span>
|
||||
<button class="toolbar-btn" id="moveTopBtn" title="Move to the top">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><rect x="4" y="1" width="8" height="2" rx="0.5" fill="#4caf50"/><path d="M8 5l-4 5h8z" fill="#4caf50"/><path d="M8 9l-4 5h8z" fill="#4caf50"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" id="moveUpBtn" title="Move up">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M8 2l-5 6h10z" fill="#4caf50"/><rect x="6" y="8" width="4" height="6" rx="0.5" fill="#4caf50"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" id="moveDownBtn" title="Move down">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><rect x="6" y="2" width="4" height="6" rx="0.5" fill="#4caf50"/><path d="M8 14l-5-6h10z" fill="#4caf50"/></svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" id="moveBottomBtn" title="Move to the bottom">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M8 7l-4-5h8z" fill="#4caf50"/><path d="M8 11l-4-5h8z" fill="#4caf50"/><rect x="4" y="13" width="8" height="2" rx="0.5" fill="#4caf50"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<table class="queue-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datei</th>
|
||||
<th>Hoster</th>
|
||||
<th>Status</th>
|
||||
<th>Progress</th>
|
||||
<th>Speed</th>
|
||||
<th>Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="queueBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="view" id="accounts-view">
|
||||
<div class="actions-bar">
|
||||
<button class="btn btn-primary" id="addAccountBtn">+ Account hinzufügen</button>
|
||||
</div>
|
||||
<div id="accountsList" class="accounts-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="view" id="settings-view">
|
||||
<div class="settings">
|
||||
<h2>Globale Einstellungen</h2>
|
||||
<div class="setting-row">
|
||||
<label>Gesamt-Upload-Limit (KB/s, 0 = unlimitiert)</label>
|
||||
<input type="number" id="globalSpeed" min="0" value="0" />
|
||||
<div class="queue-container" id="queueContainer">
|
||||
<table class="queue-table" id="queueTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-filename sortable" data-col="filename" data-sort="filename">Filename<span class="col-resizer"></span></th>
|
||||
<th class="col-size sortable" data-col="size" data-sort="size">Uploaded / Size<span class="col-resizer"></span></th>
|
||||
<th class="col-host sortable" data-col="host" data-sort="host">Host<span class="col-resizer"></span></th>
|
||||
<th class="col-status sortable" data-col="status" data-sort="status">Status<span class="col-resizer"></span></th>
|
||||
<th class="col-elapsed" data-col="elapsed">Zeit<span class="col-resizer"></span></th>
|
||||
<th class="col-remaining" data-col="remaining">Rest<span class="col-resizer"></span></th>
|
||||
<th class="col-speed sortable" data-col="speed" data-sort="speed">Speed<span class="col-resizer"></span></th>
|
||||
<th class="col-progress sortable" data-col="progress" data-sort="progress">Progress</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="queueBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>Parallele Uploads über alle Hoster (0 = nur pro Hoster)</label>
|
||||
<input type="number" id="globalParallel" min="0" max="100" value="0" />
|
||||
|
||||
<div class="queue-actions" id="queueActions" style="display:none">
|
||||
<button class="btn btn-xs btn-primary" id="copyAllLinksBtn">Alle Links kopieren</button>
|
||||
<button class="btn btn-xs btn-secondary" id="retryFailedBtn" style="display:none">Fehlgeschlagene erneut</button>
|
||||
<button class="btn btn-xs btn-secondary" id="importLogBtn" title="Log importieren — bereits hochgeladene aus Queue entfernen">Log importieren</button>
|
||||
</div>
|
||||
<button class="btn btn-primary" id="saveSettingsBtn">Speichern</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="view" id="log-view">
|
||||
<div class="actions-bar">
|
||||
<button class="btn" id="refreshLogBtn">Aktualisieren</button>
|
||||
<button class="btn" id="openLogFolderBtn">Log-Ordner öffnen</button>
|
||||
</div>
|
||||
<pre id="rotLog" class="log-pre"></pre>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Account modal -->
|
||||
<div class="modal-overlay" id="accountModal" style="display:none">
|
||||
<div class="modal-card">
|
||||
<h3>Account hinzufügen</h3>
|
||||
<div class="setting-row">
|
||||
<label>Hoster</label>
|
||||
<select id="accHoster">
|
||||
<option value="clouddrop.cc">Clouddrop (API)</option>
|
||||
<option value="byse.sx">Byse (API)</option>
|
||||
<option value="vidmoly.me">Vidmoly (Login)</option>
|
||||
<option value="doodstream.com">Doodstream (nicht in 2.0 POC)</option>
|
||||
<option value="voe.sx">VOE (nicht in 2.0 POC)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting-row" id="accLoginRow" style="display:none">
|
||||
<label>Username</label>
|
||||
<input type="text" id="accUsername" />
|
||||
</div>
|
||||
<div class="setting-row" id="accPasswordRow" style="display:none">
|
||||
<label>Passwort</label>
|
||||
<input type="password" id="accPassword" />
|
||||
</div>
|
||||
<div class="setting-row" id="accApiKeyRow">
|
||||
<label>API Key</label>
|
||||
<input type="password" id="accApiKey" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" id="accCancelBtn">Abbrechen</button>
|
||||
<button class="btn btn-primary" id="accSaveBtn">Speichern</button>
|
||||
<div class="resize-handle" id="recentFilesResizer"></div>
|
||||
<div class="recent-files-panel" id="recentFilesPanel">
|
||||
<div class="recent-files-header">
|
||||
<div class="recent-tabs">
|
||||
<button class="recent-tab active" data-panel="filesTab">Files</button>
|
||||
<button class="recent-tab" data-panel="statsTab">Stats</button>
|
||||
</div>
|
||||
<span class="recent-files-hint" id="recentFilesHint">Zuletzt erzeugte Upload-Links</span>
|
||||
<button class="btn btn-xs btn-secondary" id="exportRecentFilesBtn" title="Alle Zeilen als Datei exportieren (Zeit, Hoster, Link, Dateiname)">Exportieren</button>
|
||||
<button class="btn btn-xs btn-danger" id="clearRecentFilesBtn" title="Alle Links aus diesem Panel entfernen">Alle entfernen</button>
|
||||
</div>
|
||||
<div class="recent-tab-body active" id="filesTab">
|
||||
<div class="recent-files-table-wrap">
|
||||
<table class="recent-files-table">
|
||||
<thead id="recentFilesHead">
|
||||
<tr>
|
||||
<th class="col-date sortable" data-recent-sort="date">Datum<span class="sort-indicator">▼</span></th>
|
||||
<th class="col-filename sortable" data-recent-sort="filename">Filename<span class="sort-indicator">↕</span></th>
|
||||
<th class="col-host sortable" data-recent-sort="host">Host<span class="sort-indicator">↕</span></th>
|
||||
<th class="col-link sortable" data-recent-sort="link">Link<span class="sort-indicator">↕</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="recentFilesBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recent-tab-body" id="statsTab">
|
||||
<div class="stats-grid">
|
||||
<div class="stats-col">
|
||||
<h4>Files in queue (count)</h4>
|
||||
<div class="stats-row"><span>total:</span><span id="statQueueTotal">0</span></div>
|
||||
<div class="stats-row"><span>done:</span><span id="statQueueDone">0</span></div>
|
||||
<div class="stats-row"><span>remaining:</span><span id="statQueueRemaining">0</span></div>
|
||||
<div class="stats-row"><span>in progress:</span><span id="statQueueInProgress">0</span></div>
|
||||
<div class="stats-row"><span>error:</span><span id="statQueueError">0</span></div>
|
||||
</div>
|
||||
<div class="stats-col">
|
||||
<h4>File size in queue</h4>
|
||||
<div class="stats-row"><span>total:</span><span id="statSizeTotal">0 B</span></div>
|
||||
<div class="stats-row"><span>remaining:</span><span id="statSizeRemaining">0 B</span></div>
|
||||
</div>
|
||||
<div class="stats-col">
|
||||
<h4>Session</h4>
|
||||
<div class="stats-row"><span>Upload speed:</span><span id="statSpeed">0 B/s</span></div>
|
||||
<div class="stats-row"><span>Remaining time:</span><span id="statEta">--:--</span></div>
|
||||
<div class="stats-row"><span>Run time:</span><span id="statRunTime">00:00:00</span></div>
|
||||
<div class="stats-row"><span>Uploaded (this run):</span><span id="statSessionBytes">0 B</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
<div id="accounts-view" class="view">
|
||||
<div class="accounts-container">
|
||||
<div class="accounts-header">
|
||||
<div>
|
||||
<h2>Accounts</h2>
|
||||
<p class="settings-hint">Hoster-Zugangsdaten verwalten und prüfen</p>
|
||||
</div>
|
||||
<div class="accounts-header-actions">
|
||||
<button class="btn btn-secondary" id="accountsRunHealthCheckBtn">Accounts prüfen</button>
|
||||
<label class="auto-check-label accounts-auto-check" title="Automatischer Check vor dem Upload">
|
||||
<input type="checkbox" id="autoHealthCheckToggle" checked>
|
||||
<span>Auto-Check vor Upload</span>
|
||||
</label>
|
||||
<button class="btn btn-primary" id="addAccountBtn">+ Account hinzufügen</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="health-check-results account-health-results" id="healthCheckResults"></div>
|
||||
<div class="accounts-list" id="accountsList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="app.js"></script>
|
||||
<div class="modal-overlay" id="accountModal" style="display:none">
|
||||
<div class="modal-card">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<h3 id="accountModalTitle">Account hinzufügen</h3>
|
||||
<p id="accountModalSubtitle">Wähle einen Hoster und gib deine Zugangsdaten ein.</p>
|
||||
</div>
|
||||
<button class="icon-btn" id="closeAccountModalBtn" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="settings-row" id="accountHosterRow">
|
||||
<label>Hoster</label>
|
||||
<select class="key-input" id="accountHosterSelect" style="max-width:300px"></select>
|
||||
</div>
|
||||
<div id="accountCredsFields"></div>
|
||||
<div class="account-modal-status" id="accountModalStatus"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="cancelAccountModalBtn">Abbrechen</button>
|
||||
<button class="btn btn-primary" id="saveAccountBtn">Anlegen & prüfen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="deleteAccountModal" style="display:none">
|
||||
<div class="modal-card" style="width:min(400px,100%)">
|
||||
<div class="modal-header">
|
||||
<div><h3>Account löschen?</h3></div>
|
||||
<button class="icon-btn" id="closeDeleteModalBtn" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="deleteAccountMessage">Account wirklich löschen?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="cancelDeleteBtn">Abbrechen</button>
|
||||
<button class="btn btn-danger" id="confirmDeleteBtn">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-view" class="view">
|
||||
<div class="settings-container">
|
||||
<h2>Upload-Einstellungen</h2>
|
||||
<p class="settings-hint">Hoster-Einstellungen erscheinen erst, sobald ein Account hinterlegt ist. Änderungen werden automatisch gespeichert.</p>
|
||||
<div class="settings-hosters" id="settingsHosters"></div>
|
||||
<div class="settings-save-row">
|
||||
<span class="save-feedback" id="saveFeedback">Änderungen werden automatisch gespeichert.</span>
|
||||
<button class="btn btn-secondary" id="saveSettingsBtn">Jetzt speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="history-view" class="view">
|
||||
<div class="history-container">
|
||||
<div class="history-header">
|
||||
<h2>Upload-Verlauf</h2>
|
||||
<div style="display:flex; gap:8px">
|
||||
<button class="btn btn-secondary" id="exportHistoryBtn">Verlauf exportieren</button>
|
||||
<button class="btn btn-secondary" id="clearHistoryBtn">Verlauf löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="historyContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="context-menu" id="contextMenu" style="display:none">
|
||||
<div class="ctx-item" data-action="start-selected">Ausgewählte starten</div>
|
||||
<div class="ctx-item" data-action="retry-selected">Erneut versuchen</div>
|
||||
<div class="ctx-separator"></div>
|
||||
<div class="ctx-item" data-action="copy-links">Links kopieren</div>
|
||||
<div class="ctx-item" data-action="copy-all-links">Alle Links kopieren</div>
|
||||
<div class="ctx-separator"></div>
|
||||
<div class="ctx-item" data-action="delete-selected">Entfernen</div>
|
||||
<div class="ctx-item" data-action="delete-all">Alle entfernen</div>
|
||||
<div class="ctx-submenu ctx-hoster-delete-submenu" style="display:none">
|
||||
<div class="ctx-item ctx-item-danger">Hoster entfernen ▸</div>
|
||||
<div class="ctx-submenu-items ctx-hoster-delete-items"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="context-menu" id="recentContextMenu" style="display:none">
|
||||
<div class="ctx-item" data-action="recent-copy-links">Links kopieren</div>
|
||||
<div class="ctx-item" data-action="recent-delete">Entfernen</div>
|
||||
</div>
|
||||
|
||||
<div class="statusbar" id="statusbar">
|
||||
<span class="sb-state" id="sbState">Bereit</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-speed" id="sbSpeed">0 kB/s</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-total" id="sbTotal">0 B</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-eta" id="sbEta">ETA --:--</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-connections" id="sbConnections">Aktive Verbindungen 0</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-queue-count" id="sbQueueCount">Gesamt 0</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-remaining-count" id="sbRemainingCount">Remaining 0</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-progress-count" id="sbInProgressCount">In Progress 0</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-done-count" id="sbDoneCount">Done 0</span>
|
||||
<span class="sb-separator">|</span>
|
||||
<span class="sb-error-count" id="sbErrorCount">Error 0</span>
|
||||
</div>
|
||||
|
||||
<div class="copy-toast" id="copyToast"></div>
|
||||
|
||||
<div class="shutdown-overlay" id="shutdownOverlay" style="display:none">
|
||||
<div class="shutdown-box">
|
||||
<p id="shutdownMessage">System wird heruntergefahren in <span id="shutdownSeconds">60</span>s...</p>
|
||||
<button class="btn btn-danger" id="cancelShutdownBtn">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="hosterModal" style="display:none">
|
||||
<div class="modal-card">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<h3>Upload-Ziele auswählen</h3>
|
||||
<p>Dateien wurden hinzugefügt. Wähle jetzt die Hoster für den Upload.</p>
|
||||
</div>
|
||||
<button class="icon-btn" id="closeHosterModalBtn" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-actions-inline">
|
||||
<button class="btn btn-xs btn-secondary" id="selectAllHostersBtn">Alle</button>
|
||||
<button class="btn btn-xs btn-secondary" id="clearHostersBtn">Keine</button>
|
||||
</div>
|
||||
<div class="hoster-modal-list" id="hosterModalList"></div>
|
||||
<p class="modal-hint" id="hosterModalHint"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="cancelHosterModalBtn">Abbrechen</button>
|
||||
<button class="btn btn-primary" id="confirmHosterModalBtn">Übernehmen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="tauri-shim.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+903
-230
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,208 @@
|
||||
// tauri-shim.js — re-creates the Electron-era `window.api` surface on top of
|
||||
// Tauri 2's core.invoke + event.listen so the v1 renderer keeps working
|
||||
// unchanged.
|
||||
//
|
||||
// Every method here maps to exactly one Rust #[tauri::command] in
|
||||
// src-tauri/src/commands.rs. Event subscriptions go through window.__TAURI__.event.listen.
|
||||
|
||||
(function () {
|
||||
const T = window.__TAURI__;
|
||||
if (!T) {
|
||||
console.error('Tauri runtime not available');
|
||||
window.api = new Proxy({}, { get: () => async () => null });
|
||||
return;
|
||||
}
|
||||
const invoke = T.core.invoke;
|
||||
const listen = T.event.listen;
|
||||
const dialog = T.dialog || {};
|
||||
|
||||
// Helper: wrap a listen() call to return an unsubscribe fn like v1 did.
|
||||
function on(name, handler) {
|
||||
let unlisten = () => {};
|
||||
listen(name, (ev) => { try { handler(ev.payload); } catch (e) { console.error(e); } })
|
||||
.then(fn => { unlisten = fn; });
|
||||
return () => { try { unlisten(); } catch {} };
|
||||
}
|
||||
|
||||
const api = {
|
||||
// --- Config ---
|
||||
getConfig: () => invoke('get_config'),
|
||||
saveConfig: (config) => invoke('save_config', { config }),
|
||||
getHistory: () => invoke('get_history'),
|
||||
clearHistory: () => invoke('clear_history'),
|
||||
exportHistory: (format) => invoke('export_history', { format }),
|
||||
|
||||
// --- Hoster settings ---
|
||||
getHosterSettings: () => invoke('get_hoster_settings'),
|
||||
saveHosterSettings: (settings) => invoke('save_hoster_settings', { settings }),
|
||||
|
||||
// --- Global settings ---
|
||||
getGlobalSettings: () => invoke('get_global_settings'),
|
||||
saveGlobalSettings: (settings) => invoke('save_global_settings', { settings }),
|
||||
saveGlobalSettingsSync: (settings) => invoke('save_global_settings', { settings }),
|
||||
|
||||
// --- Window state ---
|
||||
setAlwaysOnTop: (value) => invoke('set_always_on_top', { value }),
|
||||
getAlwaysOnTop: () => invoke('get_always_on_top'),
|
||||
setShutdownAfterFinish: (mode) => invoke('set_shutdown_after_finish', { mode }),
|
||||
getShutdownAfterFinish: () => invoke('get_shutdown_after_finish'),
|
||||
cancelShutdown: () => invoke('cancel_shutdown'),
|
||||
|
||||
// --- File selection ---
|
||||
selectFiles: async () => {
|
||||
if (!dialog || !dialog.open) return null;
|
||||
const picked = await dialog.open({ multiple: true, directory: false });
|
||||
if (!picked) return null;
|
||||
return Array.isArray(picked) ? picked : [picked];
|
||||
},
|
||||
selectFolder: async () => {
|
||||
if (!dialog || !dialog.open) return null;
|
||||
const picked = await dialog.open({ multiple: false, directory: true });
|
||||
if (!picked) return null;
|
||||
// v1 returned an array of files in the folder; we return the folder and
|
||||
// let resolveFolderFiles handle enumeration.
|
||||
const files = await invoke('resolve_folder_files', { folderPath: picked });
|
||||
return files || [];
|
||||
},
|
||||
resolveFolderFiles: (folderPath) => invoke('resolve_folder_files', { folderPath }),
|
||||
|
||||
// --- Clipboard ---
|
||||
copyToClipboard: (text) => invoke('copy_to_clipboard', { text }),
|
||||
|
||||
// --- Path / file utils ---
|
||||
getPathForFile: (file) => {
|
||||
// In Tauri we never go through drag-drop file objects; paths come direct.
|
||||
// This keeps the renderer happy when it tries to resolve a drag event.
|
||||
return (file && file.path) ? file.path : '';
|
||||
},
|
||||
|
||||
// --- Uploads ---
|
||||
startUpload: (payload) => invoke('start_upload', { payload }),
|
||||
addJobsToBatch: (payload) => invoke('add_jobs_to_batch', { payload }),
|
||||
cancelSelectedJobs: (jobIds) => invoke('cancel_jobs', { jobIds }),
|
||||
cancelUpload: () => invoke('cancel_batch'),
|
||||
finishAfterActive: () => invoke('finish_after_active'),
|
||||
|
||||
// --- Health check ---
|
||||
runHealthCheck: (payload) => invoke('run_health_check', { payload }),
|
||||
|
||||
// --- Backup ---
|
||||
exportBackup: async () => {
|
||||
if (!dialog || !dialog.save) return { ok: false, canceled: true };
|
||||
const target = await dialog.save({
|
||||
defaultPath: `multi-hoster-backup-${new Date().toISOString().slice(0,10)}.mhu`,
|
||||
filters: [{ name: 'Multi-Hoster Backup', extensions: ['mhu'] }]
|
||||
});
|
||||
if (!target) return { ok: false, canceled: true };
|
||||
try {
|
||||
await invoke('export_backup', { targetPath: target });
|
||||
return { ok: true, path: target };
|
||||
} catch (e) { return { ok: false, error: String(e) }; }
|
||||
},
|
||||
importBackup: async (legacyPassword) => {
|
||||
if (legacyPassword) {
|
||||
try {
|
||||
const cfg = await invoke('import_backup_saved', { legacyPassword });
|
||||
return { ok: true, config: cfg };
|
||||
} catch (e) {
|
||||
const msg = String(e);
|
||||
if (msg === 'needs-password' || msg.includes('needs-password')) return { ok: false, needsPassword: true };
|
||||
return { ok: false, error: msg };
|
||||
}
|
||||
}
|
||||
if (!dialog || !dialog.open) return { ok: false, canceled: true };
|
||||
const src = await dialog.open({
|
||||
multiple: false, directory: false,
|
||||
filters: [{ name: 'Multi-Hoster Backup', extensions: ['mhu'] }]
|
||||
});
|
||||
if (!src) return { ok: false, canceled: true };
|
||||
try {
|
||||
const cfg = await invoke('import_backup', { sourcePath: src });
|
||||
return { ok: true, config: cfg };
|
||||
} catch (e) {
|
||||
const msg = String(e);
|
||||
if (msg === 'needs-password' || msg.includes('needs-password')) return { ok: false, needsPassword: true };
|
||||
return { ok: false, error: msg };
|
||||
}
|
||||
},
|
||||
|
||||
// --- Log / files ---
|
||||
readOwnUploadLog: () => invoke('read_own_upload_log'),
|
||||
importUploadLog: () => invoke('import_upload_log'),
|
||||
saveTextFile: async (defaultName, content, filters) => {
|
||||
if (!dialog || !dialog.save) return { ok: false, canceled: true };
|
||||
const target = await dialog.save({
|
||||
defaultPath: defaultName,
|
||||
filters: filters || [{ name: 'Textdatei', extensions: ['txt', 'csv', 'log'] }]
|
||||
});
|
||||
if (!target) return { ok: false, canceled: true };
|
||||
try {
|
||||
await invoke('save_text_file', { path: target, content });
|
||||
return { ok: true, path: target };
|
||||
} catch (e) { return { ok: false, error: String(e) }; }
|
||||
},
|
||||
openLogFolder: () => invoke('open_log_folder'),
|
||||
|
||||
// --- Update / version ---
|
||||
getVersion: () => invoke('get_version'),
|
||||
checkForUpdate: () => invoke('check_for_update'),
|
||||
installUpdate: () => invoke('install_update'),
|
||||
|
||||
// --- Folder monitor ---
|
||||
folderMonitorStart: (settings) => invoke('folder_monitor_start', { settings }),
|
||||
folderMonitorStop: () => invoke('folder_monitor_stop'),
|
||||
folderMonitorStatus: () => invoke('folder_monitor_status'),
|
||||
folderMonitorSelectFolder: async () => {
|
||||
if (!dialog || !dialog.open) return null;
|
||||
return dialog.open({ multiple: false, directory: true });
|
||||
},
|
||||
|
||||
// --- Remote control ---
|
||||
remoteGetSettings: () => invoke('remote_get_settings'),
|
||||
remoteSaveSettings: (settings) => invoke('remote_save_settings', { settings }),
|
||||
remoteGenerateToken: () => invoke('remote_generate_token'),
|
||||
remoteStatus: () => invoke('remote_status'),
|
||||
|
||||
// --- Drop target ---
|
||||
showDropTarget: () => invoke('show_drop_target'),
|
||||
hideDropTarget: () => invoke('hide_drop_target'),
|
||||
|
||||
// --- Debug ---
|
||||
debugLog: (msg) => invoke('debug_log', { msg }),
|
||||
|
||||
// --- Events ---
|
||||
onUpdateAvailable: (cb) => on('app:update-available', cb),
|
||||
onUpdateProgress: (cb) => on('app:update-progress', cb),
|
||||
onUploadProgress: (cb) => on('upload-progress', cb),
|
||||
onUploadBatchDone: (cb) => on('upload-batch-done', cb),
|
||||
onUploadStats: (cb) => on('upload-stats', cb),
|
||||
onShutdownCountdown: (cb) => on('shutdown-countdown', cb),
|
||||
onAccountSwitched: (cb) => on('account-switched', cb),
|
||||
onAccountRotationLog: (cb) => on('account-rotation-log', cb),
|
||||
onUploadLogFallback: (cb) => on('upload-log-fallback', cb),
|
||||
onLogPathAutoUpdated: (cb) => on('log-path-auto-updated', cb),
|
||||
onDropTargetFiles: (cb) => on('drop-target-files', cb),
|
||||
onFolderMonitorNewFiles: (cb) => on('folder-monitor-new-files', cb),
|
||||
onRemoteClientCount: (cb) => on('remote:client-count', cb),
|
||||
};
|
||||
|
||||
// Tauri's file-drop event delivers paths; v1's drop handler expected
|
||||
// FileList objects. Translate and inject synthetic drop events.
|
||||
try {
|
||||
listen('tauri://drag-drop', (ev) => {
|
||||
const paths = (ev.payload && (ev.payload.paths || ev.payload)) || [];
|
||||
if (!Array.isArray(paths) || paths.length === 0) return;
|
||||
const files = paths.map(p => ({ path: p, name: p.split(/[\\/]/).pop(), size: 0, type: '' }));
|
||||
const dz = document.getElementById('dropZone');
|
||||
if (!dz) return;
|
||||
const ev2 = new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer: new DataTransfer() });
|
||||
try {
|
||||
Object.defineProperty(ev2.dataTransfer, 'files', { value: files, writable: false });
|
||||
} catch {}
|
||||
// v1 path resolution goes through window.api.getPathForFile — already wired.
|
||||
dz.dispatchEvent(ev2);
|
||||
});
|
||||
} catch {}
|
||||
|
||||
window.api = api;
|
||||
})();
|
||||
Reference in New Issue
Block a user