Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/repo-settings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"description": "Finds public proxies that work on the sites you need. Browse a 150-source catalog, collect supported feeds, check against your services, and use ready lists, a rotating gateway and an API. macOS/Windows app, CLI, Docker, 12 languages.",
"description": "Desktop proxy workbench for macOS and Windows, plus CLI and Docker. Browse 150 cataloged sources, collect 106 supported feeds, verify proxies against your own services, then use ranked exports, pools, a rotating gateway and a local API.",
"homepage_from_pages": true,
"has_discussions": true,
"has_issues": true,
"has_wiki": false,
"has_projects": false,
"topics": ["proxy", "proxy-checker", "proxy-list", "free-proxy", "free-proxy-list", "proxy-scraper", "socks5", "socks5-proxy", "http-proxy", "proxy-tester", "elite-proxy", "anonymity", "web-scraping", "dnsbl", "geoip", "python", "asyncio", "rotating-proxy", "self-hosted", "docker"],
"topics": ["proxy", "proxy-checker", "proxy-list", "free-proxy", "http-proxy", "socks4", "socks5", "rotating-proxy", "proxy-gateway", "proxy-api", "desktop-app", "macos", "windows", "anonymity", "web-scraping", "dnsbl", "geoip", "python", "self-hosted", "docker"],
"pages": {"branch": "main", "path": "/docs"}
}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

The format follows Keep a Changelog and semantic versioning.

## [3.0.3] — 2026-09-27

### Fixed

- Scan target URLs remain visible beside their HTTP/HTTPS selector on desktop and tablet screens.
- The Results page keeps proxy rows unobstructed; bulk scope actions sit below the table until a row is selected.
- Example proxy and denylist placeholders use reserved documentation IP ranges.
- Export publication retries brief Windows file locks while preserving the previous published files if a lock persists.

### Documentation

- Rebuilt screenshots, the interface tour and social preview from the current app with synthetic data; refreshed English and Russian guides and the project website to describe all eight tabs, installation choices and limits accurately.

## [3.0.2] — 2026-09-27

### Fixed
Expand Down
135 changes: 61 additions & 74 deletions README.md

Large diffs are not rendered by default.

162 changes: 65 additions & 97 deletions README.ru.md

Large diffs are not rendered by default.

Binary file modified docs/assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/details-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/gateway-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/keys-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/pools-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/results-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/results-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/ru/details-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/ru/gateway-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/ru/keys-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/ru/pools-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/ru/results-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/ru/results-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/ru/scan-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/ru/sources-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/screenshots/scan-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/screenshots/sources-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
463 changes: 326 additions & 137 deletions docs/index.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packaging/windows-installer.iss
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
; Per-user installer for the Windows desktop build.
;
; iscc /DProductVersion=3.0.2 /DOutDir=C:\path\to\dist /DSourceDir=C:\path\to\dist packaging\windows-installer.iss
; iscc /DProductVersion=3.0.3 /DOutDir=C:\path\to\dist /DSourceDir=C:\path\to\dist packaging\windows-installer.iss
;
; PrivilegesRequired=lowest is the whole point: the app writes to per-user
; folders, so it never needs an administrator, and it never installs anything
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/branding.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

PRODUCT_NAME = "Proxy Workbench"
PRODUCT_ID = "ProxyWorkbench"
PRODUCT_VERSION = "3.0.2"
PRODUCT_VERSION = "3.0.3"
DEFAULT_REQUEST_PROFILE = "workbench"
PROJECT_URL = "https://github.com/DavidVoitenko/proxy-workbench"
# Newest built-in source list, fetched only when the user asks for it. This is
Expand Down
6 changes: 3 additions & 3 deletions proxy_workbench/exportsvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from . import core, formats
from .i18n import tr
from .proxytool import (PROTOCOL_ALIASES, PROTOCOL_EXPORTS, PROTOCOLS, PROXYCHAINS_TYPES,
proxy_protocol, reputation_status)
proxy_protocol, reputation_status, retry_file_access)

__all__ = [
'ARTIFACT_KINDS', 'CLIENT_BINARY_ENV', 'CLIENT_SIDECAR_NAME', 'CLIENT_TARGET_ENV', 'DIAGNOSTIC_POINTER_NAME',
Expand Down Expand Up @@ -1488,8 +1488,8 @@ def _atomic_write(path: Path, content: str) -> None:
"""Replace one file in one step; a reader never sees a half-written pointer."""
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_name(path.name + '.tmp')
temporary.write_bytes(content.encode('utf-8'))
os.replace(temporary, path)
retry_file_access(lambda: temporary.write_bytes(content.encode('utf-8')))
retry_file_access(lambda: os.replace(temporary, path))


def write_snapshot(directory: Any, rows: Sequence[Mapping[str, Any]], *, scope: ExportScope,
Expand Down
36 changes: 20 additions & 16 deletions proxy_workbench/proxytool.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,23 +560,27 @@ def public_url(value):
ATOMIC_REPLACE_ATTEMPTS = 40


def atomic(path, content):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
temp = path.with_name(path.name + '.tmp')
temp.write_text(content, encoding='utf-8')
# Windows refuses to replace a file another process has open for reading
# (the GUI polls progress while the CLI writes it); such locks are brief.
def retry_file_access(operation):
"""Retry a brief Windows file lock without hiding a persistent I/O error."""
for attempt in range(ATOMIC_REPLACE_ATTEMPTS):
try:
temp.replace(path)
return
return operation()
except PermissionError:
if attempt == ATOMIC_REPLACE_ATTEMPTS - 1:
raise
time.sleep(0.05)


def atomic(path, content):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
temp = path.with_name(path.name + '.tmp')
retry_file_access(lambda: temp.write_text(content, encoding='utf-8'))
# Windows refuses to replace a file another process has open for reading
# (the GUI polls progress while the CLI writes it); such locks are brief.
retry_file_access(lambda: temp.replace(path))


EXPORT_GENERATION_RETENTION = 3
SNAPSHOT_SCHEMA_VERSION = 1
MIN_FRESHNESS_SECONDS = 2 * 60 * 60
Expand Down Expand Up @@ -758,21 +762,21 @@ def _publish_legacy_files(directory, generation, names, report, before_commit=No
for name in legacy_names:
source = generation/name
temporary = directory/(name+'.publish-tmp')
temporary.unlink(missing_ok=True)
retry_file_access(lambda: temporary.unlink(missing_ok=True))
if name == 'status.json':
temporary.write_text(json.dumps(report, indent=2) + '\n', encoding='utf-8')
retry_file_access(lambda: temporary.write_text(json.dumps(report, indent=2) + '\n', encoding='utf-8'))
else:
shutil.copyfile(source, temporary)
retry_file_access(lambda: shutil.copyfile(source, temporary))
staged[name] = temporary
for name in legacy_names:
target = directory/name
if target.exists() or target.is_symlink():
backup = directory/('.'+name+'.publish-backup')
backup.unlink(missing_ok=True)
target.replace(backup)
retry_file_access(lambda: backup.unlink(missing_ok=True))
retry_file_access(lambda: target.replace(backup))
backups[name] = backup
for name, temporary in staged.items():
temporary.replace(directory/name)
retry_file_access(lambda: temporary.replace(directory/name))
installed.append(directory/name)
if before_commit is not None:
before_commit()
Expand All @@ -782,7 +786,7 @@ def _publish_legacy_files(directory, generation, names, report, before_commit=No
path.unlink()
for name, backup in reversed(list(backups.items())):
with contextlib.suppress(OSError):
backup.replace(directory/name)
retry_file_access(lambda: backup.replace(directory/name))
raise
finally:
for temporary in staged.values():
Expand Down
8 changes: 4 additions & 4 deletions proxy_workbench/ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const messages = {
'identity.denylist': 'Local denylist',
'identity.local': 'Local denylist',
'identity.localHint': 'IPs or CIDR blocks permanently excluded from tests and exports.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# comment',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# comment',
'identity.denylistHint': 'IP, CIDR or exact proxy address. Stored only in data/denylist.txt and never included in the code.',
'identity.applyDenylist': 'Apply the local denylist during collection, checks and export',
'profile.workbench': 'Workbench profile',
Expand Down Expand Up @@ -1426,7 +1426,7 @@ const messages = {
'identity.denylist': 'Локальный denylist',
'identity.local': 'Локальный denylist',
'identity.localHint': 'IP и CIDR, навсегда исключённые из проверок и экспорта.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# комментарий',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# комментарий',
'identity.denylistHint': 'IP, CIDR или точный адрес прокси. Хранится только в data/denylist.txt и не включается в код.',
'identity.applyDenylist': 'Применять локальный denylist при сборе, проверке и экспорте',
'profile.workbench': 'Рабочий профиль',
Expand Down Expand Up @@ -5143,8 +5143,8 @@ function updateSelectionUI() {

if (bar) {
const hasRows = document.querySelectorAll('.proxy-select-box').length > 0;
// The bar is also the only home of the bulk-scope selector: keep it visible
// on a non-empty table even with nothing ticked, hiding selection-only controls.
// The bar is also the only home of the bulk-scope selector. With no rows
// selected, CSS keeps it in the page flow instead of covering table rows.
const isVisible = count > 0 || (currentTab === 'results' && hasRows);
bar.classList.toggle('hidden', !isVisible);
bar.classList.toggle('active', isVisible);
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Lokale Sperrliste',
'identity.local': 'Lokale Sperrliste',
'identity.localHint': 'IPs oder CIDR-Blöcke, die dauerhaft von Tests und Exporten ausgeschlossen sind.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# Kommentar',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# Kommentar',
'identity.denylistHint': 'IP, CIDR oder exakte Proxy-Adresse. Wird nur in data/denylist.txt gespeichert und nie im Code mitgeliefert.',
'identity.applyDenylist': 'Lokale Sperrliste bei Erfassung, Prüfungen und Export anwenden',
'profile.workbench': 'Workbench-Profil',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Lista de bloqueo local',
'identity.local': 'Lista de bloqueo local',
'identity.localHint': 'IP o bloques CIDR excluidos permanentemente de las pruebas y las exportaciones.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# comentario',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# comentario',
'identity.denylistHint': 'IP, CIDR o dirección exacta de proxy. Se guarda solo en data/denylist.txt y nunca se incluye en el código.',
'identity.applyDenylist': 'Aplicar la lista de bloqueo local durante la recopilación, las comprobaciones y la exportación',
'profile.workbench': 'Perfil Workbench',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Liste d\'exclusion locale',
'identity.local': 'Liste d\'exclusion locale',
'identity.localHint': 'IP ou blocs CIDR exclus définitivement des tests et des exports.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# commentaire',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# commentaire',
'identity.denylistHint': 'IP, CIDR ou adresse de proxy exacte. Stocké uniquement dans data/denylist.txt et jamais inclus dans le code.',
'identity.applyDenylist': 'Appliquer la liste d\'exclusion locale pendant la collecte, les vérifications et l\'export',
'profile.workbench': 'Profil Workbench',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/it.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Lista di esclusione locale',
'identity.local': 'Lista di esclusione locale',
'identity.localHint': 'IP o blocchi CIDR esclusi in modo permanente da test ed esportazioni.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# commento',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# commento',
'identity.denylistHint': 'IP, CIDR o indirizzo proxy esatto. Salvato solo in data/denylist.txt e mai incluso nel codice.',
'identity.applyDenylist': 'Applica la lista di esclusione locale durante raccolta, verifiche ed esportazione',
'profile.workbench': 'Profilo Workbench',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'ローカル拒否リスト',
'identity.local': 'ローカル拒否リスト',
'identity.localHint': 'テストとエクスポートから恒久的に除外するIPまたはCIDRブロック。',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# コメント',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# コメント',
'identity.denylistHint': 'IP、CIDRまたは正確なプロキシアドレス。data/denylist.txt のみに保存され、コードには含まれません。',
'identity.applyDenylist': '収集・チェック・エクスポート時にローカル拒否リストを適用する',
'profile.workbench': 'Workbenchプロファイル',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/pl.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Lokalna lista blokowania',
'identity.local': 'Lokalna lista blokowania',
'identity.localHint': 'Adresy IP lub bloki CIDR trwale wykluczone z testów i eksportów.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# komentarz',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# komentarz',
'identity.denylistHint': 'IP, CIDR lub dokładny adres proxy. Zapisywane tylko w data/denylist.txt i nigdy nie dołączane do kodu.',
'identity.applyDenylist': 'Stosuj lokalną listę blokowania podczas zbierania, sprawdzania i eksportu',
'profile.workbench': 'Profil Workbench',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Lista de bloqueio local',
'identity.local': 'Lista de bloqueio local',
'identity.localHint': 'IPs ou blocos CIDR permanentemente excluídos dos testes e exportações.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# comentário',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# comentário',
'identity.denylistHint': 'IP, CIDR ou endereço exato de proxy. Armazenado apenas em data/denylist.txt e nunca incluído no código.',
'identity.applyDenylist': 'Aplicar a lista de bloqueio local durante coleta, verificações e exportação',
'profile.workbench': 'Perfil Workbench',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/tr.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Yerel engel listesi',
'identity.local': 'Yerel engel listesi',
'identity.localHint': 'Testlerden ve dışa aktarmalardan kalıcı olarak hariç tutulan IP\'ler veya CIDR blokları.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# yorum',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# yorum',
'identity.denylistHint': 'IP, CIDR veya tam proxy adresi. Yalnızca data/denylist.txt içinde saklanır ve asla koda dahil edilmez.',
'identity.applyDenylist': 'Toplama, kontroller ve dışa aktarma sırasında yerel engel listesini uygula',
'profile.workbench': 'Workbench profili',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/uk.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': 'Локальний чорний список',
'identity.local': 'Локальний чорний список',
'identity.localHint': 'IP або CIDR-блоки, назавжди виключені з тестів та експортів.',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# коментар',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# коментар',
'identity.denylistHint': 'IP, CIDR або точна адреса проксі. Зберігається лише в data/denylist.txt і ніколи не включається в код.',
'identity.applyDenylist': 'Застосовувати локальний чорний список під час збору, перевірок та експорту',
'profile.workbench': 'Профіль Workbench',
Expand Down
2 changes: 1 addition & 1 deletion proxy_workbench/ui/i18n/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'identity.denylist': '本地黑名单',
'identity.local': '本地黑名单',
'identity.localHint': '永久排除在检测和导出之外的 IP 或 CIDR 网段。',
'identity.denylistPlaceholder': '11.0.0.0/24\nhttp://11.0.0.1:8080\n# comment',
'identity.denylistPlaceholder': '192.0.2.0/24\nhttp://192.0.2.10:8080\n# comment',
'identity.denylistHint': 'IP、CIDR 或精确代理地址。仅存储在 data/denylist.txt 中,绝不写入代码。',
'identity.applyDenylist': '在采集、检测和导出时应用本地黑名单',
'profile.workbench': 'Workbench 方案',
Expand Down
Loading
Loading