console-error-scanner 1.16.3__py3-none-any.whl

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.
Files changed (41) hide show
  1. console_error_scanner/__init__.py +5 -0
  2. console_error_scanner/__main__.py +370 -0
  3. console_error_scanner/app.py +1870 -0
  4. console_error_scanner/app.tcss +91 -0
  5. console_error_scanner/i18n.py +74 -0
  6. console_error_scanner/locale/de.json +417 -0
  7. console_error_scanner/locale/en.json +417 -0
  8. console_error_scanner/models/__init__.py +1 -0
  9. console_error_scanner/models/history.py +220 -0
  10. console_error_scanner/models/robots.py +154 -0
  11. console_error_scanner/models/scan_result.py +265 -0
  12. console_error_scanner/models/settings.py +242 -0
  13. console_error_scanner/models/sitemap.py +347 -0
  14. console_error_scanner/models/whitelist.py +226 -0
  15. console_error_scanner/screens/__init__.py +1 -0
  16. console_error_scanner/screens/diet_advisor.py +128 -0
  17. console_error_scanner/screens/error_detail.py +164 -0
  18. console_error_scanner/screens/history.py +187 -0
  19. console_error_scanner/screens/scan_summary.py +234 -0
  20. console_error_scanner/screens/settings.py +292 -0
  21. console_error_scanner/screens/top_errors.py +304 -0
  22. console_error_scanner/screens/whitelist.py +154 -0
  23. console_error_scanner/services/__init__.py +1 -0
  24. console_error_scanner/services/image_clipboard.py +104 -0
  25. console_error_scanner/services/preview_service.py +291 -0
  26. console_error_scanner/services/rate_limit.py +56 -0
  27. console_error_scanner/services/reporter.py +561 -0
  28. console_error_scanner/services/scanner.py +965 -0
  29. console_error_scanner/services/site_score.py +151 -0
  30. console_error_scanner/widgets/__init__.py +1 -0
  31. console_error_scanner/widgets/bar_chart.py +60 -0
  32. console_error_scanner/widgets/preview_panel.py +356 -0
  33. console_error_scanner/widgets/results_table.py +594 -0
  34. console_error_scanner/widgets/stats_panel.py +387 -0
  35. console_error_scanner/widgets/summary_panel.py +176 -0
  36. console_error_scanner-1.16.3.dist-info/METADATA +509 -0
  37. console_error_scanner-1.16.3.dist-info/RECORD +41 -0
  38. console_error_scanner-1.16.3.dist-info/WHEEL +5 -0
  39. console_error_scanner-1.16.3.dist-info/entry_points.txt +2 -0
  40. console_error_scanner-1.16.3.dist-info/licenses/LICENSE +202 -0
  41. console_error_scanner-1.16.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,417 @@
1
+ {
2
+ "binding.quit": "Quit",
3
+ "binding.crawl": "Crawl",
4
+ "binding.load_sitemap": "Load sitemap",
5
+ "binding.history": "History",
6
+ "binding.report": "Report",
7
+ "binding.jira": "JIRA table",
8
+ "binding.top_errors": "Top 10 errors",
9
+ "binding.summary": "Summary",
10
+ "binding.whitelist": "Whitelist",
11
+ "binding.log": "Log",
12
+ "binding.errors_only": "Show errors only",
13
+ "binding.errors_show_all": "Show all",
14
+ "binding.filter": "Filter",
15
+ "binding.clear_filter": "Clear filter",
16
+ "binding.copy_details": "Copy details",
17
+ "binding.http_codes": "HTTP codes",
18
+ "binding.info": "Info",
19
+ "binding.settings": "Settings",
20
+ "binding.theme": "Theme",
21
+ "binding.close": "Close",
22
+
23
+ "tooltip.quit": "Exits the application.",
24
+ "tooltip.crawl": "Starts a new scan run. If no sitemap is loaded, asks for the URL.",
25
+ "tooltip.load_sitemap": "Opens a file dialog to load a local sitemap.xml.",
26
+ "tooltip.history": "Opens the list of past scans (re-use a URL).",
27
+ "tooltip.report": "Saves HTML and JSON reports of the current scan.",
28
+ "tooltip.jira": "Builds a JIRA table of the pages with errors - copied to the clipboard, ready to paste into a ticket. Format (Markdown for Jira Cloud or wiki markup) in the settings under Export.",
29
+ "tooltip.top_errors": "Shows the ten most frequent errors as a bar chart.",
30
+ "tooltip.summary": "Reopens the scan summary: site score, findings, largest pages and memory hogs. Only available after a completed scan.",
31
+ "tooltip.whitelist": "Shows the loaded whitelist patterns.",
32
+ "tooltip.log": "Toggles the log panel.",
33
+ "tooltip.errors_only": "Filters the table to pages with errors.",
34
+ "tooltip.copy_details": "Copies the right detail pane to the clipboard.",
35
+ "tooltip.http_codes": "Opens a reference table of all common HTTP status codes (2xx/3xx/4xx/5xx) with meaning and explanation.",
36
+ "tooltip.info": "Shows the about dialog with version and license.",
37
+ "tooltip.settings": "Opens settings (language, consent, scroll, concurrency, whitelist, …).",
38
+ "tooltip.theme": "Cycles to the next retro theme. Full picker via Ctrl+P → \"theme\".",
39
+
40
+ "notify.no_urls": "No URLs found!",
41
+ "notify.scan_running": "Scan already running!",
42
+ "notify.no_urls_loaded": "No URLs loaded! Please load a sitemap first.",
43
+ "notify.scan_error": "Scan error: {error}",
44
+ "notify.no_results": "No results available!",
45
+ "notify.not_scanned": "No pages scanned yet!",
46
+ "notify.reports_saved": "Reports saved: {json_path}, {html_path}",
47
+ "notify.no_url_selected": "No URL selected.",
48
+ "notify.details_copied": "Details copied",
49
+ "notify.jira_copied": "JIRA table with {count} error pages copied",
50
+ "notify.json_copied": "JSON of {count} pages copied to the clipboard",
51
+ "notify.text_copied": "Text of {count} pages copied to the clipboard",
52
+ "notify.no_errors_jira": "No errors - nothing to export.",
53
+ "notify.theme": "Theme: {name}",
54
+ "notify.no_whitelist": "No whitelist loaded. Set the path in settings (s).",
55
+ "notify.no_results_for_top": "No results yet for the Top 10 view.",
56
+
57
+ "log.version": "Console Error Scanner v{version}",
58
+ "log.theme_active": "Theme: {name}",
59
+ "log.theme_unknown": "Theme \"{name}\" is unknown - using the default",
60
+ "log.config": "Concurrency: {concurrency} | Timeout: {timeout}s | Console-Level: {level} | {consent} | {scroll}",
61
+ "log.consent_on": "Consent: ON",
62
+ "log.consent_off": "Consent: OFF",
63
+ "log.scroll_on": "Scroll: ON",
64
+ "log.scroll_off": "Scroll: OFF",
65
+ "log.robots_blocked": "robots.txt: {blocked} of {total} pages blocked - skipping them",
66
+ "log.robots_clear": "robots.txt respected - no page blocked",
67
+ "log.robots_ignored": "robots.txt: OFF (ignored)",
68
+ "log.whitelist_loaded": "Whitelist loaded: {count} patterns from {path}",
69
+ "log.whitelist_error": "Whitelist error: {error}",
70
+ "log.loading_sitemap": "Loading sitemap…",
71
+ "log.loading_local_sitemap": "Loading local sitemap: {url}",
72
+ "log.searching_sitemap": "Searching sitemap for: {url}",
73
+ "log.sitemap_error": "Sitemap error: {error}",
74
+ "log.loading_sitemap_url": "Loading sitemap: {url}",
75
+ "log.unexpected_error": "Unexpected error: {error}",
76
+ "log.no_urls_in_sitemap": "No URLs found in sitemap.",
77
+ "log.urls_loaded": "{count} URLs loaded",
78
+ "log.history_updated": "History updated",
79
+ "log.scan_complete": "Scan completed in {duration}",
80
+ "log.site_score": "Site score: {score}% ({grade})",
81
+ "log.scan_result": "Result: {errors_pages} pages with errors | Console: {console} | 404: {http_404} | 4xx: {http_4xx} | 5xx: {http_5xx}{ignored}",
82
+ "log.whitelist_suppressed": "{count} errors suppressed by whitelist",
83
+ "log.json_report": "JSON report: {path}",
84
+ "log.html_report": "HTML report: {path}",
85
+ "log.jira_copied": "JIRA table with {count} error pages copied to the clipboard.",
86
+ "log.no_errors_jira": "No errors found - no JIRA table generated.",
87
+ "log.history_params": "History: parameters applied",
88
+ "log.sitemap_label": "Sitemap: {url}",
89
+ "log.cookies_label": "Cookies: {cookies}",
90
+ "log.filter_label": "Filter: {filter}",
91
+ "log.settings_saved": "Settings saved",
92
+ "log.whitelist_reloaded": "Whitelist reloaded: {count} patterns",
93
+ "log.whitelist_cleared": "Whitelist removed",
94
+ "log.sitemap_still_loading": "… Loading sitemap ({seconds}s) …",
95
+ "log.setting_changed": "{label}: {old} → {new}",
96
+ "log.no_settings_changed": "Settings unchanged",
97
+
98
+ "subtitle.loading_sitemap": "Loading sitemap…",
99
+ "subtitle.loading_sitemap_dots": "Loading sitemap{dots}",
100
+ "subtitle.urls_count": "{count} URLs",
101
+ "subtitle.scan_complete": "{count} URLs · scan complete",
102
+ "subtitle.scanning": "Scanning {bar} {pct}% ({current}/{total}) · ~{remaining} · ⌀ {avg}s/page",
103
+ "subtitle.scanning_start": "Scanning {bar} 0% (0/{total})",
104
+
105
+ "sitemap_error.title": "Error",
106
+ "sitemap_error.footer": "ESC = Close",
107
+ "sitemap_error.sitemap_error": "Sitemap error:\n\n{error}",
108
+ "sitemap_error.unexpected_error": "Unexpected error:\n\n{error}",
109
+
110
+ "about.description": "Scans websites for JavaScript console\nerrors and HTTP errors (404, 5xx).",
111
+
112
+ "error_detail_screen.title": "Error details: {url}",
113
+ "error_detail_screen.footer": "ESC / q = Close",
114
+ "error_detail_screen.http_status": "HTTP status: {code}",
115
+ "error_detail_screen.load_time": "Load time: {time}ms",
116
+ "error_detail_screen.retries": "Retries: {count}",
117
+ "error_detail_screen.total_errors": "Total errors: {total}{ignored}",
118
+ "error_detail_screen.no_errors": "No errors.",
119
+ "error_detail_screen.source": " Source: {source}",
120
+ "error_detail_screen.whitelist_hits": "Whitelist hits ({count})",
121
+
122
+ "history.title": "Scan history",
123
+ "history.empty": "No scans in history yet.\n\nStart a scan with a sitemap URL,\nand it will appear here.",
124
+ "history.col_number": "#",
125
+ "history.col_date": "Date",
126
+ "history.col_url": "URL",
127
+ "history.col_params": "Parameters",
128
+ "history.btn_select": "Select",
129
+ "history.btn_close": "Close",
130
+
131
+ "top_errors.title": "Top 10 errors",
132
+ "top_errors.footer": "ESC / q = Close",
133
+ "top_errors.no_errors": "No errors found.",
134
+ "top_errors.total": "Total: {count} errors on ",
135
+ "top_errors.pages": "{count} pages",
136
+ "top_errors.most_errors": "Pages with the most errors",
137
+ "top_errors.empty": "(empty)",
138
+
139
+ "whitelist.title": "Whitelist",
140
+ "whitelist.empty": "No whitelist loaded.\n\nIn settings (key s), enter the path to a\nwhitelist.json file. Loaded patterns will appear here.",
141
+ "whitelist.path": "Path: {path}",
142
+ "whitelist.active": "Active: {count} pattern(s)",
143
+ "whitelist.col_number": "#",
144
+ "whitelist.col_pattern": "Pattern",
145
+ "whitelist.col_hits": "Hits",
146
+ "whitelist.footer": "ESC / q = Close",
147
+
148
+ "header.title": "Console Error Scanner",
149
+ "header.sitemap": "Sitemap",
150
+ "header.urls": "URLs",
151
+ "header.scanned": "Scanned",
152
+ "header.duration": "Duration",
153
+ "header.score": "Score",
154
+ "header.concurrency": "Threads",
155
+ "header.timeout": "Timeout",
156
+ "header.level": "Console level",
157
+ "header.consent": "Cookie consent",
158
+ "header.scroll": "Scroll",
159
+ "header.whitelist": "Whitelist",
160
+ "header.with_errors": "With errors",
161
+ "header.console_err": "Console errors",
162
+ "header.console_warn": "Console warnings",
163
+ "header.http_404": "404",
164
+ "header.http_4xx": "4xx",
165
+ "header.http_5xx": "5xx",
166
+ "header.timeouts": "Timeouts",
167
+ "header.ignored": "Ignored",
168
+ "header.value_on": "ON",
169
+ "header.value_off": "OFF",
170
+ "header.value_none": "—",
171
+
172
+ "summary.no_sitemap": "No sitemap loaded.",
173
+
174
+ "detail.no_url": "No URL selected.\n\nPick a URL from the list on the left to view its details.",
175
+ "detail.url_header": "URL",
176
+ "detail.url": "URL",
177
+ "detail.http": "HTTP",
178
+ "detail.load_time": "Load time (Load)",
179
+ "detail.dom_content_loaded": "DOMContentLoaded",
180
+ "detail.requests": "Requests",
181
+ "detail.size": "Size",
182
+ "detail.retries_label": "Retries",
183
+ "detail.content_type": "Content-Type",
184
+ "detail.last_modified": "Last-Modified",
185
+ "detail.timeout": "Timeout while loading the page.",
186
+ "detail.page_heading": "Page",
187
+ "detail.http_heading": "HTTP headers",
188
+ "detail.http_collapsed_hint": "{count} headers collapsed — click the title to expand.",
189
+ "detail.errors_heading": "Errors ({count})",
190
+ "detail.warnings_heading": "Warnings ({count})",
191
+ "detail.whitelist_heading": "Whitelist ({count})",
192
+ "detail.info_heading": "Info",
193
+ "detail.status": "Status: ",
194
+ "detail.retries": "{count} retries",
195
+ "detail.error_summary": "Errors: {errors} errors, {warns} warns, {http_404} 404, {http_4xx} 4xx, {http_5xx} 5xx",
196
+ "detail.ignored_suffix": ", {count} ignored",
197
+ "detail.no_errors": "No errors found.",
198
+ "detail.scanning": "Scanning…",
199
+ "detail.not_scanned": "Not scanned yet.",
200
+ "detail.console_errors": "Console errors ({count})",
201
+ "detail.console_warnings": "Console warnings ({count})",
202
+ "detail.csp_violations": "CSP violations ({count})",
203
+ "detail.http_404": "HTTP 404 ({count})",
204
+ "detail.http_4xx": "HTTP 4xx ({count})",
205
+ "detail.http_5xx": "HTTP 5xx ({count})",
206
+ "detail.whitelist_hits": "Whitelist hits ({count})",
207
+ "detail.more_lines": "… ({count} more lines)",
208
+ "detail.source": " Source: ",
209
+ "detail.no_text": "(no text)",
210
+
211
+ "table.filter_placeholder": "Filter (URL, status …)",
212
+ "table.count": " {count} URLs",
213
+ "table.count_filtered": " {shown} of {total} URLs (filtered)",
214
+ "table.col_number": "#",
215
+ "table.col_status": "Status",
216
+ "table.col_url": "URL",
217
+ "table.col_http": "HTTP",
218
+ "table.col_time": "Time",
219
+ "table.col_size": "Size",
220
+ "table.col_requests": "Req",
221
+ "table.tooltip_size": "Actual transfer size of all resources from the same host loaded during the scan (uncached). Per resource the largest response counts (no double counting); streamed video/audio is excluded. This is not the HTML document size but the host's total weight.",
222
+ "table.tooltip_time": "Browser load time up to the Load event (Navigation Timing API, same semantics as DevTools 'Load'). Note: during a parallel scan multiple tabs share CPU and bandwidth - the absolute values are therefore higher than a single DevTools measurement and are only meant for relative comparison within the same scan.",
223
+ "table.tooltip_requests": "Number of ALL requests the page triggered during the scan (document, scripts, images, fonts, XHR, ...) - matches the request count in the Edge network monitor.",
224
+ "table.col_errors": "Errors",
225
+ "table.col_warns": "Warns",
226
+ "table.col_404": "404",
227
+ "table.col_4xx": "4xx",
228
+ "table.col_5xx": "5xx",
229
+ "table.col_ignored": "Ignored",
230
+
231
+ "scanner.start": "Starting scan of {total} URLs (Concurrency: {concurrency}, Timeout: {timeout}s)",
232
+ "scanner.browser_started": "Browser started",
233
+ "scanner.scanning_url": "Scanning ({current}/{total}): {url}",
234
+ "scanner.result_errors": " | {count} errors",
235
+ "scanner.critical_error": "Critical error: {error}",
236
+ "scanner.browser_closed": "Browser closed",
237
+ "scanner.retry": "Retry {attempt}/{max} for {url} in {wait}s ({error})",
238
+ "scanner.waiting_network": "Waiting for network…",
239
+ "scanner.browser_recovery": "Browser recovery…",
240
+ "scanner.browser_recovery_failed": "Browser recovery failed: {error}",
241
+ "scanner.failed_after_retries": "Failed after {max} attempts: {error}",
242
+ "scanner.hard_timeout": "Hard timeout - page did not respond, skipped: {url}",
243
+ "scanner.consent_hidden": "Consent banner hidden (no consent accepted)",
244
+ "scanner.consent_accepted": "Consent accepted ({provider})",
245
+ "scanner.consent_button_clicked": "Consent button clicked: {selector}",
246
+ "scanner.no_scroll_needed": "No scroll needed (page fits viewport)",
247
+ "scanner.lazy_loading_done": "Lazy loading: {steps} scroll steps performed",
248
+ "scanner.lazy_loading_failed": "Lazy loading scroll failed: {error}",
249
+
250
+ "sitemap.fetch_failed": "Could not load sitemap after {retries} attempts: {error}",
251
+ "sitemap.file_read_error": "Could not read file: {path} ({error})",
252
+ "sitemap.xml_parse_error": "Could not parse sitemap XML: {error}",
253
+ "sitemap.discovery_robots_search": "Searching sitemap in robots.txt: {url}",
254
+ "sitemap.discovery_robots_entry": "robots.txt entry found: {url}",
255
+ "sitemap.discovery_found": "Sitemap found: {url}",
256
+ "sitemap.discovery_unreachable": "{url} unreachable, continuing …",
257
+ "sitemap.discovery_robots_unreachable": "robots.txt unreachable",
258
+ "sitemap.discovery_trying_paths": "Trying common sitemap paths …",
259
+ "sitemap.discovery_testing": "Testing: {url}",
260
+ "sitemap.discovery_not_found": "No sitemap found for {url}\n\nTested: robots.txt + {count} common paths.\nPlease specify the sitemap URL directly, e.g.:\n {example}\n\nTip: You can create a sitemap with the Sitemap Generator:\n https://michaelblaess.github.io/sitemap-generator",
261
+
262
+ "report.title": "Console Error Scanner Report",
263
+ "report.created": "Created: {timestamp} | Sitemap: {url}",
264
+ "report.urls_total": "Total URLs",
265
+ "report.scanned": "Scanned",
266
+ "report.with_errors": "With errors",
267
+ "report.site_score": "Site score",
268
+ "report.avg_size": "Avg page size",
269
+ "report.load_time": "Load time",
270
+ "report.dom_content_loaded": "DOMContentLoaded",
271
+ "report.requests": "Requests",
272
+ "report.duration": "Duration",
273
+ "report.whitelist_hits": "Whitelist hits ({count})",
274
+
275
+ "settings.tab_scanner": "Scanner",
276
+ "settings.tab_export": "Export",
277
+ "settings.jira_format_label": "JIRA table format:",
278
+ "settings.jira_format_tip": "Format of the JIRA table on export (key j). Markdown for Jira Cloud, wiki markup for older Server/Data Center instances.",
279
+ "settings.jira_format_markdown": "Markdown (Jira Cloud)",
280
+ "settings.jira_format_wiki": "Wiki markup (Server/DC)",
281
+ "settings.jira_format_hint": "Format for the JIRA export (key j). Markdown = Jira Cloud (e.g. example.atlassian.net): a markdown table is auto-converted into a real table when pasted into the comment field. The Cloud editor no longer understands the old wiki markup. Wiki markup = older Jira Server/Data Center instances.",
282
+ "jira.header": "||URL||HTTP||Status||Console||404||4xx||5xx||Details||",
283
+ "settings.tab_network": "Network",
284
+ "settings.proxy_url_label": "Proxy URL",
285
+ "settings.proxy_url_tip": "Optional HTTP proxy for crawl, sitemap and preview browser, e.g. http://proxy.example.com:8080 (corporate proxy/Zscaler). Leave empty for a direct connection.",
286
+ "settings.proxy_hint": "Behind a corporate proxy (e.g. Zscaler) enter the proxy here, otherwise connections fail with \"All connection attempts failed\". Format: http://HOST:PORT. Applies to httpx AND the Playwright browser.",
287
+ "settings.consent_label": "Cookie consent",
288
+ "settings.consent_checkbox": "Automatically accept consent",
289
+ "settings.consent_tip": "Clicks cookie banners (Usercentrics, OneTrust, Cookiebot, …) away automatically so tracking scripts load and their errors become visible. Off = banners are only visually hidden.",
290
+ "settings.scroll_label": "Lazy loading",
291
+ "settings.scroll_checkbox": "Enable lazy loading",
292
+ "settings.scroll_tip": "Scrolls every page to the bottom once and waits until lazy-loaded images and content become visible. Without this step, 404 images below the first viewport stay undetected.",
293
+ "settings.robots_label": "robots.txt",
294
+ "settings.robots_checkbox": "Respect robots.txt",
295
+ "settings.robots_tip": "Sitemap pages blocked by Disallow are skipped; the count appears in the log. Only pages are checked, not the resources they load. Turn this off only for your own sites, e.g. when a staging system blocks everything.",
296
+ "settings.concurrency_label": "Threads",
297
+ "settings.rate_label": "Rate limit",
298
+ "settings.rate_tip": "Caps how many pages are checked per minute. Without a limit the scanner works as fast as the target responds - on a large sitemap that quickly means several hundred requests per minute. Because every page goes through a real browser and loads scripts, fonts and images along the way, one page weighs several times an ordinary request. On a production system that can noticeably degrade response times. Enabled by default.",
299
+ "settings.rate_checkbox": "Throttle requests",
300
+ "settings.rate_value": "{count} pages/minute - {step}",
301
+ "settings.rate_step_gentle": "very gentle",
302
+ "settings.rate_step_careful": "gentle, recommended for production systems",
303
+ "settings.rate_step_brisk": "brisk",
304
+ "settings.rate_step_reckless": "no restraint, for test and development systems only",
305
+ "log.rate_on": "Rate limit active: at most {count} pages/minute",
306
+ "log.rate_off": "No rate limit - up to {count} pages at once, as fast as the target responds. Enable a rate limit in the settings for production systems.",
307
+ "settings.concurrency_tip": "Number of parallel browser tabs. More = faster, but higher CPU/RAM load and higher risk of rate limits.",
308
+ "settings.timeout_label": "Timeout (s)",
309
+ "settings.timeout_tip": "Maximum wait time per page (in seconds) before the scan of that URL is cancelled as timeout.",
310
+ "settings.size_warn_label": "Size warning (MB)",
311
+ "settings.size_warn_tip": "From this page size (in MB) the URL is highlighted red in the list and marked with a warning symbol. 0 = off.",
312
+ "settings.score_weight_label": "Score weight errors (%)",
313
+ "settings.score_weight_tip": "Weight of the error rate in the site score (0-100 %). The remainder applies to page size. E.g. 60 = 60 % errors, 40 % size.",
314
+ "diet.title": "Diet advisor: {url}",
315
+ "diet.subtitle": "Largest resources ({size} total)",
316
+ "diet.none": "No resource data available - please re-scan the page.",
317
+ "diet.tip": "Tip: serving images as WebP/AVIF and properly scaled usually saves the most.",
318
+ "summary.title": "Scan complete",
319
+ "summary.score_label": "Site score:",
320
+ "summary.score_breakdown": "Errors {err}% (weight {err_w}%) · Size {size}% (weight {size_w}%)",
321
+ "summary.findings": "Findings",
322
+ "summary.pages": "Pages: {total} total, {clean} clean, {err} with errors",
323
+ "summary.errors": "Errors: {errors} · Warnings: {warnings}",
324
+ "summary.avg_size": "Avg page size: {size}",
325
+ "summary.biggest_pages": "Largest pages",
326
+ "summary.big_fish": "Big fish (largest resources)",
327
+ "settings.console_level_label": "Console level",
328
+ "settings.console_level_tip": "Which console.* messages are captured: error = errors only; warn = errors + warnings (default); all = additionally info/log/debug. Info/log/debug are NOT captured as long as the level is „warn“.",
329
+ "settings.preview_label": "Page preview",
330
+ "settings.preview_checkbox": "Show a screenshot of the selected page",
331
+ "settings.preview_tip": "Renders a screenshot of the selected URL in the detail pane. Screenshots are taken in the background via a Playwright sidecar and cached to disk (HTTP validator + TTL). Right-click the image to copy it to the clipboard.",
332
+ "settings.headless_label": "Browser",
333
+ "settings.headless_checkbox": "Launch browser visibly (for debugging)",
334
+ "settings.headless_tip": "By default Chromium runs invisibly in the background. Enable to see the browser during the scan — only useful for debugging.",
335
+ "settings.user_agent_label": "User-Agent",
336
+ "settings.user_agent_placeholder": "Empty = realistic Chrome default",
337
+ "settings.user_agent_tip": "Custom User-Agent for browser requests. Empty = realistic Chrome 131 on Windows (bypasses most bot-detection heuristics).",
338
+ "settings.whitelist_label": "Whitelist path",
339
+ "settings.whitelist_placeholder": "Path to whitelist.json (empty = off)",
340
+ "settings.whitelist_tip": "JSON file with wildcard patterns (fnmatch syntax) that are ignored as known errors. Empty = no whitelist. Applied immediately.",
341
+ "settings.cookies_label": "Cookies",
342
+ "settings.cookies_placeholder": "name=value; name2=value2",
343
+ "settings.cookies_tip": "Cookies set in the browser before every scan. Format: name=value; separate multiple with ; or ,.",
344
+ "settings.app_hint": "Settings apply from the next scan; current scan data is preserved.",
345
+ "settings.info_icon": "(?)",
346
+ "settings.storage.config": "Settings (settings.json)",
347
+ "settings.storage.history": "Scan history",
348
+ "settings.storage.whitelist": "Whitelist (whitelist.json)",
349
+ "settings.storage.fault": "Hard crashes (fault.log)",
350
+ "settings.storage.crash": "Crash reports",
351
+ "settings.storage.preview_cache": "Preview cache",
352
+
353
+ "preview.select": "Pick a URL from the list to see a preview here.",
354
+ "preview.loading": "Creating screenshot",
355
+ "preview.loading_hint": "May take 2-3 seconds - accepting cookie consent and loading images (lazy-loading).",
356
+ "preview.phase.navigate": "Loading page",
357
+ "preview.phase.consent": "Accepting cookie consent",
358
+ "preview.phase.render": "Loading images",
359
+ "preview.phase.capture": "Creating screenshot",
360
+ "preview.none": "No preview available.",
361
+ "preview.error": "Preview failed.",
362
+ "preview.menu_copy": "Copy to clipboard",
363
+ "preview.menu_save": "Save as PNG…",
364
+ "preview.tooltip": "Right-click: copy to clipboard · Shift + right-click: save as PNG",
365
+
366
+ "ctx.open_url": "Open URL in browser",
367
+ "ctx.copy_url": "Copy URL",
368
+ "ctx.show_details": "Show details",
369
+ "ctx.diet": "Diet advisor",
370
+ "ctx.copy_details": "Copy details",
371
+ "ctx.export_jira": "Export JIRA",
372
+ "ctx.export_json": "Export JSON",
373
+ "ctx.export_text": "Export text (clipboard)",
374
+ "ctx.rescan": "Rescan this URL",
375
+ "ctx.show_errors_only": "Show errors only",
376
+ "ctx.show_all": "Show all",
377
+ "ctx.url_copied": "URL copied",
378
+ "ctx.rescanning": "Re-scanning: {url}",
379
+ "ctx.rescan_busy": "Please wait — a running scan blocks single-URL rescan.",
380
+ "ctx.wl_add": "Whitelist: {msg}",
381
+ "ctx.wl_remove": "Remove from whitelist: {msg}",
382
+ "ctx.wl_added": "Added to whitelist: {pattern}",
383
+ "ctx.wl_removed": "Removed {count} pattern(s) from whitelist",
384
+ "ctx.wl_already": "Already in the whitelist.",
385
+ "ctx.wl_none_removed": "No matching whitelist pattern found.",
386
+ "ctx.wl_no_file": "No whitelist file available.",
387
+ "ctx.wl_save_failed": "Could not save the whitelist.",
388
+ "preview.copied": "Screenshot copied to clipboard",
389
+ "preview.copy_failed": "Copy failed: {error}",
390
+ "preview.saved": "Screenshot saved: {path}",
391
+ "preview.no_image": "No screenshot available.",
392
+
393
+ "url_input.title": "Enter URL",
394
+ "url_input.prompt": "Website URL, sitemap URL or local sitemap.xml:",
395
+ "url_input.placeholder": "https://www.example.com",
396
+
397
+ "file_picker.title": "Select sitemap file",
398
+ "file_picker.open_button": "Open",
399
+ "file_picker.cancel_button": "Cancel",
400
+ "file_picker.filter_xml": "Sitemap (*.xml)",
401
+ "file_picker.filter_all": "All files",
402
+
403
+ "cli.banner_1": "Console Error Scanner v{version}",
404
+ "cli.banner_2": "Scans websites for console errors and HTTP errors (404, 5xx)",
405
+ "cli.examples": "Examples:",
406
+ "cli.keybindings": "Keybindings in the TUI:",
407
+ "cli.invalid_cookie": "Invalid: --cookie {cookie} (format: NAME=VALUE)",
408
+ "binding.cancel_scan": "Cancel scan",
409
+ "tooltip.cancel_scan": "Stops the running scan - the pages already checked stay in the table",
410
+ "notify.scan_cancelling": "Cancelling the scan ...",
411
+ "notify.scan_cancelled": "Cancelled - {count} checked pages stay visible.",
412
+ "notify.no_scan_active": "No scan is running.",
413
+ "log.cancel_requested": "Cancellation requested - pages already under way still finish ...",
414
+ "log.scan_cancelled": "Scan cancelled - {count} pages checked, results stay in the table.",
415
+ "subtitle.cancelling": "Cancelling ...",
416
+ "subtitle.cancelled": "Scan cancelled"
417
+ }
@@ -0,0 +1 @@
1
+ """Datenmodelle fuer Console Error Scanner."""
@@ -0,0 +1,220 @@
1
+ """History-Modell fuer Console Error Scanner.
2
+
3
+ Speichert und laedt vergangene Scan-Konfigurationen aus
4
+ ~/.console-error-scanner/history.json.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import getpass
10
+ import json
11
+ import logging
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from urllib.parse import urlparse
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class HistoryEntry:
22
+ """Einzelner Eintrag in der Scan-History.
23
+
24
+ Speichert alle Parameter eines Scans, damit dieser
25
+ spaeter wiederholt werden kann.
26
+
27
+ Attributes:
28
+ sitemap_url: URL der gescannten Sitemap.
29
+ timestamp: Zeitstempel im ISO-Format.
30
+ user: Benutzername zum Zeitpunkt des Scans.
31
+ concurrency: Parallele Browser-Tabs.
32
+ timeout: Timeout pro Seite in Sekunden.
33
+ console_level: Console-Level (error/warn/all).
34
+ url_filter: URL-Filter (Teilstring).
35
+ user_agent: Custom User-Agent oder leer.
36
+ cookies: Liste der Cookies als Dicts.
37
+ whitelist_path: Pfad zur Whitelist-JSON oder leer.
38
+ accept_consent: True=Consent akzeptieren, False=nur Banner verstecken.
39
+ trigger_lazy_load: True=Seite scrollen fuer Lazy-Loading.
40
+ """
41
+
42
+ sitemap_url: str
43
+ timestamp: str = ""
44
+ user: str = ""
45
+ concurrency: int = 8
46
+ timeout: int = 60
47
+ console_level: str = "warn"
48
+ url_filter: str = ""
49
+ user_agent: str = ""
50
+ cookies: list[dict[str, str]] = field(default_factory=list)
51
+ whitelist_path: str = ""
52
+ accept_consent: bool = True
53
+ trigger_lazy_load: bool = True
54
+
55
+ def to_dict(self) -> dict:
56
+ """Konvertiert den Eintrag in ein Dictionary fuer JSON.
57
+
58
+ Returns:
59
+ Dictionary mit allen Feldern.
60
+ """
61
+ return {
62
+ "sitemap_url": self.sitemap_url,
63
+ "timestamp": self.timestamp,
64
+ "user": self.user,
65
+ "concurrency": self.concurrency,
66
+ "timeout": self.timeout,
67
+ "console_level": self.console_level,
68
+ "url_filter": self.url_filter,
69
+ "user_agent": self.user_agent,
70
+ "cookies": self.cookies,
71
+ "whitelist_path": self.whitelist_path,
72
+ "accept_consent": self.accept_consent,
73
+ "trigger_lazy_load": self.trigger_lazy_load,
74
+ }
75
+
76
+ @staticmethod
77
+ def from_dict(data: dict) -> HistoryEntry:
78
+ """Erstellt einen HistoryEntry aus einem Dictionary.
79
+
80
+ Args:
81
+ data: Dictionary mit den Feldern des Eintrags.
82
+
83
+ Returns:
84
+ Neuer HistoryEntry.
85
+ """
86
+ return HistoryEntry(
87
+ sitemap_url=data.get("sitemap_url", ""),
88
+ timestamp=data.get("timestamp", ""),
89
+ user=data.get("user", ""),
90
+ concurrency=data.get("concurrency", 8),
91
+ timeout=data.get("timeout", 60),
92
+ console_level=data.get("console_level", "warn"),
93
+ url_filter=data.get("url_filter", ""),
94
+ user_agent=data.get("user_agent", ""),
95
+ cookies=data.get("cookies", []),
96
+ whitelist_path=data.get("whitelist_path", ""),
97
+ accept_consent=data.get("accept_consent", True),
98
+ trigger_lazy_load=data.get("trigger_lazy_load", True),
99
+ )
100
+
101
+ def display_label(self) -> str:
102
+ """Erzeugt ein kompaktes Label fuer die Anzeige in der History-Liste.
103
+
104
+ Format: "2026-02-13 14:30 | www.example.com | --cookie ... | --whitelist ..."
105
+
106
+ Returns:
107
+ Kurzform-String fuer die Listenanzeige.
108
+ """
109
+ # Datum kuerzen: nur YYYY-MM-DD HH:MM
110
+ date_part = self.timestamp[:16].replace("T", " ") if self.timestamp else "?"
111
+
112
+ # Hostname extrahieren
113
+ try:
114
+ host = urlparse(self.sitemap_url).hostname or self.sitemap_url
115
+ except Exception:
116
+ host = self.sitemap_url
117
+
118
+ parts = [date_part, host]
119
+
120
+ if self.cookies:
121
+ cookie_names = ", ".join(c.get("name", "?") for c in self.cookies)
122
+ parts.append(f"--cookie {cookie_names}")
123
+
124
+ if self.whitelist_path:
125
+ parts.append(f"--whitelist {self.whitelist_path}")
126
+
127
+ if self.url_filter:
128
+ parts.append(f"--filter {self.url_filter}")
129
+
130
+ if self.user_agent:
131
+ parts.append("--user-agent ...")
132
+
133
+ if not self.accept_consent:
134
+ parts.append("--no-consent")
135
+
136
+ if not self.trigger_lazy_load:
137
+ parts.append("--no-scroll")
138
+
139
+ return " | ".join(parts)
140
+
141
+
142
+ class History:
143
+ """Verwaltet die Scan-History in ~/.console-error-scanner/history.json.
144
+
145
+ Stellt statische Methoden zum Laden, Speichern und Hinzufuegen
146
+ von History-Eintraegen bereit.
147
+ """
148
+
149
+ HISTORY_DIR = Path.home() / ".console-error-scanner"
150
+ HISTORY_FILE = HISTORY_DIR / "history.json"
151
+ MAX_ENTRIES = 50
152
+
153
+ @staticmethod
154
+ def load() -> list[HistoryEntry]:
155
+ """Laedt die History aus der JSON-Datei.
156
+
157
+ Gibt eine leere Liste zurueck bei Fehler oder fehlender Datei.
158
+
159
+ Returns:
160
+ Liste der HistoryEntry-Objekte (neueste zuerst).
161
+ """
162
+ if not History.HISTORY_FILE.is_file():
163
+ return []
164
+
165
+ try:
166
+ raw = History.HISTORY_FILE.read_text(encoding="utf-8")
167
+ data = json.loads(raw)
168
+ if not isinstance(data, list):
169
+ return []
170
+ return [HistoryEntry.from_dict(item) for item in data]
171
+ except Exception as exc:
172
+ logger.warning("History konnte nicht geladen werden: %s", exc)
173
+ return []
174
+
175
+ @staticmethod
176
+ def save(entries: list[HistoryEntry]) -> None:
177
+ """Speichert die History in die JSON-Datei.
178
+
179
+ Erstellt das Verzeichnis falls es nicht existiert.
180
+
181
+ Args:
182
+ entries: Liste der HistoryEntry-Objekte.
183
+ """
184
+ try:
185
+ History.HISTORY_DIR.mkdir(parents=True, exist_ok=True)
186
+ data = [entry.to_dict() for entry in entries]
187
+ History.HISTORY_FILE.write_text(
188
+ json.dumps(data, indent=2, ensure_ascii=False),
189
+ encoding="utf-8",
190
+ )
191
+ except Exception as exc:
192
+ logger.warning("History konnte nicht gespeichert werden: %s", exc)
193
+
194
+ @staticmethod
195
+ def add(entry: HistoryEntry) -> None:
196
+ """Fuegt einen neuen Eintrag an den Anfang der History hinzu.
197
+
198
+ Laedt die aktuelle History, stellt den neuen Eintrag voran,
199
+ kuerzt auf MAX_ENTRIES und speichert.
200
+
201
+ Args:
202
+ entry: Der neue HistoryEntry.
203
+ """
204
+ # Timestamp und User setzen falls nicht vorhanden
205
+ if not entry.timestamp:
206
+ entry.timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
207
+ if not entry.user:
208
+ try:
209
+ entry.user = getpass.getuser()
210
+ except Exception:
211
+ entry.user = "unknown"
212
+
213
+ entries = History.load()
214
+ entries.insert(0, entry)
215
+
216
+ # Auf Maximum kuerzen
217
+ if len(entries) > History.MAX_ENTRIES:
218
+ entries = entries[: History.MAX_ENTRIES]
219
+
220
+ History.save(entries)