aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2022-10-18 17:18:32 +0200
committerWojtek Kosior <koszko@koszko.org>2022-10-18 20:13:45 +0200
commit14eeee3fbc0a839d918149765d2134d05cd14601 (patch)
treedec1ca7dbc8668120e53e8ea45328246313199d1
parent76f4b6769d0acaeffbd8e8a003fcbb3e2fbea107 (diff)
downloadhaketilo-hydrilla-14eeee3fbc0a839d918149765d2134d05cd14601.tar.gz
haketilo-hydrilla-14eeee3fbc0a839d918149765d2134d05cd14601.zip
[proxy] upon Haketilo launch automatically open Haketilo landing page in user's default web browser
* The landing page instructs user to configure browser's proxy settings. * It is now possible to choose the IP address to listen on via command line parameter. * The browser launching behavior can be switched off via command line parameter.
-rw-r--r--src/hydrilla/locales/en_US/LC_MESSAGES/messages.po216
-rw-r--r--src/hydrilla/mitmproxy_launcher/launch.py11
-rw-r--r--src/hydrilla/proxy/addon.py96
-rw-r--r--src/hydrilla/proxy/policies/__init__.py2
-rw-r--r--src/hydrilla/proxy/policies/web_ui.py21
-rw-r--r--src/hydrilla/proxy/state.py25
-rw-r--r--src/hydrilla/proxy/state_impl/base.py30
-rw-r--r--src/hydrilla/proxy/state_impl/concrete_state.py43
-rw-r--r--src/hydrilla/proxy/web_ui/__init__.py1
-rw-r--r--src/hydrilla/proxy/web_ui/_app.py13
-rw-r--r--src/hydrilla/proxy/web_ui/root.py69
-rw-r--r--src/hydrilla/proxy/web_ui/templates/base.html.jinja97
-rw-r--r--src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja116
-rw-r--r--src/hydrilla/proxy/web_ui/templates/import.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/index.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/landing.html.jinja49
-rw-r--r--src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja2
-rw-r--r--src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja2
-rw-r--r--src/hydrilla/url_patterns.py24
28 files changed, 598 insertions, 241 deletions
diff --git a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po
index b311f8f..eac53fe 100644
--- a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po
+++ b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: hydrilla 2.0\n"
"Report-Msgid-Bugs-To: koszko@koszko.org\n"
-"POT-Creation-Date: 2022-10-10 16:44+0200\n"
+"POT-Creation-Date: 2022-10-18 17:03+0200\n"
"PO-Revision-Date: 2022-02-12 00:00+0000\n"
"Last-Translator: Wojtek Kosior <koszko@koszko.org>\n"
"Language: en_US\n"
@@ -18,70 +18,70 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.0\n"
-#: src/hydrilla/builder/build.py:84 src/hydrilla/builder/local_apt.py:123
-#: src/hydrilla/builder/local_apt.py:428
+#: src/hydrilla/builder/build.py:81 src/hydrilla/builder/local_apt.py:120
+#: src/hydrilla/builder/local_apt.py:426
msgid "couldnt_execute_{}_is_it_installed"
msgstr "Could not execute '{}'. Is the tool installed and reachable via PATH?"
-#: src/hydrilla/builder/build.py:88 src/hydrilla/builder/local_apt.py:127
-#: src/hydrilla/builder/local_apt.py:432
+#: src/hydrilla/builder/build.py:85 src/hydrilla/builder/local_apt.py:124
+#: src/hydrilla/builder/local_apt.py:430
msgid "command_{}_failed"
msgstr "The following command finished execution with a non-zero exit status: {}"
-#: src/hydrilla/builder/build.py:204
+#: src/hydrilla/builder/build.py:201
msgid "path_contains_double_dot_{}"
msgstr ""
"Attempt to load '{}' which includes a forbidden parent reference ('..') "
"in the path."
-#: src/hydrilla/builder/build.py:211
+#: src/hydrilla/builder/build.py:210
msgid "loading_{}_outside_package_dir"
msgstr "Attempt to load '{}' which lies outside package source directory."
-#: src/hydrilla/builder/build.py:215
+#: src/hydrilla/builder/build.py:214
msgid "loading_reserved_index_json"
msgstr "Attempt to load 'index.json' which is a reserved filename."
-#: src/hydrilla/builder/build.py:222
+#: src/hydrilla/builder/build.py:221
msgid "referenced_file_{}_missing"
msgstr "Referenced file '{}' is missing."
-#: src/hydrilla/builder/build.py:413
+#: src/hydrilla/builder/build.py:412
msgid "report_spdx_not_in_copyright_list"
msgstr ""
"Told to generate 'report.spdx' but 'report.spdx' is not listed among "
"copyright files. Refusing to proceed."
-#: src/hydrilla/builder/build.py:490
+#: src/hydrilla/builder/build.py:489
msgid "build_package_from_srcdir_to_dstdir"
msgstr ""
"Build Hydrilla package from `scrdir` and write the resulting files under "
"`dstdir`."
-#: src/hydrilla/builder/build.py:492
+#: src/hydrilla/builder/build.py:491
msgid "source_directory_to_build_from"
msgstr "Source directory to build from."
-#: src/hydrilla/builder/build.py:494
+#: src/hydrilla/builder/build.py:493
msgid "path_instead_of_index_json"
msgstr ""
"Path to file to be processed instead of index.json (if not absolute, "
"resolved relative to srcdir)."
-#: src/hydrilla/builder/build.py:496
+#: src/hydrilla/builder/build.py:495
msgid "path_instead_for_piggyback_files"
msgstr ""
"Path to a non-standard directory with foreign packages' archive files to "
"use."
-#: src/hydrilla/builder/build.py:498
+#: src/hydrilla/builder/build.py:497
msgid "built_package_files_destination"
msgstr "Destination directory to write built package files to."
-#: src/hydrilla/builder/build.py:500
-#: src/hydrilla/mitmproxy_launcher/launch.py:65
-#: src/hydrilla/server/serve.py:207 src/hydrilla/server/serve.py:225
-#: src/hydrilla/server/serve.py:265
+#: src/hydrilla/builder/build.py:499
+#: src/hydrilla/mitmproxy_launcher/launch.py:64
+#: src/hydrilla/server/serve.py:211 src/hydrilla/server/serve.py:229
+#: src/hydrilla/server/serve.py:269
#, python-format
msgid "%(prog)s_%(version)s_license"
msgstr ""
@@ -92,79 +92,79 @@ msgstr ""
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law."
-#: src/hydrilla/builder/build.py:501 src/hydrilla/server/serve.py:226
-#: src/hydrilla/server/serve.py:266
+#: src/hydrilla/builder/build.py:500 src/hydrilla/server/serve.py:230
+#: src/hydrilla/server/serve.py:270
msgid "version_printing"
msgstr "Print version information and exit."
-#: src/hydrilla/builder/common_errors.py:61
+#: src/hydrilla/builder/common_errors.py:58
msgid "STDOUT_OUTPUT_heading"
msgstr "## Command's standard output ##"
-#: src/hydrilla/builder/common_errors.py:64
+#: src/hydrilla/builder/common_errors.py:61
msgid "STDERR_OUTPUT_heading"
msgstr "## Command's standard error output ##"
-#: src/hydrilla/builder/local_apt.py:156
+#: src/hydrilla/builder/local_apt.py:153
msgid "distro_{}_unknown"
msgstr "Attempt to use an unknown software distribution '{}'."
-#: src/hydrilla/builder/local_apt.py:200
+#: src/hydrilla/builder/local_apt.py:197
msgid "couldnt_import_{}_is_it_installed"
msgstr ""
"Could not import '{}'. Is the module installed and visible to this Python"
" instance?"
-#: src/hydrilla/builder/local_apt.py:208
+#: src/hydrilla/builder/local_apt.py:205
msgid "gpg_couldnt_recv_key_{}"
msgstr "Could not import PGP key '{}'."
-#: src/hydrilla/builder/local_apt.py:327
+#: src/hydrilla/builder/local_apt.py:325
msgid "apt_install_output_not_understood"
msgstr "The output of an 'apt-get install' command was not understood."
-#: src/hydrilla/builder/local_apt.py:353
+#: src/hydrilla/builder/local_apt.py:351
msgid "apt_download_gave_bad_filename_{}"
msgstr "The 'apt-get download' command produced a file with unexpected name '{}'."
-#: src/hydrilla/builder/piggybacking.py:110
+#: src/hydrilla/builder/piggybacking.py:109
msgid "loading_{}_outside_piggybacked_dir"
msgstr ""
"Attempt to load '{}' which lies outside piggybacked packages files root "
"directory."
-#: src/hydrilla/item_infos.py:91
+#: src/hydrilla/item_infos.py:88
msgid "err.item_info.filename_invalid_{}"
msgstr "Item definition conatains an illegal path: {}"
-#: src/hydrilla/item_infos.py:516
+#: src/hydrilla/item_infos.py:511
#, python-brace-format
msgid "uuid_mismatch_{identifier}"
msgstr "Two different uuids were specified for item '{identifier}'."
-#: src/hydrilla/json_instances.py:87
+#: src/hydrilla/json_instances.py:84
msgid "bad_json_comment_line_{line_num}_char_{char_num}"
msgstr ""
"JSON document contains an invalid comment at line {line_num}, char "
"{char_num}."
-#: src/hydrilla/json_instances.py:138
+#: src/hydrilla/json_instances.py:135
msgid "unknown_schema_{}"
msgstr "JSON document declares its schema as '{}' which is not a known schema."
-#: src/hydrilla/json_instances.py:189
+#: src/hydrilla/json_instances.py:186
msgid "err.util.text_in_{}_not_valid_json"
msgstr "Not a valid JSON file: {}"
-#: src/hydrilla/json_instances.py:192
+#: src/hydrilla/json_instances.py:189
msgid "err.util.text_not_valid_json"
msgstr "Not a valid JSON file."
-#: src/hydrilla/json_instances.py:207
+#: src/hydrilla/json_instances.py:204
msgid "no_schema_number_in_instance"
msgstr "JSON schema number is missing from a document."
-#: src/hydrilla/mitmproxy_launcher/launch.py:58
+#: src/hydrilla/mitmproxy_launcher/launch.py:53
msgid "cli_help.haketilo"
msgstr ""
"Run Haketilo proxy.\n"
@@ -172,76 +172,96 @@ msgstr ""
"This command starts Haketilo as a local HTTP proxy which a web browser "
"can then use."
-#: src/hydrilla/mitmproxy_launcher/launch.py:60
+#: src/hydrilla/mitmproxy_launcher/launch.py:55
+msgid "cli_opt.haketilo.listen_host"
+msgstr "IP address port number the proxy should listen on."
+
+#: src/hydrilla/mitmproxy_launcher/launch.py:57
msgid "cli_opt.haketilo.port"
-msgstr "TCP port number the proxy should listen on (1-65535)."
+msgstr "TCP port number the proxy should listen on."
+
+#: src/hydrilla/mitmproxy_launcher/launch.py:59
+msgid "cli_opt.haketilo.launch_browser"
+msgstr ""
+"Whether Haketilo should try to open its landing page in your default "
+"browser. Defaults to yes ('-L')."
-#: src/hydrilla/mitmproxy_launcher/launch.py:63
+#: src/hydrilla/mitmproxy_launcher/launch.py:62
msgid "cli_opt.haketilo.dir"
msgstr "Data directory for Haketilo to use."
-#: src/hydrilla/mitmproxy_launcher/launch.py:66
+#: src/hydrilla/mitmproxy_launcher/launch.py:65
msgid "cli_opt.haketilo.version"
msgstr "Print version information and exit"
-#: src/hydrilla/proxy/addon.py:128
-msgid "haketilo_dir_already_configured"
+#: src/hydrilla/proxy/addon.py:167
+msgid "warn.proxy.setting_already_configured_{}"
+msgstr ""
+"Attempt was made to configure Mitmproxy addon's option '{}' which has "
+"already been configured."
+
+#: src/hydrilla/proxy/addon.py:201
+msgid "warn.proxy.couldnt_launch_browser"
msgstr ""
-"Attempt was made to configure Haketilo Mitmproxy addon with data "
-"directory path but it has already been configured."
+"Failed to open a URL in a web browser. Do you have a default web browser "
+"configured?"
-#: src/hydrilla/proxy/addon.py:183
+#: src/hydrilla/proxy/addon.py:245
msgid "err.proxy.unknown_error_{}_try_again"
msgstr ""
"Haketilo experienced an error. Try again.\n"
"\n"
"{}"
-#: src/hydrilla/proxy/policies/payload_resource.py:264
+#: src/hydrilla/proxy/policies/payload_resource.py:261
msgid "api.file_not_found"
msgstr "Requested file could not be found."
-#: src/hydrilla/proxy/policies/payload_resource.py:387
+#: src/hydrilla/proxy/policies/payload_resource.py:384
msgid "api.resource_not_enabled_for_access"
msgstr "Requested resource is not enabled for access."
-#: src/hydrilla/proxy/state_impl/concrete_state.py:92
+#: src/hydrilla/proxy/state_impl/concrete_state.py:89
msgid "err.proxy.unknown_db_schema"
msgstr ""
"Haketilo's data files have been altered, possibly by a newer version of "
"Haketilo."
-#: src/hydrilla/proxy/state_impl/concrete_state.py:96
+#: src/hydrilla/proxy/state_impl/concrete_state.py:93
msgid "err.proxy.no_sqlite_foreign_keys"
msgstr ""
"This installation of Haketilo uses an SQLite version which does not "
"support foreign key constraints."
-#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:101
+#: src/hydrilla/proxy/state_impl/concrete_state.py:218
+msgid "warn.proxy.failed_to_register_landing_page_at_{}"
+msgstr "Failed to register landing page at \"{}\"."
+
+#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:99
msgid "web_ui.base.title.haketilo_proxy"
msgstr "Haketilo"
-#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:284
+#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:82
msgid "web_ui.base.nav.home"
msgstr "Home"
-#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:285
+#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:83
msgid "web_ui.base.nav.rules"
msgstr "Script blocking"
-#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:286
+#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:84
msgid "web_ui.base.nav.packages"
msgstr "Packages"
-#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:287
+#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:85
msgid "web_ui.base.nav.libraries"
msgstr "Libraries"
-#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:288
+#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:86
msgid "web_ui.base.nav.repos"
msgstr "Repositories"
-#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:289
+#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:87
msgid "web_ui.base.nav.import"
msgstr "Import"
@@ -844,6 +864,36 @@ msgstr "Available packages"
msgid "web_ui.packages.enabled_version_{}"
msgstr "enabled version {}"
+#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:23
+msgid "web_ui.landing.title"
+msgstr "Landing page"
+
+#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:27
+msgid "web_ui.landing.heading.haketilo_is_running"
+msgstr "Haketilo is running"
+
+#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:31
+msgid "web_ui.landing.web_ui.landing.what_to_do_1"
+msgstr ""
+"In order to access web pages through Haketilo, make sure your browser is "
+"configured to use it as a proxy for both HTTP and HTTPs. Please use the "
+"following values."
+
+#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:34
+msgid "web_ui.landing.host_label"
+msgstr "Address"
+
+#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:40
+msgid "web_ui.landing.port_label"
+msgstr "Port"
+
+#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:47
+msgid "web_ui.landing.html.what_to_do_2"
+msgstr ""
+"If you've configured your browser properly, you can visit <a "
+"href=\"http://hkt.mitm.it\">http://hkt.mitm.it</a>. It's Haketilo "
+"configuration page that's hosted locally &quot;inside&quot; the proxy."
+
#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:24
msgid "web_ui.prompts.auto_install_error.title"
msgstr "Installation failure"
@@ -1142,53 +1192,53 @@ msgstr "Actions"
msgid "web_ui.rules.single.remove_button"
msgstr "Remove rule"
-#: src/hydrilla/server/malcontent.py:80
+#: src/hydrilla/server/malcontent.py:77
msgid "err.server.malcontent_path_not_dir_{}"
msgstr "Provided 'malcontent_dir' path does not name a directory: {}"
-#: src/hydrilla/server/malcontent.py:99
+#: src/hydrilla/server/malcontent.py:96
msgid "err.server.couldnt_load_item_from_{}"
msgstr "Couldn't load item from {}."
-#: src/hydrilla/server/malcontent.py:112
+#: src/hydrilla/server/malcontent.py:109
msgid "err.server.no_file_{required_by}_{ver}_{file}_{sha256}"
msgstr ""
"'{required_by}', version '{ver}' uses a file named {file} with SHA256 "
"hash of {sha256}, but the file is missing."
-#: src/hydrilla/server/malcontent.py:136
+#: src/hydrilla/server/malcontent.py:133
msgid "err.server.item_{item}_in_file_{file}"
msgstr "Item {item} incorrectly present under {file}."
-#: src/hydrilla/server/malcontent.py:142
+#: src/hydrilla/server/malcontent.py:139
msgid "item_version_{ver}_in_file_{file}"
msgstr "Item version {ver} incorrectly present under {file}."
-#: src/hydrilla/server/malcontent.py:169
+#: src/hydrilla/server/malcontent.py:166
msgid "err.server.no_dep_{resource}_{ver}_{dep}"
msgstr "Unknown dependency '{dep}' of resource '{resource}', version '{ver}'."
-#: src/hydrilla/server/malcontent.py:184
+#: src/hydrilla/server/malcontent.py:181
msgid "err.server.no_payload_{mapping}_{ver}_{payload}"
msgstr "Unknown payload '{payload}' of mapping '{mapping}', version '{ver}'."
-#: src/hydrilla/server/malcontent.py:199
+#: src/hydrilla/server/malcontent.py:196
msgid "err.server.no_mapping_{required_by}_{ver}_{required}"
msgstr "Unknown mapping '{required}' required by '{required_by}', version '{ver}'."
-#: src/hydrilla/server/malcontent.py:227
+#: src/hydrilla/server/malcontent.py:224
msgid "server.err.couldnt_register_{mapping}_{ver}_{pattern}"
msgstr ""
"Couldn't register mapping '{mapping}', version '{ver}' (pattern "
"'{pattern}')."
-#: src/hydrilla/server/serve.py:84
+#: src/hydrilla/server/serve.py:81
msgid "err.server.opt_hydrilla_parent_not_implemented"
msgstr ""
"Hydrilla was told to connect to a parent Hydrilla server but this feature"
" is not yet implemented."
-#: src/hydrilla/server/serve.py:213
+#: src/hydrilla/server/serve.py:217
msgid "serve_hydrilla_packages_explain_wsgi_considerations"
msgstr ""
"Serve Hydrilla packages.\n"
@@ -1197,36 +1247,36 @@ msgstr ""
"Hydrilla instance. For better performance, consider deployment using "
"WSGI."
-#: src/hydrilla/server/serve.py:216
+#: src/hydrilla/server/serve.py:220
msgid "directory_to_serve_from_overrides_config"
msgstr ""
"Directory to serve files from. Overrides value from the config file (if "
"any)."
-#: src/hydrilla/server/serve.py:218
+#: src/hydrilla/server/serve.py:222
msgid "project_url_to_display_overrides_config"
msgstr ""
"Project url to display on generated HTML pages. Overrides value from the "
"config file (if any)."
-#: src/hydrilla/server/serve.py:220
+#: src/hydrilla/server/serve.py:224
msgid "tcp_port_to_listen_on_overrides_config"
msgstr ""
"TCP port number to listen on (0-65535). Overrides value from the config "
"file (if any)."
-#: src/hydrilla/server/serve.py:223
+#: src/hydrilla/server/serve.py:227
msgid "path_to_config_file_explain_default"
msgstr ""
"Path to Hydrilla server configuration file (optional, by default Hydrilla"
" loads its own config file, which in turn tries to load "
"/etc/hydrilla/config.json)."
-#: src/hydrilla/server/serve.py:255
+#: src/hydrilla/server/serve.py:259
msgid "config_option_{}_not_supplied"
msgstr "Missing configuration option '{}'."
-#: src/hydrilla/server/serve.py:259
+#: src/hydrilla/server/serve.py:263
msgid "serve_hydrilla_packages_wsgi_help"
msgstr ""
"Serve Hydrilla packages.\n"
@@ -1235,39 +1285,39 @@ msgstr ""
"HTTP server like Apache2 or Nginx. You can configure Hydrilla through the"
" /etc/hydrilla/config.json file."
-#: src/hydrilla/url_patterns.py:136
+#: src/hydrilla/url_patterns.py:139
msgid "err.url_pattern_{}.bad"
msgstr "Not a valid Haketilo URL pattern: {}"
-#: src/hydrilla/url_patterns.py:139
+#: src/hydrilla/url_patterns.py:142
msgid "err.url_{}.bad"
msgstr "Not a valid URL: {}"
-#: src/hydrilla/url_patterns.py:146
+#: src/hydrilla/url_patterns.py:149
msgid "err.url_pattern_{}.bad_scheme"
msgstr "URL pattern has an unknown scheme: {}"
-#: src/hydrilla/url_patterns.py:149
+#: src/hydrilla/url_patterns.py:152
msgid "err.url_{}.bad_scheme"
msgstr "URL has an unknown scheme: {}"
-#: src/hydrilla/url_patterns.py:154
+#: src/hydrilla/url_patterns.py:157
msgid "err.url_pattern_{}.special_scheme_port"
msgstr "URL pattern has an explicit port while it shouldn't: {}"
-#: src/hydrilla/url_patterns.py:166
+#: src/hydrilla/url_patterns.py:169
msgid "err.url_pattern_{}.bad_port"
msgstr "URL pattern has a port outside of allowed range (1-65535): {}"
-#: src/hydrilla/url_patterns.py:169
+#: src/hydrilla/url_patterns.py:172
msgid "err.url_{}.bad_port"
msgstr "URL has a port outside of allowed range (1-65535): {}"
-#: src/hydrilla/url_patterns.py:190
+#: src/hydrilla/url_patterns.py:193
msgid "err.url_pattern_{}.has_query"
msgstr "URL pattern has a query string while it shouldn't: {}"
-#: src/hydrilla/url_patterns.py:194
+#: src/hydrilla/url_patterns.py:197
msgid "err.url_pattern_{}.has_frag"
msgstr "URL pattern has a fragment string while it shouldn't: {}"
diff --git a/src/hydrilla/mitmproxy_launcher/launch.py b/src/hydrilla/mitmproxy_launcher/launch.py
index 82766b9..4fe31db 100644
--- a/src/hydrilla/mitmproxy_launcher/launch.py
+++ b/src/hydrilla/mitmproxy_launcher/launch.py
@@ -29,6 +29,7 @@
import sys
import os
import subprocess as sp
+import typing as t
from pathlib import Path
# The following import requires at least Python 3.8. There is no point adding
@@ -54,13 +55,16 @@ addons = [HaketiloAddon()]
help=_('cli_opt.haketilo.listen_host'))
@click.option('-p', '--port', default=8080, type=click.IntRange(1, 65535),
help=_('cli_opt.haketilo.port'))
+@click.option('-L/-l', '--launch-browser/--no-launch-browser', default=True,
+ help=_('cli_opt.haketilo.launch_browser'))
@click.option('-d', '--directory', default='~/.haketilo/',
type=click.Path(file_okay=False),
help=_('cli_opt.haketilo.dir'))
@click.version_option(version=_version.version, prog_name='Haketilo proxy',
message=_('%(prog)s_%(version)s_license'),
help=_('cli_opt.haketilo.version'))
-def launch(listen_host: str, port: int, directory: str):
+def launch(listen_host: str, port: int, launch_browser: bool, directory: str) \
+ -> t.NoReturn:
directory_path = Path(os.path.expanduser(directory)).resolve()
directory_path.mkdir(parents=True, exist_ok=True)
@@ -69,6 +73,8 @@ def launch(listen_host: str, port: int, directory: str):
script_path.write_text(addon_script_text)
+ launch_browser_str = 'true' if launch_browser else 'false'
+
sys.argv = [
'mitmdump',
'--listen-host', listen_host,
@@ -77,6 +83,9 @@ def launch(listen_host: str, port: int, directory: str):
'--set', 'upstream_cert=false',
'--set', 'connection_strategy=lazy',
'--set', f'haketilo_dir={directory_path}',
+ '--set', f'haketilo_listen_host={listen_host}',
+ '--set', f'haketilo_listen_port={port}',
+ '--set', f'haketilo_launch_browser={launch_browser_str}',
'--scripts', str(script_path)
]
diff --git a/src/hydrilla/proxy/addon.py b/src/hydrilla/proxy/addon.py
index 328d4a1..cca7924 100644
--- a/src/hydrilla/proxy/addon.py
+++ b/src/hydrilla/proxy/addon.py
@@ -48,6 +48,7 @@ from ..exceptions import HaketiloException
from ..translations import smart_gettext as _
from ..url_patterns import parse_url, ParsedUrl
from .state_impl import ConcreteHaketiloState
+from . import state
from . import policies
from . import http_messages
@@ -83,27 +84,47 @@ class MitmproxyHeadersWrapper():
return self.headers.items(multi=True)
+class LoggerToMitmproxy(state.Logger):
+ def warn(self, msg: str) -> None:
+ ctx.log.warn(f'Haketilo: {msg}')
+
+
@dc.dataclass(frozen=True)
class FlowHandlingData:
request_url: ParsedUrl
policy: policies.Policy
+@dc.dataclass
+class PassedOptions:
+ haketilo_dir: t.Optional[str] = None
+ haketilo_listen_host: t.Optional[str] = None
+ haketilo_listen_port: t.Optional[int] = None
+ haketilo_launch_browser: t.Optional[bool] = None
+
+ @property
+ def fully_configured(self) -> bool:
+ return (self.haketilo_dir is not None and
+ self.haketilo_listen_host is not None and
+ self.haketilo_listen_port is not None and
+ self.haketilo_launch_browser is not None)
+
+
magical_mitm_it_url_reg = re.compile(r'^http://mitm.it(/.*)?$')
dummy_url = parse_url('http://dummy.replacement.url')
@dc.dataclass
class HaketiloAddon:
- """
- .......
- """
- configured: bool = False
- configured_lock: Lock = dc.field(default_factory=Lock)
+ initial_options: PassedOptions = PassedOptions()
+ configured: bool = False
+ configured_lock: Lock = dc.field(default_factory=Lock)
flows_data: dict[int, FlowHandlingData] = dc.field(default_factory=dict)
flows_data_lock: Lock = dc.field(default_factory=Lock)
+ logger: LoggerToMitmproxy = dc.field(default_factory=LoggerToMitmproxy)
+
state: t.Optional[ConcreteHaketiloState] = None
def load(self, loader: addonmanager.Loader) -> None:
@@ -112,29 +133,74 @@ class HaketiloAddon:
name = 'haketilo_dir',
typespec = str,
default = '~/.haketilo/',
- help = "Point to a Haketilo data directory to use",
+ help = "Point to a Haketilo data directory to use"
+ )
+ loader.add_option(
+ name = 'haketilo_listen_host',
+ typespec = str,
+ default = '127.0.0.1',
+ help = "Specify the address proxy listens on"
+ )
+ loader.add_option(
+ name = 'haketilo_listen_port',
+ typespec = int,
+ default = 8080,
+ help = "Specify the port listens on"
+ )
+ loader.add_option(
+ name = 'haketilo_launch_browser',
+ typespec = bool,
+ default = True,
+ help = "Specify whether to attempt to open a browser window with Haketilo page displayed inside"
)
def configure(self, updated: set[str]) -> None:
- """...."""
- if 'haketilo_dir' not in updated:
- return
-
with self.configured_lock:
- if self.configured:
- ctx.log.warn(_('haketilo_dir_already_configured'))
+ val_names = ('dir', 'listen_host', 'listen_port', 'launch_browser')
+ for val_name in val_names:
+ key = f'haketilo_{val_name}'
+
+ if key not in updated:
+ continue
+
+ if getattr(self.initial_options, key) is not None:
+ fmt = _('warn.proxy.setting_already_configured_{}')
+ self.logger.warn(fmt.format(key))
+ continue
+
+ new_val = getattr(ctx.options, key)
+ setattr(self.initial_options, key, new_val)
+
+ if self.configured or not self.initial_options.fully_configured:
return
try:
- haketilo_dir = Path(ctx.options.haketilo_dir)
-
- self.state = ConcreteHaketiloState.make(haketilo_dir / 'store')
+ haketilo_dir = self.initial_options.haketilo_dir
+ listen_host = self.initial_options.haketilo_listen_host
+ listen_port = self.initial_options.haketilo_listen_port
+
+ self.state = ConcreteHaketiloState.make(
+ store_dir = Path(t.cast(str, haketilo_dir)) / 'store',
+ listen_host = t.cast(str, listen_host),
+ listen_port = t.cast(int, listen_port),
+ logger = self.logger
+ )
except Exception as e:
tb.print_exception(None, e, e.__traceback__)
sys.exit(1)
self.configured = True
+ def running(self) -> None:
+ with self.configured_lock:
+ assert self.configured
+
+ assert self.state is not None
+
+ if self.initial_options.haketilo_launch_browser:
+ if not self.state.launch_browser():
+ self.logger.warn(_('warn.proxy.couldnt_launch_browser'))
+
def get_handling_data(self, flow: http.HTTPFlow) -> FlowHandlingData:
policy: policies.Policy
diff --git a/src/hydrilla/proxy/policies/__init__.py b/src/hydrilla/proxy/policies/__init__.py
index 448420a..e958cbd 100644
--- a/src/hydrilla/proxy/policies/__init__.py
+++ b/src/hydrilla/proxy/policies/__init__.py
@@ -15,4 +15,4 @@ from .rule import RuleBlockPolicyFactory, RuleAllowPolicyFactory
from .misc import FallbackAllowPolicy, FallbackBlockPolicy, ErrorBlockPolicy, \
DoNothingPolicy
-from .web_ui import WebUIPolicyFactory
+from .web_ui import WebUIMainPolicyFactory, WebUILandingPolicyFactory
diff --git a/src/hydrilla/proxy/policies/web_ui.py b/src/hydrilla/proxy/policies/web_ui.py
index 9d31696..f35b0b7 100644
--- a/src/hydrilla/proxy/policies/web_ui.py
+++ b/src/hydrilla/proxy/policies/web_ui.py
@@ -47,14 +47,27 @@ class WebUIPolicy(base.Policy):
priority: t.ClassVar[base.PolicyPriority] = base.PolicyPriority._THREE
haketilo_state: state.HaketiloState
+ ui_domain: web_ui.UIDomain
def consume_request(self, request_info: http_messages.RequestInfo) \
-> http_messages.ProducedResponse:
- return web_ui.process_request(request_info, self.haketilo_state)
-
+ return web_ui.process_request(
+ request_info = request_info,
+ state = self.haketilo_state,
+ ui_domain = self.ui_domain
+ )
@dc.dataclass(frozen=True, unsafe_hash=True)
class WebUIPolicyFactory(base.PolicyFactory):
- """...."""
+ ui_domain: t.ClassVar[web_ui.UIDomain]
+
def make_policy(self, haketilo_state: state.HaketiloState) -> WebUIPolicy:
- return WebUIPolicy(haketilo_state)
+ return WebUIPolicy(haketilo_state, self.ui_domain)
+
+@dc.dataclass(frozen=True, unsafe_hash=True)
+class WebUIMainPolicyFactory(WebUIPolicyFactory):
+ ui_domain = web_ui.UIDomain.MAIN
+
+@dc.dataclass(frozen=True, unsafe_hash=True)
+class WebUILandingPolicyFactory(WebUIPolicyFactory):
+ ui_domain = web_ui.UIDomain.LANDING_PAGE
diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py
index 5e09367..7fb7bac 100644
--- a/src/hydrilla/proxy/state.py
+++ b/src/hydrilla/proxy/state.py
@@ -520,6 +520,12 @@ class HaketiloGlobalSettings:
repo_refresh_seconds: int
+class Logger(ABC):
+ @abstractmethod
+ def warn(self, msg: str) -> None:
+ ...
+
+
class MissingItemError(ValueError):
"""...."""
pass
@@ -594,3 +600,22 @@ class HaketiloState(ABC):
) -> None:
"""...."""
...
+
+ @property
+ @abstractmethod
+ def listen_host(self) -> str:
+ ...
+
+ @property
+ @abstractmethod
+ def listen_port(self) -> int:
+ ...
+
+ @abstractmethod
+ def launch_browser(self) -> bool:
+ ...
+
+ @property
+ @abstractmethod
+ def logger(self) -> Logger:
+ ...
diff --git a/src/hydrilla/proxy/state_impl/base.py b/src/hydrilla/proxy/state_impl/base.py
index 357ae88..df3287b 100644
--- a/src/hydrilla/proxy/state_impl/base.py
+++ b/src/hydrilla/proxy/state_impl/base.py
@@ -34,6 +34,7 @@ subtype.
import sqlite3
import threading
import secrets
+import webbrowser
import dataclasses as dc
import typing as t
@@ -137,6 +138,9 @@ PayloadsData = t.Mapping[st.PayloadRef, st.PayloadData]
class HaketiloStateWithFields(st.HaketiloState):
"""...."""
store_dir: Path
+ _listen_host: str
+ _listen_port: int
+ _logger: st.Logger
connection: sqlite3.Connection
settings: st.HaketiloGlobalSettings
current_cursor: t.Optional[sqlite3.Cursor] = None
@@ -251,3 +255,29 @@ class HaketiloStateWithFields(st.HaketiloState):
dependencies as well as at startup.
"""
...
+
+ @property
+ def listen_host(self) -> str:
+ if self._listen_host != '0.0.0.0':
+ return '127.0.0.1'
+
+ return self._listen_host
+
+ @property
+ def listen_port(self) -> int:
+ return self._listen_port
+
+ @property
+ def efective_listen_addr(self) -> str:
+ effective_host = self._listen_host
+ if self._listen_host == '0.0.0.0':
+ effective_host = '127.0.0.1'
+
+ return f'http://{effective_host}:{self._listen_port}'
+
+ def launch_browser(self) -> bool:
+ return webbrowser.open(self.efective_listen_addr)
+
+ @property
+ def logger(self) -> st.Logger:
+ return self._logger
diff --git a/src/hydrilla/proxy/state_impl/concrete_state.py b/src/hydrilla/proxy/state_impl/concrete_state.py
index d2d6e56..83522cf 100644
--- a/src/hydrilla/proxy/state_impl/concrete_state.py
+++ b/src/hydrilla/proxy/state_impl/concrete_state.py
@@ -198,12 +198,29 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields):
def _rebuild_structures(self, cursor: sqlite3.Cursor) -> None:
new_policy_tree = base.PolicyTree()
- ui_factory = policies.WebUIPolicyFactory(builtin=True)
- web_ui_pattern = 'http*://hkt.mitm.it/***'
- for parsed_pattern in url_patterns.parse_pattern(web_ui_pattern):
+ web_ui_main_pattern = 'http*://hkt.mitm.it/***'
+ web_ui_main_factory = policies.WebUIMainPolicyFactory(builtin=True)
+
+ for parsed_pattern in url_patterns.parse_pattern(web_ui_main_pattern):
+ new_policy_tree = new_policy_tree.register(
+ parsed_pattern = parsed_pattern,
+ item = web_ui_main_factory
+ )
+
+ web_ui_landing_pattern = f'{self.efective_listen_addr}/***'
+ web_ui_landing_factory = policies.WebUILandingPolicyFactory(
+ builtin = True
+ )
+
+ try:
+ parsed_pattern, = url_patterns.parse_pattern(web_ui_landing_pattern)
+ except url_patterns.HaketiloURLException:
+ fmt = _('warn.proxy.failed_to_register_landing_page_at_{}')
+ self.logger.warn(fmt.format(web_ui_landing_pattern))
+ else:
new_policy_tree = new_policy_tree.register(
- parsed_pattern,
- ui_factory
+ parsed_pattern = parsed_pattern,
+ item = web_ui_landing_factory
)
# Put script blocking/allowing rules in policy tree.
@@ -346,7 +363,12 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields):
self.settings = load_settings(cursor)
@staticmethod
- def make(store_dir: Path) -> 'ConcreteHaketiloState':
+ def make(
+ store_dir: Path,
+ listen_host: str,
+ listen_port: int,
+ logger: st.Logger
+ ) -> 'ConcreteHaketiloState':
store_dir.mkdir(parents=True, exist_ok=True)
connection = sqlite3.connect(
@@ -360,7 +382,10 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields):
global_settings = load_settings(connection.cursor())
return ConcreteHaketiloState(
- store_dir = store_dir,
- connection = connection,
- settings = global_settings
+ store_dir = store_dir,
+ _logger = logger,
+ _listen_host = listen_host,
+ _listen_port = listen_port,
+ connection = connection,
+ settings = global_settings
)
diff --git a/src/hydrilla/proxy/web_ui/__init__.py b/src/hydrilla/proxy/web_ui/__init__.py
index a5dddab..1ae5dba 100644
--- a/src/hydrilla/proxy/web_ui/__init__.py
+++ b/src/hydrilla/proxy/web_ui/__init__.py
@@ -4,4 +4,5 @@
#
# Available under the terms of Creative Commons Zero v1.0 Universal.
+from ._app import UIDomain
from .root import process_request
diff --git a/src/hydrilla/proxy/web_ui/_app.py b/src/hydrilla/proxy/web_ui/_app.py
index ab15918..f54f72e 100644
--- a/src/hydrilla/proxy/web_ui/_app.py
+++ b/src/hydrilla/proxy/web_ui/_app.py
@@ -4,6 +4,8 @@
#
# Available under the terms of Creative Commons Zero v1.0 Universal.
+import enum
+import dataclasses as dc
import typing as t
import flask
@@ -11,8 +13,17 @@ import flask
from .. import state as st
+class UIDomain(enum.Enum):
+ MAIN = enum.auto()
+ LANDING_PAGE = enum.auto()
+
+@dc.dataclass(init=False)
class WebUIApp(flask.Flask):
- _haketilo_state: st.HaketiloState
+ _haketilo_state: st.HaketiloState
+ _haketilo_ui_domain: t.ClassVar[UIDomain]
def get_haketilo_state() -> st.HaketiloState:
return t.cast(WebUIApp, flask.current_app)._haketilo_state
+
+def get_haketilo_ui_domain() -> UIDomain:
+ return t.cast(WebUIApp, flask.current_app)._haketilo_ui_domain
diff --git a/src/hydrilla/proxy/web_ui/root.py b/src/hydrilla/proxy/web_ui/root.py
index 18ea18e..57dc958 100644
--- a/src/hydrilla/proxy/web_ui/root.py
+++ b/src/hydrilla/proxy/web_ui/root.py
@@ -29,6 +29,7 @@
.....
"""
+import dataclasses as dc
import typing as t
from threading import Lock
@@ -71,10 +72,21 @@ def get_settings() -> st.HaketiloGlobalSettings:
return _app.get_haketilo_state().get_settings()
+@dc.dataclass(init=False)
class WebUIAppImpl(_app.WebUIApp):
+ # Flask app is not thread-safe and has to be accompanied by an ugly lock.
+ # This can cause slow requests to block other requests, so we might need a
+ # better workaround at some later point.
+ _haketilo_app_lock: Lock
+
+ _haketilo_blueprints: t.ClassVar[t.Sequence[flask.Blueprint]]
+ _haketilo_ui_domain: t.ClassVar[_app.UIDomain]
+
def __init__(self):
super().__init__(__name__)
+ self._haketilo_app_lock = Lock()
+
self.jinja_options = {
**self.jinja_options,
'loader': jinja2.PackageLoader(__package__),
@@ -99,19 +111,13 @@ class WebUIAppImpl(_app.WebUIApp):
self.before_request(authenticate_by_referrer)
- for blueprint in [
- rules.bp, repos.bp, items.bp, items_import.bp, prompts.bp
- ]:
- self.register_blueprint(blueprint)
+ for bp in self._haketilo_blueprints:
+ self.register_blueprint(bp)
-# Flask app is not thread-safe and has to be accompanied by an ugly lock. This
-# can cause slow requests to block other requests, so we might need a better
-# workaround at some later point.
-app = WebUIAppImpl()
-app_lock = Lock()
+home_bp = flask.Blueprint('home', __package__)
-@app.route('/', methods=['GET'])
+@home_bp.route('/', methods=['GET'])
def home(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response:
state = _app.get_haketilo_state()
@@ -122,7 +128,7 @@ def home(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response:
)
return flask.make_response(html, 200)
-@app.route('/', methods=['POST'])
+@home_bp.route('/', methods=['POST'])
def home_post() -> werkzeug.Response:
action = flask.request.form['action']
@@ -149,22 +155,57 @@ def home_post() -> werkzeug.Response:
return flask.redirect(flask.url_for('.home'), 303)
+blueprints_main = \
+ (rules.bp, repos.bp, items.bp, items_import.bp, prompts.bp, home_bp)
+
+@dc.dataclass(init=False)
+class AppMain(WebUIAppImpl):
+ _haketilo_blueprints = blueprints_main
+ _haketilo_ui_domain = _app.UIDomain.MAIN
+
+
+landing_bp = flask.Blueprint('landing_page', __package__)
+
+@landing_bp.route('/', methods=['GET'])
+def landing(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response:
+ state = _app.get_haketilo_state()
+
+ html = flask.render_template(
+ 'landing.html.jinja',
+ listen_host = state.listen_host,
+ listen_port = state.listen_port
+ )
+ return flask.make_response(html, 200)
+
+@dc.dataclass(init=False)
+class AppLandingPage(WebUIAppImpl):
+ _haketilo_blueprints = (landing_bp,)
+ _haketilo_ui_domain = _app.UIDomain.LANDING_PAGE
+
+
+apps_seq = [AppMain(), AppLandingPage()]
+apps = dict((app._haketilo_ui_domain, app) for app in apps_seq)
+
+
def process_request(
request_info: http_messages.RequestInfo,
- state: st.HaketiloState
+ state: st.HaketiloState,
+ ui_domain: _app.UIDomain = _app.UIDomain.MAIN
) -> http_messages.ProducedResponse:
path = '/'.join(('', *request_info.url.path_segments))
if (request_info.url.has_trailing_slash):
path += '/'
- with app_lock:
+ app = apps[ui_domain]
+
+ with app._haketilo_app_lock:
app._haketilo_state = state
app.jinja_env.install_gettext_translations(make_translation())
flask_response = app.test_client().open(
path = path,
- base_url = f'{request_info.url.scheme}://hkt.mitm.it',
+ base_url = request_info.url.url_without_path,
method = request_info.method,
query_string = request_info.url.query,
headers = [*request_info.headers.items()],
diff --git a/src/hydrilla/proxy/web_ui/templates/base.html.jinja b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
index e4760bf..f89b39a 100644
--- a/src/hydrilla/proxy/web_ui/templates/base.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
@@ -17,11 +17,9 @@ You can choose to use either of these licenses or both.
I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
--#}
+#}
<!DOCTYPE html>
-{% set settings = get_settings() %}
-
{% macro button_row(buttons_data, common_fields={}) %}
<div class="flex-row">
{% for classes, text, extra_fields in buttons_data %}
@@ -223,96 +221,13 @@ code in a proprietary work, I am not going to enforce this in court.
.hide {
display: none !important;
}
-
- ul#nav {
- -moz-user-select: none;
- user-select: none;
- display: flex;
- justify-content: stretch;
- white-space: nowrap;
- background-color: #e0e0e0;
- margin: 0;
- padding: 0;
- border-bottom: 2px solid #444;
- overflow-x: scroll;
- }
-
- li.nav-entry, li.nav-separator {
- list-style-type: none;
- }
-
- li.nav-entry {
- background-color: #70af70;
- font-size: 115%;
- cursor: pointer;
- text-align: center;
- flex: 1 1 0;
- }
-
- li.nav-separator {
- flex: 0 0 2px;
- background-color: inherit;
- }
-
- li.big-separator {
- flex: 4 0 2px;
- }
-
- li.nav-entry:hover {
- box-shadow: 0 6px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
- }
-
- ul#nav > li.nav-active {
- background-color: #65A065;
- color: #222;
- box-shadow: none;
- cursor: default;
- }
-
- ul#nav > li > :only-child {
- display: block;
- padding: 10px;
- }
- {% endblock %}
+ {% endblock style %}
</style>
- {% endblock %}
+ {% endblock head %}
</head>
<body>
- {% set active_endpoint = get_current_endpoint() %}
- {%
- set navigation_bar = [
- ('home', _('web_ui.base.nav.home'), false),
- ('rules.rules', _('web_ui.base.nav.rules'), false),
- ('items.packages', _('web_ui.base.nav.packages'), false),
- ('items.libraries', _('web_ui.base.nav.libraries'), true),
- ('repos.repos', _('web_ui.base.nav.repos'), false),
- ('import.items_import', _('web_ui.base.nav.import'), false)
- ]
- %}
- <ul id="nav">
- {%
- for endpoint, label, advanced_user_only in navigation_bar
- if not advanced_user_only or settings.advanced_user
- %}
- {% if not loop.first %}
- {% set sep_classes = ['nav-separator'] %}
- {% if loop.last %}
- {% do sep_classes.append('big-separator') %}
- {% endif %}
- <li class="{{ sep_classes|join(' ') }}"></li>
- {% endif %}
-
- {% if endpoint == active_endpoint %}
- <li class="nav-entry nav-active"><div>{{ label }}</div></li>
- {% else %}
- <li class="nav-entry">
- <a href="{{ url_for(endpoint) }}" draggable="false">
- {{ label }}
- </a>
- </li>
- {% endif %}
- {% endfor %}
- </ul>
- <div id="main">{% block main required %}{% endblock %}</div>
+ {% block body %}
+ <div id="main">{% block main required %}{% endblock %}</div>
+ {% endblock body %}
</body>
</html>
diff --git a/src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja b/src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja
new file mode 100644
index 0000000..6a6de99
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja
@@ -0,0 +1,116 @@
+{#
+SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+Proxy web UI base page template of htk.mitm.it meta-site.
+
+This file is part of Hydrilla&Haketilo.
+
+Copyright (C) 2022 Wojtek Kosior
+
+Dual licensed under
+* GNU General Public License v3.0 or later and
+* Creative Commons Attribution Share Alike 4.0 International.
+
+You can choose to use either of these licenses or both.
+
+
+I, Wojtek Kosior, thereby promise not to sue for violation of this
+file's licenses. Although I request that you do not make use of this
+code in a proprietary work, I am not going to enforce this in court.
+#}
+{% extends "base.html.jinja" %}
+
+{% set settings = get_settings() %}
+
+{% block style %}
+ {{ super() }}
+ ul#nav {
+ -moz-user-select: none;
+ user-select: none;
+ display: flex;
+ justify-content: stretch;
+ white-space: nowrap;
+ background-color: #e0e0e0;
+ margin: 0;
+ padding: 0;
+ border-bottom: 2px solid #444;
+ overflow-x: scroll;
+ }
+
+ li.nav-entry, li.nav-separator {
+ list-style-type: none;
+ }
+
+ li.nav-entry {
+ background-color: #70af70;
+ font-size: 115%;
+ cursor: pointer;
+ text-align: center;
+ flex: 1 1 0;
+ }
+
+ li.nav-separator {
+ flex: 0 0 2px;
+ background-color: inherit;
+ }
+
+ li.big-separator {
+ flex: 4 0 2px;
+ }
+
+ li.nav-entry:hover {
+ box-shadow: 0 6px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
+ }
+
+ ul#nav > li.nav-active {
+ background-color: #65A065;
+ color: #222;
+ box-shadow: none;
+ cursor: default;
+ }
+
+ ul#nav > li > :only-child {
+ display: block;
+ padding: 10px;
+ }
+{% endblock style %}
+
+{% block body %}
+ {% set active_endpoint = get_current_endpoint() %}
+ {%
+ set navigation_bar = [
+ ('home.home', _('web_ui.base.nav.home'), false),
+ ('rules.rules', _('web_ui.base.nav.rules'), false),
+ ('items.packages', _('web_ui.base.nav.packages'), false),
+ ('items.libraries', _('web_ui.base.nav.libraries'), true),
+ ('repos.repos', _('web_ui.base.nav.repos'), false),
+ ('import.items_import', _('web_ui.base.nav.import'), false)
+ ]
+ %}
+ <ul id="nav">
+ {%
+ for endpoint, label, advanced_user_only in navigation_bar
+ if not advanced_user_only or settings.advanced_user
+ %}
+ {% if not loop.first %}
+ {% set sep_classes = ['nav-separator'] %}
+ {% if loop.last %}
+ {% do sep_classes.append('big-separator') %}
+ {% endif %}
+ <li class="{{ sep_classes|join(' ') }}"></li>
+ {% endif %}
+
+ {% if endpoint == active_endpoint %}
+ <li class="nav-entry nav-active"><div>{{ label }}</div></li>
+ {% else %}
+ <li class="nav-entry">
+ <a href="{{ url_for(endpoint) }}" draggable="false">
+ {{ label }}
+ </a>
+ </li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+
+ {{ super() }}
+{% endblock body %}
diff --git a/src/hydrilla/proxy/web_ui/templates/import.html.jinja b/src/hydrilla/proxy/web_ui/templates/import.html.jinja
index 7636b77..6ec9947 100644
--- a/src/hydrilla/proxy/web_ui/templates/import.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/import.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.import.title') }} {% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/index.html.jinja
index e42f5e9..010c2ed 100644
--- a/src/hydrilla/proxy/web_ui/templates/index.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/index.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.home.title') }} {% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja
index e517f3b..ccfa6b9 100644
--- a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% macro version_with_repo(info) -%}
{{ info.info.version_string }}
diff --git a/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja
index 0a72b64..0996b8b 100644
--- a/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.libraries.title') }} {% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja
index bc6b5bb..b79d594 100644
--- a/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.packages.title') }} {% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/landing.html.jinja b/src/hydrilla/proxy/web_ui/templates/landing.html.jinja
new file mode 100644
index 0000000..6314cfd
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/landing.html.jinja
@@ -0,0 +1,49 @@
+{#
+SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+Proxy web UI landing page.
+
+This file is part of Hydrilla&Haketilo.
+
+Copyright (C) 2022 Wojtek Kosior
+
+Dual licensed under
+* GNU General Public License v3.0 or later and
+* Creative Commons Attribution Share Alike 4.0 International.
+
+You can choose to use either of these licenses or both.
+
+
+I, Wojtek Kosior, thereby promise not to sue for violation of this
+file's licenses. Although I request that you do not make use of this
+code in a proprietary work, I am not going to enforce this in court.
+#}
+{% extends "base.html.jinja" %}
+
+{% block title %} {{ _('web_ui.landing.title') }} {% endblock %}
+
+{% block main %}
+ <h3>
+ {{ _('web_ui.landing.heading.haketilo_is_running') }}
+ </h3>
+
+ <p>
+ {{ _('web_ui.landing.web_ui.landing.what_to_do_1') }}
+ </p>
+
+ {{ label(_('web_ui.landing.host_label')) }}
+
+ <p>
+ {{ listen_host }}
+ </p>
+
+ {{ label(_('web_ui.landing.port_label')) }}
+
+ <p>
+ {{ listen_port }}
+ </p>
+
+ <p class="has-colored-links">
+ {{ _('web_ui.landing.html.what_to_do_2')|safe }}
+ </p>
+{% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja
index f4f600c..a17e61d 100644
--- a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %}
{{ _('web_ui.prompts.auto_install_error.title') }}
diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja
index ba06ae9..2df38b3 100644
--- a/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja
@@ -19,7 +19,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %}
{{ _('web_ui.prompts.package_suggestion.title') }}
diff --git a/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja
index f635444..106af53 100644
--- a/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.repos.add.title') }} {% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja
index e670b59..4f09ae6 100644
--- a/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %}{{ _('web_ui.repos.title') }}{% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja
index 04075c4..c4b7a9a 100644
--- a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.repos.single.title') }} {% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja
index 430c5ca..6d21ccd 100644
--- a/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.rules.add.title') }} {% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja
index 799eaba..57cc8ad 100644
--- a/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %}{{ _('web_ui.rules.title') }}{% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja
index 9bf2c75..95e8009 100644
--- a/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja
@@ -18,7 +18,7 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this
file's licenses. Although I request that you do not make use of this
code in a proprietary work, I am not going to enforce this in court.
#}
-{% extends "base.html.jinja" %}
+{% extends "hkt_mitm_it_base.html.jinja" %}
{% block title %} {{ _('web_ui.rules.single.title') }} {% endblock %}
diff --git a/src/hydrilla/url_patterns.py b/src/hydrilla/url_patterns.py
index 115a040..cc68820 100644
--- a/src/hydrilla/url_patterns.py
+++ b/src/hydrilla/url_patterns.py
@@ -42,6 +42,12 @@ from immutables import Map
from .translations import smart_gettext as _
from .exceptions import HaketiloException
+
+class HaketiloURLException(HaketiloException):
+ """Type used for exceptions generated when parsing a URL or URL pattern."""
+ pass
+
+
default_ports: t.Mapping[str, int] = Map(http=80, https=443, ftp=21)
ParsedUrlType = t.TypeVar('ParsedUrlType', bound='ParsedUrl')
@@ -131,9 +137,9 @@ def _parse_pattern_or_url(
(parse_result.scheme != 'file' and not has_hostname):
if is_pattern:
msg = _('err.url_pattern_{}.bad').format(orig_url)
- raise HaketiloException(msg)
+ raise HaketiloURLException(msg)
else:
- raise HaketiloException(_('err.url_{}.bad') .format(url))
+ raise HaketiloURLException(_('err.url_{}.bad') .format(url))
# Verify the URL uses a known scheme and extract it.
scheme = parse_result.scheme
@@ -141,15 +147,15 @@ def _parse_pattern_or_url(
if parse_result.scheme not in supported_schemes:
if is_pattern:
msg = _('err.url_pattern_{}.bad_scheme').format(orig_url)
- raise HaketiloException(msg)
+ raise HaketiloURLException(msg)
else:
- raise HaketiloException(_('err.url_{}.bad_scheme').format(url))
+ raise HaketiloURLException(_('err.url_{}.bad_scheme').format(url))
# Extract and keep information about special pattern schemas used.
if is_pattern and orig_url.startswith('http*:'):
if parse_result.port:
fmt = _('err.url_pattern_{}.special_scheme_port')
- raise HaketiloException(fmt.format(orig_url))
+ raise HaketiloURLException(fmt.format(orig_url))
# Extract URL's explicit port or deduce the port based on URL's protocol.
try:
@@ -161,9 +167,9 @@ def _parse_pattern_or_url(
if port_out_of_range:
if is_pattern:
msg = _('err.url_pattern_{}.bad_port').format(orig_url)
- raise HaketiloException(msg)
+ raise HaketiloURLException(msg)
else:
- raise HaketiloException(_('err.url_{}.bad_port').format(url))
+ raise HaketiloURLException(_('err.url_{}.bad_port').format(url))
port = explicit_port or default_ports.get(parse_result.scheme)
@@ -185,11 +191,11 @@ def _parse_pattern_or_url(
if is_pattern:
if parse_result.query:
msg = _('err.url_pattern_{}.has_query').format(orig_url)
- raise HaketiloException(msg)
+ raise HaketiloURLException(msg)
if parse_result.fragment:
msg = _('err.url_pattern_{}.has_frag').format(orig_url)
- raise HaketiloException(msg)
+ raise HaketiloURLException(msg)
query = parse_result.query