97 lines
4.4 KiB
HTML
97 lines
4.4 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="color-scheme" content="dark">
|
|
<meta name="theme-color" content="#111827">
|
|
<title>Immich Share Sync</title>
|
|
<link rel="stylesheet" href="/styles.css">
|
|
</head>
|
|
<body>
|
|
<a class="source-ribbon" href="https://code.brunner.ninja/feedc0de/immich-sync" target="_blank" rel="noopener noreferrer" aria-label="View and fork Immich Share Sync at code.brunner.ninja">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
<path d="m8.7 16.6-4.6-4.6 4.6-4.6 1.4 1.4L6.9 12l3.2 3.2-1.4 1.4Zm6.6 0-1.4-1.4 3.2-3.2-3.2-3.2 1.4-1.4 4.6 4.6-4.6 4.6ZM10.4 19l2.9-14 2 .4-2.9 14-2-.4Z"/>
|
|
</svg>
|
|
<span>Fork me at code.brunner.ninja</span>
|
|
</a>
|
|
<div class="ambient ambient-one"></div>
|
|
<div class="ambient ambient-two"></div>
|
|
<main>
|
|
<header class="hero">
|
|
<div class="brand-mark" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span></div>
|
|
<div>
|
|
<p class="eyebrow">Independent Immich servers, one complete album</p>
|
|
<h1>Immich Share Sync</h1>
|
|
<p class="lede">Compare two public shares by checksum and stream only the missing originals. Nothing is deleted and nothing is stored here.</p>
|
|
</div>
|
|
<div id="connection" class="connection idle"><i></i><span>Idle</span></div>
|
|
</header>
|
|
|
|
<section class="workspace" aria-label="Share links">
|
|
<article id="leftCard" class="share-card left">
|
|
<div class="card-heading">
|
|
<span class="side-label">A</span>
|
|
<div><h2>Left share</h2><p id="leftStatus">Waiting for a link</p></div>
|
|
<span id="leftState" class="state idle">Idle</span>
|
|
</div>
|
|
<label>Immich share URL
|
|
<input id="leftUrl" type="url" autocomplete="url" spellcheck="false" placeholder="https://photos.example/share/…" required>
|
|
</label>
|
|
<label>Password <span>if protected</span>
|
|
<input id="leftPassword" type="password" autocomplete="off" placeholder="Optional">
|
|
</label>
|
|
<div id="leftDetails" class="details hidden"></div>
|
|
</article>
|
|
|
|
<div class="exchange" aria-hidden="true">
|
|
<span>→</span><span>←</span>
|
|
</div>
|
|
|
|
<article id="rightCard" class="share-card right">
|
|
<div class="card-heading">
|
|
<span class="side-label">B</span>
|
|
<div><h2>Right share</h2><p id="rightStatus">Waiting for a link</p></div>
|
|
<span id="rightState" class="state idle">Idle</span>
|
|
</div>
|
|
<label>Immich share URL
|
|
<input id="rightUrl" type="url" autocomplete="url" spellcheck="false" placeholder="https://photos.example/share/…" required>
|
|
</label>
|
|
<label>Password <span>if protected</span>
|
|
<input id="rightPassword" type="password" autocomplete="off" placeholder="Optional">
|
|
</label>
|
|
<div id="rightDetails" class="details hidden"></div>
|
|
</article>
|
|
</section>
|
|
|
|
<section class="controls">
|
|
<button id="inspect" class="primary" type="button">Inspect both shares</button>
|
|
<p id="inspectionMessage">Connect both sides to discover available synchronization directions.</p>
|
|
<div id="syncOptions" class="sync-options hidden">
|
|
<button type="button" data-direction="left-to-right"><strong>A → B</strong><span></span></button>
|
|
<button type="button" data-direction="bidirectional"><strong>A ↔ B</strong><span></span></button>
|
|
<button type="button" data-direction="right-to-left"><strong>B → A</strong><span></span></button>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="activity" class="activity hidden" aria-live="polite">
|
|
<div class="activity-heading">
|
|
<div><p class="eyebrow">Live WebSocket status</p><h2 id="activityTitle">Synchronization</h2></div>
|
|
<button id="abort" class="danger" type="button">Abort & disconnect</button>
|
|
</div>
|
|
<div class="progress-track"><div id="progressBar"></div></div>
|
|
<div class="progress-copy"><span id="progressText">Preparing…</span><span id="progressCount">0 / 0</span></div>
|
|
<ol id="events" class="events"></ol>
|
|
</section>
|
|
|
|
<footer>
|
|
<span>Ephemeral by design</span>
|
|
<span>Checksum comparison</span>
|
|
<span>Originals streamed in memory</span>
|
|
<span>No deletion</span>
|
|
</footer>
|
|
</main>
|
|
<script src="/app.js" defer></script>
|
|
</body>
|
|
</html>
|