web-snapshot-cli 1.0.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- web_snapshot_cli-1.2.0/.github/workflows/pages.yml +33 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/PKG-INFO +3 -1
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/README.md +2 -0
- web_snapshot_cli-1.2.0/docs/CNAME +1 -0
- web_snapshot_cli-1.2.0/docs/app.js +221 -0
- web_snapshot_cli-1.2.0/docs/index.html +200 -0
- web_snapshot_cli-1.2.0/docs/style.css +289 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/pyproject.toml +1 -1
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/__init__.py +1 -1
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/cli.py +2 -2
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/downloader.py +17 -15
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/utils.py +99 -1
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/wordlist.py +8 -7
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/tests/test_crawler.py +27 -0
- web_snapshot_cli-1.2.0/tests/test_utils.py +96 -0
- web_snapshot_cli-1.2.0/tests/test_wordlist.py +69 -0
- web_snapshot_cli-1.0.0/tests/test_utils.py +0 -26
- web_snapshot_cli-1.0.0/tests/test_wordlist.py +0 -32
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/.github/workflows/ci.yml +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/.github/workflows/publish.yml +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/.gitignore +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/LICENSE +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/__main__.py +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/crawl_policy.py +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/manifest.py +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/restore.py +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/wordlists/__init__.py +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/wordlists/common.txt +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/src/snapshot/wordlists/large.txt +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/tests/test_crawl_policy.py +0 -0
- {web_snapshot_cli-1.0.0 → web_snapshot_cli-1.2.0}/tests/test_features.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Deploy GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- ".github/workflows/pages.yml"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pages: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: pages
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
deploy:
|
|
22
|
+
environment:
|
|
23
|
+
name: github-pages
|
|
24
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: actions/configure-pages@v5
|
|
29
|
+
- uses: actions/upload-pages-artifact@v3
|
|
30
|
+
with:
|
|
31
|
+
path: docs
|
|
32
|
+
- uses: actions/deploy-pages@v4
|
|
33
|
+
id: deployment
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: web-snapshot-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Take offline snapshots of any website and restore them locally
|
|
5
5
|
Project-URL: Homepage, https://github.com/codingsushi79/Snapshot
|
|
6
6
|
Project-URL: Repository, https://github.com/codingsushi79/Snapshot
|
|
@@ -45,6 +45,8 @@ Then run:
|
|
|
45
45
|
snapshot https://example.com ./mirror
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
**Command generator:** [snapshot.sushii.dev](https://snapshot.sushii.dev/)
|
|
49
|
+
|
|
48
50
|
## Usage
|
|
49
51
|
|
|
50
52
|
### Snapshot a single page
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
snapshot.sushii.dev
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const $ = (sel) => document.querySelector(sel);
|
|
2
|
+
const $$ = (sel) => document.querySelectorAll(sel);
|
|
3
|
+
|
|
4
|
+
const PRESETS = {
|
|
5
|
+
quick: {
|
|
6
|
+
crawl: false,
|
|
7
|
+
gobuster: false,
|
|
8
|
+
sitemap: false,
|
|
9
|
+
maxPages: 50,
|
|
10
|
+
depth: 3,
|
|
11
|
+
crawlDelay: 0,
|
|
12
|
+
},
|
|
13
|
+
crawl: {
|
|
14
|
+
crawl: true,
|
|
15
|
+
gobuster: false,
|
|
16
|
+
sitemap: true,
|
|
17
|
+
maxPages: 200,
|
|
18
|
+
depth: 5,
|
|
19
|
+
crawlDelay: 0,
|
|
20
|
+
resume: true,
|
|
21
|
+
verbose: true,
|
|
22
|
+
},
|
|
23
|
+
full: {
|
|
24
|
+
crawl: true,
|
|
25
|
+
gobuster: true,
|
|
26
|
+
sitemap: true,
|
|
27
|
+
maxPages: 5000,
|
|
28
|
+
depth: 10,
|
|
29
|
+
crawlDelay: 0.5,
|
|
30
|
+
wordlistExt: "html,php,asp,aspx",
|
|
31
|
+
resume: true,
|
|
32
|
+
verbose: true,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function shellQuote(value) {
|
|
37
|
+
if (!value) return '""';
|
|
38
|
+
if (/^[A-Za-z0-9_./~-]+$/.test(value)) return value;
|
|
39
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function linesFromTextarea(id) {
|
|
43
|
+
const el = $(id);
|
|
44
|
+
if (!el || !el.value.trim()) return [];
|
|
45
|
+
return el.value
|
|
46
|
+
.split("\n")
|
|
47
|
+
.map((l) => l.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRestoreMode() {
|
|
52
|
+
return document.querySelector(".mode-tab.active")?.dataset.mode === "restore";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildCommand() {
|
|
56
|
+
if (isRestoreMode()) {
|
|
57
|
+
return buildRestoreCommand();
|
|
58
|
+
}
|
|
59
|
+
return buildSnapshotCommand();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildRestoreCommand() {
|
|
63
|
+
const parts = ["snapshot", "-restore"];
|
|
64
|
+
const dir = $("#outputDir").value.trim() || "./mirror";
|
|
65
|
+
const port = $("#port").value;
|
|
66
|
+
const host = $("#host").value.trim();
|
|
67
|
+
const noOpen = $("#noOpen").checked;
|
|
68
|
+
|
|
69
|
+
parts.push(shellQuote(dir));
|
|
70
|
+
if (port && port !== "8080") parts.push("--port", port);
|
|
71
|
+
if (host && host !== "127.0.0.1") parts.push("--host", shellQuote(host));
|
|
72
|
+
if (noOpen) parts.push("--no-open");
|
|
73
|
+
|
|
74
|
+
return parts.join(" ");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function buildSnapshotCommand() {
|
|
78
|
+
const parts = ["snapshot"];
|
|
79
|
+
const flags = [];
|
|
80
|
+
|
|
81
|
+
if ($("#crawl").checked) flags.push("--crawl");
|
|
82
|
+
if ($("#gobuster").checked) flags.push("--gobuster");
|
|
83
|
+
if ($("#sitemap").checked) flags.push("--sitemap");
|
|
84
|
+
if ($("#resume").checked) flags.push("--resume");
|
|
85
|
+
if ($("#verbose").checked) flags.push("--verbose");
|
|
86
|
+
if ($("#dryRun").checked) flags.push("--dry-run");
|
|
87
|
+
if ($("#noAssets").checked) flags.push("--no-assets");
|
|
88
|
+
if (!$("#robots").checked) flags.push("--no-robots");
|
|
89
|
+
if (!$("#sameOrigin").checked) flags.push("--no-same-origin");
|
|
90
|
+
|
|
91
|
+
const lang = $("#lang").value;
|
|
92
|
+
if (lang && lang !== "html") flags.push("--lang", lang);
|
|
93
|
+
|
|
94
|
+
const maxPages = $("#maxPages").value;
|
|
95
|
+
if (maxPages && maxPages !== "50") flags.push("--max-pages", maxPages);
|
|
96
|
+
|
|
97
|
+
const depth = $("#depth").value;
|
|
98
|
+
if (depth && depth !== "3") flags.push("--depth", depth);
|
|
99
|
+
|
|
100
|
+
const timeout = $("#timeout").value;
|
|
101
|
+
if (timeout && timeout !== "15") flags.push("--timeout", timeout);
|
|
102
|
+
|
|
103
|
+
const concurrency = $("#concurrency").value;
|
|
104
|
+
if (concurrency && concurrency !== "16") flags.push("--concurrency", concurrency);
|
|
105
|
+
|
|
106
|
+
const crawlDelay = $("#crawlDelay").value;
|
|
107
|
+
if (crawlDelay && crawlDelay !== "0" && crawlDelay !== "0.0") {
|
|
108
|
+
flags.push("--crawl-delay", crawlDelay);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const userAgent = $("#userAgent").value.trim();
|
|
112
|
+
if (userAgent) flags.push("--user-agent", shellQuote(userAgent));
|
|
113
|
+
|
|
114
|
+
const wordlistExt = $("#wordlistExt").value.trim();
|
|
115
|
+
if (wordlistExt) flags.push("--wordlist-ext", shellQuote(wordlistExt));
|
|
116
|
+
|
|
117
|
+
for (const cookie of linesFromTextarea("#cookies")) {
|
|
118
|
+
flags.push("--cookie", shellQuote(cookie));
|
|
119
|
+
}
|
|
120
|
+
for (const header of linesFromTextarea("#headers")) {
|
|
121
|
+
flags.push("--header", shellQuote(header));
|
|
122
|
+
}
|
|
123
|
+
for (const inc of linesFromTextarea("#includes")) {
|
|
124
|
+
flags.push("--include", shellQuote(inc));
|
|
125
|
+
}
|
|
126
|
+
for (const exc of linesFromTextarea("#excludes")) {
|
|
127
|
+
flags.push("--exclude", shellQuote(exc));
|
|
128
|
+
}
|
|
129
|
+
for (const wl of linesFromTextarea("#wordlists")) {
|
|
130
|
+
flags.push("--wordlist", shellQuote(wl));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
parts.push(...flags);
|
|
134
|
+
|
|
135
|
+
let url = $("#url").value.trim();
|
|
136
|
+
if (url && !/^https?:\/\//i.test(url)) url = `https://${url}`;
|
|
137
|
+
const out = $("#outputDir").value.trim() || "./mirror";
|
|
138
|
+
|
|
139
|
+
parts.push(shellQuote(url || "https://example.com"));
|
|
140
|
+
parts.push(shellQuote(out));
|
|
141
|
+
|
|
142
|
+
return parts.join(" ");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function updateCommand() {
|
|
146
|
+
const cmd = buildCommand();
|
|
147
|
+
$("#command").textContent = cmd;
|
|
148
|
+
|
|
149
|
+
const restoreHint = $("#restoreHint");
|
|
150
|
+
if (isRestoreMode()) {
|
|
151
|
+
restoreHint.classList.add("hidden");
|
|
152
|
+
} else {
|
|
153
|
+
restoreHint.classList.remove("hidden");
|
|
154
|
+
const dir = $("#outputDir").value.trim() || "./mirror";
|
|
155
|
+
$("#restoreCommand").textContent = `snapshot -restore ${shellQuote(dir)}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function applyPreset(name) {
|
|
160
|
+
const p = PRESETS[name];
|
|
161
|
+
if (!p) return;
|
|
162
|
+
|
|
163
|
+
$("#crawl").checked = !!p.crawl;
|
|
164
|
+
$("#gobuster").checked = !!p.gobuster;
|
|
165
|
+
$("#sitemap").checked = !!p.sitemap;
|
|
166
|
+
$("#resume").checked = !!p.resume;
|
|
167
|
+
$("#verbose").checked = !!p.verbose;
|
|
168
|
+
if (p.maxPages != null) $("#maxPages").value = p.maxPages;
|
|
169
|
+
if (p.depth != null) $("#depth").value = p.depth;
|
|
170
|
+
if (p.crawlDelay != null) $("#crawlDelay").value = p.crawlDelay;
|
|
171
|
+
if (p.wordlistExt != null) $("#wordlistExt").value = p.wordlistExt;
|
|
172
|
+
|
|
173
|
+
setMode("snapshot");
|
|
174
|
+
updateCommand();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function setMode(mode) {
|
|
178
|
+
$$(".mode-tab").forEach((tab) => {
|
|
179
|
+
tab.classList.toggle("active", tab.dataset.mode === mode);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const snapshotOnly = $$(".snapshot-only");
|
|
183
|
+
const restoreOnly = $$(".restore-only");
|
|
184
|
+
|
|
185
|
+
snapshotOnly.forEach((el) => el.classList.toggle("hidden", mode === "restore"));
|
|
186
|
+
restoreOnly.forEach((el) => el.classList.toggle("hidden", mode !== "restore"));
|
|
187
|
+
|
|
188
|
+
updateCommand();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function copyCommand() {
|
|
192
|
+
const text = $("#command").textContent;
|
|
193
|
+
await navigator.clipboard.writeText(text);
|
|
194
|
+
const btn = $("#copyBtn");
|
|
195
|
+
btn.textContent = "Copied!";
|
|
196
|
+
btn.classList.add("copied");
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
btn.textContent = "Copy";
|
|
199
|
+
btn.classList.remove("copied");
|
|
200
|
+
}, 1500);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function init() {
|
|
204
|
+
$$(".mode-tab").forEach((tab) => {
|
|
205
|
+
tab.addEventListener("click", () => setMode(tab.dataset.mode));
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
$$(".preset-btn").forEach((btn) => {
|
|
209
|
+
btn.addEventListener("click", () => applyPreset(btn.dataset.preset));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
$("#copyBtn").addEventListener("click", copyCommand);
|
|
213
|
+
|
|
214
|
+
document.addEventListener("input", updateCommand);
|
|
215
|
+
document.addEventListener("change", updateCommand);
|
|
216
|
+
|
|
217
|
+
setMode("snapshot");
|
|
218
|
+
updateCommand();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>snapshot — command generator</title>
|
|
7
|
+
<meta name="description" content="Build snapshot CLI commands for offline website mirrors." />
|
|
8
|
+
<link rel="stylesheet" href="style.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header>
|
|
12
|
+
<h1>snapshot command generator</h1>
|
|
13
|
+
<p>
|
|
14
|
+
Build install and run commands for
|
|
15
|
+
<a href="https://github.com/codingsushi79/Snapshot">web-snapshot-cli</a>
|
|
16
|
+
</p>
|
|
17
|
+
</header>
|
|
18
|
+
|
|
19
|
+
<div class="layout">
|
|
20
|
+
<div class="panel">
|
|
21
|
+
<div class="mode-tabs">
|
|
22
|
+
<button type="button" class="mode-tab active" data-mode="snapshot">Snapshot</button>
|
|
23
|
+
<button type="button" class="mode-tab" data-mode="restore">Restore</button>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="presets snapshot-only">
|
|
27
|
+
<button type="button" class="preset-btn" data-preset="quick">Single page</button>
|
|
28
|
+
<button type="button" class="preset-btn" data-preset="crawl">Crawl</button>
|
|
29
|
+
<button type="button" class="preset-btn" data-preset="full">Full + gobuster</button>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<h2>Target</h2>
|
|
33
|
+
<div class="field snapshot-only">
|
|
34
|
+
<label for="url">Website URL</label>
|
|
35
|
+
<input type="url" id="url" placeholder="https://example.com" value="https://example.com" />
|
|
36
|
+
</div>
|
|
37
|
+
<div class="field">
|
|
38
|
+
<label for="outputDir">Output directory</label>
|
|
39
|
+
<input type="text" id="outputDir" placeholder="./mirror or ~/sites/example" value="./mirror" />
|
|
40
|
+
<div class="hint">Where files are saved (snapshot) or served from (restore)</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<hr class="section-divider snapshot-only" />
|
|
44
|
+
|
|
45
|
+
<h2 class="snapshot-only">Discovery</h2>
|
|
46
|
+
<div class="check-grid snapshot-only">
|
|
47
|
+
<label class="check-item">
|
|
48
|
+
<input type="checkbox" id="crawl" />
|
|
49
|
+
<div>--crawl<span>Follow same-origin links</span></div>
|
|
50
|
+
</label>
|
|
51
|
+
<label class="check-item">
|
|
52
|
+
<input type="checkbox" id="gobuster" />
|
|
53
|
+
<div>--gobuster<span>Wordlist discovery; skips any page mentioning 404</span></div>
|
|
54
|
+
</label>
|
|
55
|
+
<label class="check-item">
|
|
56
|
+
<input type="checkbox" id="sitemap" />
|
|
57
|
+
<div>--sitemap<span>Seed from sitemap.xml / robots.txt</span></div>
|
|
58
|
+
</label>
|
|
59
|
+
<label class="check-item">
|
|
60
|
+
<input type="checkbox" id="resume" />
|
|
61
|
+
<div>--resume<span>Skip already-downloaded files</span></div>
|
|
62
|
+
</label>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="field snapshot-only">
|
|
66
|
+
<label for="wordlists">Extra wordlists (one per line)</label>
|
|
67
|
+
<textarea id="wordlists" placeholder="common large /path/to/SecLists/Discovery/Web-Content/common.txt"></textarea>
|
|
68
|
+
<div class="hint">Builtin names: <code>common</code>, <code>large</code>. --gobuster uses both by default. Any page whose full text contains <code>404</code> is skipped, even on HTTP 200.</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="field snapshot-only">
|
|
71
|
+
<label for="wordlistExt">Wordlist extensions</label>
|
|
72
|
+
<input type="text" id="wordlistExt" placeholder="html,php,asp,aspx" />
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<hr class="section-divider snapshot-only" />
|
|
76
|
+
|
|
77
|
+
<h2 class="snapshot-only">Crawl limits</h2>
|
|
78
|
+
<div class="row snapshot-only">
|
|
79
|
+
<div class="field">
|
|
80
|
+
<label for="maxPages">Max pages</label>
|
|
81
|
+
<input type="number" id="maxPages" value="50" min="1" />
|
|
82
|
+
</div>
|
|
83
|
+
<div class="field">
|
|
84
|
+
<label for="depth">Depth</label>
|
|
85
|
+
<input type="number" id="depth" value="3" min="1" />
|
|
86
|
+
</div>
|
|
87
|
+
<div class="field">
|
|
88
|
+
<label for="concurrency">Concurrency</label>
|
|
89
|
+
<input type="number" id="concurrency" value="16" min="1" />
|
|
90
|
+
</div>
|
|
91
|
+
<div class="field">
|
|
92
|
+
<label for="crawlDelay">Crawl delay (sec)</label>
|
|
93
|
+
<input type="number" id="crawlDelay" value="0" min="0" step="0.1" />
|
|
94
|
+
</div>
|
|
95
|
+
<div class="field">
|
|
96
|
+
<label for="timeout">Timeout (sec)</label>
|
|
97
|
+
<input type="number" id="timeout" value="15" min="1" step="0.5" />
|
|
98
|
+
</div>
|
|
99
|
+
<div class="field">
|
|
100
|
+
<label for="lang">Output format</label>
|
|
101
|
+
<select id="lang">
|
|
102
|
+
<option value="html">html</option>
|
|
103
|
+
<option value="md">md</option>
|
|
104
|
+
</select>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<hr class="section-divider snapshot-only" />
|
|
109
|
+
|
|
110
|
+
<h2 class="snapshot-only">Filters & auth</h2>
|
|
111
|
+
<div class="field snapshot-only">
|
|
112
|
+
<label for="includes">Include globs (one per line)</label>
|
|
113
|
+
<textarea id="includes" placeholder="/docs/* /blog/*"></textarea>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="field snapshot-only">
|
|
116
|
+
<label for="excludes">Exclude globs (one per line)</label>
|
|
117
|
+
<textarea id="excludes" placeholder="/admin/* /drafts/*"></textarea>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="field snapshot-only">
|
|
120
|
+
<label for="cookies">Cookies (name=value, one per line)</label>
|
|
121
|
+
<textarea id="cookies" placeholder="session=abc123"></textarea>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="field snapshot-only">
|
|
124
|
+
<label for="headers">Headers (Name: Value, one per line)</label>
|
|
125
|
+
<textarea id="headers" placeholder="Authorization: Bearer TOKEN"></textarea>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="field snapshot-only">
|
|
128
|
+
<label for="userAgent">Custom User-Agent</label>
|
|
129
|
+
<input type="text" id="userAgent" placeholder="Leave blank for default" />
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<hr class="section-divider" />
|
|
133
|
+
|
|
134
|
+
<h2>Options</h2>
|
|
135
|
+
<div class="check-grid snapshot-only">
|
|
136
|
+
<label class="check-item">
|
|
137
|
+
<input type="checkbox" id="robots" checked />
|
|
138
|
+
<div>--robots<span>Respect robots.txt</span></div>
|
|
139
|
+
</label>
|
|
140
|
+
<label class="check-item">
|
|
141
|
+
<input type="checkbox" id="sameOrigin" checked />
|
|
142
|
+
<div>--same-origin<span>Same origin only</span></div>
|
|
143
|
+
</label>
|
|
144
|
+
<label class="check-item">
|
|
145
|
+
<input type="checkbox" id="noAssets" />
|
|
146
|
+
<div>--no-assets<span>Skip CSS/JS/images/animations</span></div>
|
|
147
|
+
</label>
|
|
148
|
+
<label class="check-item">
|
|
149
|
+
<input type="checkbox" id="verbose" />
|
|
150
|
+
<div>--verbose<span>Detailed logs</span></div>
|
|
151
|
+
</label>
|
|
152
|
+
<label class="check-item">
|
|
153
|
+
<input type="checkbox" id="dryRun" />
|
|
154
|
+
<div>--dry-run<span>Don't write files</span></div>
|
|
155
|
+
</label>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="row restore-only hidden">
|
|
158
|
+
<div class="field">
|
|
159
|
+
<label for="port">Port</label>
|
|
160
|
+
<input type="number" id="port" value="8080" min="1" />
|
|
161
|
+
</div>
|
|
162
|
+
<div class="field">
|
|
163
|
+
<label for="host">Host</label>
|
|
164
|
+
<input type="text" id="host" value="127.0.0.1" />
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<label class="check-item restore-only hidden">
|
|
168
|
+
<input type="checkbox" id="noOpen" />
|
|
169
|
+
<div>--no-open<span>Don't open browser</span></div>
|
|
170
|
+
</label>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<div class="panel output-panel">
|
|
174
|
+
<h2>Generated command</h2>
|
|
175
|
+
<div class="command-box">
|
|
176
|
+
<code id="command">snapshot https://example.com ./mirror</code>
|
|
177
|
+
<button type="button" class="copy-btn" id="copyBtn">Copy</button>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="install-line">
|
|
181
|
+
Install first:
|
|
182
|
+
<code>pip install web-snapshot-cli</code>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="install-line" id="restoreHint">
|
|
186
|
+
After snapshot, restore locally:
|
|
187
|
+
<code id="restoreCommand">snapshot -restore ./mirror</code>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<footer>
|
|
193
|
+
<a href="https://github.com/codingsushi79/Snapshot">GitHub</a>
|
|
194
|
+
·
|
|
195
|
+
<a href="https://pypi.org/project/web-snapshot-cli/">PyPI</a>
|
|
196
|
+
</footer>
|
|
197
|
+
|
|
198
|
+
<script src="app.js"></script>
|
|
199
|
+
</body>
|
|
200
|
+
</html>
|