Download
364 lines 14.1 KiBHTMLModified 2026-10-16 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Backer-Upper</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <header>
      <h1>Backer-Upper</h1>
      <span id="status-bar"></span>
      <button id="btn-close-app" title="Close">✕</button>
    </header>

    <main>
      <!-- DriveSelect -->
      <div id="screen-drive-select" class="screen">
        <div class="screen-header">
          <h2>Select Drive</h2>
          <button id="btn-refresh">↻ Refresh</button>
        </div>
        <div id="drive-list" class="drive-list">
          <div class="empty-state">Loading drives…</div>
        </div>
        <div class="btn-row">
          <button id="btn-format-drive" class="danger-outline" disabled>Format for Backup</button>
          <button id="btn-open-drive" class="primary" disabled>Open Drive</button>
        </div>
        <div id="drive-select-error" class="error-text" style="display: none"></div>
      </div>

      <!-- PasswordPrompt -->
      <div id="screen-password" class="screen">
        <div class="dialog">
          <h2 id="password-heading">Unlock Drive</h2>
          <div class="card">
            <p>This drive is LUKS-encrypted. Enter the passphrase to unlock it.</p>
            <div class="form-grid">
              <label>Passphrase</label>
              <input type="password" id="password-input" placeholder="Passphrase" />
            </div>
            <div id="password-error" class="error-text" style="display: none"></div>
            <div class="btn-row">
              <button id="btn-password-cancel">Cancel</button>
              <button id="btn-password-unlock" class="primary">Unlock</button>
            </div>
          </div>
        </div>
      </div>

      <!-- ConfigEditor -->
      <div id="screen-config" class="screen">
        <div class="screen-header">
          <h2>Backup Configuration</h2>
          <button id="btn-eject">⏏ Eject</button>
        </div>
        <div class="meta-row">
          <span id="config-mount-point" class="muted"></span>
          <span id="config-last-backup" class="muted"></span>
        </div>

        <div class="card card-flush">
          <table class="jobs-table">
            <thead>
              <tr>
                <th>Name</th>
                <th>Source → Drive</th>
                <th>Mode</th>
                <th>Enabled</th>
                <th></th>
              </tr>
            </thead>
            <tbody id="jobs-tbody"></tbody>
          </table>
        </div>

        <div class="btn-row">
          <button id="btn-add-job">+ Add Job</button>
          <button id="btn-save-config" style="display: none" class="primary">Save Config</button>
          <span id="unsaved-indicator" style="display: none">Unsaved changes</span>
          <button id="btn-wipe-free-space" class="danger-outline">Wipe Free Space…</button>
          <button id="btn-restore" class="danger-outline">Restore from Backup…</button>
          <button id="btn-next" class="primary">Begin Backup…</button>
        </div>
        <div id="config-error" class="error-text" style="display: none"></div>
      </div>

      <!-- JobEdit -->
      <div id="screen-job-edit" class="screen">
        <h2 id="job-edit-heading">Edit Job</h2>
        <div class="card">
          <div class="form-grid">
            <label>Name</label>
            <input type="text" id="job-name" placeholder="e.g. Home" />
            <label>Source folder</label>
            <input type="text" id="job-source" placeholder="/home/user" />
            <label>Destination (on drive)</label>
            <input type="text" id="job-dest" placeholder="home" />
            <label>Mode</label>
            <div class="radio-group">
              <label class="radio-option">
                <input type="radio" name="job-mode" value="Backup" />
                <span>
                  <strong>📸 Snapshot</strong>
                  <span class="radio-desc"
                    >Exact copy of current state; deletions are mirrored</span
                  >
                </span>
              </label>
              <label class="radio-option">
                <input type="radio" name="job-mode" value="Media" />
                <span>
                  <strong>📚 Hoard</strong>
                  <span class="radio-desc"
                    >Only adds missing files; nothing is ever deleted from backup</span
                  >
                </span>
              </label>
            </div>
            <label>Enabled</label>
            <input type="checkbox" id="job-enabled" />
            <label for="job-exclude-caches">Skip caches</label>
            <span class="checkbox-with-desc">
              <input type="checkbox" id="job-exclude-caches" />
              <span class="radio-desc"
                >Skip directories marked with a <code>CACHEDIR.TAG</code> file (Cargo, pip, ccache,
                browsers…)</span
              >
            </span>
          </div>
          <div class="excludes-section">
            <div class="excludes-section-header">
              <label class="excludes-label">Files to back up</label>
              <span class="excludes-hint">Uncheck to exclude</span>
            </div>
            <div id="excludes-tree" class="excludes-tree">
              <div class="tree-msg"><span class="spinner"></span> Loading…</div>
            </div>
            <div class="presets-row">
              <span class="presets-row-label">Suggested:</span>
              <div id="presets-chips" class="presets-chips"></div>
            </div>
            <label class="excludes-label excludes-patterns-label">Exclude patterns</label>
            <textarea id="job-excludes-manual" rows="5" placeholder="*.iso&#10;*.log"></textarea>
          </div>
          <div class="btn-row">
            <button id="btn-job-cancel">Cancel</button>
            <button id="btn-job-save" class="primary">Save Job</button>
            <button id="btn-job-delete" class="danger">Delete Job</button>
          </div>
        </div>
      </div>

      <!-- Preview -->
      <div id="screen-preview" class="screen">
        <h2>Review Backup Commands</h2>
        <p class="muted">These rsync commands will run in order:</p>
        <div id="preview-commands"></div>
        <div id="preview-empty" class="empty-state" style="display: none">No enabled jobs.</div>
        <div class="btn-row">
          <button id="btn-preview-cancel">← Back</button>
          <button id="btn-run-backup" class="primary">▶ Run Backup</button>
        </div>
      </div>

      <!-- Wipe Free Space -->
      <div id="screen-wipe" class="screen">
        <h2>Wiping Free Space…</h2>
        <div id="wipe-status-banner" class="banner" style="display: none"></div>
        <p class="muted">
          Writing zeros to all unallocated space on the drive. This cannot be cancelled cleanly
          mid-way — let it finish for best results.
        </p>
        <div class="progress-wrap">
          <div id="wipe-progress-bar" class="progress-bar" style="width: 0%"></div>
          <span class="progress-label" id="wipe-progress-label">0%</span>
        </div>
        <div class="muted" id="wipe-size-info"></div>
        <div class="btn-row" id="wipe-btn-row">
          <button id="btn-wipe-cancel">Cancel</button>
        </div>
      </div>

      <!-- Backup -->
      <div id="screen-backup" class="screen">
        <h2>Backup</h2>
        <div id="backup-status-banner" class="banner" style="display: none"></div>
        <div id="backup-job-info"></div>

        <div class="progress-wrap">
          <div id="backup-progress-bar" class="progress-bar" style="width: 0%"></div>
          <div id="backup-progress-label" class="progress-label">0%</div>
        </div>

        <div class="muted" id="backup-elapsed"></div>

        <div class="log-section">
          <div class="log-label">Output Log</div>
          <div id="backup-log" class="log-box"></div>
        </div>

        <div class="btn-row" id="backup-btn-row">
          <button id="btn-backup-pause">Pause</button>
          <button id="btn-backup-cancel">Cancel</button>
        </div>
      </div>

      <!-- Restore -->
      <div id="screen-restore" class="screen">
        <div class="screen-header">
          <h2>Restore from Backup</h2>
        </div>

        <div class="banner danger">
          <strong>⚠ This will overwrite files on your computer.</strong>
          Files from the selected backup will be copied to their original locations, replacing any
          local versions. This cannot be undone — make sure you have any important unsaved work
          elsewhere before proceeding.
        </div>

        <div class="card">
          <div class="section-label">Restore from</div>
          <div id="restore-snapshot-list"></div>
        </div>

        <div class="card">
          <div class="section-label">Jobs to restore</div>
          <div id="restore-jobs-list"></div>
        </div>

        <div class="card">
          <label class="radio-option">
            <input type="checkbox" id="restore-delete-extra" />
            <span>
              <strong>Also delete local files not in the backup</strong>
              <span class="radio-desc"
                >Permanently removes local files that don't exist in the selected backup. Use with
                caution.</span
              >
            </span>
          </label>
        </div>

        <div class="card">
          <div class="form-grid">
            <label>Confirm</label>
            <input
              type="text"
              id="restore-confirm-input"
              placeholder='Type "RESTORE" to confirm'
              autocomplete="off"
            />
          </div>
          <div id="restore-validation-msg" class="error-text" style="display: none"></div>
        </div>

        <div id="restore-error" class="error-text" style="display: none"></div>

        <div class="btn-row">
          <button id="btn-restore-cancel">Cancel</button>
          <button id="btn-do-restore" class="primary" disabled>Preview Restore →</button>
        </div>
      </div>

      <!-- RestorePreview -->
      <div id="screen-restore-preview" class="screen">
        <div class="screen-header">
          <h2>Preview Restore</h2>
        </div>
        <p class="muted">These files would be changed. No files have been written yet.</p>
        <div class="log-section">
          <div class="log-label">Dry-run output</div>
          <div id="restore-preview-log" class="log-box"></div>
        </div>
        <div id="restore-preview-empty" class="empty-state" style="display: none">
          No files would be changed by this restore.
        </div>
        <div id="restore-preview-error" class="error-text" style="display: none"></div>
        <div class="btn-row">
          <button id="btn-restore-preview-back">← Change Settings</button>
          <button id="btn-restore-preview-proceed" class="danger">Restore from Backup</button>
        </div>
      </div>

      <!-- FormatSetup -->
      <div id="screen-format-setup" class="screen">
        <h2>Format Drive for Backup</h2>

        <div id="format-main-content">
          <div class="banner danger">
            <strong>DANGER — ALL DATA ON THIS DRIVE WILL BE PERMANENTLY DESTROYED</strong>
            <span id="format-drive-info-line"></span>
          </div>

          <div class="format-columns">
            <div class="format-col-probe">
              <div class="card terminal">
                <div id="format-probe-content">
                  <em>Reading drive contents…</em>
                </div>
              </div>

              <details>
                <summary>Commands that will run</summary>
                <div id="format-cmd-preview" class="log-box"></div>
              </details>
            </div>

            <div class="format-col-form">
              <div class="card">
                <div class="form-grid">
                  <label>Drive label</label>
                  <input type="text" id="format-label" value="Backup" />
                  <label>Filesystem</label>
                  <select id="format-fstype">
                    <option value="btrfs">btrfs (recommended)</option>
                    <option value="ext4">ext4</option>
                  </select>
                  <label>Encryption</label>
                  <label class="checkbox-inline">
                    <input type="checkbox" id="format-luks" checked />
                    LUKS encryption
                  </label>
                  <div id="format-luks-section" style="display: contents">
                    <label>Passphrase</label>
                    <input type="password" id="format-pass1" placeholder="Choose a passphrase" />
                    <label>Confirm</label>
                    <input type="password" id="format-pass2" placeholder="Repeat passphrase" />
                  </div>
                  <label id="format-confirm-label">Type device path</label>
                  <input type="text" id="format-confirm-input" placeholder="/dev/sdX" />
                </div>
                <div id="format-validation-msg" style="display: none" class="error-text"></div>
                <div id="format-error" class="error-text" style="display: none"></div>

                <div class="btn-row">
                  <button id="btn-format-cancel">Cancel</button>
                  <button id="btn-do-format" class="danger" disabled>
                    Erase &amp; Format Drive
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- FormatProgress -->
      <div id="screen-format-progress" class="screen">
        <h2>Formatting Drive…</h2>
        <div id="format-progress-banner" class="banner" style="display: none"></div>
        <div id="format-step-label"></div>
        <div id="format-step-dots" class="step-dots"></div>
        <div id="format-log" class="log-box"></div>
        <div class="btn-row" id="format-done-btn-row" style="display: none">
          <button id="btn-format-done" class="primary">Back to Drive Select</button>
        </div>
      </div>
    </main>

    <script type="module" src="app.js"></script>
  </body>
</html>