mirror of
				https://github.com/dgtlmoon/changedetection.io.git
				synced 2025-10-31 06:37:41 +00:00 
			
		
		
		
	Compare commits
	
		
			11 Commits
		
	
	
		
			cross-plat
			...
			optional-p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 459f42aa69 | ||
|   | b988c62b75 | ||
|   | 3a596b2ae6 | ||
|   | f2849b14ba | ||
|   | a06b3da00c | ||
|   | 35ac213a7d | ||
|   | 46dac6366c | ||
|   | d6f93c080d | ||
|   | 7e31ba8ea7 | ||
|   | 5bd7865b02 | ||
|   | 2cb4beeda0 | 
| @@ -243,6 +243,14 @@ class base_html_playwright(Fetcher): | ||||
|         if proxy_override: | ||||
|             self.proxy = {'server': proxy_override} | ||||
|  | ||||
|         if self.proxy: | ||||
|             # Playwright needs separate username and password values | ||||
|             from urllib.parse import urlparse | ||||
|             parsed = urlparse(self.proxy.get('server')) | ||||
|             if parsed.username: | ||||
|                 self.proxy['username'] = parsed.username | ||||
|                 self.proxy['password'] = parsed.password | ||||
|  | ||||
|     def screenshot_step(self, step_n=''): | ||||
|  | ||||
|         # There's a bug where we need to do it twice or it doesnt take the whole page, dont know why. | ||||
| @@ -370,7 +378,7 @@ class base_html_playwright(Fetcher): | ||||
|                 context.close() | ||||
|                 browser.close() | ||||
|                 print ("Content Fetcher > Content was empty") | ||||
|                 raise EmptyReply(url=url, status_code=None) | ||||
|                 raise EmptyReply(url=url, status_code=response.status) | ||||
|  | ||||
|             # Bug 2(?) Set the viewport size AFTER loading the page | ||||
|             self.page.set_viewport_size({"width": 1280, "height": 1024}) | ||||
|   | ||||
| @@ -426,6 +426,13 @@ class watchForm(commonSettingsForm): | ||||
|         return result | ||||
|  | ||||
|  | ||||
| class SingleExtraProxy(Form): | ||||
|  | ||||
|     # maybe better to set some <script>var.. | ||||
|     proxy_name = StringField('Name', [validators.Optional()], render_kw={"placeholder": "Name"}) | ||||
|     proxy_url = StringField('URL', [validators.Optional()], render_kw={"placeholder": "http://user:pass@...:3128"}) | ||||
|     # @todo do the validation here instead | ||||
|  | ||||
| # datastore.data['settings']['requests'].. | ||||
| class globalSettingsRequestForm(Form): | ||||
|     time_between_check = FormField(TimeBetweenCheckForm) | ||||
| @@ -433,6 +440,15 @@ class globalSettingsRequestForm(Form): | ||||
|     jitter_seconds = IntegerField('Random jitter seconds ± check', | ||||
|                                   render_kw={"style": "width: 5em;"}, | ||||
|                                   validators=[validators.NumberRange(min=0, message="Should contain zero or more seconds")]) | ||||
|     extra_proxies = FieldList(FormField(SingleExtraProxy), min_entries=5) | ||||
|  | ||||
|     def validate_extra_proxies(self, extra_validators=None): | ||||
|         for e in self.data['extra_proxies']: | ||||
|             if e.get('proxy_name') or e.get('proxy_url'): | ||||
|                 if not e.get('proxy_name','').strip() or not e.get('proxy_url','').strip(): | ||||
|                     self.extra_proxies.errors.append('Both a name, and a Proxy URL is required.') | ||||
|                     return False | ||||
|  | ||||
|  | ||||
| # datastore.data['settings']['application'].. | ||||
| class globalSettingsApplicationForm(commonSettingsForm): | ||||
|   | ||||
| @@ -15,11 +15,12 @@ class model(dict): | ||||
|                 'headers': { | ||||
|                 }, | ||||
|                 'requests': { | ||||
|                     'timeout': int(getenv("DEFAULT_SETTINGS_REQUESTS_TIMEOUT", "45")),  # Default 45 seconds | ||||
|                     'time_between_check': {'weeks': None, 'days': None, 'hours': 3, 'minutes': None, 'seconds': None}, | ||||
|                     'extra_proxies': [], # Configurable extra proxies via the UI | ||||
|                     'jitter_seconds': 0, | ||||
|                     'proxy': None, # Preferred proxy connection | ||||
|                     'time_between_check': {'weeks': None, 'days': None, 'hours': 3, 'minutes': None, 'seconds': None}, | ||||
|                     'timeout': int(getenv("DEFAULT_SETTINGS_REQUESTS_TIMEOUT", "45")),  # Default 45 seconds | ||||
|                     'workers': int(getenv("DEFAULT_SETTINGS_REQUESTS_WORKERS", "10")),  # Number of threads, lower is better for slow connections | ||||
|                     'proxy': None # Preferred proxy connection | ||||
|                 }, | ||||
|                 'application': { | ||||
|                     'api_access_token_enabled': True, | ||||
|   | ||||
| @@ -8,6 +8,15 @@ set -e | ||||
| docker run --network changedet-network -d --name squid-one --hostname squid-one --rm -v `pwd`/tests/proxy_list/squid.conf:/etc/squid/conf.d/debian.conf ubuntu/squid:4.13-21.10_edge | ||||
| docker run --network changedet-network -d --name squid-two --hostname squid-two --rm -v `pwd`/tests/proxy_list/squid.conf:/etc/squid/conf.d/debian.conf ubuntu/squid:4.13-21.10_edge | ||||
|  | ||||
| # Used for configuring a custom proxy URL via the UI | ||||
| docker run --network changedet-network -d \ | ||||
|   --name squid-custom \ | ||||
|   --hostname squid-squid-custom \ | ||||
|   --rm \ | ||||
|   -v `pwd`/tests/proxy_list/squid-auth.conf:/etc/squid/conf.d/debian.conf \ | ||||
|   -v `pwd`/tests/proxy_list/squid-passwords.txt:/etc/squid3/passwords \ | ||||
|   ubuntu/squid:4.13-21.10_edge | ||||
|  | ||||
|  | ||||
| ## 2nd test actually choose the preferred proxy from proxies.json | ||||
|  | ||||
| @@ -32,3 +41,19 @@ then | ||||
|   echo "Did not see a request to chosen.changedetection.io in the squid logs (while checking preferred proxy - squid two)" | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
|  | ||||
| # Test the UI configurable proxies | ||||
|  | ||||
| docker run --network changedet-network \ | ||||
|   test-changedetectionio \ | ||||
|   bash -c 'cd changedetectionio && pytest tests/proxy_list/test_select_custom_proxy.py' | ||||
|  | ||||
|  | ||||
| # Should see a request for one.changedetection.io in there | ||||
| docker logs squid-custom 2>/dev/null|grep "TCP_TUNNEL.200.*changedetection.io" | ||||
| if [ $? -ne 0 ] | ||||
| then | ||||
|   echo "Did not see a valid request to changedetection.io in the squid logs (while checking preferred proxy - squid two)" | ||||
|   exit 1 | ||||
| fi | ||||
|   | ||||
| @@ -0,0 +1,17 @@ | ||||
| ul#requests-extra_proxies { | ||||
|   list-style: none; | ||||
|   /* tidy up the table to look more "inline" */ | ||||
|   li { | ||||
|     > label { | ||||
|       display: none; | ||||
|     } | ||||
|  | ||||
|   } | ||||
|   /* each proxy entry is a `table` */ | ||||
|   table { | ||||
|     tr { | ||||
|       display: inline; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -2,10 +2,11 @@ | ||||
|  * -- BASE STYLES -- | ||||
|  */ | ||||
|  | ||||
| @import "parts/_variables"; | ||||
| @import "parts/_spinners"; | ||||
| @import "parts/_browser-steps"; | ||||
| @import "parts/_arrows"; | ||||
| @import "parts/_browser-steps"; | ||||
| @import "parts/_extra_proxies"; | ||||
| @import "parts/_spinners"; | ||||
| @import "parts/_variables"; | ||||
|  | ||||
| body { | ||||
|   color: var(--color-text); | ||||
|   | ||||
| @@ -1,6 +1,139 @@ | ||||
| /* | ||||
|  * -- BASE STYLES -- | ||||
|  */ | ||||
| .arrow { | ||||
|   border: solid #1b98f8; | ||||
|   border-width: 0 2px 2px 0; | ||||
|   display: inline-block; | ||||
|   padding: 3px; } | ||||
|   .arrow.right { | ||||
|     transform: rotate(-45deg); | ||||
|     -webkit-transform: rotate(-45deg); } | ||||
|   .arrow.left { | ||||
|     transform: rotate(135deg); | ||||
|     -webkit-transform: rotate(135deg); } | ||||
|   .arrow.up, .arrow.asc { | ||||
|     transform: rotate(-135deg); | ||||
|     -webkit-transform: rotate(-135deg); } | ||||
|   .arrow.down, .arrow.desc { | ||||
|     transform: rotate(45deg); | ||||
|     -webkit-transform: rotate(45deg); } | ||||
|  | ||||
| #browser_steps { | ||||
|   /* convert rows to horizontal cells */ } | ||||
|   #browser_steps th { | ||||
|     display: none; } | ||||
|   #browser_steps li { | ||||
|     list-style: decimal; | ||||
|     padding: 5px; } | ||||
|     #browser_steps li:not(:first-child):hover { | ||||
|       opacity: 1.0; } | ||||
|     #browser_steps li .control { | ||||
|       padding-left: 5px; | ||||
|       padding-right: 5px; } | ||||
|       #browser_steps li .control a { | ||||
|         font-size: 70%; } | ||||
|     #browser_steps li.empty { | ||||
|       padding: 0px; | ||||
|       opacity: 0.35; } | ||||
|       #browser_steps li.empty .control { | ||||
|         display: none; } | ||||
|     #browser_steps li:hover { | ||||
|       background: #eee; } | ||||
|     #browser_steps li > label { | ||||
|       display: none; } | ||||
|  | ||||
| #browser-steps-fieldlist { | ||||
|   height: 100%; | ||||
|   overflow-y: scroll; } | ||||
|  | ||||
| #browser-steps .flex-wrapper { | ||||
|   display: flex; | ||||
|   flex-flow: row; | ||||
|   height: 600px; | ||||
|   /*@todo make this dynamic */ } | ||||
|  | ||||
| /*  this is duplicate :( */ | ||||
| #browsersteps-selector-wrapper { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   overflow-y: scroll; | ||||
|   position: relative; | ||||
|   /* nice tall skinny one */ } | ||||
|   #browsersteps-selector-wrapper > img { | ||||
|     position: absolute; | ||||
|     max-width: 100%; } | ||||
|   #browsersteps-selector-wrapper > canvas { | ||||
|     position: relative; | ||||
|     max-width: 100%; } | ||||
|     #browsersteps-selector-wrapper > canvas:hover { | ||||
|       cursor: pointer; } | ||||
|   #browsersteps-selector-wrapper .loader { | ||||
|     position: absolute; | ||||
|     left: 50%; | ||||
|     top: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
|     margin-left: -40px; | ||||
|     z-index: 100; | ||||
|     max-width: 350px; | ||||
|     text-align: center; } | ||||
|   #browsersteps-selector-wrapper .spinner, #browsersteps-selector-wrapper .spinner:after { | ||||
|     width: 80px; | ||||
|     height: 80px; | ||||
|     font-size: 3px; } | ||||
|   #browsersteps-selector-wrapper #browsersteps-click-start { | ||||
|     color: var(--color-grey-400); } | ||||
|     #browsersteps-selector-wrapper #browsersteps-click-start:hover { | ||||
|       cursor: pointer; } | ||||
|  | ||||
| ul#requests-extra_proxies { | ||||
|   list-style: none; | ||||
|   /* tidy up the table to look more "inline" */ | ||||
|   /* each proxy entry is a `table` */ } | ||||
|   ul#requests-extra_proxies li > label { | ||||
|     display: none; } | ||||
|   ul#requests-extra_proxies table tr { | ||||
|     display: inline; } | ||||
|  | ||||
| /* spinner */ | ||||
| .spinner, | ||||
| .spinner:after { | ||||
|   border-radius: 50%; | ||||
|   width: 10px; | ||||
|   height: 10px; } | ||||
|  | ||||
| .spinner { | ||||
|   margin: 0px auto; | ||||
|   font-size: 3px; | ||||
|   vertical-align: middle; | ||||
|   display: inline-block; | ||||
|   text-indent: -9999em; | ||||
|   border-top: 1.1em solid rgba(38, 104, 237, 0.2); | ||||
|   border-right: 1.1em solid rgba(38, 104, 237, 0.2); | ||||
|   border-bottom: 1.1em solid rgba(38, 104, 237, 0.2); | ||||
|   border-left: 1.1em solid #2668ed; | ||||
|   -webkit-transform: translateZ(0); | ||||
|   -ms-transform: translateZ(0); | ||||
|   transform: translateZ(0); | ||||
|   -webkit-animation: load8 1.1s infinite linear; | ||||
|   animation: load8 1.1s infinite linear; } | ||||
|  | ||||
| @-webkit-keyframes load8 { | ||||
|   0% { | ||||
|     -webkit-transform: rotate(0deg); | ||||
|     transform: rotate(0deg); } | ||||
|   100% { | ||||
|     -webkit-transform: rotate(360deg); | ||||
|     transform: rotate(360deg); } } | ||||
|  | ||||
| @keyframes load8 { | ||||
|   0% { | ||||
|     -webkit-transform: rotate(0deg); | ||||
|     transform: rotate(0deg); } | ||||
|   100% { | ||||
|     -webkit-transform: rotate(360deg); | ||||
|     transform: rotate(360deg); } } | ||||
|  | ||||
| /** | ||||
|  * CSS custom properties (aka variables). | ||||
|  */ | ||||
| @@ -138,130 +271,6 @@ html[data-darkmode="true"] { | ||||
|     html[data-darkmode="true"] .watch-table .unviewed.error { | ||||
|       color: var(--color-watch-table-error); } | ||||
|  | ||||
| /* spinner */ | ||||
| .spinner, | ||||
| .spinner:after { | ||||
|   border-radius: 50%; | ||||
|   width: 10px; | ||||
|   height: 10px; } | ||||
|  | ||||
| .spinner { | ||||
|   margin: 0px auto; | ||||
|   font-size: 3px; | ||||
|   vertical-align: middle; | ||||
|   display: inline-block; | ||||
|   text-indent: -9999em; | ||||
|   border-top: 1.1em solid rgba(38, 104, 237, 0.2); | ||||
|   border-right: 1.1em solid rgba(38, 104, 237, 0.2); | ||||
|   border-bottom: 1.1em solid rgba(38, 104, 237, 0.2); | ||||
|   border-left: 1.1em solid #2668ed; | ||||
|   -webkit-transform: translateZ(0); | ||||
|   -ms-transform: translateZ(0); | ||||
|   transform: translateZ(0); | ||||
|   -webkit-animation: load8 1.1s infinite linear; | ||||
|   animation: load8 1.1s infinite linear; } | ||||
|  | ||||
| @-webkit-keyframes load8 { | ||||
|   0% { | ||||
|     -webkit-transform: rotate(0deg); | ||||
|     transform: rotate(0deg); } | ||||
|   100% { | ||||
|     -webkit-transform: rotate(360deg); | ||||
|     transform: rotate(360deg); } } | ||||
|  | ||||
| @keyframes load8 { | ||||
|   0% { | ||||
|     -webkit-transform: rotate(0deg); | ||||
|     transform: rotate(0deg); } | ||||
|   100% { | ||||
|     -webkit-transform: rotate(360deg); | ||||
|     transform: rotate(360deg); } } | ||||
|  | ||||
| #browser_steps { | ||||
|   /* convert rows to horizontal cells */ } | ||||
|   #browser_steps th { | ||||
|     display: none; } | ||||
|   #browser_steps li { | ||||
|     list-style: decimal; | ||||
|     padding: 5px; } | ||||
|     #browser_steps li:not(:first-child):hover { | ||||
|       opacity: 1.0; } | ||||
|     #browser_steps li .control { | ||||
|       padding-left: 5px; | ||||
|       padding-right: 5px; } | ||||
|       #browser_steps li .control a { | ||||
|         font-size: 70%; } | ||||
|     #browser_steps li.empty { | ||||
|       padding: 0px; | ||||
|       opacity: 0.35; } | ||||
|       #browser_steps li.empty .control { | ||||
|         display: none; } | ||||
|     #browser_steps li:hover { | ||||
|       background: #eee; } | ||||
|     #browser_steps li > label { | ||||
|       display: none; } | ||||
|  | ||||
| #browser-steps-fieldlist { | ||||
|   height: 100%; | ||||
|   overflow-y: scroll; } | ||||
|  | ||||
| #browser-steps .flex-wrapper { | ||||
|   display: flex; | ||||
|   flex-flow: row; | ||||
|   height: 600px; | ||||
|   /*@todo make this dynamic */ } | ||||
|  | ||||
| /*  this is duplicate :( */ | ||||
| #browsersteps-selector-wrapper { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   overflow-y: scroll; | ||||
|   position: relative; | ||||
|   /* nice tall skinny one */ } | ||||
|   #browsersteps-selector-wrapper > img { | ||||
|     position: absolute; | ||||
|     max-width: 100%; } | ||||
|   #browsersteps-selector-wrapper > canvas { | ||||
|     position: relative; | ||||
|     max-width: 100%; } | ||||
|     #browsersteps-selector-wrapper > canvas:hover { | ||||
|       cursor: pointer; } | ||||
|   #browsersteps-selector-wrapper .loader { | ||||
|     position: absolute; | ||||
|     left: 50%; | ||||
|     top: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
|     margin-left: -40px; | ||||
|     z-index: 100; | ||||
|     max-width: 350px; | ||||
|     text-align: center; } | ||||
|   #browsersteps-selector-wrapper .spinner, #browsersteps-selector-wrapper .spinner:after { | ||||
|     width: 80px; | ||||
|     height: 80px; | ||||
|     font-size: 3px; } | ||||
|   #browsersteps-selector-wrapper #browsersteps-click-start { | ||||
|     color: var(--color-grey-400); } | ||||
|     #browsersteps-selector-wrapper #browsersteps-click-start:hover { | ||||
|       cursor: pointer; } | ||||
|  | ||||
| .arrow { | ||||
|   border: solid #1b98f8; | ||||
|   border-width: 0 2px 2px 0; | ||||
|   display: inline-block; | ||||
|   padding: 3px; } | ||||
|   .arrow.right { | ||||
|     transform: rotate(-45deg); | ||||
|     -webkit-transform: rotate(-45deg); } | ||||
|   .arrow.left { | ||||
|     transform: rotate(135deg); | ||||
|     -webkit-transform: rotate(135deg); } | ||||
|   .arrow.up, .arrow.asc { | ||||
|     transform: rotate(-135deg); | ||||
|     -webkit-transform: rotate(-135deg); } | ||||
|   .arrow.down, .arrow.desc { | ||||
|     transform: rotate(45deg); | ||||
|     -webkit-transform: rotate(45deg); } | ||||
|  | ||||
| body { | ||||
|   color: var(--color-text); | ||||
|   background: var(--color-background-page); } | ||||
|   | ||||
| @@ -36,7 +36,6 @@ class ChangeDetectionStore: | ||||
|         self.datastore_path = datastore_path | ||||
|         self.json_store_path = "{}/url-watches.json".format(self.datastore_path) | ||||
|         self.needs_write = False | ||||
|         self.proxy_list = None | ||||
|         self.start_time = time.time() | ||||
|         self.stop_thread = False | ||||
|         # Base definition for all watchers | ||||
| @@ -116,11 +115,6 @@ class ChangeDetectionStore: | ||||
|             secret = secrets.token_hex(16) | ||||
|             self.__data['settings']['application']['api_access_token'] = secret | ||||
|  | ||||
|         # Proxy list support - available as a selection in settings when text file is imported | ||||
|         proxy_list_file = "{}/proxies.json".format(self.datastore_path) | ||||
|         if path.isfile(proxy_list_file): | ||||
|             self.import_proxy_list(proxy_list_file) | ||||
|  | ||||
|         # Bump the update version by running updates | ||||
|         self.run_updates() | ||||
|  | ||||
| @@ -463,10 +457,30 @@ class ChangeDetectionStore: | ||||
|                     print ("Removing",item) | ||||
|                     unlink(item) | ||||
|  | ||||
|     def import_proxy_list(self, filename): | ||||
|         with open(filename) as f: | ||||
|             self.proxy_list = json.load(f) | ||||
|             print ("Registered proxy list", list(self.proxy_list.keys())) | ||||
|     @property | ||||
|     def proxy_list(self): | ||||
|         proxy_list = {} | ||||
|         proxy_list_file = os.path.join(self.datastore_path, 'proxies.json') | ||||
|  | ||||
|         # Load from external config file | ||||
|         if path.isfile(proxy_list_file): | ||||
|             with open("{}/proxies.json".format(self.datastore_path)) as f: | ||||
|                 proxy_list = json.load(f) | ||||
|  | ||||
|         # Mapping from UI config if available | ||||
|         extras = self.data['settings']['requests'].get('extra_proxies') | ||||
|         if extras: | ||||
|             i=0 | ||||
|             for proxy in extras: | ||||
|                 i += 0 | ||||
|                 if proxy.get('proxy_name') and proxy.get('proxy_url'): | ||||
|                     k = "ui-" + str(i) + proxy.get('proxy_name') | ||||
|                     proxy_list[k] = {'label': proxy.get('proxy_name'), 'url': proxy.get('proxy_url')} | ||||
|  | ||||
|  | ||||
|         return proxy_list if len(proxy_list) else None | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     def get_preferred_proxy_for_watch(self, uuid): | ||||
| @@ -476,11 +490,10 @@ class ChangeDetectionStore: | ||||
|         :return: proxy "key" id | ||||
|         """ | ||||
|  | ||||
|         proxy_id = None | ||||
|         if self.proxy_list is None: | ||||
|             return None | ||||
|  | ||||
|         # If its a valid one | ||||
|         # If it's a valid one | ||||
|         watch = self.data['watching'].get(uuid) | ||||
|  | ||||
|         if watch.get('proxy') and watch.get('proxy') in list(self.proxy_list.keys()): | ||||
| @@ -493,8 +506,9 @@ class ChangeDetectionStore: | ||||
|             if self.proxy_list.get(system_proxy_id): | ||||
|                 return system_proxy_id | ||||
|  | ||||
|         # Fallback - Did not resolve anything, use the first available | ||||
|         if system_proxy_id is None: | ||||
|  | ||||
|         # Fallback - Did not resolve anything, or doesnt exist, use the first available | ||||
|         if system_proxy_id is None or not self.proxy_list.get(system_proxy_id): | ||||
|             first_default = list(self.proxy_list)[0] | ||||
|             return first_default | ||||
|  | ||||
|   | ||||
| @@ -21,6 +21,7 @@ | ||||
|             <li class="tab"><a href="#fetching">Fetching</a></li> | ||||
|             <li class="tab"><a href="#filters">Global Filters</a></li> | ||||
|             <li class="tab"><a href="#api">API</a></li> | ||||
|             <li class="tab"><a href="#proxies">CAPTCHA & Proxies</a></li> | ||||
|         </ul> | ||||
|     </div> | ||||
|     <div class="box-wrap inner"> | ||||
| @@ -170,7 +171,21 @@ nav | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="tab-pane-inner" id="proxies"> | ||||
|  | ||||
|                 <p><strong>Tip</strong>: You can connect <a href="https://brightdata.grsm.io/n0r16zf7eivq">BrightData <strong>WebUnlocker</strong></a> proxies to work around CAPTCHA.<br/> | ||||
|                     Simply <a href="https://brightdata.grsm.io/n0r16zf7eivq">register</a> and paste in the Proxy URL below.<br/> | ||||
|                     You can also add extra location/country proxies.</br> | ||||
|                 </p> | ||||
|  | ||||
|                 <p>Please use our referrer link for BrightData <a href="https://brightdata.grsm.io/n0r16zf7eivq">https://brightdata.grsm.io/n0r16zf7eivq</a></p> | ||||
|  | ||||
|  | ||||
|                 <div class="pure-control-group"> | ||||
|                     {{ render_field(form.requests.form.extra_proxies) }} | ||||
|  | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div id="actions"> | ||||
|                 <div class="pure-control-group"> | ||||
|                     {{ render_button(form.save_button) }} | ||||
|   | ||||
							
								
								
									
										48
									
								
								changedetectionio/tests/proxy_list/squid-auth.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								changedetectionio/tests/proxy_list/squid-auth.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| acl localnet src 0.0.0.1-0.255.255.255  # RFC 1122 "this" network (LAN) | ||||
| acl localnet src 10.0.0.0/8             # RFC 1918 local private network (LAN) | ||||
| acl localnet src 100.64.0.0/10          # RFC 6598 shared address space (CGN) | ||||
| acl localnet src 169.254.0.0/16         # RFC 3927 link-local (directly plugged) machines | ||||
| acl localnet src 172.16.0.0/12          # RFC 1918 local private network (LAN) | ||||
| acl localnet src 192.168.0.0/16         # RFC 1918 local private network (LAN) | ||||
| acl localnet src fc00::/7               # RFC 4193 local private network range | ||||
| acl localnet src fe80::/10              # RFC 4291 link-local (directly plugged) machines | ||||
| acl localnet src 159.65.224.174 | ||||
| acl SSL_ports port 443 | ||||
| acl Safe_ports port 80          # http | ||||
| acl Safe_ports port 21          # ftp | ||||
| acl Safe_ports port 443         # https | ||||
| acl Safe_ports port 70          # gopher | ||||
| acl Safe_ports port 210         # wais | ||||
| acl Safe_ports port 1025-65535  # unregistered ports | ||||
| acl Safe_ports port 280         # http-mgmt | ||||
| acl Safe_ports port 488         # gss-http | ||||
| acl Safe_ports port 591         # filemaker | ||||
| acl Safe_ports port 777         # multiling http | ||||
| acl CONNECT method CONNECT | ||||
|  | ||||
| http_access deny !Safe_ports | ||||
| http_access deny CONNECT !SSL_ports | ||||
| #http_access allow localhost manager | ||||
| http_access deny manager | ||||
| #http_access allow localhost | ||||
| #http_access allow localnet | ||||
|  | ||||
| auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid3/passwords | ||||
| auth_param basic realm proxy | ||||
| acl authenticated proxy_auth REQUIRED | ||||
| http_access allow authenticated | ||||
| http_access deny all | ||||
|  | ||||
|  | ||||
| http_port 3128 | ||||
| coredump_dir /var/spool/squid | ||||
| refresh_pattern ^ftp:           1440    20%     10080 | ||||
| refresh_pattern ^gopher:        1440    0%      1440 | ||||
| refresh_pattern -i (/cgi-bin/|\?) 0     0%      0 | ||||
| refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims | ||||
| refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims | ||||
| refresh_pattern \/InRelease$ 0 0% 0 refresh-ims | ||||
| refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims | ||||
| refresh_pattern .               0       20%     4320 | ||||
| logfile_rotate 0 | ||||
|  | ||||
							
								
								
									
										1
									
								
								changedetectionio/tests/proxy_list/squid-passwords.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								changedetectionio/tests/proxy_list/squid-passwords.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| test:$apr1$xvhFolTA$E/kz5/Rw1ewcyaSUdwqZs. | ||||
| @@ -0,0 +1,51 @@ | ||||
| #!/usr/bin/python3 | ||||
|  | ||||
| import time | ||||
| from flask import url_for | ||||
| from ..util import live_server_setup, wait_for_all_checks | ||||
|  | ||||
| # just make a request, we will grep in the docker logs to see it actually got called | ||||
| def test_select_custom(client, live_server): | ||||
|     live_server_setup(live_server) | ||||
|  | ||||
|     # Goto settings, add our custom one | ||||
|     res = client.post( | ||||
|         url_for("settings_page"), | ||||
|         data={ | ||||
|             "requests-time_between_check-minutes": 180, | ||||
|             "application-ignore_whitespace": "y", | ||||
|             "application-fetch_backend": "html_requests", | ||||
|             "requests-extra_proxies-0-proxy_name": "custom-test-proxy", | ||||
|             # test:awesome is set in tests/proxy_list/squid-passwords.txt | ||||
|             "requests-extra_proxies-0-proxy_url": "http://test:awesome@squid-custom:3128", | ||||
|         }, | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|  | ||||
|     assert b"Settings updated." in res.data | ||||
|  | ||||
|     res = client.post( | ||||
|         url_for("import_page"), | ||||
|         # Because a URL wont show in squid/proxy logs due it being SSLed | ||||
|         # Use plain HTTP or a specific domain-name here | ||||
|         data={"urls": "https://changedetection.io/CHANGELOG.txt"}, | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|  | ||||
|     assert b"1 Imported" in res.data | ||||
|     wait_for_all_checks(client) | ||||
|  | ||||
|     res = client.get(url_for("index")) | ||||
|     assert b'Proxy Authentication Required' not in res.data | ||||
|  | ||||
|  | ||||
|     res = client.get( | ||||
|         url_for("preview_page", uuid="first"), | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|     # We should see something via proxy | ||||
|     assert b'HEAD' in res.data | ||||
|  | ||||
|     # | ||||
|     # Now we should see the request in the container logs for "squid-squid-custom" because it will be the only default | ||||
|  | ||||
		Reference in New Issue
	
	Block a user