Improve setup wizard sections

This commit is contained in:
Nick Sweeting 2026-07-26 12:23:13 -07:00
parent 883c4f3c93
commit f58301b70c
5 changed files with 131 additions and 25 deletions

View File

@ -10,9 +10,14 @@
<h2 id="archivebox-setup-title">Setup access to your ArchiveBox Server: <code>{{ display_host|default:canonical_host }}</code></h2>
</div>
<fieldset class="abx-question">
<legend><b>Hosting Location:</b> What machine are you hosting this ArchiveBox server on?</legend>
<div class="abx-question-options">
<fieldset class="abx-question" data-status-id="archivebox-setup-hosting-status">
<legend>
<button class="abx-question-toggle" type="button" aria-expanded="true" aria-controls="archivebox-setup-hosting-options">
<span><b>Hosting Location:</b> What machine are you hosting this ArchiveBox server on?</span>
<span class="abx-question-toggle-icon" aria-hidden="true"></span>
</button>
</legend>
<div class="abx-question-options" id="archivebox-setup-hosting-options">
<label class="abx-question-option"><input type="radio" name="archivebox-hosting-location" value="localhost"> <span><b>Localhost</b><small>Same machine as you, such as a local laptop or desktop. Modern browsers support wildcard <code>*.archivebox.localhost</code> with no DNS or HTTPS setup required.</small></span></label>
<label class="abx-question-option"><input type="radio" name="archivebox-hosting-location" value="private"> <span><b>Private Server</b><small>NAS, Docker host, private cloud, VPS behind NAT, or another server not directly accessible from the public internet.</small></span></label>
<label class="abx-question-option"><input type="radio" name="archivebox-hosting-location" value="public"> <span><b>Public Server</b><small>A server with a public static IP and public domain, directly accessible from the internet.</small></span></label>
@ -20,9 +25,14 @@
<div class="abx-question-status" id="archivebox-setup-hosting-status">Choose where this server is hosted.</div>
</fieldset>
<fieldset class="abx-question">
<legend><b>DNS mode:</b> How will DNS point to this ArchiveBox server?</legend>
<div class="abx-question-options">
<fieldset class="abx-question" data-status-id="archivebox-setup-dns-status">
<legend>
<button class="abx-question-toggle" type="button" aria-expanded="true" aria-controls="archivebox-setup-dns-options">
<span><b>DNS mode:</b> How will DNS point to this ArchiveBox server?</span>
<span class="abx-question-toggle-icon" aria-hidden="true"></span>
</button>
</legend>
<div class="abx-question-options" id="archivebox-setup-dns-options">
<label class="abx-question-option"><input type="radio" name="archivebox-dns-mode" value="localhost"> <span><b>⭐ Localhost</b><small>No setup needed. <code id="archivebox-setup-localhost-dns-example">*.archivebox.localhost</code> works directly in modern browsers with all features.</small></span></label>
<label class="abx-question-option"><input type="radio" name="archivebox-dns-mode" value="single"> <span><b>⚠️ Single-domain DNS</b><small>Register a DNS record like <code>A archivebox.example.com</code><code>your server's IP</code>, or add an equivalent <code>/etc/hosts</code> entry pointing to your server. Some archived content that depends on JavaScript may not display properly in this mode. JavaScript still runs during capture, but viewing HTML captured by <code>wget</code>, <code>dom</code>, <code>responses</code>, <code>staticfile</code>, and similar plugins will not replay JavaScript unless wildcard DNS is used.</small></span></label>
<label class="abx-question-option"><input type="radio" name="archivebox-dns-mode" value="wildcard"> <span><b>⭐ Wildcard DNS</b><small>Point both the bare <code>BASE_URL</code> hostname and <code>*.BASE_URL</code> to this server using A/AAAA records or CNAME records. Ideal: safest isolation with all replay features. <a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Modes" target="_blank" rel="noopener noreferrer">Setup instructions</a>.</small></span></label>
@ -30,9 +40,14 @@
<div class="abx-question-status" id="archivebox-setup-dns-status">Choose a DNS mode to begin testing.</div>
</fieldset>
<fieldset class="abx-question">
<legend><b>Ingress &amp; TLS mode:</b> How will HTTPS traffic reach this ArchiveBox server?</legend>
<div class="abx-question-options">
<fieldset class="abx-question" data-status-id="archivebox-setup-tls-status">
<legend>
<button class="abx-question-toggle" type="button" aria-expanded="true" aria-controls="archivebox-setup-tls-options">
<span><b>Ingress &amp; TLS mode:</b> How will HTTPS traffic reach this ArchiveBox server?</span>
<span class="abx-question-toggle-icon" aria-hidden="true"></span>
</button>
</legend>
<div class="abx-question-options" id="archivebox-setup-tls-options">
<label class="abx-question-option"><input type="radio" name="archivebox-tls-mode" value="localhost"> <span><b>⭐ Localhost</b><small>No setup needed. HTTPS is not required for local access; all replay features remain available.</small></span></label>
<label class="abx-question-option"><input type="radio" name="archivebox-tls-mode" value="none"> <span><b>⚠️ No separate ingress service / SSL termination</b><small>Access ArchiveBox directly without an ingress service like Cloudflare/AWS/Nginx/Caddy/Traefik in front. In-browser WARC viewing will be disabled unless using <code>localhost</code> or HTTPS.</small></span></label>
<label class="abx-question-option"><input type="radio" name="archivebox-tls-mode" value="single"> <span><b>⚠️ Single-domain HTTPS certificate</b><small>One certificate from Nginx + Let's Encrypt, Caddy, Traefik, Cloudflare, Tailscale, or another ingress provider. This mode is not allowed unless also using Single-domain DNS.</small></span></label>
@ -118,4 +133,4 @@
</div>
</div>
</div>
<script src="{% static 'setup_wizard.js' %}?v=20260723-2" defer></script>
<script src="{% static 'setup_wizard.js' %}?v=20260726-1" defer></script>

View File

@ -6,6 +6,12 @@
#archivebox-setup-wizard .abx-setup-label { display:block; margin-bottom:7px; font-size:15px; font-weight:800; }
#archivebox-setup-wizard .abx-question { margin:18px 0 0; padding:16px; border:1px solid #dbe3ee; border-radius:9px; }
#archivebox-setup-wizard .abx-question legend { padding:0 6px; font-size:15px; font-weight:800; }
#archivebox-setup-wizard .abx-question-toggle { display:flex; width:100%; align-items:center; justify-content:space-between; gap:12px; padding:0; border:0; background:transparent; color:inherit; font:inherit; text-align:left; cursor:pointer; }
#archivebox-setup-wizard .abx-question-toggle-icon { color:#64748b; font-size:16px; transition:transform .15s ease; }
#archivebox-setup-wizard .abx-question.is-collapsed .abx-question-toggle-icon { transform:rotate(-90deg); }
#archivebox-setup-wizard .abx-question.is-collapsed .abx-question-options { display:none; }
#archivebox-setup-wizard .abx-question.is-valid { border-color:#15803d; background:#f0fdf4; box-shadow:0 0 0 1px #15803d; }
#archivebox-setup-wizard .abx-question.is-invalid { border-color:#dc2626; background:#fffafa; }
#archivebox-setup-wizard .abx-question-options { display:grid; gap:9px; }
#archivebox-setup-wizard .abx-question-option { display:flex; align-items:flex-start; gap:10px; padding:11px 12px; border:1px solid #dbe3ee; border-radius:7px; background:#f8fafc; color:#334155; cursor:pointer; }
#archivebox-setup-wizard .abx-question-option:has(input:checked) { border-color:#15803d; background:#f0fdf4; box-shadow:0 0 0 1px #15803d; }
@ -17,6 +23,8 @@
#archivebox-setup-wizard .abx-url-comparison-status.is-match { color:#15803d; }
#archivebox-setup-wizard .abx-url-comparison-status.is-warning { color:#b45309; }
#archivebox-setup-wizard .abx-question-status { margin-top:9px; padding:9px 11px; border-radius:7px; background:#f8fafc; color:#475569; font-weight:700; }
#archivebox-setup-wizard .abx-question.is-valid .abx-question-status { background:#dcfce7; color:#166534; }
#archivebox-setup-wizard .abx-question.is-invalid .abx-question-status { background:#fef2f2; color:#991b1b; }
#archivebox-setup-wizard .abx-setup-control { box-sizing:border-box; width:100%; height:44px !important; min-height:44px !important; margin:0; padding:0 12px !important; border:1px solid #b8c4d4 !important; border-radius:7px; background:#fff !important; color:#0f172a !important; font:14px/normal -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif !important; opacity:1 !important; }
#archivebox-setup-wizard select.abx-setup-control { appearance:auto !important; -webkit-appearance:menulist !important; }
#archivebox-setup-wizard select.abx-setup-control option { color:#0f172a !important; background:#fff !important; }

View File

@ -33,6 +33,36 @@
Array.prototype.forEach.call(inputs, function(input) { input.checked = input.value === value; });
}
function initializeQuestionSections() {
document.querySelectorAll('.abx-question[data-status-id]').forEach(function(question) {
var status = document.getElementById(question.dataset.statusId);
var toggle = question.querySelector('.abx-question-toggle');
function setCollapsed(collapsed) {
question.classList.toggle('is-collapsed', collapsed);
toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
}
function syncQuestionState() {
var statusText = status.textContent.trim();
var isValid = statusText.indexOf('✅') === 0;
var isInvalid = statusText.indexOf('❌') === 0;
question.classList.toggle('is-valid', isValid);
question.classList.toggle('is-invalid', isInvalid);
if (isValid) setCollapsed(true);
if (isInvalid) setCollapsed(false);
}
toggle.addEventListener('click', function() {
setCollapsed(!question.classList.contains('is-collapsed'));
});
new MutationObserver(syncQuestionState).observe(status, {childList: true, characterData: true, subtree: true});
syncQuestionState();
});
}
initializeQuestionSections();
var detectedUrl = new URL(baseUrlInput.value);
var detectedLocalhost = detectedUrl.hostname === 'localhost' || detectedUrl.hostname.endsWith('.localhost');
if (detectedLocalhost) {

View File

@ -167,15 +167,30 @@ def test_unconfigured_superuser_banner_uses_browser_assisted_setup_wizard():
assert "PERMISSIONS" in html
assert "/admin/machine/machine/current/change/" in html
assert "setup_wizard.css" in html
assert "setup_wizard.js?v=20260723-2" in html
assert 'data-status-id="archivebox-setup-hosting-status"' in html
assert 'aria-controls="archivebox-setup-hosting-options"' in html
assert 'data-status-id="archivebox-setup-dns-status"' in html
assert 'aria-controls="archivebox-setup-dns-options"' in html
assert 'data-status-id="archivebox-setup-tls-status"' in html
assert 'aria-controls="archivebox-setup-tls-options"' in html
assert "setup_wizard.js?v=20260726-1" in html
def test_setup_wizard_assets_enforce_selection_and_access_requirements():
assert "border-color:#15803d; background:#f0fdf4" in SETUP_WIZARD_CSS
assert "accent-color:#15803d" in SETUP_WIZARD_CSS
assert "#archivebox-setup-title code { font-size:inherit; line-height:inherit; }" in SETUP_WIZARD_CSS
assert ".abx-question.is-collapsed .abx-question-options { display:none; }" in SETUP_WIZARD_CSS
assert ".abx-question.is-valid { border-color:#15803d; background:#f0fdf4" in SETUP_WIZARD_CSS
assert ".abx-question.is-invalid { border-color:#dc2626;" in SETUP_WIZARD_CSS
assert "updateQuestionDefaults" not in SETUP_WIZARD_JS
assert "function initializeQuestionSections()" in SETUP_WIZARD_JS
assert "var isValid = statusText.indexOf('') === 0" in SETUP_WIZARD_JS
assert "var isInvalid = statusText.indexOf('') === 0" in SETUP_WIZARD_JS
assert "if (isValid) setCollapsed(true)" in SETUP_WIZARD_JS
assert "if (isInvalid) setCollapsed(false)" in SETUP_WIZARD_JS
assert "toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true')" in SETUP_WIZARD_JS
assert "canonicalHost" not in SETUP_WIZARD_JS
assert "Browser URL matches BASE_URL." in SETUP_WIZARD_JS
assert "Browser URL matches admin.BASE_URL as expected." in SETUP_WIZARD_JS

View File

@ -49,6 +49,17 @@ resolve_git_binary() {
test -x "$GIT_BINARY"
}
repo_target_branch() {
case "$1" in
archivebox)
printf 'dev\n'
;;
*)
printf 'main\n'
;;
esac
}
is_member_repo() {
local repo_root="$1"
local repo_name
@ -82,7 +93,7 @@ bootstrap_build_dependencies() {
case "$OSTYPE" in
linux*)
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
@ -90,7 +101,7 @@ bootstrap_build_dependencies() {
--binproviders=env,apt \
--overrides='{"apt":{"install_args":["build-essential"]}}' \
cc >/dev/null
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
@ -99,14 +110,14 @@ bootstrap_build_dependencies() {
--overrides='{"env":{"version":["ldapsearch","-VV"]},"apt":{"install_args":["ldap-utils","python3-dev","python3-setuptools","libssl-dev","libldap2-dev","libsasl2-dev","zlib1g-dev","libatomic1"],"version":["ldapsearch","-VV"]}}' \
ldapsearch >/dev/null
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
--lib="$ABXPKG_LIB_DIR" \
--binproviders=env \
cc >/dev/null
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
@ -121,7 +132,7 @@ bootstrap_build_dependencies() {
test -x "$ABXPKG_LIB_DIR/env/bin/ldapsearch"
;;
darwin*)
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--json \
--lib="$ABXPKG_LIB_DIR" \
@ -136,7 +147,7 @@ bootstrap_build_dependencies() {
brew_root="$(dirname "$(dirname "$brew_target")")"
export ABXPKG_BREW_ROOT="$brew_root"
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
@ -144,7 +155,7 @@ bootstrap_build_dependencies() {
--binproviders=env,brew \
--overrides='{"brew":{"install_args":["llvm"]}}' \
clang >/dev/null
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
@ -154,7 +165,7 @@ bootstrap_build_dependencies() {
ldapvc >/dev/null
PATH="$PATH:$brew_root/opt/llvm/bin" \
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
@ -162,7 +173,7 @@ bootstrap_build_dependencies() {
--binproviders=env \
clang >/dev/null
PATH="$PATH:$brew_root/opt/openldap/bin" \
uv run --no-sync abxpkg env \
uv tool run --from "$ROOT_DIR/abxpkg" abxpkg env \
--install \
--no-cache \
--json \
@ -203,7 +214,25 @@ bootstrap_build_dependencies() {
}
sync_workspace() {
uv sync --all-packages --all-extras --no-cache --active
uv sync --all-extras --no-cache --active
for repo_name in "${REPO_NAMES[@]}"; do
printf 'Syncing %s into monorepo environment\n' "$repo_name"
case "$repo_name" in
archivebox)
(
cd "$ROOT_DIR/$repo_name"
UV_PROJECT_ENVIRONMENT="$ROOT_DIR/.venv" uv sync --dev --all-extras --inexact --no-cache --active
)
;;
*)
(
cd "$ROOT_DIR/$repo_name"
UV_PROJECT_ENVIRONMENT="$ROOT_DIR/.venv" uv sync --dev --inexact --no-cache --active
)
;;
esac
done
}
ensure_setup_link() {
@ -280,10 +309,20 @@ fi
ensure_member_repo() {
local repo_name="$1"
local repo_dir="$ROOT_DIR/$repo_name"
local target_branch
local current_branch
target_branch="$(repo_target_branch "$repo_name")"
if [[ -d "$repo_dir/.git" ]]; then
printf 'Updating existing checkout: %s\n' "$repo_name"
if "$GIT_BINARY" -C "$repo_dir" -c pull.rebase=false pull --ff-only --quiet >/dev/null 2>&1; then
current_branch="$("$GIT_BINARY" -C "$repo_dir" branch --show-current)"
if [[ "$current_branch" != "$target_branch" ]]; then
"$GIT_BINARY" -C "$repo_dir" fetch --depth=1 origin "$target_branch" --quiet
"$GIT_BINARY" -C "$repo_dir" checkout -B "$target_branch" --track "origin/$target_branch" >/dev/null
fi
if "$GIT_BINARY" -C "$repo_dir" -c pull.rebase=false pull --ff-only --quiet origin "$target_branch" >/dev/null 2>&1; then
printf 'Updated: %s\n' "$repo_name"
else
printf 'Skipping pull for %s (local changes, divergent branch, detached HEAD, or no upstream)\n' "$repo_name" >&2
@ -297,7 +336,7 @@ ensure_member_repo() {
fi
printf 'Cloning %s/%s.git -> %s\n' "$GITHUB_BASE" "$repo_name" "$repo_name"
"$GIT_BINARY" clone "$GITHUB_BASE/$repo_name.git" "$repo_dir"
"$GIT_BINARY" clone --branch "$target_branch" "$GITHUB_BASE/$repo_name.git" "$repo_dir"
}
for repo_name in "${REPO_NAMES[@]}"; do
@ -315,7 +354,6 @@ rm -Rf ./*/.venv # delete all sub-repo venvs, the monorepo venv needs to take
uv venv --allow-existing "$ROOT_DIR/.venv"
# shellcheck disable=SC1091
source "$ROOT_DIR/.venv/bin/activate"
uv sync --package abxpkg --no-dev --no-cache --active
bootstrap_build_dependencies
sync_workspace
echo
@ -326,5 +364,5 @@ echo " VIRTUAL_ENV=$VIRTUAL_ENV"
echo " PYTHON_BIN=$VIRTUAL_ENV/bin/python"
echo
echo "TIPS:"
echo " - Always use 'uv run ...' within each subrepo, never in the root & never run 'python ...' directly"
echo " - Use 'uv run ...' inside subrepos for package work; their standalone uv.lock files remain authoritative for CI/release"
echo " - Always read $ROOT_DIR/README.md into context before starting any work"