Repository: marcus67/little_brother (language: python)

Issue:
Debian post-install script does not install PIP packages into virtual environment
Since the introduction of the pip3.sh helper script the Debian post-install script ignores the virtual environment. Instead, all files are installed into the system paths.


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7497a5c..1e820cc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -42,11 +42,11 @@ jobs:
       - store_artifacts:
           path: htmlcov
       - store_artifacts:
-          path: debian/little-brother_0.4.19_121.deb
+          path: debian/little-brother_0.4.26_133.deb
       - persist_to_workspace:
           root: debian
           paths:
-            - little-brother_0.4.19_121.deb
+            - little-brother_0.4.26_133.deb
   build_pypi:
     #working_directory: ~
     docker:
@@ -60,11 +60,11 @@ jobs:
       - run: git submodule update --init
       - run: PYTHONPATH=contrib/python_base_app python3 ci_toolbox.py --execute-stage BUILD --use-dev-dir=.
       - store_artifacts:
-          path: "dist/little-brother-0.4.19.tar.gz"
+          path: "dist/little-brother-0.4.26.tar.gz"
       - persist_to_workspace:
           root: dist
           paths:
-            - "little-brother-0.4.19.tar.gz"
+            - "little-brother-0.4.26.tar.gz"
   install_pypi:
     #working_directory: ~
     docker:
@@ -136,6 +136,7 @@ workflows:
         - docker:
             requires:
               - build
+              - publish_pypi
         - build_pypi
         - install_pypi:
             requires:
@@ -143,7 +144,6 @@ workflows:
         - publish_pypi:
             requires:
               - install_pypi
-              - docker
             filters:
               branches:
                 only:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fb05c26..19078fb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -28,8 +28,10 @@ stages:
   - build
   - install_and_check
   - analyze
-  - deploy
+  - owasp_check
+  - owasp_upload
   - publish
+  - deploy
   
 build_debian:
   image: marcusrickert/docker-python-app:latest
@@ -68,7 +70,7 @@ build_pypi:
   artifacts:
       when: always
       paths:
-        - dist/little-brother-0.4.19.tar.gz
+        - dist/little-brother-0.4.26.tar.gz
   variables:
     # Suppress automatic checkout for all sub modules
     GIT_SUBMODULE_STRATEGY: recursive
@@ -101,6 +103,47 @@ analyze:
     # Suppress automatic checkout for all sub modules
     GIT_SUBMODULE_STRATEGY: recursive
 
+
+
+owasp_check:
+  image:
+    name: registry.gitlab.com/gitlab-ci-utils/docker-dependency-check:latest
+    entrypoint: [""]
+  stage: owasp_check
+  script:
+    # Job will scan the project root folder and fail if any vulnerabilities with CVSS > 0 are found
+    - /usr/share/dependency-check/bin/dependency-check.sh --enableExperimental --scan "./" --format XML --project "$CI_PROJECT_NAME"
+    # Dependency Check will only fail the job based on CVSS scores, and in some cases vulnerabilities do not
+    # have CVSS scores (e.g. those from NPM audit), so they don't cause failure.  To fail for any vulnerabilities
+    # grep the resulting report for any "vulnerabilities" sections and exit if any are found (count > 0).
+  allow_failure: false
+  artifacts:
+    when: always
+    paths:
+        # Save the HTML and JSON report artifacts
+      - "./dependency-check-report.xml"
+
+owasp_upload:
+  image:
+    name: "$VULSCAN_ADAPTER_IMAGE"
+    entrypoint: ["python", "run.py"]
+  stage: owasp_upload
+  allow_failure: false
+  variables:
+    VULSCAN_SERVER_URL: "$VULSCAN_URL/analyzer/api"
+    REPORT_FILE_NAME: dependency-check-report.xml
+    FILE_FORMAT: xml
+    TOOL_NAME: owasp
+    PROJECT_ID: "little-brother"
+    PROJECT_NAME: "little-brother"
+  script:
+    - echo "Analysis successful."
+
+publish_pypi:
+  image: marcusrickert/docker-python-app:latest
+  stage: publish
+  script:
+    - PYTHONPATH=contrib/python_base_app python3 ci_toolbox.py --execute-stage PUBLISH-PYPI-PACKAGE --use-dev-dir=.
 docker:
   image: marcusrickert/docker-docker-ci:release-0.9.1
   stage: deploy
@@ -109,9 +152,3 @@ docker:
   variables:
     # Activate automatic checkout for all sub modules
     GIT_SUBMODULE_STRATEGY: recursive
-
-publish_pypi:
-  image: marcusrickert/docker-python-app:latest
-  stage: publish
-  script:
-    - PYTHONPATH=contrib/python_base_app python3 ci_toolbox.py --execute-stage PUBLISH-PYPI-PACKAGE --use-dev-dir=.
\ No newline at end of file
diff --git a/ADVANCED_TOPICS.md b/ADVANCED_TOPICS.md
index ab1ce04..9499474 100644
--- a/ADVANCED_TOPICS.md
+++ b/ADVANCED_TOPICS.md
@@ -11,7 +11,7 @@ of the configuration files. See here for templates:
 *   <A HREF="etc/master.config">`master.config`</A>: A configuration file to run the 
     application on a single host.
 
-*   <A HREF="etc/slave.config">`slave.config`</A>: A configuration file to run the 
+*   <A HREF="etc/client.config">`client.config`</A>: A configuration file to run the 
     application on a client. This file is relatively simple since it only contains the details to connect 
     to the master host.
 
@@ -27,14 +27,14 @@ the relative path can be changed. Edit the appropriate settings in the configura
 
     [StatusServer]
     port=PORT
-    proxy_prefix=PREFIX
+    base_url=PREFIX
 
 After restarting the server (see above) the web frontend can be reached at 
 http://localhost:PORT/PREFIX.
 
 ## Users and UIDs
 
-The default behavior of little brother is to retrieve user names and UIDs from the file `/etc/passwd` using standard
+The default behavior of little brother is to retrieve usernames and UIDs from the file `/etc/passwd` using standard
 Python libraries. All users in `/etc/passwd` are eligible to be monitored which match these additional criteria:
 
 *   the UID is between 500 and 65000,
@@ -53,7 +53,7 @@ to override the defaults.
 
 Using `/etc/passwd`, however, may be a problem in two cases:
 
-*   The master host does not have the users defined which will be monitored on the slaves.
+*   The master host does not have the users defined which will be monitored on the clients.
 *   The master host is using an LDAP server to store the users and their credentials.
 
 In both cases you can provide the users and their respective UIDs explicitly in the configuration file:
@@ -61,13 +61,13 @@ In both cases you can provide the users and their respective UIDs explicitly in
     [UnixUserHandler]
     user_list=USERNAME1:UID1, USERNAME2:UID2, ...
  
-The `USERNAME*` and `UID*` must match those on the slave hosts.
+The `USERNAME*` and `UID*` must match those on the client hosts.
 
 ## Providing a Mapping between UIDs
 
-When using a master-slave setup the assumption is that UIDs on the master host and the slave hosts match. In this
-case no further configuration will be necessary. If, however, there are differences between master and slaves, it is
-possible to provide a mapping of the UIDs for the user names. This mapping is done for each group of hosts sharing
+When using a master-client set up the assumption is that UIDs on the master host and the client hosts match. In this
+case no further configuration will be necessary. If, however, there are differences between master and clients, it is
+possible to provide a mapping of the UIDs for the usernames. This mapping is done for each group of hosts sharing
 the same mapping by defining a `LoginMapping*` entry in the configuration file of the master host:
 
     [LoginMappingSomeServerGroup]
@@ -77,27 +77,27 @@ the same mapping by defining a `LoginMapping*` entry in the configuration file o
     
 The name after `LoginMapping` can be chosen freely. It just has to be unique across all mappings. The actual name
 of the server is given by option `server_group`. It is followed by entries for each user in format `username:UID`.
-The `username` has to match the username on the master. The `uid` is the UID for that user on the slaves of the
-server group. Note that the actual username of the user on the slaves is irrelevant for the mapping!
+The `username` has to match the username on the master. The `uid` is the UID for that user on the clients of the
+server group. Note that the actual username of the user on the clients is irrelevant for the mapping!
 
-In the slave configuration the name of the server group has to be configured:
+In the client configuration the name of the server group has to be configured:
 
     [AppControl]
     server_group = SomeServerGroup
   
 ### Example
 
-In the example below there are two server groups with two slaves each: `AB` and `CD`. The blue boxes contain the
-local usernames ans UIDs on all servers. The green boxes contain the required configuration for `LittleBrother`.
+In the example below there are two server groups with two clients each: `AB` and `CD`. The blue boxes contain the
+local usernames and UIDs on all servers. The green boxes contain the required configuration for `LittleBrother`.
 
-![Login Mappings](doc/login-mappings.png)
+![Login Mappings](doc/login-mappings.drawio.png)
 
 Note the following aspects:
 
-*   The UIDs on the master and the slaves do not match.
-*   The login mappings always contain the usernames on the master. They may differ from the usernames on the slaves
-   (see `sam` on the slave who is called `sammy` on servers in group `AB`).
-*   The login mappings always contain the UIDs on the slaves.
+*   The UIDs on the master and the clients do not match.
+*   The login mappings always contain the usernames on the master. They may differ from the usernames on the clients
+   (see `sam` on the client who is called `sammy` on servers in group `AB`).
+*   The login mappings always contain the UIDs on the clients.
 *   If users do not exist on servers of a group there is no need to supply a mapping entry. See mapping for group
 `CD`.   
 
@@ -109,7 +109,7 @@ options are configured in the master configuration file. See section `[LdapUserH
 `[UnixUserHandler]`. If at least one mandatory setting is missing `[UnixUserHandler]` will be used as a fallback. 
 
 All users/accounts in the administration group (setting `ldap_admin_group_name`) 
-will be able to login and have access to the restricted pages. The logged in user will be displayed in the 
+will be able to login and have access to the restricted pages. The logged-in user will be displayed in the 
 menu bar as shown below (`mr` in this case).
 
 ![MenubarLdapLogin](doc/menubar-ldap-login.png)
@@ -117,7 +117,29 @@ menu bar as shown below (`mr` in this case).
 If a user group (setting `ldap_user_object_class`)
 is provided all users in that group will be offered as potential users to be monitored. If the group is missing
 all users in `/etc/passwd` will be offered that fulfill the same requirements as described above for the 
-section `[UnixUserHandler]`. Also as above, the requirements can be changed using the same options. 
+section `[UnixUserHandler]`. Also as above, the requirements can be changed using the same options.
+
+### Installation of the LDAP Package Extension
+
+The functionality required to use the LDAP interface is not included in either `python-base-app` or `little-brother`.
+Hence, it is not included in the Debian package either. If you want to use it you will have to load an extra package
+AFTER the successful installation of the Debian package. Note that the extension is not relevant for the clients, since
+the LDAP configuration solely takes place on the master.
+
+Let's assume that you have successfully installed LittleBrother using the Debian package. In this case you should
+have a virtual Python environment at `/var/lib/little-brother/virtualenv/`. We need to use the `pip` located in that
+directory as follows:
+
+    /var/lib/little-brother/virtualenv/bin/pip install python-base-app-ldap-extension
+
+Note that this step will require Python to compile the LDAP package so it will take considerably longer than installing
+a source-only Python package. This was also the rationale behind excluding the LDAP extension from the 
+`python-base-app` since all other dependencies of that package are source-only.
+
+After the package has been successfully installed make sure you have your master configuration setup correctly for LDAP
+and restart the LittleBrothe process:
+
+    systemctl restart little-brother
 
 ## Migrating From Older Revisions 
 
@@ -145,26 +167,26 @@ configuration file to the database, `LittleBrother` will start an automatic migr
 configuration file upon startup. For every subsequent run the user and rule set data in the file will be ignored.
 These settings can be removed. If they are still found a warning will be issued.  
 
-## Installation on a Slave Host (Client Server Mode)
+## Installation on a Client Host (Client Server/Master Mode)
 
-In addition to the master host any number of slave hosts may be configured. The assumption is that the users
-to be monitored have login permission to all those hosts and that the user names on all hosts match. In this
+In addition to the master host any number of client hosts may be configured. The assumption is that the users
+to be monitored have login permission to all those hosts and that the usernames on all hosts match. In this
 case access times on all hosts are communicated to the master host and accumulated there. The master will
 apply the rule sets and determine which users have exceeded their access times. 
 
 
-The basic installation of `LittleBrother` on a slave host is basically the same as on the master host. See the 
+The basic installation of `LittleBrother` on a client host is basically the same as on the master host. See the 
 main [README](README.md) on how to install the Debian package. Then follow these steps:
 
-*   Copy the slave configuration template <A HREF="etc/slave.config">`slave.config`</A> to 
-`/etc/little-brother/little-brother.conf` on each slave host.
+*   Copy the client configuration template <A HREF="etc/client.config">`client.config`</A> to 
+`/etc/little-brother/little-brother.conf` on each client host.
 
 *   Choose a secret access token and set this token in the configuration on the master host:   
 
         [MasterConnector]
         access_token=SOME_LONG_AND_SECRET_TOKEN
 
-*   Set the same access token in the configuration on all slave hosts. On the latter also the address of the 
+*   Set the same access token in the configuration on all client hosts. On the latter also the address of the 
 master host must be set. Replace `[MASTERHOST]` by the appropriate host DNS name or IP address and
 `[PORT]` by the appropriate port.
 
@@ -172,24 +194,24 @@ master host must be set. Replace `[MASTERHOST]` by the appropriate host DNS name
         host_url=http://[MASTERHOST]:[PORT]
         access_token=SOME_LONG_AND_SECRET_TOKEN
 
-Note that for the time being the communication between slaves and master is always simple HTTP.
+Note that for the time being the communication between clients and master is always simple HTTP.
  
-Restart the application on the slave host again by issuing
+Restart the application on the client host again by issuing
 
     systemctl start little-brother
     
-Beside the Debian package, there is also a Docker image available which can be used on the slave host. 
+Beside the Debian package, there is also a Docker image available which can be used on the client host. 
 See [Docker](DOCKER.md) for details.
 
 ## Using a Full Fledged Database as Backend
 
 The default backend for `LittleBrother` is a Sqlite file oriented database. It works out of the box. If you prefer
-a more mature backend you can switch to a full fledged database such as MySQL or MariaDB. This is possible, since 
+a more mature backend you can switch to a full-fledged database such as MySQL or MariaDB. This is possible, since 
 the persistence uses the abstraction layer [SQLAlchemy](https://www.sqlalchemy.org/) which can be used with many 
-different database systems. Currently, `LittleBrother` should work with MySQL, MariaDB and PostgreSQL.
+database systems. Currently, `LittleBrother` should work with MySQL, MariaDB and PostgreSQL.
 
-**IMPORTANT NOTE**: The steps shown below only refer to the MASTER host. The slave(s) should ALWAYS use the simple
-sqlite backend no matter which kind of backend the master will use! This is due to the fact that the slaves never have
+**IMPORTANT NOTE**: The steps shown below only refer to the MASTER host. The client(s) should ALWAYS use the simple
+sqlite backend no matter which kind of backend the master will use! This is due to the fact that the clients never have
 to store any data persistently. This is completely handled by the master.
 
 ### Configuring the Database
@@ -219,7 +241,7 @@ must be set as follows:
 | MariaDB     | `mysql+pymysql`      | 3306                |
 | PostgreSQL  | `postgresql`         | 5432                |
 
-Note that in revision 64 and above there is no default value for `database_user` anymore (is was `little_brother`).
+Note that in revision 64 and above there is no default value for `database_user` anymore. It was `little_brother`.
 The name has to be set explicitly now!
 
 ### Create the Database
@@ -259,7 +281,7 @@ The `[StatusServer]` configuration section of the master host should contain the
 
     [StatusServer]
     ...
-    proxy_prefix=/LittleBrother
+    base_url=/LittleBrother
     ...
 
 ## Monitoring the Application
@@ -268,21 +290,21 @@ The `[StatusServer]` configuration section of the master host should contain the
 
 ## Network Tempering Detection
 
-The master-slave approach of `LittleBrother` results in all runtime data about processes to be held and evaluated on
-the master node. It is there that the decision to terminate a user session will be made. The slaves only execute
-these decisions. If the network connection between master and slaves is cut the slaves are basically "headless".
+The master-client approach of `LittleBrother` results in all runtime data about processes to be held and evaluated on
+the master node. It is there that the decision to terminate a user session will be made. The clients only execute
+these decisions. If the network connection between master and clients is cut the clients are basically "headless".
 This could be abused by a user (whose activity does not depend on network access) to simply cut the connection to
 the master by *pulling the plug*.
 
 As of version 0.3.13 a detected network downtime will result in an automatic termination of user sessions on the
-affected slaves. The default timeout is 10 times the default scan interval of 5 seconds, that is 50 seconds. This value
+affected clients. The default timeout is 10 times the default scan interval of 5 seconds, that is 50 seconds. This value
 (`maximum_time_without_send_events`) can be changed in the configuration file:
 
-    # Number of seconds before warnings are issued about missing connectivity (no successful send events from slave to master)
+    # Number of seconds before warnings are issued about missing connectivity (no successful send events from client to master)
     # Defaults to 3 * DEFAULT_CHECK_INTERVAL.
     warning_time_without_send_events = 15
     
-    # Number of seconds before slave terminates processes due to missing connectivity (no successful send events from slave to master)
+    # Number of seconds before client terminates processes due to missing connectivity (no successful send events from client to master)
     # Defaults to 10 * DEFAULT_CHECK_INTERVAL.
     maximum_time_without_send_events = 50
  
@@ -323,7 +345,7 @@ We start out the same way by taking a look at the menu entry:
 
 ![Minecraft-Launcher-Settings](doc/minecraft-settings.png)
 
-In this case the command ("Befehl" in German) contains the exlicit path so that we can skip the `which` command.
+In this case the command ("Befehl" in German) contains the explicit path so that we can skip the `which` command.
 We continue by issuing the `file` command:
 
 ![Minecraft-Launcher-Type-Check](doc/minecraft-type-check.png)
@@ -333,3 +355,92 @@ and type `ps uax|grep APPNAME` at the prompt. Deliberately omit the path of the
 the process list returns `/vol/java8/bin/java -jar /vol/mirecraft/Minecraft.jar` as the actual application call.
 We cannot use `java` as the pattern since this would be too general and prevent ANY java application from being
 started. A better choice is the name of the Java JAR, which is `Minecraft.jar` in our case
+
+## Configuring Firewall Support
+
+One of the major drawbacks of `LittleBrother` is its restriction as to which devices can be controlled. Although it
+is possible to monitor any device that is *pingable* it may not be possible to deactivate the same device once the 
+playtime has been exceeded. As of version 0.4.23, at least it is possible to restrict internet access of the relevant 
+devices by installing `iptables` rules during the time that a monitored user is not allowed to use the computer. 
+Since most modern games depend on internet access this is tantamount to deactivating the games.  
+
+### Prerequisites
+
+There are some prerequisites before the firewall feature can be used:
+
+* The firewall support will only work in a master-client setup of `LittleBrother` since the master will be used
+  to restrict the network access of the clients. If you only have a master node, unfortunately, there's no
+  support yet.
+* The network traffic of the clients has to routed through the master. This usually requires two changes in the network 
+  setup:
+  * The clients have to be configured in such a way that their default route points to the master. This can be done
+    manually in the setup of the interfaces or can be triggered by using a DHCP server on the master from which the
+    clients draw their IP addresses and also their default route.
+  * The master has to be configured to forward IP packages. This requires a setting in `sysctl`. 
+    See [here](https://linuxconfig.org/how-to-turn-on-off-ip-forwarding-in-linux) for example. 
+* The package `iptables` has to be activated. In most cases it should suffice to install the package if not already 
+  on the system. 
+* The default chain `FORWARD` has to exist. This should also be the default. You can find out by issuing
+ 
+      iptables -L FORWARD
+
+  If this command does not throw an error everything should be OK.
+  
+* The devices/hosts that need to be blocked have to be configured as devices in `LittleBrother`, the monitoring
+  and the blocking of the devices have to be activated. 
+  See the [Web Frontend Manual](WEB_FRONTEND_MANUAL.md) for details. 
+* There should be not any other programs creating `iptables` entries for the monitored devices since `LittleBrother` 
+  will assume that all rules for the IP addresses of the devices (as sources) are handled by itself. This allows the 
+  host process to clean out all entries for the clients after a crash. Otherwise, some rules may be left in the system
+  which would require the user to issue manual `iptables` commands to fix it. 
+
+### Routing in `iptables`
+
+The diagram below shows the routing which is assumed for the master-client setup for `iptables`. There are two
+clients A and B. Both have the IP address of the master node as their default route. The master has its default
+route set to the IP address of the DSL modem.
+
+Client A is assigned to user A and the flags "is monitored" and "blockable" are  activated for client A.
+
+Normally, all traffic from the client A is routed through the master and ends up in the DSL modem. When the client has
+to be restricted because user A has exceeded his time budget, `LittleBrother` will insert a `FORWARD` saying that all 
+packets from the source IP address (`-s`)of the clients to all destinations (`-d 0.0.0.0/0`) have to be dropped.
+The client B is not affected by this.
+
+Note that this setup only makes sense if there is a one-to-one relationship between users and clients since there
+is no way to restrict network from a specific user on a client. The blocking `iptables` rule always affects ALL users
+on the client. If a client is assigned to several users, `LittleBrother` will block it if at least one of the users
+has exceeded his time limit!
+
+![iptables-routing](doc/iptables-firewall.drawio.png)
+
+**Note** 
+* All IP addresses in this example except for `0.0.0.0` are arbitrary and may differ from your setup!
+* Firewall rules which are added by LittleBrother will show the comment `generated by LittleBrother`. If you run
+  into trouble because LittleBrother has not succeeded in removing the rules upon exit you can use the comment to
+  manually remove them.
+
+### Configuration
+
+The configuration consists of supplying the IP addresses which are supposed to be blocked for the clients. In the 
+normal case this is just the default route to the artificial IP address `0.0.0.0`. The default IPs to be blocked can
+be set in the configuration file of the master node:
+
+    [FirewallHandler]
+    target_ip[0] = 0.0.0.0
+
+If you want block specific hosts instead, you can supply one or several concrete IP addresses or DNS names, e.g.
+
+    [FirewallHandler]
+    target_ip[0] = twerion.net
+    target_ip[1] = purpleprison.net
+    target_ip[2] = sgpggb.de
+
+would block the top three Minecraft servers in Germany.
+
+**Note**: 
+
+* It is possible to override the default IPs above on a per-device basis. See the section *Configuring Devices*
+in the [Web Frontend Manual](WEB_FRONTEND_MANUAL.md).
+* In order to activate the firewall extension you will have to configure at least one IP address in the configuration
+  file of the master. Just defining IP addresses for a device will **NOT** activate the firewall extension!
diff --git a/CHANGES.md b/CHANGES.md
index f7e0d8a..302aca4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,6 +4,57 @@
 
 This document lists all changes of `LittleBrother` with the most recent changes at the top.
 
+## Version 0.4.26 Revision 133 (July 25th, 2023)
+
+* Rename pytest folder to `pytests` (see https://stackoverflow.com/questions/62581901/attribute-error-module-pytest-has-no-attribute-hookimpl/69062733#69062733)
+* Fix error in `migrate_client_device_configs()`
+* Fix error in `patched_firewall_handler_test_result()`
+* General upgrade of most packages
+* Bump python_base_app to 0.2.45
+* Closes https://github.com/marcus67/little_brother/issues/177
+
+## Version 0.4.25 Revision 132 (July 3rd, 2022)
+
+* Re-activate all images (left deactivated by mistake for testing purposes)
+* Upgrade to package `Flask` > 2
+* Upgrade to `some-flask-helpers` 0.2.3
+
+## Version 0.4.24 Revision 131 (July 3rd, 2022)
+
+* Fixed broken Docker images (Debian, Ubuntu, and Alpine)
+* Re-Activated Arch Linux Docker image
+* Removed LDAP dependencies in all images
+* Add missing PIP dependency for `packaging` (caused trouble on Alpine)
+* Remove vagrant directory
+
+## Version 0.4.23 Revision 129 (July 2nd, 2022)
+
+* Closes #169, see [here](https://github.com/marcus67/little_brother/issues/169)
+* Closes #172, see [here](https://github.com/marcus67/little_brother/issues/172)
+* Closes #174, see [here](https://github.com/marcus67/little_brother/issues/174)
+* Upgrade to `python_base_app` version 0.2.42
+* Finalize renaming "slave" into "client"
+* Lock width of multiline text fields in forms
+* Bump `selenium` to 4.3.0
+
+## Version 0.4.22 Revision 123 (March 6th, 2022)
+
+* Deactivate Arch Linux Docker image due to incompatibility of `glibc`
+
+## Version 0.4.21 Revision 123 (March 6th, 2022)
+
+* Update documentation on generic installation script
+* Update documentation on distributions
+* Re-activate the build of all Docker images
+* Upgrade to `python_base_app` version 0.2.38
+
+## Version 0.4.20 Revision 122 (March 3rd, 2022)
+
+* Configuration for Docker images based on Arch Linux and Alpine
+* Use of corrected `generic-install.sh`
+* Closes #170, see [here](https://github.com/marcus67/little_brother/issues/170)
+* Upgrade to `python_base_app` version 0.2.37
+
 ## Version 0.4.19 Revision 121 (February 13th, 2022)
 
 * Upgrade to `python_base_app` version 0.2.36 (lots of trouble releasing `python_base_app`)
@@ -50,7 +101,7 @@ This document lists all changes of `LittleBrother` with the most recent changes
 
 ## Version 0.4.11 Revision 110 (November 22nd, 2021)
 
-* Requeue outgoing events on slave in case API call was not successful (potential fix for 
+* Requeue outgoing events on client in case API call was not successful (potential fix for 
   [issue 157](https://github.com/marcus67/little_brother/issues/157))
 
 ## Version 0.4.10 Revision 109 (October 12th, 2021)
@@ -294,7 +345,7 @@ This document lists all changes of `LittleBrother` with the most recent changes
 ## Version 0.3.1 Revision 67 (July 20th, 2020)
 
 *   Fix issue with master process not detecting user activity when new users are activated
-*   Change the default configuration on the Docker slave image:
+*   Change the default configuration on the Docker client image:
     *   Set sqlite as the default backend
     *   Deactivate audio output and pop notifications
 
@@ -329,7 +380,7 @@ This document lists all changes of `LittleBrother` with the most recent changes
 *   Deactivate settings changes to log handling by alembic
 *   Support broadcasting of user settings, activate on all relevant changes
 *   Increase connection pool size
-*   Activate mandatory sqlite backend for slaves
+*   Activate mandatory sqlite backend for clients
 *   Make sure database session are always closed
 *   Deploy etc/master.config as a template
 *   Closes #93, see [here](https://github.com/marcus67/little_brother/issues/93)
@@ -442,14 +493,14 @@ This document lists all changes of `LittleBrother` with the most recent changes
 
 ## Version 0.1 Revision 48 (December 26th, 2019)
 
-*   Added Docker image for slave process
+*   Added Docker image for client process
 *   Support for overriding of settings using environment
 *   Abstraction of the audio engine (class `BaseAudioPlayer`)
 *   Support for `mpg123` as audio engine (new)
 *   Support for `playsound` as audio engine (rewritten as engine)
 *   Support for `pyglet` as audio engine (new)
-*   Correct handling of default locale on slave device
-*   Distribution of login mappings from master to slave (for Docker containers and macOS)
+*   Correct handling of default locale on client host
+*   Distribution of login mappings from master to client (for Docker containers and macOS)
 *   New CI stage to build docker images
 *   Consistent hiding of sensitive variable values in logging
 *   Packages `sudo` and `procps` added to the Debian package dependencies
diff --git a/DOCKER.md b/DOCKER.md
index 30a90ec..fc38849 100644
--- a/DOCKER.md
+++ b/DOCKER.md
@@ -5,13 +5,16 @@
 
 ## Overview
 
-Currently, there are three Docker images available for `LittleBrother`:
+Currently, there are four Docker images available for `LittleBrother`:
 * [little-brother-client](https://hub.docker.com/repository/docker/marcusrickert/little-brother-client) 
-  containing a LittleBrother client process based on a Debian base image.
+  containing a LittleBrother client based on a Debian base image.
 * [little-brother-ubuntu-client](https://hub.docker.com/repository/docker/marcusrickert/little-brother-ubuntu-client) 
-  containing a LittleBrother client process based on an Ubuntu base image.
+  containing a LittleBrother client based on an Ubuntu base image.
+* [little-brother-alpine-client](https://hub.docker.com/repository/docker/marcusrickert/little-brother-alpine-client) 
+  containing a LittleBrother client based on an Alpine Linux base image. This is the smallest image for
+  `LittleBrother`!
 * [little-brother-slave](https://hub.docker.com/repository/docker/marcusrickert/little-brother-slave) 
-  containing a LittleBrother client process based on a Debian base image. This image is obsolete for naming reasons
+  containing a LittleBrother client based on a Debian base image. This image is obsolete for naming reasons
   and **will not be updated**. Please, migrate to one of the images above.
 
 See [this repository](https://github.com/marcus67/docker-little-brother) for docker-compose support files. 
diff --git a/NON-DEBIAN-INSTALLATION.md b/NON-DEBIAN-INSTALLATION.md
index 747da79..8822833 100644
--- a/NON-DEBIAN-INSTALLATION.md
+++ b/NON-DEBIAN-INSTALLATION.md
@@ -1,6 +1,8 @@
 ![LittleBrother-Logo](little_brother/static/icons/icon-baby-panda-128x128.png)
 ![CentOS-Logo](doc/centos-logo.png)
 ![OpenSuSe-Logo](doc/opensuse-logo.png)
+![ArchLinux-Logo](doc/arch-linux-logo.jpeg)
+![Alpine-Logo](doc/alpine-linux-logo.png)
 
 # Installation on Non-Debian Distributions
 
@@ -13,34 +15,39 @@ This page will outline how to do this.
 
 Install the following Linux packages. Note that the exact names may differ in your distribution.
 
-| Debian Package     | CentOS Package        | SuSe Package |
-| ------------------ | --------------------- | ------------ |
-|   `python3`        | `python3`             | TODO         |
-|   `python3-pip`    | `python3-pip`         | TODO         |
-|   `python-dev`     | `python2-devel`       | TODO         |
-|   `python3-dev`    | `python3-devel`       | TODO         |
-|   `sudo`           | `sudo`                | TODO         |
-|   `gcc`            | `gcc`                 | TODO         |
-|   `procps`         | `procps-ng`           | TODO         |
-|   `virtualenv`     | `python3-virtualenv`  | TODO         |
+| Debian Package           | CentOS Package            | Arch Linux Package   | Alpine package        |
+|--------------------------|-------------------------- |----------------------|-----------------------|
+|   preinstalled           | preinstalled              | preinstalled         | `bash`                |
+|   `python3`              | `python3`                 | `python3`            | `python3`             |
+|   `python3-pip`          | `python3-pip`             | `python-pip`         | `py3-pip`             |
+|   `python3-dev`          | `python3-devel`           | not required         | `python3-devel`       |
+|   `sudo`                 | `sudo`                    | `sudo`               | `sudo`                |
+|   `procps` (master only) | `procps-ng` (master only) | TODO                 | TODO                  |
+|   `virtualenv`           | `python3-virtualenv`      | `python-virtualenv`  | `py3-virtualenv`      |
+|   `libsasl2-dev`         | TODO                      | not required         | `libsasl`             |
+|   not required           | TODO                      | not required         | `libldap=2.5.58`      |
+|   not required           | TODO                      | not required         | `libldapcpp=2.5.58`   |
+|   `libldap2-dev`         | TODO                      | not required         | `openldap-dev=2.5.58` |
+|   not required           | TODO                      | not required         | `libffi-dev`          |
+|   `libssl-dev`           | TODO                      | not required         | not required          |
+|   not required           | TODO                      | `curl`               | `curl`                |
+|   not required           | TODO                      | `gcc`                | `gcc`                 |
 
 ## Installation Steps
 
-*   Download the current GitHub project as a 
-    [zipped tar file](https://github.com/marcus67/little_brother/archive/master.zip) and store it locally.
+* Download the current GitHub project as a 
+  [master branch zipped tar file](https://github.com/marcus67/little_brother/archive/master.zip) or
+  [release branch zipped tar file](https://github.com/marcus67/little_brother/archive/release.zip) 
+ and store it locally.
     
-*   Unzip the archive by executing `unzip master.zip`.
+* Unzip the archive by executing `unzip master.zip`.
 
-*   The base directory of the unzipped archive containing the `README.md` will be referenced as `$INSTALL_BASE_DIR`
+* The base directory of the unzipped archive containing the `README.md` will be referenced as `$INSTALL_BASE_DIR`
     hence forward.
 
-*   Download the current PIP3 packages for `LittleBrother`, `python_base_app` and `some_flask_helpers` from 
-    the test PyPi repository using these links 
-    
-    *   [LittleBrother](https://test.pypi.org/project/little-brother/#files) 
-    *   [python_base_app](https://test.pypi.org/project/python-base-app/#files) 
-    *   [some_flask_helpers](https://test.pypi.org/project/some-flask-helpers/#files) 
-    
-    and store a copy of them into `/tmp`.
-    Note: By default, these three files are included in the Debian package.
-*   Change to `root` and execute the script [`$INSTALL_BASE_DIR/bin/generic-install.sh`](bin/generic-install.sh)
+* Open the script [`$INSTALL_BASE_DIR/bin/generic-install.sh`](bin/generic-install.sh) and scan the header
+  for required environment settings. 
+
+* Set environment variables if applicable (or patch the script).
+
+* Change to `root` and execute the script [`$INSTALL_BASE_DIR/bin/generic-install.sh`](bin/generic-install.sh)
diff --git a/OPERATIONAL_MONITORING.md b/OPERATIONAL_MONITORING.md
index 1a4579d..c47da17 100644
--- a/OPERATIONAL_MONITORING.md
+++ b/OPERATIONAL_MONITORING.md
@@ -7,7 +7,7 @@
 The application `LittleBrother` has a simple HTTP health check endpoint with relative url `/health` which 
 can be monitored by systems such as `icinga`. 
 The endpoint will always return HTTP code `200` and the text `OK`. It is automatically active for the master
-host since the master host always has its web frontend active anyway. For the slaves the endpoint has to be
+host since the master host always has its web frontend active anyway. For the clients the endpoint has to be
 activated by setting at least the port the section `[StatusServer]` as in the example below.
 
     ...
diff --git a/README.md b/README.md
index 60bfeb1..54daeab 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,9 @@ The latest major feature changes are:
 
 | Version  | Feature/Fix                                                   | (Issue) Link                                                         |
 |----------|---------------------------------------------------------------|----------------------------------------------------------------------|
-| 0.4.17   | *Bug Fix*: Remove incompatibilty with new `alembic` version   | [Issue 166](https://github.com/marcus67/little_brother/issues/166)   |
+| 0.4.23   | Optionally use `iptables` to restrict network access          | [Issue 169](https://github.com/marcus67/little_brother/issues/169)   |
+| 0.4.20   | *Bug Fix*: Use Python virtual environment again               | [Issue 170](https://github.com/marcus67/little_brother/issues/170)   |
+| 0.4.17   | *Bug Fix*: Remove incompatibility with new `alembic` version  | [Issue 166](https://github.com/marcus67/little_brother/issues/166)   |
 | 0.4.16   | *Bug Fix*: Ignore invalid hosts during ping                   | [Issue 165](https://github.com/marcus67/little_brother/issues/165)   |
 | 0.4.15   | *Bug Fix*: Do not fail on Debian package upgrades             | [Issue 158](https://github.com/marcus67/little_brother/issues/158)   |
 | 0.4.14   | *Bug Fix*: Correct detection of users in master-only setups   | [Issue 163](https://github.com/marcus67/little_brother/issues/163)   |
@@ -158,15 +160,17 @@ So far, `LittleBrother` has only been released as a Debian package. For other no
 there is some basic support using a generic installation script. 
 See [this page](NON-DEBIAN-INSTALLATION.md) for details.
 
-| Distribution | Version       | Architecture | Comments                                                               | Most Recent Test |
-| ------------ | ------------- | ------------ | ---------------------------------------------------------------------- | ---------------- |
-| Ubuntu       | 20.04         | amd64        |                                                                        | 28.JAN.2022      |
-| Debian       | 11 (bullseye) | amd64        | Feedback from a user as regular install using Mate desktop             | 28.JAN.2022      |
-| Ubuntu       | 18.10         | amd64        | See [pip3 issue](https://github.com/marcus67/little_brother/issues/53) | 03.JUN.2019      |
-| Debian       | buster        | amd64        | This distribution (buster-slim) is used as base image for Docker       | 01.JAN.2020      |
-| Debian       | 10.3 (buster) | amd64        | Feedback from a user as regular install with Mate desktop              | 05.MAR.2020      |
-| Mint         | 19            | amd64        |                                                                        | 03.JAN.2020      |
-| Debian       | stretch       | armv6l       |                                                                        | 23.MAY.2020      |
+| Distribution | Version       | Architecture | Comments                                                                                                                                                     | Most Recent Test |
+| ------------ | ------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- |
+| Ubuntu       | 20.04         | amd64        | This version is used as base image for the [Ubuntu Docker image](https://hub.docker.com/repository/docker/marcusrickert/little-brother-ubuntu-client)        | 28.JAN.2022      |
+| Debian       | 11 (bullseye) | amd64        | Feedback from a user as regular install using Mate desktop                                                                                                   | 06.MAR.2022      |
+| Ubuntu       | 18.10         | amd64        | See [pip3 issue](https://github.com/marcus67/little_brother/issues/53)                                                                                       | 03.JUN.2019      |
+| Debian       | buster        | amd64        | The version `buster-slim` is used as base image for the [Debian Docker image](https://hub.docker.com/repository/docker/marcusrickert/little-brother-client)  | 01.JAN.2020      |
+| Debian       | 10.3 (buster) | amd64        | Feedback from a user as regular install with Mate desktop                                                                                                    | 05.MAR.2020      |
+| Mint         | 19            | amd64        |                                                                                                                                                              | 03.JAN.2020      |
+| Arch Linux   |               | amd64        | See https://aur.archlinux.org/packages/little-brother                                                                                                        | 06.MAR.2022      |
+| Debian       | stretch       | armv6l       |                                                                                                                                                              | 23.MAY.2020      |
+| Alpine       | v3.13         | amd64        | This distribution is used as base image for the [Alpine Docker Image](https://hub.docker.com/repository/docker/marcusrickert/little-brother-alpine-client)   | 06.MAR.2022      |
 
 ## Quick Install (Snap)
 
@@ -224,9 +228,10 @@ various additional aspects that may require additional configuration.
 | Mapping UIDs                | UIDs are synchronized across all hosts                                     | Each host (group) can have different UIDs                       | See [Advanced Configuration](ADVANCED_TOPICS.md)        |
 | Scanning Interval           | Every 5 seconds                                                            | Any other interval                                              | See [Advanced Configuration](ADVANCED_TOPICS.md)        |
 | Reverse proxy setup         | No reverse proxy                                                           | Run little-brother behind a reverse proxy (e.g. `nginx`)        | See [Advanced Configuration](ADVANCED_TOPICS.md)        |
-| Docker Support              | Client is installed as Debian package                                      | Client is run as Docker container                               | See [Docker](DOCKER.md).                                 |
+| Docker Support              | Client is installed as Debian package                                      | Client is run as Docker container                               | See [Docker](DOCKER.md).                                |
 | Prometheus Support          | Not activated                                                              | Activate Prometheus server port and provide run time statistics | See [Operational Monitoring](OPERATIONAL_MONITORING.md).|
 | Network Tempering Detection | Automatic logout of monitored users after a network downtime of 50 seconds | Set a different time out                                        | See [Advanced Configuration](ADVANCED_TOPICS.md)        |
+| Firewall support            | Do not restrict network access of client hosts                             | Configure targets IP addresses to be blocked                    | See [Advanced Configuration](ADVANCED_TOPICS.md)        |
 
 ### Setting the Admin Password
 
diff --git a/WEB_FRONTEND_MANUAL.md b/WEB_FRONTEND_MANUAL.md
index e765e4e..5ba042d 100644
--- a/WEB_FRONTEND_MANUAL.md
+++ b/WEB_FRONTEND_MANUAL.md
@@ -84,18 +84,18 @@ After successful login the user will be display in the menu bar.
 
 ![Menubar-Topology](doc/menubar-topology.png)
 
-Although `LittleBrother` can run on a single PC (master), it is possible to monitor several slave PCs at the same time.
+Although `LittleBrother` can run on a single PC (master), it is possible to monitor several client PCs at the same time.
 In order to keep the overview over the network of PCs the tab `Topology` provides a list of the currently known PCs.
 
 ![Topology](doc/topology.png)
 
-It shows the master and all slave nodes with the following columns:
+It shows the master and all client nodes with the following columns:
 
-* *Node Type*: Denotes the type of node ("Master" or "Slave"). There is exactly one master and `0..N` slaves. If
+* *Node Type*: Denotes the type of node ("Master" or "Client"). There is exactly one master and `0..N` clients. If
     the node process is running in a Docker container it will have an additional `(Docker)` remark.
     
-* *Node Name*: Denotes Unique name of the node. I usually corresponds to the host DNS name but it can be set in the
-    configuration file.
+* *Node Name*: Denotes the unique name of the node. It usually corresponds to the host DNS name, but it can be set in 
+  the configuration file.
     
 * *App Version*: Denotes the version of the `LittleBrother` process on that node.
 
@@ -119,11 +119,11 @@ in red.
 #### Outdated Clients and Version Checks
 
 If a client is detected whose version is older than 0.3.9 the additional metadata (AppVersion, Revision and Python
-Version) is not available. In this case the AppVersion will show "< 0.3.9" in red color. If the slave version is 
+Version) is not available. In this case the AppVersion will show "< 0.3.9" in red color. If the client version is 
 smaller than the master version, the AppVersion will also show in red. See screenshot above.
 
 As of version 0.4.9 of `LittleBrother` the master node will check SourceForge for versions automatically. If a 
-new version is found, this version will be reported in a banner underneath the navigation bar. The bannner also
+new version is found, this version will be reported in a banner underneath the navigation bar. The banner also
 includes a download link.
 
 ![Version Check Banner](doc/version-check-banner.png)
@@ -136,7 +136,7 @@ checked once a day.
 If the master has not received a message from the client for more than a certain number of seconds (defaults to `60`)
 the value in column *Time Since Last Ping* will be displayed in red. See screenshot above.
 
-**Note:** The master cannot differentiate between slaves that are only temporarily not available and slaves which have
+**Note:** The master cannot differentiate between clients that are only temporarily not available and clients which have
 completely been removed from the topology. In the latter case the master has to be restarted to "forget" the 
 removed clients.
 
@@ -217,7 +217,7 @@ and having the length of the selected number of minutes.
 to play at least until the end of the time extension. It will, however, **not** change the daily limit, nor will it
 influence the way minimum break times are computed.
 
-*   In the rule summary, active restrictive rules will still be displayed but they will be inactive. The only rule
+*   In the rule summary, active restrictive rules will still be displayed, but they will be inactive. The only rule
 that will always show the "right" time is the one regarding the remaining number of minutes in the current session.
 
 *   In contrast to the normal rule sets which usually have trouble to cover computer times spanning midnight
@@ -227,7 +227,7 @@ elegantly, time extensions will handle this case nicely.
 
 In addition to the time extensions handled by the administrator, it is also possible to grant a certain time a day
 which can be used by the user to request time extensions on the fly. This optional time is administered with the
-corresponding ruleset. See the configuration of rulesets further below.
+corresponding rule set. See the configuration of rule sets further below.
 
 The idea is that time extensions may be required very urgently at the end of session to "kill that final invader" or
 maybe save a game context. For the user, it would be contact the administrator to ask for a time extension 
@@ -245,11 +245,11 @@ Before a user can be monitored she has to be added to the users list of `LittleB
 users list is empty. The application will try to retrieve the users on the master host by querying the 
 `/etc/passwd` file using standard Python libraries. See [Users and UIDs in README](README.md) for details. 
 
-All users found that are not being monitored yet will be offered in the drop down menu following "Add to monitoring":
+All users found that are not being monitored yet will be offered in the dropdown menu following "Add to monitoring":
 
 ![Users-Add-User-From-List](doc/users-add-user-list.png)
 
-To add the user choose the user name from the drop down list and click the add button:
+To add the user choose the username from the dropdown list and click the add button:
 
 ![Add-Button](doc/add-button.png)
 
@@ -356,7 +356,7 @@ a *fair* break duration as follows:
 *   If a user has played less than her maximum session time the break time will be enforced proportionally that is
 the effective break time compared to the configured break time will have the same ratio as the actual session time
 to the maximum session time. Example: the maximum session time is set to 1 hour and the break time to 30 minutes. The
-user decides to play for 40 minutes in her session which accounts for two thirds of the maximum session time. Hence
+user decides to play for 40 minutes in her session which accounts for two thirds of the maximum session time. Hence,
 the break time will be `2/3*30=20` minutes.    
 
 ### Rule Contexts
@@ -393,20 +393,20 @@ federal state in Germany. In this case the value of `context_details` denotes th
 *   `Schleswig-Holstein`, or
 *   `Thüringen`.
 
-Note that the vacation schedules are retrieved from the web site 
+Note that the vacation schedules are retrieved from the website 
 [www.mehr-schulferien.de](https://www.mehr-schulferien.de). The author of `LittleBrother` cannot be held responsible 
 for incorrect schedules.
  
 ### Associating Devices with Users
 
 In addition to the hosts which are scanned for logins of the monitored users `LittleBrother` can be configured to
-also scan the activity on other devices, e.g. game consoles or smart phones. The assumption is that there is activity 
+also scan the activity on other devices, e.g. game consoles or smartphones. The assumption is that there is activity 
 on one of these
 devices when they respond to a [ping](https://searchnetworking.techtarget.com/definition/ping). Of course, this is a
-simplification but it may work in some cases. Please, consider the inherent restrictions:
+simplification, but it may work in some cases. Please, consider the inherent restrictions:
 
 *   The fact that a ping is received just means that the device is "on". However, there may not be any activity on it.
-In this case the play time will be overestimated. Also some devices may automatically turn on to check for updates.
+In this case the play time will be overestimated. Also, some devices may automatically turn on to check for updates.
 This will result in phantom play time (sometimes at very odd hours of a day).
 
 *   The ping cannot differentiate between users. So this setup only works well, if the devices can clearly be
@@ -415,9 +415,9 @@ associated with monitored users and are not shared too often.
 *   Devices which can be played offline will not respond to a ping if disconnected from the local network. In this 
 case the play time will be underestimated.
 
-*   The ping returned by smart phones is sometimes hard to interpret. The correlation between successful pings and
+*   The ping returned by smartphones is sometimes hard to interpret. The correlation between successful pings and
 actual usage is small. The monitoring has to be improved by actually measuring the delay of the ping which requires
-some fine tuning.
+some fine-tuning.
 
 **Note**: The following steps are only available if devices have already been set up. See section 
 *Configuring Devices* below. 
@@ -444,12 +444,15 @@ In order to change the settings of a device association clicking on the device n
 
 The columns contain the following details:
 
-*   *Monitored*: denotes if the device is scanned for the user. Use this switch to temporarily deactivate the scanning.
+* *Monitored*: denotes if the device is scanned for the user. Use this switch to temporarily deactivate the scanning.
 The flag is off by default. It needs to be activated for a new association.
+* *Blockable*: denotes if the network communication of this device is to be blocked when the user has exceeded her
+  time budget. This is done by inserting an appropriate rule into `iptables`. See 
+* [Advanced Topics](ADVANCED_TOPICS.md) for setting up this functionality.   
 
-*   *Percent*: denotes the percentage with which the active time of this device will added as play time to the 
+* *Percent*: denotes the percentage with which the active time of this device will be added as play time to the 
 associated user. The default is 100%. Choosing a value below 100% can be interpreted as e.g. a "social" bonus for a 
-console which allows multi player games or e.g. a typical fraction of time that the user spends on the device 
+console which allows multiplayer games or e.g. a typical fraction of time that the user spends on the device 
 compared to the overall use of the device (by other users).
 
 To save the settings click on the save button:    
@@ -465,10 +468,13 @@ activity on other devices by using the ICMP protocol (`ping`). The assumption is
 quick response to ping it is currently active (or rather has an active user logged in). If, on the other hand,
 the response is slow or if there is no response at all the device is regarded as inactive.
 
-Note that `LittleBrother` is not able terminate any processes on the monitored devices so that the users
+Note that `LittleBrother` is not able to terminate any processes on the monitored devices so that the users
 may easily exceed their permitted access times there. However, any login times on these devices are added to
 the access times on the Linux hosts so that the remaining access time is still influenced. Also, the minimum
-break time will apply the point of time when the last other device became inactive.      
+break time will apply the point of time when the last other device became inactive.    
+
+As of version 0.4.23 `LittleBrother` may be able to at least restrict network access to the monitored devices.
+See the section on `iptables` in [Advanced Topics](ADVANCED_TOPICS.md) for details.
 
 The top level of the devices page shows a list of all configured devices. If the devices are associated with users
 their names are shown and also their respective usage percentages.
@@ -481,13 +487,13 @@ Clicking on a device name opens the second level showing details on the specific
 
 The fields have the following meaning:
 
-*   *Name*: The logical name of the device which will show up in lists. The logical name has to be unique across all
+* *Name*: The logical name of the device which will show up in lists. The logical name has to be unique across all
 devices.
 
-*   *Host Name*: The DNS name or IP address of the device. This address will be used for *pinging* the device. It has to
-be a valid address which can be resolved by the name server. Otherwise the entry will be rejected. Also, the hostname
-has to be unique across all devices. If you want to monitor devices in your local WIFI you have to see to it 
-that your WIFI router always issues the same IP numbers to the same devices. Usually, there is a way to configure the
+* *Host Name*: The DNS name or IP address of the device. This address will be used for *pinging* the device. It has to
+be a valid address which can be resolved by the name server. Otherwise, the entry will be rejected. Also, the hostname
+has to be unique across all devices. If you want to monitor devices in your local WI-FI you have to see to it 
+that your WI-FI router always issues the same IP numbers to the same devices. Usually, there is a way to configure the
 router accordingly. Look out for *persistent IP addresses* in the manual.
 
     As of version 0.3.12 of `LittleBrother` the host name may also have an extended format permitting to ping devices
@@ -513,19 +519,22 @@ router accordingly. Look out for *persistent IP addresses* in the manual.
     name, whereas the `device.host.name` will not be checked. For the time being, format errors in the specification above
     or a wrong device host name will silently be ignored resulting in the device being regarded as *down* all the time.   
 
-*   *Min Activity Duration \[s\]*:  Denotes how many seconds a ping has to be responsive before the device is regarded 
+* *Min Activity Duration \[s\]*:  Denotes how many seconds a ping has to be responsive before the device is regarded 
 as active. Durations shorter than this period will be ignored completely.
 
-*   *Max Active Response Delay \[ms\]*: Denotes the maximum response time (measured in milliseconds) a ping may have to 
+* *Max Active Response Delay \[ms\]*: Denotes the maximum response time (measured in milliseconds) a ping may have to 
 still be regarded as 'active'. For game consoles (often using the local area network) a low value of 10 \[ms\] is 
-usually OK. For smart phone using WIFI the value needs to be higher since these devices have a more sluggish response. 
-For iPhones over WIFI values between 50 \[ms\] and 80 \[ms\] have been tested to work pretty well. You will have to 
-monitor the value a little bit during the initial phase. If `LittleBrother` often detects activity where there is none, 
+usually OK. For smartphone using WI-FI the value needs to be higher since these devices have a more sluggish response. 
+For iPhones over WI-FI values between 50 \[ms\] and 80 \[ms\] have been tested to work pretty well. You will have to 
+monitor the value a bit during the initial phase. If `LittleBrother` often detects activity where there is none, 
 decrease the value. If, on the other had, activity often goes undetected, increase the value.
 
-*   *Sample Size*: Denotes the number of delay samples used to compute the effective delay as moving average. This value
-can probably be left as it is.   
-
+* *Sample Size*: Denotes the number of delay samples used to compute the effective delay as moving average. This value
+can probably be left as it is.
+* *Blocked URLs*: Use this text are to override the globally URLs which are blocked for the device if the assigned user
+  has exceeded his time budget. Use one DNS name or IP address per line. Each DNS address is checked before the entry
+  can be saved. **Note:** These entries completely *replace* the default entries. If you want to have some of the 
+  default entries active you will have to repeat them here! 
 To make changes enter the desired values and click the save button.
 
 ![Save-Button](doc/save-button.png)
diff --git a/bin/generic-install.sh b/bin/generic-install.sh
index 09d1e2a..5316bb8 100755
--- a/bin/generic-install.sh
+++ b/bin/generic-install.sh
@@ -1,6 +1,6 @@
 #! /bin/bash
 
-#    Copyright (C) 2019  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/python_base_app
 #
@@ -23,12 +23,94 @@
 # but only to python_base_app/templates/debian_postinst.template.sh!             #
 ##################################################################################
 
+##################################################################################
+# PARAMETERS                                                                     #
+##################################################################################
+# When set, will deactivate portions that are not applicable to Docker containers
+RUNNING_IN_DOCKER=${RUNNING_IN_DOCKER:-}
+
+# When set, contains an extra PIP index to download from
+# This will be required when trying to install the version of the `master` branch since the required PIP packages
+# may not be available ot pypi.org yet. In this case, add the extra index https://test.pypi.org/simple/
+TEST_PYPI_EXTRA_INDEX=${TEST_PYPI_EXTRA_INDEX:-}
+
+# When set, will create the application user with a specific user id
+APP_UID=${APP_UID:-}
+
+# When set, will create the application group with a specific group id
+APP_GID=${APP_UID:-}
+
+##################################################################################
+
+if [ -f /etc/os-release ] ; then
+  . /etc/os-release
+else
+  echo "Cannot read /etc/os-release!"
+  exit 2
+fi
+
+echo "Detected operating system architecture '${ID}'."
+
+function add_group() {
+  group_name=$1
+  group_id=$2
+
+  if [ "$ID" == "alpine" ] ; then
+    if [ "${group_id}" == "" ] ; then
+      addgroup ${group_name}
+    else
+      addgroup -g ${group_id} ${group_name}
+    fi
+  else
+    if [ "${group_id}" == "" ] ; then
+      groupadd little-brother
+    else
+      groupadd --gid ${group_id} ${group_name}
+    fi
+
+  fi
+}
+
+function add_user() {
+  user_name=$1
+  group_name=$2
+  user_id=$3
+
+  if [ "$ID" == "alpine" ] ; then
+    if  [ "${user_id}" == "" ] ; then
+        adduser -G ${group_name} -g "" -H -D ${user_name}
+    else
+        adduser -G ${group_name} -u ${user_id} -g "" -H -D ${user_name}
+    fi
+  else
+    if  [ "${user_id}" == "" ] ; then
+        useradd --gid ${group_name} --no-create-home ${user_name}
+    else
+        useradd --gid ${group_name} --uid ${user_id} --no-create-home ${user_name}
+    fi
+  fi
+
+}
+
+function add_user_to_group() {
+  user_name=$1
+  group_name=$2
+
+  if [ "$ID" == "alpine" ] ; then
+    adduser ${user_name} ${group_name}
+  else
+    usermod -aG ${group_name} ${user_name}
+  fi
+}
+
+if [ "$RUNNING_IN_DOCKER" == "" ] ; then
+export VIRTUAL_ENV_DIR=/var/lib/little-brother/virtualenv
+fi
 
 ETC_DIR=/etc/little-brother
 LOG_DIR=/var/log/little-brother
 SPOOL_DIR=/var/spool/little-brother
 LIB_DIR=/var/lib/little-brother
-VIRTUAL_ENV_DIR=/var/lib/little-brother/virtualenv
 SYSTEMD_DIR=/lib/systemd/system
 TMPFILE_DIR=/usr/lib/tmpfiles.d
 SUDOERS_DIR=/etc/sudoers.d
@@ -38,11 +120,13 @@ ROOT_DIR=
 SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]})
 INSTALL_BASE_DIR=$(realpath $SCRIPT_DIR/..)
 BIN_DIR=${INSTALL_BASE_DIR}/bin
-PIP3=${LIB_DIR}/pip3.sh
-chmod +x ${PIP3}
 
 
 
+echo "Creating lib directories..."
+echo "    * ${LIB_DIR}"
+mkdir -p ${LIB_DIR}
+
 echo "Running generic installation script with base directory located in $INSTALL_BASE_DIR..."
 
 if [ ! "$EUID" == "0" ] ; then
@@ -50,43 +134,53 @@ if [ ! "$EUID" == "0" ] ; then
     exit 2
 fi
 
+PIP3=${SCRIPT_DIR}/pip3.sh
+chmod +x ${PIP3}
+echo "Downloading Pip packages to $LIB_DIR..."
+${PIP3} download -d $LIB_DIR --no-deps little_brother==0.4.26
+
+${PIP3} download -d $LIB_DIR --no-deps python_base_app==0.2.45
+
+${PIP3} download -d $LIB_DIR --no-deps some_flask_helpers==0.2.3
+
+
 echo "Checking if all Pip packages have been downloaded to $LIB_DIR..."
-if [ ! -f $LIB_DIR/little-brother-0.4.19.tar.gz ] ; then
-  echo "ERROR: package little-brother-0.4.19.tar.gz not found in $LIB_DIR!"
+if [ ! -f $LIB_DIR/little-brother-0.4.26.tar.gz ] ; then
+  echo "ERROR: package little-brother-0.4.26.tar.gz not found in $LIB_DIR!"
   echo "Download from test.pypi.org and execute again."
   exit 2
 else
-  echo "Package little-brother-0.4.19.tar.gz was found."
+  echo "Package little-brother-0.4.26.tar.gz was found."
 fi
 
-if [ ! -f $LIB_DIR/python-base-app-0.2.36.tar.gz ] ; then
-  echo "ERROR: package python-base-app-0.2.36.tar.gz not found in $LIB_DIR!"
+if [ ! -f $LIB_DIR/python-base-app-0.2.45.tar.gz ] ; then
+  echo "ERROR: package python-base-app-0.2.45.tar.gz not found in $LIB_DIR!"
   echo "Download from test.pypi.org and execute again."
   exit 2
 else
-  echo "Package python-base-app-0.2.36.tar.gz was found."
+  echo "Package python-base-app-0.2.45.tar.gz was found."
 fi
 
-if [ ! -f $LIB_DIR/some-flask-helpers-0.2.2.tar.gz ] ; then
-  echo "ERROR: package some-flask-helpers-0.2.2.tar.gz not found in $LIB_DIR!"
+if [ ! -f $LIB_DIR/some-flask-helpers-0.2.3.tar.gz ] ; then
+  echo "ERROR: package some-flask-helpers-0.2.3.tar.gz not found in $LIB_DIR!"
   echo "Download from test.pypi.org and execute again."
   exit 2
 else
-  echo "Package some-flask-helpers-0.2.2.tar.gz was found."
+  echo "Package some-flask-helpers-0.2.3.tar.gz was found."
 fi
 
-mkdir -p ${SYSTEMD_DIR}
-cp ${INSTALL_BASE_DIR}/etc/little-brother.service ${SYSTEMD_DIR}/little-brother.service
-echo "Execute systemctl daemon-reload..."
-systemctl daemon-reload
+if [ "$RUNNING_IN_DOCKER" == "" ] ; then
+  mkdir -p ${SYSTEMD_DIR}
+  cp ${INSTALL_BASE_DIR}/etc/little-brother.service ${SYSTEMD_DIR}/little-brother.service
+fi
 mkdir -p ${SUDOERS_DIR}
 cp ${INSTALL_BASE_DIR}/etc/little-brother.sudo ${SUDOERS_DIR}/little-brother
 mkdir -p ${APPARMOR_DIR}
 cp ${INSTALL_BASE_DIR}/etc/little-brother.apparmor ${APPARMOR_DIR}/little-brother.conf
-TARGET_DIRECTORY=${ROOT_DIR}/$(dirname etc/little-brother/slave.config )
+TARGET_DIRECTORY=${ROOT_DIR}/$(dirname etc/little-brother/client.config )
 mkdir -p ${TARGET_DIRECTORY}
-echo "Deploying extra file '$INSTALL_BASE_DIR/etc/slave.config' to '${ROOT_DIR}/etc/little-brother/slave.config'..."
-cp -f $INSTALL_BASE_DIR/etc/slave.config ${ROOT_DIR}/etc/little-brother/slave.config
+echo "Deploying extra file '$INSTALL_BASE_DIR/etc/client.config' to '${ROOT_DIR}/etc/little-brother/client.config'..."
+cp -f $INSTALL_BASE_DIR/etc/client.config ${ROOT_DIR}/etc/little-brother/client.config
 TARGET_DIRECTORY=${ROOT_DIR}/$(dirname etc/little-brother/master.config )
 mkdir -p ${TARGET_DIRECTORY}
 echo "Deploying extra file '$INSTALL_BASE_DIR/etc/master.config' to '${ROOT_DIR}/etc/little-brother/master.config'..."
@@ -94,31 +188,22 @@ cp -f $INSTALL_BASE_DIR/etc/master.config ${ROOT_DIR}/etc/little-brother/master.
 
 
 
-# endif for if generic_script
+
+
 if grep -q 'little-brother:' /etc/group ; then
     echo "Group 'little-brother' already exists. Skipping group creation."
 else
     #echo "Adding group 'little-brother'..."
-    if [ "${APP_GID}" == "" ] ; then
-        groupadd little-brother
-    else
-	      groupadd --gid ${APP_GID} little-brother
-    fi
+    add_group little-brother ${APP_GID}
 fi
 if grep -q 'little-brother:' /etc/passwd ; then
     echo "User 'little-brother' already exists. Skipping user creation."
 else
-    if  [ "${APP_UID}" == "" ] ; then
-#        adduser --gid little-brother --gecos "" --no-create-home --disabled-password little-brother
-        useradd --gid little-brother --no-create-home little-brother
-    else
-#        adduser --gid little-brother --uid ${APP_UID} --gecos "" --no-create-home --disabled-password little-brother
-        useradd --gid little-brother --uid ${APP_UID} --no-create-home little-brother
-    fi
+    add_user little-brother little-brother ${APP_UID}
 fi
 
 set -e
-usermod -aG audio little-brother
+  add_user_to_group little-brother audio
 
 
 echo "Creating directories..."
@@ -137,6 +222,7 @@ else
   cp -f /etc/little-brother/master.config /etc/little-brother/little-brother.config
 fi
 
+if [ "${VIRTUAL_ENV_DIR}" != "" ] ; then
 
 echo "Creating symbolic link /usr/local/bin/run_little_brother.py --> ${VIRTUAL_ENV_DIR}/bin/run_little_brother.py..."
 ln -fs ${VIRTUAL_ENV_DIR}/bin/run_little_brother.py /usr/local/bin/run_little_brother.py
@@ -146,6 +232,9 @@ ln -fs ${VIRTUAL_ENV_DIR}/bin/run_little_brother_test_suite.py /usr/local/bin/ru
 echo "Creating virtual Python environment in ${VIRTUAL_ENV_DIR}..."
 
 virtualenv -p /usr/bin/python3 ${VIRTUAL_ENV_DIR}
+echo "Activating virtual Python environment in ${VIRTUAL_ENV_DIR}..."
+. ${VIRTUAL_ENV_DIR}/bin/activate
+fi
 
 echo "Setting ownership..."
 echo "    * little-brother.little-brother ${ETC_DIR}"
@@ -160,9 +249,10 @@ chown -R little-brother.little-brother ${LIB_DIR}
 
 echo "    * little-brother.little-brother /etc/little-brother/little-brother.config"
 chown little-brother.little-brother /etc/little-brother/little-brother.config
-echo "    * ${SYSTEMD_DIR}/little-brother.service"
-chown root.root ${SYSTEMD_DIR}/little-brother.service
-
+  if [ "$RUNNING_IN_DOCKER" == "" ] ; then
+  echo "    * ${SYSTEMD_DIR}/little-brother.service"
+  chown root.root ${SYSTEMD_DIR}/little-brother.service
+  fi
 echo "    * ${SUDOERS_DIR}"
 chown root.root ${SUDOERS_DIR}
 echo "    * ${SUDOERS_DIR}/little-brother"
@@ -184,22 +274,27 @@ chmod -R og-rwx ${SPOOL_DIR}
 echo "    * little-brother.little-brother /etc/little-brother/little-brother.config"
 chmod og-rwx /etc/little-brother/little-brother.config
 
-${PIP3} --version
 ${PIP3} install wheel # setuptools
 echo "Installing PIP packages..."
-echo "  * little-brother-0.4.19.tar.gz"
-echo "  * python-base-app-0.2.36.tar.gz"
-echo "  * some-flask-helpers-0.2.2.tar.gz"
+echo "  * little-brother-0.4.26.tar.gz"
+echo "  * python-base-app-0.2.45.tar.gz"
+echo "  * some-flask-helpers-0.2.3.tar.gz"
 # see https://stackoverflow.com/questions/19548957/can-i-force-pip-to-reinstall-the-current-version
-${PIP3} install --upgrade --force-reinstall \
-     ${LIB_DIR}/little-brother-0.4.19.tar.gz\
-     ${LIB_DIR}/python-base-app-0.2.36.tar.gz\
-     ${LIB_DIR}/some-flask-helpers-0.2.2.tar.gz
-
-
-echo "Removing installation file ${LIB_DIR}/little-brother-0.4.19.tar.gz..."
-rm ${LIB_DIR}/little-brother-0.4.19.tar.gz
-echo "Removing installation file ${LIB_DIR}/python-base-app-0.2.36.tar.gz..."
-rm ${LIB_DIR}/python-base-app-0.2.36.tar.gz
-echo "Removing installation file ${LIB_DIR}/some-flask-helpers-0.2.2.tar.gz..."
-rm ${LIB_DIR}/some-flask-helpers-0.2.2.tar.gz
\ No newline at end of file
+${PIP3} install --upgrade --ignore-installed \
+     ${LIB_DIR}/little-brother-0.4.26.tar.gz\
+     ${LIB_DIR}/python-base-app-0.2.45.tar.gz\
+     ${LIB_DIR}/some-flask-helpers-0.2.3.tar.gz
+
+
+echo "Removing installation file ${LIB_DIR}/little-brother-0.4.26.tar.gz..."
+rm ${LIB_DIR}/little-brother-0.4.26.tar.gz
+echo "Removing installation file ${LIB_DIR}/python-base-app-0.2.45.tar.gz..."
+rm ${LIB_DIR}/python-base-app-0.2.45.tar.gz
+echo "Removing installation file ${LIB_DIR}/some-flask-helpers-0.2.3.tar.gz..."
+rm ${LIB_DIR}/some-flask-helpers-0.2.3.tar.gz
+if [ "$RUNNING_IN_DOCKER" == "" ] ; then
+  echo "Execute systemctl daemon-reload..."
+  set +e
+  systemctl daemon-reload
+  set -e
+fi
\ No newline at end of file
diff --git a/bin/pip3.sh b/bin/pip3.sh
index 1b40d1a..049f8a7 100755
--- a/bin/pip3.sh
+++ b/bin/pip3.sh
@@ -42,7 +42,7 @@ if [ "${PIP3}" == "" ] ; then
   exit 1
 fi
 
-EXTRA_INDEX_URL=""
+EXTRA_INDEX_URL="$TEST_PYPI_EXTRA_INDEX"
 
 
 if [ "${EXTRA_INDEX_URL}" == "" ] ; then
diff --git a/contrib/python_base_app b/contrib/python_base_app
index 5260853..b6d3e01 160000
--- a/contrib/python_base_app
+++ b/contrib/python_base_app
@@ -1,1 +1,1 @@
-Subproject commit 5260853a4a47a115a0ba9ab406852e708a8a6a8d
+Subproject commit b6d3e01bb10267175119e082f0916ffacdcd91a6
diff --git a/contrib/some_flask_helpers b/contrib/some_flask_helpers
index acb1d45..03f3b38 160000
--- a/contrib/some_flask_helpers
+++ b/contrib/some_flask_helpers
@@ -1,1 +1,1 @@
-Subproject commit acb1d453861d89a2168ccb1c633da6f8b04cea7b
+Subproject commit 03f3b38118412bd2bc19d91e6405b2d16cbf5eb7
diff --git a/doc/alpine-linux-logo.png b/doc/alpine-linux-logo.png
new file mode 100644
index 0000000..1db6e43
Binary files /dev/null and b/doc/alpine-linux-logo.png differ
diff --git a/doc/arch-linux-logo.jpeg b/doc/arch-linux-logo.jpeg
new file mode 100644
index 0000000..12aeb38
Binary files /dev/null and b/doc/arch-linux-logo.jpeg differ
diff --git a/doc/devices-level-1.png b/doc/devices-level-1.png
index df8b554..5eaba1c 100644
Binary files a/doc/devices-level-1.png and b/doc/devices-level-1.png differ
diff --git a/doc/devices-level-2.png b/doc/devices-level-2.png
index 0db8f74..a52af56 100644
Binary files a/doc/devices-level-2.png and b/doc/devices-level-2.png differ
diff --git a/doc/iptables-firewall.drawio.png b/doc/iptables-firewall.drawio.png
new file mode 100644
index 0000000..3a9e851
Binary files /dev/null and b/doc/iptables-firewall.drawio.png differ
diff --git a/doc/login-mappings.drawio b/doc/login-mappings.drawio
deleted file mode 100644
index f9cca2c..0000000
--- a/doc/login-mappings.drawio
+++ /dev/null
@@ -1,1 +0,0 @@
-<mxfile host="Electron" modified="2021-03-21T22:30:54.669Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.6.2 Chrome/83.0.4103.122 Electron/9.2.0 Safari/537.36" etag="x-FLSYH-1rjqB5gpBigH" version="13.6.2" type="device"><diagram id="U0HtFw2GSimi6QTFJ8yl" name="Page-1">7VrbcqM4EP0aP4ZC4v5ok8nMw8zWbKVqt2pfXAKErYpALMixs1+/zdUGYY+TtR17E6cqQKvRpc+h1d0wMfxk8zUn2fKHiCifYD3aTIz7CcYI6SYcSslLLfG8RrDIWdQobQWP7B/aCPVGumIRLXqKUgguWdYXhiJNaSh7MpLnYt1XiwXvj5qRBVUEjyHhqvRPFslluy7b2zZ8o2yxbIZ2sVM3JKRVblZSLEkk1jsi48vE8HMhZH2WbHzKS+O1dqnve9jT2k0sp6k85gb/6ecTfzaXvxd/bR42ofUtD/+4s+penglfNQtuJitfWgtAL2BsuJjBCrJSGHKxgk5n6yWT9DEjYSlcA/4gW8qEwxWC05hx7gsu8qofI46pHYZlNzIXT3SnJXK8QNehpZkMzSXd7F0l6mwHpKMioTJ/AZXmBstuzN3wDVvN9XqLntVCstwBzvAaIWkYs+j63hoVThq7vsLG+Iw2Lm3FgK1TzhYpyAIhpUjGjB/jPca3A9uyT2N8jDXD2/k5zrVhgX6NRSZYKqtxrdnEugfLkCKrXUvMNjTqA0Aaw4dgIprXANaPeUnoiBTL8o7qgiWVr2mP9yxZwBo4C+A/AQLQaC4ppwUrQBCKJFtBj3OSluI8YSnhZcNPmhcCzud+o6EVz4vTwIeMAX5uDz4TW5qnAGhamq0iaCPN1Xd/5pkANT4BPfA86p5m76KA+4C6juZcG6DmJ6D7AbUdRxtgiFSf+s4I2p8I7kfQsfAQQe8K3arz/8NwvmZyOX+k+fMp8TStDqgWT/WBtLFm9mAbg3aPzsmhdRVof5CixGQIMBhF9kHsB4+pSOkg0mxECtjDMDVhUVQOMxre5mKVRh0bYpHKJi1E5olyBDzEDJ4rtBv4jDyPI2EqPluUqisYPXLyTO+mHwckBCD1MDGOiF3si6KkJnY1SrOPgxLWD6YMlulp1jujpGYINUr+x0HJdvpFEdNBmrOL2ntjpAb9NUb3Hwcjx7T6j47laPbBLemyEI1FhDaXjTGgJa7tBdK/V2VNc+aLVc7KuEL/ja63Yjhb1McHKkP4n5GiWMPY07bDIG91AhGAHCw0RXpZIZxqmqZqFSTZaqF9WpwVZKuG+2owjXoZ7dwGtGvLcpEIV0mF6K8qc0FNmO9BVw4dcKblI6exVAt2EaFuPFqws0OXBvGJ3Lc3jIQMNXpFxgjREDob09QA9QJMG+Ve8LIljHWAfcmOnn0c/5zb519Rd6ZryO3/4RNFgINgwhjjpoUvyk3varjZUQnt9Ys9xiH0ybjX7sGjjDMslXHne3c0kglWFQyQfYWYJIPj9AbyDXyi13vYxZrZj5PsI3cs42wYqS+VFIz8G4hkT4WRi/vZRhnJqqn6hTFSc/X/6Lg5k5LTeZALuaS5Foo0PtJ5T6zZNMt8GDYXvKycqiFFRZ/5oiKPcT8d6+Rq3bhF3cgcc+MuDgzbvoAbx+7gxeVISRY5Fw1q8dh7ylvhnz+i8sm/Qw5QeSdwBQwc+9Tn/Rj4XSxY+oNkGUsX1SY5nb3VFSZ1L3NAB6ZbQOd69T4KpqLXgXKVvh1P4VpcZCTtcbldf6l8F5OE8Zd6EGgiSVbdahgl9cLOcGlluH6rakl1BahbQV3cqBLLQyuoZ3tFK8DdCpo0pEp6D0/4zTnUEZQ7DPZbRx4hMnjLN/rUY4hc+ofbJHJHA8hEL0yD296kBr/T7FGWc0Tlr/tgr1ditl+/RcHl9kPXqm3nc2Hjy78=</diagram></mxfile>
\ No newline at end of file
diff --git a/doc/login-mappings.drawio.png b/doc/login-mappings.drawio.png
new file mode 100644
index 0000000..222e23e
Binary files /dev/null and b/doc/login-mappings.drawio.png differ
diff --git a/doc/login-mappings.png b/doc/login-mappings.png
deleted file mode 100644
index 280268e..0000000
Binary files a/doc/login-mappings.png and /dev/null differ
diff --git a/doc/users-devices-level-3.png b/doc/users-devices-level-3.png
index cb748ec..f61ac66 100644
Binary files a/doc/users-devices-level-3.png and b/doc/users-devices-level-3.png differ
diff --git a/docker/little-brother-alpine-client/Dockerfile b/docker/little-brother-alpine-client/Dockerfile
new file mode 100644
index 0000000..e7e7081
--- /dev/null
+++ b/docker/little-brother-alpine-client/Dockerfile
@@ -0,0 +1,56 @@
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+FROM alpine:latest
+LABEL maintainer="marcus.rickert@web.de"
+ARG BRANCH
+ARG REPO_DOWNLOAD_BASE_URL
+ARG TEST_PYPI_EXTRA_INDEX
+ENV MASTER_HOST_URL=http://localhost:5555
+ENV MASTER_ACCESS_TOKEN=SOME_LONG_AND_SECRET_TOKEN
+ENV RUNNING_IN_DOCKER=1
+RUN apk update && \
+    apk add bash \
+            sudo \
+            python3 \
+            python3-dev \
+            py3-pip \
+            unzip \
+            linux-headers \
+            build-base \
+            gcc \
+            curl && \
+    pip install --ignore-installed --upgrade pip && \
+    curl -L ${REPO_DOWNLOAD_BASE_URL}${BRANCH}.zip -o /tmp/repo.zip && \
+    cd /tmp && \
+    unzip /tmp/repo.zip && \
+    /tmp/little_brother-*/bin/generic-install.sh && \
+    rm -rf /tmp/little_brother-* && \
+    rm -f /tmp/*.apk && \
+    rm -f /tmp/repo.zip && \
+    apk del py3-pip \
+            python3-dev \
+            build-base \
+            linux-headers \
+            unzip \
+            gcc
+COPY assets/entrypoint.sh /entrypoint.sh
+COPY assets/little-brother.config /etc
+ENTRYPOINT ["/entrypoint.sh"]
+CMD []
+USER little-brother
diff --git a/docker/little-brother-alpine-client/assets/.gitignore b/docker/little-brother-alpine-client/assets/.gitignore
new file mode 100644
index 0000000..c00df13
--- /dev/null
+++ b/docker/little-brother-alpine-client/assets/.gitignore
@@ -0,0 +1,1 @@
+*.deb
diff --git a/docker/little-brother-alpine-client/assets/entrypoint.sh b/docker/little-brother-alpine-client/assets/entrypoint.sh
new file mode 100755
index 0000000..3dd1e12
--- /dev/null
+++ b/docker/little-brother-alpine-client/assets/entrypoint.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+echo "Starting Little-Brother in Client Mode"
+export MasterConnector__host_url=${MASTER_HOST_URL}
+export MasterConnector__access_token=${MASTER_ACCESS_TOKEN}
+export StatusServer__app_secret=${APP_SECRET}
+run_little_brother.py --config /etc/little-brother.config --loglevel ${LOGLEVEL}
diff --git a/docker/little-brother-alpine-client/assets/little-brother.config b/docker/little-brother-alpine-client/assets/little-brother.config
new file mode 100644
index 0000000..248c775
--- /dev/null
+++ b/docker/little-brother-alpine-client/assets/little-brother.config
@@ -0,0 +1,101 @@
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# This file contains a LittleBrother configuration for an optional client host.
+
+[LittleBrother]
+# Interval in seconds between applying the rules to the collected process statistics. Default: 5
+#check_interval = 5
+
+# Logging level of the application. Default: INFO
+# Allowed values: DEBUG, INFO, WARNING, ERROR
+#log_level=DEBUG
+
+# Run the application in debugging mode. If active any exception will print a
+# stack trace and terminate the application. Do not activate in "production" mode.
+# Default: False
+#debug_mode=True
+
+[AppControl]
+# Use this to replace the canonical hostname retrieved from the system. This hostname will accur in all statistics.
+#hostname = MY.BEAUTIFIED.HOSTNAME
+
+[MasterConnector]
+# URL of the master host
+host_url=http://mymaster.mydomain:myport
+
+# Unique token that identifies a client host with the master host. It has to match the
+# corresponding entry on the master host.
+access_token=SOME_LONG_AND_SECRET_TOKEN
+
+[StatusServer]
+port=5555
+app_secret=SOME_APP_SECRET
+
+[Persistence]
+# The client host will work with a simple file based persistence. The file will be created in /var/spool/little-brother.
+# Note that the client(s) should ALWAYS use sqlite backend no matter which kind of backend the master will use!
+database_driver=sqlite
+
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# Note that the audio handling by the client process is obsolete. Use the little-brother-taskbar instead.
+# See https://pypi.org/project/little-brother-taskbar/
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+#[AudioHandler]
+# Set the volume of the audio output in percent. Default: None, hence no attempt will be made to change the setting.
+#audio_mixer_volume=150
+
+# Select the engine of the speech output.
+# Permitted values:
+# * pyttsx3 : Use the library of the same name to generate the speech output.
+# * google : Use the libray 'python_google_speak' to generate the speech output. Note that this library requires an
+#            internet connection!
+# * external : Use an external command to generate the speech output. See setting 'speech_generator_cmd_line'.
+# The default value is None, which deactivates spoken notifications.
+#speech_engine=google
+
+# If true, the generated files will be cached in the spool directory (see below). Default: False
+#cache_audio_files = True
+
+# Set the speed of the speech generator in words per minutes. Default: 100
+# speech_words_per_minute = 80
+
+# Set the mimimum waiting time in seconds before a spoken messages is repeated. Any request to issue the same messages
+# before this time will be silently ignored. Default: 30
+#mininum_waiting_time_before_repeat = 20  # seconds
+
+# Set the directory where generated speech files will be cached. Default: /var/spool/little-brother
+#spool_dir = "/tmp"
+
+# Set the prefix of the filenames used for generated speech files. Default: little-brother-speech-
+#audio_file_prefix = "my-prefix-"
+
+# Set the path of the mixer tool. Default: /usr/bin/amixer
+#audio_mixer_bin = '/bin/amixer'
+
+# Set the default locale of the speech output if the monitored user does not have a locale configured. If both are unset
+# the system default locale will be used. Default: None
+#locale = de_DE
+
+# Set the command line for the external speech generation tool.
+# Default: /usr/bin/festival --tts --language american_english {inline}
+# The command line may contain the following patterns will be replaced before its execution:
+# * {infile} : name of a file containing the text to be spoken
+# * {text}: text to be spoken
+
+#audio_player=mpg123
diff --git a/docker/little-brother-arch-linux-base/Dockerfile b/docker/little-brother-arch-linux-base/Dockerfile
new file mode 100644
index 0000000..77d2d87
--- /dev/null
+++ b/docker/little-brother-arch-linux-base/Dockerfile
@@ -0,0 +1,34 @@
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Note on the installation of libldap-2.4.59-2-x86_64.pkg.tar.zst: The current version of libldap does not supply
+# the librnary ldap_r anymore but the current Python package python_ldap still depends on it. So the compilation of
+# the Pip package fails. We have to use an older version of libldap to make it work!
+
+FROM archlinux:base
+LABEL maintainer="marcus.rickert@web.de"
+RUN pacman -Sy && \
+    pacman -S  sudo \
+               python3 \
+               python-pip \
+               python-virtualenv \
+               cmake \
+               gcc \
+               curl \
+               unzip \
+               --noconfirm
diff --git a/docker/little-brother-arch-linux-base/assets/.gitignore b/docker/little-brother-arch-linux-base/assets/.gitignore
new file mode 100644
index 0000000..c00df13
--- /dev/null
+++ b/docker/little-brother-arch-linux-base/assets/.gitignore
@@ -0,0 +1,1 @@
+*.deb
diff --git a/docker/little-brother-arch-linux-client/Dockerfile b/docker/little-brother-arch-linux-client/Dockerfile
new file mode 100644
index 0000000..1767e78
--- /dev/null
+++ b/docker/little-brother-arch-linux-client/Dockerfile
@@ -0,0 +1,37 @@
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ARG DOCKER_REGISTRY_ORG_UNIT
+ARG TAG
+FROM $DOCKER_REGISTRY_ORG_UNIT/little-brother-arch-linux-base:$TAG
+ARG BRANCH
+ARG REPO_DOWNLOAD_BASE_URL
+ARG TEST_PYPI_EXTRA_INDEX
+LABEL maintainer="marcus.rickert@web.de"
+ENV MASTER_HOST_URL=http://localhost:5555
+ENV MASTER_ACCESS_TOKEN=SOME_LONG_AND_SECRET_TOKEN
+ENV RUNNING_IN_DOCKER=1
+RUN curl -L ${REPO_DOWNLOAD_BASE_URL}${BRANCH}.zip -o /tmp/repo.zip && \
+    cd /tmp && \
+    unzip /tmp/repo.zip && \
+    /tmp/little_brother-*/bin/generic-install.sh
+COPY assets/entrypoint.sh /entrypoint.sh
+COPY assets/little-brother.config /etc
+ENTRYPOINT ["/entrypoint.sh"]
+CMD []
+USER little-brother
diff --git a/docker/little-brother-arch-linux-client/assets/.gitignore b/docker/little-brother-arch-linux-client/assets/.gitignore
new file mode 100644
index 0000000..c00df13
--- /dev/null
+++ b/docker/little-brother-arch-linux-client/assets/.gitignore
@@ -0,0 +1,1 @@
+*.deb
diff --git a/docker/little-brother-arch-linux-client/assets/entrypoint.sh b/docker/little-brother-arch-linux-client/assets/entrypoint.sh
new file mode 100755
index 0000000..3dd1e12
--- /dev/null
+++ b/docker/little-brother-arch-linux-client/assets/entrypoint.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+echo "Starting Little-Brother in Client Mode"
+export MasterConnector__host_url=${MASTER_HOST_URL}
+export MasterConnector__access_token=${MASTER_ACCESS_TOKEN}
+export StatusServer__app_secret=${APP_SECRET}
+run_little_brother.py --config /etc/little-brother.config --loglevel ${LOGLEVEL}
diff --git a/docker/little-brother-arch-linux-client/assets/little-brother.config b/docker/little-brother-arch-linux-client/assets/little-brother.config
new file mode 100644
index 0000000..248c775
--- /dev/null
+++ b/docker/little-brother-arch-linux-client/assets/little-brother.config
@@ -0,0 +1,101 @@
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# This file contains a LittleBrother configuration for an optional client host.
+
+[LittleBrother]
+# Interval in seconds between applying the rules to the collected process statistics. Default: 5
+#check_interval = 5
+
+# Logging level of the application. Default: INFO
+# Allowed values: DEBUG, INFO, WARNING, ERROR
+#log_level=DEBUG
+
+# Run the application in debugging mode. If active any exception will print a
+# stack trace and terminate the application. Do not activate in "production" mode.
+# Default: False
+#debug_mode=True
+
+[AppControl]
+# Use this to replace the canonical hostname retrieved from the system. This hostname will accur in all statistics.
+#hostname = MY.BEAUTIFIED.HOSTNAME
+
+[MasterConnector]
+# URL of the master host
+host_url=http://mymaster.mydomain:myport
+
+# Unique token that identifies a client host with the master host. It has to match the
+# corresponding entry on the master host.
+access_token=SOME_LONG_AND_SECRET_TOKEN
+
+[StatusServer]
+port=5555
+app_secret=SOME_APP_SECRET
+
+[Persistence]
+# The client host will work with a simple file based persistence. The file will be created in /var/spool/little-brother.
+# Note that the client(s) should ALWAYS use sqlite backend no matter which kind of backend the master will use!
+database_driver=sqlite
+
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# Note that the audio handling by the client process is obsolete. Use the little-brother-taskbar instead.
+# See https://pypi.org/project/little-brother-taskbar/
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+#[AudioHandler]
+# Set the volume of the audio output in percent. Default: None, hence no attempt will be made to change the setting.
+#audio_mixer_volume=150
+
+# Select the engine of the speech output.
+# Permitted values:
+# * pyttsx3 : Use the library of the same name to generate the speech output.
+# * google : Use the libray 'python_google_speak' to generate the speech output. Note that this library requires an
+#            internet connection!
+# * external : Use an external command to generate the speech output. See setting 'speech_generator_cmd_line'.
+# The default value is None, which deactivates spoken notifications.
+#speech_engine=google
+
+# If true, the generated files will be cached in the spool directory (see below). Default: False
+#cache_audio_files = True
+
+# Set the speed of the speech generator in words per minutes. Default: 100
+# speech_words_per_minute = 80
+
+# Set the mimimum waiting time in seconds before a spoken messages is repeated. Any request to issue the same messages
+# before this time will be silently ignored. Default: 30
+#mininum_waiting_time_before_repeat = 20  # seconds
+
+# Set the directory where generated speech files will be cached. Default: /var/spool/little-brother
+#spool_dir = "/tmp"
+
+# Set the prefix of the filenames used for generated speech files. Default: little-brother-speech-
+#audio_file_prefix = "my-prefix-"
+
+# Set the path of the mixer tool. Default: /usr/bin/amixer
+#audio_mixer_bin = '/bin/amixer'
+
+# Set the default locale of the speech output if the monitored user does not have a locale configured. If both are unset
+# the system default locale will be used. Default: None
+#locale = de_DE
+
+# Set the command line for the external speech generation tool.
+# Default: /usr/bin/festival --tts --language american_english {inline}
+# The command line may contain the following patterns will be replaced before its execution:
+# * {infile} : name of a file containing the text to be spoken
+# * {text}: text to be spoken
+
+#audio_player=mpg123
diff --git a/docker/little-brother-base/Dockerfile b/docker/little-brother-base/Dockerfile
index b8a7472..729d8fb 100644
--- a/docker/little-brother-base/Dockerfile
+++ b/docker/little-brother-base/Dockerfile
@@ -1,13 +1,11 @@
-FROM marcusrickert/docker-minipython:release-0.9
+FROM marcusrickert/docker-minipython:release-3.10.9
 LABEL maintainer="marcus.rickert@web.de"
 ENV RUNNING_IN_DOCKER=1
 COPY assets/*.deb /tmp
 # See https://superuser.com/questions/1456989/how-to-configure-apt-in-debian-buster-after-release
 RUN DEBIAN_FRONTEND=noninteractive \
         apt-get update --allow-releaseinfo-change && \
-        apt-get install -y --no-install-recommends \
-            alsaplayer-alsa=0.99.81-2 \
-            mpg123=1.25.10-2
+        apt-get install -y --no-install-recommends
 RUN (dpkg -i /tmp/*.deb || true) && \
     DEBIAN_FRONTEND=noninteractive \
         apt-get install -f -y --no-install-recommends
diff --git a/docker/little-brother-ubuntu-base/Dockerfile b/docker/little-brother-ubuntu-base/Dockerfile
index 4851109..39ec355 100644
--- a/docker/little-brother-ubuntu-base/Dockerfile
+++ b/docker/little-brother-ubuntu-base/Dockerfile
@@ -15,7 +15,7 @@
 #    You should have received a copy of the GNU General Public License along
 #    with this program; if not, write to the Free Software Foundation, Inc.,
 #    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-FROM marcusrickert/docker-ubuntu-minipython:release-0.9.1
+FROM marcusrickert/docker-ubuntu-minipython:release-22.10
 LABEL maintainer="marcus.rickert@web.de"
 ENV RUNNING_IN_DOCKER=1
 COPY assets/*.deb /tmp
diff --git a/etc/.gitignore b/etc/.gitignore
index 8854077..ef2305c 100644
--- a/etc/.gitignore
+++ b/etc/.gitignore
@@ -1,2 +1,2 @@
 app.config
-slave-app.config
+client-app.config
diff --git a/etc/slave.config b/etc/client.config
similarity index 100%
rename from etc/slave.config
rename to etc/client.config
diff --git a/etc/little-brother.service b/etc/little-brother.service
index 9c20ba1..326e2f1 100644
--- a/etc/little-brother.service
+++ b/etc/little-brother.service
@@ -1,4 +1,4 @@
-#    Copyright (C) 2019  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
diff --git a/etc/little-brother.sudo b/etc/little-brother.sudo
index 373045d..6ddbc1f 100644
--- a/etc/little-brother.sudo
+++ b/etc/little-brother.sudo
@@ -18,3 +18,6 @@
 
 little-brother ALL=(root) NOPASSWD: /bin/kill *
 little-brother ALL=(root) NOPASSWD: /bin/launchctl *
+little-brother ALL=(root) NOPASSWD: /usr/sbin/iptables -n --line-numbers -L FORWARD
+little-brother ALL=(root) NOPASSWD: /usr/sbin/iptables -I FORWARD -p all -j DROP -s *
+little-brother ALL=(root) NOPASSWD: /usr/sbin/iptables -D FORWARD *
diff --git a/etc/master.config b/etc/master.config
index c44ec73..cd9e86f 100644
--- a/etc/master.config
+++ b/etc/master.config
@@ -85,9 +85,14 @@ scan_active=true
 
 #[MasterConnector]
 
-# Unique token that identifies a client host with the master host. It has to match the
+# Unique token that authorizes a client host with the master host. It has to match the
 # corresponding entry on the client hosts.
-# access_token=SOME_LONG_AND_SECRET_TOKEN
+#access_token=SOME_LONG_AND_SECRET_TOKEN
+
+# Time in seconds that a client waits while sending data to the master before a timeout occurs. In a stable network
+# this value can be set to a smaller value resulting in a decreased likelihood that the slave will "freeze" when the
+# master has some transient trouble.
+#request_timeout = 5
 
 [ClientProcessHandler]
 # Interval in seconds between two checks of the processes on a host. Default: 10
@@ -121,7 +126,7 @@ port=5555
 # Set a unique secret which will be used to generate HTML cookies
 app_secret=SOME_OTHER_LONG_AND_SECRET_TOKEN
 # Set a prefix for all web pages of the application. Default: None, which implies no prefix at all
-#proxy_prefix=/LittleBrother
+#base_url=/LittleBrother
 
 [UnixUserHandler]
 # Set the name of the administration user
@@ -129,6 +134,18 @@ admin_username=admin
 # Set the password for the administration user
 admin_password=test123
 
+# Activate the following settings to configure the default route of the internet access. This will enable LittleBrother
+# to use iptables to limit the internet access for client. See ADVANCED_TOPICS.md for detail.
+#[FirewallHandler]
+#target_ip[0] = 0.0.0.0
+
+# Set the duration for which iptables entries are cached in the handler. Smaller values are more secure since the
+# likelihood of a discrepancy between iptables and the cache becomes smaller, larger values improve performance.
+#cache_ttl = 300
+
+[DeviceActivationManager]
+check_interval = 5
+
 #[LdapUserHandler]
 
 # Number of minutes that LDAP data is cached before re-reading
diff --git a/etc/messages.pot b/etc/messages.pot
index 717c27d..3b647af 100644
--- a/etc/messages.pot
+++ b/etc/messages.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -65,7 +65,7 @@ msgid "Master"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
+msgid "Client"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -76,8 +76,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -286,11 +286,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -301,7 +301,7 @@ msgid "Username"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -316,7 +316,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -453,7 +453,7 @@ msgstr ""
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr ""
 
@@ -494,18 +494,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -713,7 +717,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -742,23 +746,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
diff --git a/little_brother/alembic/versions/a271817b1d64_issue_169.py b/little_brother/alembic/versions/a271817b1d64_issue_169.py
new file mode 100644
index 0000000..0b36cb9
--- /dev/null
+++ b/little_brother/alembic/versions/a271817b1d64_issue_169.py
@@ -0,0 +1,27 @@
+"""issue-169
+
+Revision ID: a271817b1d64
+Revises: 9713cef84918
+Create Date: 2022-03-24 19:44:00.724780
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'a271817b1d64'
+down_revision = '9713cef84918'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('user2device', sa.Column('blockable', sa.Boolean(), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('user2device', 'blockable')
+    # ### end Alembic commands ###
diff --git a/little_brother/alembic/versions/ed5e0310d209_blocked_urls_at_device_level.py b/little_brother/alembic/versions/ed5e0310d209_blocked_urls_at_device_level.py
new file mode 100644
index 0000000..df3ea14
--- /dev/null
+++ b/little_brother/alembic/versions/ed5e0310d209_blocked_urls_at_device_level.py
@@ -0,0 +1,27 @@
+"""blocked_urls at device level
+
+Revision ID: ed5e0310d209
+Revises: a271817b1d64
+Create Date: 2022-06-19 13:27:26.938827
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'ed5e0310d209'
+down_revision = 'a271817b1d64'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('device', sa.Column('blocked_urls', sa.String(length=2048), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('device', 'blocked_urls')
+    # ### end Alembic commands ###
diff --git a/little_brother/api/api_view_handler.py b/little_brother/api/api_view_handler.py
index 9265ebb..fca9be0 100644
--- a/little_brother/api/api_view_handler.py
+++ b/little_brother/api/api_view_handler.py
@@ -178,7 +178,7 @@ def api_events(self):
                 client_stats = self.app_control.receive_client_stats(p_json_data=json_client_stats)
 
             else:
-                # old format: 2 entries without slave statistics
+                # old format: 2 entries without client statistics
                 (hostname, json_events) = event_info
 
             msg = "Received {count} events from host '{hostname}'"
diff --git a/little_brother/api/master_connector.py b/little_brother/api/master_connector.py
index 9264357..b056712 100644
--- a/little_brother/api/master_connector.py
+++ b/little_brother/api/master_connector.py
@@ -45,15 +45,15 @@ def receive_events(self, p_json_data):
         access_token = p_json_data[constants.JSON_ACCESS_TOKEN]
         hostname = p_json_data[constants.JSON_HOSTNAME]
         json_events = p_json_data[constants.JSON_EVENTS]
-        json_slave_stats = p_json_data.get(constants.JSON_CLIENT_STATS, None)
+        json_client_stats = p_json_data.get(constants.JSON_CLIENT_STATS, None)
 
         if access_token != self._config.access_token:
             fmt = "Received invalid access token from host '{hostname}'"
             self._logger.warning(fmt.format(hostname=hostname))
             return None
 
-        if json_slave_stats is not None:
-            return (hostname, json_events, json_slave_stats)
+        if json_client_stats is not None:
+            return (hostname, json_events, json_client_stats)
 
         else:
             return (hostname, json_events)
diff --git a/little_brother/app.py b/little_brother/app.py
index bf50248..234ce7b 100644
--- a/little_brother/app.py
+++ b/little_brother/app.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (C) 2019-2021  Marcus Rickert
+# Copyright (C) 2019-2022  Marcus Rickert
 #
 # See https://github.com/marcus67/little_brother
 # This program is free software; you can redistribute it and/or modify
@@ -16,6 +16,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 import os.path
+from typing import Optional
 
 import alembic.config
 import alembic.util.messaging
@@ -36,6 +37,13 @@
 from little_brother.api.version_checker import VersionCheckerConfigModel, \
     SECTION_NAME as VERSION_CHECKER_SECTION_NAME, SOURCEFORGE_CHANNEL_INFOS
 from little_brother.app_control import AppControl, AppControlConfigModel, SECTION_NAME as APP_CONTROL_SECTION_NAME
+from little_brother.devices.device_activation_manager import DeviceActivationManager
+from little_brother.devices.device_activation_manager_config_model import DeviceActivationManagerConfigModel, \
+    SECTION_NAME as DEVICE_ACTIVATION_MANAGER_SECTION_NAME
+from little_brother.devices.firewall_device_activation_handler import FirewallDeviceActivationHandler
+from little_brother.devices.firewall_handler import FirewallHandler
+from little_brother.devices.firewall_handler_config_model import FirewallHandlerConfigModel, \
+    SECTION_NAME as FIREWALL_HANDLER_SECTION_NAME
 from little_brother.german_vacation_context_rule_handler import GermanVacationContextRuleHandler
 from little_brother.persistence import persistence
 from little_brother.persistence.persistent_rule_set_entity_manager import RuleSetEntityManager
@@ -49,7 +57,6 @@
 from python_base_app import audio_handler
 from python_base_app import base_app
 from python_base_app import configuration
-from python_base_app import ldap_user_handler
 from python_base_app import pinger
 from python_base_app import unix_user_handler
 from python_base_app.base_user_handler import BaseUserHandler
@@ -62,6 +69,8 @@
 DEFAULT_USER_HANDLER = unix_user_handler.HANDLER_NAME
 DEFAULT_CLEAN_HISTORY_INTERVAL = 24 * 60 * 60  # seconds
 
+LDAP_USER_HANDLER_SECTION_NAME = "LdapUserHandler"
+
 
 class AppConfigModel(base_app.BaseAppConfigModel):
 
@@ -107,12 +116,14 @@ def __init__(self, p_pid_file, p_arguments, p_app_name):
         self._master_connector = None
         self._rule_set_section_handler = None
         self._client_device_section_handler = None
-        self._prometheus_client : PrometheusClient = None
+        self._prometheus_client: Optional[PrometheusClient] = None
         self._user_handler = None
         self._locale_helper = None
         self._pinger = None
+        self._firewall_handler: Optional[FirewallHandler] = None
+        self._device_activation_manager: Optional[DeviceActivationManager] = None
 
-    def prepare_configuration(self, p_configuration):
+    def prepare_configuration(self, p_configuration: configuration.Configuration):
 
         app_control_section = AppControlConfigModel()
         p_configuration.add_section(app_control_section)
@@ -155,8 +166,14 @@ def prepare_configuration(self, p_configuration):
         user_handler_section = unix_user_handler.UnixUserHandlerConfigModel()
         p_configuration.add_section(user_handler_section)
 
-        ldap_handler_section = ldap_user_handler.LdapUserHandlerConfigModel()
-        p_configuration.add_section(ldap_handler_section)
+        section_handler_definition = configuration.OptionalSectionHandlerDefinition()
+        section_handler_definition.section_name = LDAP_USER_HANDLER_SECTION_NAME
+        section_handler_definition.package_name = "python_base_app_ldap_extension"
+        section_handler_definition.module_name = "ldap_user_handler"
+        section_handler_definition.config_model_class_name = "LdapUserHandlerConfigModel"
+
+        p_configuration.register_optional_section_handler_definition(
+            p_optional_section_handler_definition=section_handler_definition)
 
         self._login_mapping_section_handler = login_mapping.LoginMappingSectionHandler()
         p_configuration.register_section_handler(p_section_handler=self._login_mapping_section_handler)
@@ -167,6 +184,12 @@ def prepare_configuration(self, p_configuration):
         version_checker_section = VersionCheckerConfigModel()
         p_configuration.add_section(version_checker_section)
 
+        firewall_handler_section = FirewallHandlerConfigModel()
+        p_configuration.add_section(firewall_handler_section)
+
+        device_activation_manager_section = DeviceActivationManagerConfigModel()
+        p_configuration.add_section(device_activation_manager_section)
+
         return super(App, self).prepare_configuration(p_configuration=p_configuration)
 
     def is_master(self):
@@ -216,7 +239,7 @@ def prepare_services(self, p_full_startup=True):
 
         super().prepare_services(p_full_startup=p_full_startup)
 
-        # TODO: Activate in memory sqlite backend for slaves
+        # TODO: Activate in memory sqlite backend for clients
         self._persistence = persistence.Persistence(
             p_config=self._config[persistence.SECTION_NAME])
 
@@ -273,7 +296,6 @@ def prepare_services(self, p_full_startup=True):
         dependency_injection.container[PrometheusClient] = self._prometheus_client
 
         unix_user_handler_config = self._config[unix_user_handler.SECTION_NAME]
-        ldap_user_handler_config = self._config[ldap_user_handler.SECTION_NAME]
         status_server_config = self._config[web_server.SECTION_NAME]
 
         self.init_babel(p_localeselector=self.get_request_locale)
@@ -286,8 +308,14 @@ def prepare_services(self, p_full_startup=True):
 
         if self.is_master():
             if status_server_config.is_active():
-                if ldap_user_handler_config.is_active():
-                    self._user_handler = ldap_user_handler.LdapUserHandler(p_config=ldap_user_handler_config)
+                # The section name has to be manually synchronized with the name in python_base_app_ldap_extension!
+                ldap_user_handler_config = self._config[LDAP_USER_HANDLER_SECTION_NAME]
+
+                if ldap_user_handler_config is not None and ldap_user_handler_config.is_active():
+                    # Note that we will only end up here if the configuration contains the section 'LdapUserHandler'
+                    from python_base_app_ldap_extension.ldap_user_handler import LdapUserHandler
+
+                    self._user_handler = LdapUserHandler(p_config=ldap_user_handler_config)
 
                 else:
                     if status_server_config.admin_password is not None:
@@ -375,7 +403,7 @@ def prepare_services(self, p_full_startup=True):
             raise configuration.ConfigurationException(msg)
 
         else:
-            msg = "Slave instance will not start web server due to missing port number"
+            msg = "Client instance will not start web server due to missing port number"
             self._logger.warn(msg)
 
         self._version_checker = VersionChecker(p_config=self._config[VERSION_CHECKER_SECTION_NAME],
@@ -383,11 +411,29 @@ def prepare_services(self, p_full_startup=True):
 
         dependency_injection.container[VersionChecker] = self._version_checker
 
-
         task = base_app.RecurringTask(p_name="app_control.check", p_handler_method=self._app_control.check,
                                       p_interval=self._app_control.check_interval)
         self.add_recurring_task(p_recurring_task=task)
 
+        device_activation_manager_config: DeviceActivationManagerConfigModel = \
+            self._config[DEVICE_ACTIVATION_MANAGER_SECTION_NAME]
+
+        self._device_activation_manager = DeviceActivationManager(p_config=device_activation_manager_config)
+
+        firewall_handler_config: FirewallHandlerConfigModel = self._config[FIREWALL_HANDLER_SECTION_NAME]
+
+        if firewall_handler_config.is_active():
+            self._firewall_handler = FirewallHandler(p_config=firewall_handler_config)
+            dependency_injection.container[FirewallHandler] = self._firewall_handler
+
+            firewall_device_activation_handler = FirewallDeviceActivationHandler()
+            self._device_activation_manager.add_handler(firewall_device_activation_handler)
+
+        task = self._device_activation_manager.get_recurring_task()
+
+        if task is not None:
+            self.add_recurring_task(p_recurring_task=task)
+
     def run_special_commands(self, p_arguments):
 
         if p_arguments.stamp_databases:
@@ -438,6 +484,9 @@ def stop_services(self):
         fmt = "Shutting down services -- START"
         self._logger.info(fmt)
 
+        if self._device_activation_manager is not None:
+            self._device_activation_manager.shutdown()
+
         if self._status_server is not None:
             self._status_server.stop_server()
             self._status_server.destroy()
diff --git a/little_brother/app_control.py b/little_brother/app_control.py
index fce3f89..8a5e52d 100644
--- a/little_brother/app_control.py
+++ b/little_brother/app_control.py
@@ -17,11 +17,11 @@
 
 import datetime
 import socket
-import sys
-import time
 
 import distro
 import prometheus_client
+import sys
+import time
 
 from little_brother import admin_event
 from little_brother import client_stats
@@ -56,7 +56,7 @@
 MINIMUM_VERSION_WITH_CLIENT_STAT_SUPPORT = "0.3.9"
 
 CSS_CLASS_MAXIMUM_PING_EXCEEDED = "node_inactive"
-CSS_CLASS_SLAVE_VERSION_OUTDATED = "node_outdated"
+CSS_CLASS_CLIENT_VERSION_OUTDATED = "node_outdated"
 
 # Dummy function to trigger extraction by pybabel...
 _ = lambda x, y=None: x
@@ -307,7 +307,7 @@ def start(self):
         #             self.queue_broadcast_event_start_master()
 
         else:
-            fmt = "Starting application in SLAVE mode communicating with master at URL {master_url}"
+            fmt = "Starting application in CLIENT mode communicating with master at URL {master_url}"
             self._logger.info(fmt.format(master_url=self.master_connector._get_api_url()))
 
             self.queue_event_start_client()
@@ -377,8 +377,8 @@ def update_client_info(self, p_hostname, p_client_stats=None, p_suppress_send_st
                                      p_master_version=self.get_client_version(),
                                      )
             self._client_infos[p_hostname] = client_info
-            self.send_config_to_slave(p_hostname)
-            self._user_manager.send_login_mapping_to_slave(p_hostname)
+            self.send_config_to_client(p_hostname)
+            self._user_manager.send_login_mapping_to_client(p_hostname)
 
         client_info.last_message = tools.get_current_time()
         client_info.client_stats = p_client_stats
@@ -386,15 +386,15 @@ def update_client_info(self, p_hostname, p_client_stats=None, p_suppress_send_st
     def handle_event_start_client(self, p_event):
 
         self.update_client_info(p_event.hostname, p_suppress_send_state_update=True)
-        self.send_config_to_slave(p_event.hostname)
-        self._user_manager.send_login_mapping_to_slave(p_event.hostname)
+        self.send_config_to_client(p_event.hostname)
+        self._user_manager.send_login_mapping_to_client(p_event.hostname)
         self._process_handler_manager.send_historic_process_infos()
 
     def handle_event_start_master(self, p_event):
 
         self._process_handler_manager.queue_artificial_activation_events()
 
-    def send_config_to_slave(self, p_hostname):
+    def send_config_to_client(self, p_hostname):
 
         config = {}
 
@@ -411,10 +411,10 @@ def send_config_to_slave(self, p_hostname):
 
         self.queue_event_update_config(p_hostname=p_hostname, p_config=config)
 
-    def send_config_to_all_slaves(self):
+    def send_config_to_all_clients(self):
 
         for client in self._client_infos.values():
-            self.send_config_to_slave(p_hostname=client.host_name)
+            self.send_config_to_client(p_hostname=client.host_name)
 
     def process_rule_sets_for_all_users(self, p_reference_time):
 
@@ -637,4 +637,4 @@ def add_new_user(self, p_session_context, p_username, p_locale=None):
                                               p_locale=p_locale)
         self._user_manager.add_monitored_user(p_username=p_username)
         self._user_manager.reset_users(p_session_context=p_session_context)
-        self.send_config_to_all_slaves()
+        self.send_config_to_all_clients()
diff --git a/little_brother/client_device_handler.py b/little_brother/client_device_handler.py
index d3075d9..d20958b 100644
--- a/little_brother/client_device_handler.py
+++ b/little_brother/client_device_handler.py
@@ -264,7 +264,7 @@ def get_current_active_pinfo(self, p_hostname, p_username):
 
         for pinfo in self._process_infos.values():
             if pinfo.hostname == p_hostname and pinfo.username == p_username and \
-                    max_start_time is None or pinfo.start_time > max_start_time:
+                    (max_start_time is None or pinfo.start_time > max_start_time):
                 max_start_time = pinfo.start_time
                 most_recent_pinfo = pinfo
 
diff --git a/little_brother/client_info.py b/little_brother/client_info.py
index 9d78857..c2dcc04 100644
--- a/little_brother/client_info.py
+++ b/little_brother/client_info.py
@@ -24,7 +24,7 @@
 MINIMUM_VERSION_WITH_CLIENT_STAT_SUPPORT = "0.3.9"
 
 CSS_CLASS_MAXIMUM_PING_EXCEEDED = "node_inactive"
-CSS_CLASS_SLAVE_VERSION_OUTDATED = "node_outdated"
+CSS_CLASS_CLIENT_VERSION_OUTDATED = "node_outdated"
 
 def _(x):
     return x
@@ -65,7 +65,7 @@ def linux_distribution(self):
 
     @property
     def node_type(self):
-        return _("Master") if self.is_master else _("Slave")
+        return _("Master") if self.is_master else _("Client")
 
     @property
     def seconds_without_ping(self):
@@ -114,6 +114,6 @@ def version_class(self):
                 client_version = LAST_VERSION_WITHOUT_CLIENT_STAT_SUPPORT
 
             if version.parse(client_version) < version.parse(self.master_version):
-                return CSS_CLASS_SLAVE_VERSION_OUTDATED
+                return CSS_CLASS_CLIENT_VERSION_OUTDATED
 
         return ""
diff --git a/little_brother/constants.py b/little_brother/constants.py
index 4393ab6..62b7d84 100644
--- a/little_brother/constants.py
+++ b/little_brother/constants.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (C) 2019-2021  Marcus Rickert
+# Copyright (C) 2019-2022  Marcus Rickert
 #
 # See https://github.com/marcus67/little_brother
 # This program is free software; you can redistribute it and/or modify
@@ -46,7 +46,7 @@
 DEVICE_MIN_MAX_ACTIVE_PING_DELAY = 1
 DEVICE_MAX_MAX_ACTIVE_PING_DELAY = 1000
 
-DEVICE_MIN_SAMPLE_SIZE = 5
+DEVICE_MIN_SAMPLE_SIZE = 2
 DEVICE_MAX_SAMPLE_SIZE = 100
 
 LANGUAGES = {
@@ -68,7 +68,7 @@
 
 TEXT_SEPERATOR = ' <i style="font-size: 0.5rem; vertical-align: +25%" class="fas fa-circle fa-sm"></i> '
 
-API_URL = "/api"
+API_URL = "/api/"
 API_REL_URL_EVENTS = "events"
 API_URL_EVENTS = os.path.join(API_URL, API_REL_URL_EVENTS)
 
diff --git a/little_brother/db_migrations.py b/little_brother/db_migrations.py
index 99a0468..a3c6475 100644
--- a/little_brother/db_migrations.py
+++ b/little_brother/db_migrations.py
@@ -142,6 +142,7 @@ def migrate_client_device_configs(self, p_client_device_configs, persistent_user
                     user2device.percent = constants.DEFAULT_USER2DEVICE_PERCENT
                     user2device.active = True
                     user2device.user = user
+                    session.add(user2device)
 
                 else:
                     msg = "Username '{username}' for found for device '{device_name}' -> not linking to user!"
diff --git a/little_brother/devices/__init__.py b/little_brother/devices/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/vagrant/settings.rb b/little_brother/devices/base_device_activation_handler.py
similarity index 62%
rename from vagrant/settings.rb
rename to little_brother/devices/base_device_activation_handler.py
index 24a9344..1ada9ac 100644
--- a/vagrant/settings.rb
+++ b/little_brother/devices/base_device_activation_handler.py
@@ -1,6 +1,6 @@
-# coding: utf-8
-#
-# Copyright (C) 2019  Marcus Rickert
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2019-2022  Marcus Rickert
 #
 # See https://github.com/marcus67/little_brother
 # This program is free software; you can redistribute it and/or modify
@@ -15,21 +15,13 @@
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-# Type and version of the VM
-VAGRANT_BOX='ubuntu/trusty64'
-VAGRANT_BOX_VERSION='v20190429.0.1'
-
-# Version of the VBOX guest additions
-VIRTUALBOX_GUEST_ADDITIONS_VERSION="6.0.2"
+import abc
 
-# Logical name of the VM (among other things appearing in the VirtualBox GUI)
-VM_NAME='little-brother'
+from little_brother.persistence.persistent_device import Device
 
-# RAM of the VM in Megabytes
-VM_MEMORY=4096
 
-# Number of CPUs of the VM
-VM_CPUS=2
+class BaseDeviceActivationHandler(abc.ABC):
 
-# Video RAM size in Megabytes
-VM_VRAM=128
+    @abc.abstractmethod
+    def set_usage_permission_for_device(self, p_device:Device, p_usage_permitted:bool):
+        pass
diff --git a/little_brother/devices/device_activation_manager.py b/little_brother/devices/device_activation_manager.py
new file mode 100644
index 0000000..6ed7bcf
--- /dev/null
+++ b/little_brother/devices/device_activation_manager.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2019-2022  Marcus Rickert
+#
+# See https://github.com/marcus67/little_brother
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from typing import Optional
+
+from little_brother import dependency_injection
+from little_brother.devices.base_device_activation_handler import BaseDeviceActivationHandler
+from little_brother.devices.device_activation_manager_config_model import DeviceActivationManagerConfigModel
+from little_brother.persistence.persistent_dependency_injection_mix_in import PersistenceDependencyInjectionMixIn
+from little_brother.persistence.persistent_device import Device
+from little_brother.persistence.session_context import SessionContext
+from little_brother.user_manager import UserManager
+from python_base_app import log_handling
+from python_base_app import tools
+from python_base_app.base_app import RecurringTask
+
+
+class DeviceActivationManager(PersistenceDependencyInjectionMixIn):
+
+    def __init__(self, p_config:DeviceActivationManagerConfigModel):
+        super().__init__()
+
+        self._config : DeviceActivationManagerConfigModel = p_config
+        self._logger = log_handling.get_logger(self.__class__.__name__)
+
+        self._handlers :  Optional[list[BaseDeviceActivationHandler]] = []
+        self._user_manager : Optional[UserManager] = None
+
+    @property
+    def user_manager(self) -> UserManager:
+        if self._user_manager is None:
+            self._user_manager = dependency_injection.container[UserManager]
+
+        return self._user_manager
+
+    def shutdown(self):
+        self._logger.info("Shutting down -> unblock all devices...")
+        self.check_device_activation_status(p_force_usage_permitted=True)
+
+    def add_handler(self, p_handler: BaseDeviceActivationHandler):
+        self._handlers.append(p_handler)
+
+    def set_usage_permission_status_for_device(self, p_device:Device, p_usage_permitted: bool):
+        for handler in self._handlers:
+            handler.set_usage_permission_for_device(p_device=p_device, p_usage_permitted=p_usage_permitted)
+
+    def check_device_activation_status(self, p_force_usage_permitted=False):
+        with SessionContext(p_persistence=self.persistence) as session_context:
+            for device in self.device_entity_manager.devices(p_session_context=session_context):
+                usage_permitted = True
+                set_status = tools.is_valid_ip_address_or_dns_name(device.hostname)
+
+                if not p_force_usage_permitted:
+                    for user2device in device.users:
+                        if user2device.active and user2device.blockable and user2device.user.active:
+                            set_status = True
+                            user_status = self.user_manager.get_current_user_status(
+                                p_session_context=session_context, p_username=user2device.user.username)
+
+                            if user_status is not None and not user_status.activity_allowed:
+                                usage_permitted = False
+                                break
+
+                if set_status:
+                    self.set_usage_permission_status_for_device(p_device=device,
+                                                                p_usage_permitted=usage_permitted)
+
+
+    def get_recurring_task(self) -> Optional[RecurringTask]:
+        if len(self._handlers) == 0:
+            self._logger.info("No handlers for device activation registered.")
+            return None
+
+        task = RecurringTask(
+            p_name="DeviceActivationManager.check_device_activation_status",
+            p_handler_method=lambda: self.check_device_activation_status(),
+            p_interval=self._config.check_interval,
+            p_ignore_exceptions=True)
+
+        return task
diff --git a/little_brother/devices/device_activation_manager_config_model.py b/little_brother/devices/device_activation_manager_config_model.py
new file mode 100644
index 0000000..b72e05a
--- /dev/null
+++ b/little_brother/devices/device_activation_manager_config_model.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2019-2022  Marcus Rickert
+#
+# See https://github.com/marcus67/little_brother
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from python_base_app import configuration
+
+SECTION_NAME = "DeviceActivationManager"
+DEFAULT_CHECK_INTERVAL = 60
+
+class DeviceActivationManagerConfigModel(configuration.ConfigModel):
+
+    def __init__(self):
+        super().__init__(p_section_name=SECTION_NAME)
+
+        self.check_interval: int = DEFAULT_CHECK_INTERVAL
diff --git a/little_brother/devices/firewall_device_activation_handler.py b/little_brother/devices/firewall_device_activation_handler.py
new file mode 100644
index 0000000..d6f8c44
--- /dev/null
+++ b/little_brother/devices/firewall_device_activation_handler.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2019-2022  Marcus Rickert
+#
+# See https://github.com/marcus67/little_brother
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from typing import Optional
+
+from little_brother import dependency_injection
+from little_brother.devices.base_device_activation_handler import BaseDeviceActivationHandler
+from little_brother.devices.firewall_handler import FirewallHandler
+from little_brother.persistence.persistent_device import Device
+from python_base_app import log_handling
+
+
+class FirewallDeviceActivationHandler(BaseDeviceActivationHandler):
+
+    def __init__(self):
+        self._firewall_handler: Optional[FirewallHandler] = None
+        self._logger = log_handling.get_logger(self.__class__.__name__)
+
+    @property
+    def firewall_handler(self) -> FirewallHandler:
+        if self._firewall_handler is None:
+            self._firewall_handler = dependency_injection.container[FirewallHandler]
+
+        return self._firewall_handler
+
+    def set_usage_permission_for_device(self, p_device: Device, p_usage_permitted: bool):
+        self._logger.debug(f"Set usage permission for device '{p_device.device_name}' to {p_usage_permitted}")
+
+        self.firewall_handler.set_usage_permission_for_ip(p_ip_address=p_device.ip_address,
+                                                          p_blocked_ip_addresses=p_device.list_of_blocked_ip_addresses,
+                                                          p_usage_permitted=p_usage_permitted)
diff --git a/little_brother/devices/firewall_entry.py b/little_brother/devices/firewall_entry.py
new file mode 100644
index 0000000..cb319f8
--- /dev/null
+++ b/little_brother/devices/firewall_entry.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2019-2022  Marcus Rickert
+#
+# See https://github.com/marcus67/little_brother
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+def key(p_source, p_destination):
+    return p_source + "|" + p_destination
+
+
+class FirewallEntry:
+
+    def key(self):
+        return key(p_source=self.source, p_destination=self.destination)
+
+    def __init__(self):
+
+        self.index = None
+        self.target = None
+        self.source = None
+        self.destination = None
+        self.protocol = None
+        self.comment = None
diff --git a/little_brother/devices/firewall_handler.py b/little_brother/devices/firewall_handler.py
new file mode 100644
index 0000000..610133b
--- /dev/null
+++ b/little_brother/devices/firewall_handler.py
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2019-2022  Marcus Rickert
+#
+# See https://github.com/marcus67/little_brother
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import datetime
+import re
+import shlex
+import subprocess
+from typing import Optional
+
+from little_brother.devices.firewall_entry import FirewallEntry, key
+from little_brother.devices.firewall_handler_config_model import FirewallHandlerConfigModel, DEFAULT_PROTOCOL, \
+    DEFAULT_TARGET, DEFAULT_COMMENT
+from python_base_app import log_handling, tools
+from python_base_app.configuration import ConfigurationException
+
+
+class FirewallHandler:
+
+    def __init__(self, p_config: FirewallHandlerConfigModel):
+        self._logger = log_handling.get_logger(self.__class__.__name__)
+
+        self._config: FirewallHandlerConfigModel = p_config
+        self._entries: Optional[list[FirewallEntry]] = None
+        self._last_table_scan: Optional[datetime.datetime] = None
+
+    @property
+    def entries(self) -> Optional[list[FirewallEntry]]:
+        self.read_forward_entries()
+        return self._entries
+
+    def get_active_forward_entries(self, p_ip_address) -> dict[str, FirewallEntry]:
+
+        source = tools.get_ip_address_by_dns_name(p_dns_name=p_ip_address)
+
+        return {entry.key(): entry for entry in self.entries
+                if (entry.protocol == DEFAULT_PROTOCOL and
+                    entry.source == source and
+                    entry.target == DEFAULT_TARGET)}
+
+    def add_entry_to_cache(self, p_new_entry: FirewallEntry):
+
+        for entry in self.entries:
+            entry.index += 1
+
+        self.entries.insert(0, p_new_entry)
+
+    def remove_entry_from_cache(self, p_entry: FirewallEntry):
+
+        self.entries.remove(p_entry)
+
+        for entry in self._entries:
+            if entry.index > p_entry.index:
+                entry.index -= 1
+
+    def remove_entry(self, p_entry):
+
+        self._logger.info(f"Removing iptables entry for blocking {p_entry.source} -> {p_entry.destination}...")
+
+        iptables_command = self._config.iptables_remove_forward_command_pattern.format(index=p_entry.index)
+        command = shlex.split(self._config.sudo_command + " " + iptables_command)
+
+        self._logger.debug(f"Executing command {command} in subprocess")
+        proc = subprocess.run(command, stdout=subprocess.PIPE)
+
+        if proc.returncode >= 1:
+            raise ConfigurationException(f"{command} returns exit code {proc.returncode}")
+
+        self.remove_entry_from_cache(p_entry=p_entry)
+
+    def remove_entries(self, p_forward_entries: dict[str, FirewallEntry]):
+        for entry in p_forward_entries.values():
+            self.remove_entry(p_entry=entry)
+
+    def add_missing_entry(self, p_ip_address: str, p_target_ip: str, p_comment:str):
+
+        self._logger.info(f"Adding iptables entry for blocking {p_ip_address} -> {p_target_ip}...")
+
+        iptables_command = self._config.iptables_add_forward_command_pattern.format(
+            source_ip=p_ip_address,
+            destination_ip=p_target_ip,
+            comment=p_comment
+        )
+
+        # Remove the specification of the default route (= "any IP address") from the command since this is not an
+        # allowed ip address! However, iptables will list the default route as such so we will use the string "0.0.0.0"
+        # everywhere else.
+        iptables_command = iptables_command.replace("-d 0.0.0.0", "")
+
+        command = shlex.split(self._config.sudo_command + " " + iptables_command)
+
+        self._logger.debug(f"Executing command {command} in subprocess")
+        proc = subprocess.run(command, stdout=subprocess.PIPE)
+
+        if proc.returncode >= 1:
+            raise ConfigurationException(f"{command} returns exit code {proc.returncode}")
+
+        new_entry = FirewallEntry()
+        new_entry.index = 1
+        new_entry.source = p_ip_address
+        new_entry.destination = p_target_ip
+        new_entry.target = DEFAULT_TARGET
+        new_entry.protocol = DEFAULT_PROTOCOL
+        new_entry.comment = p_comment
+
+        self.add_entry_to_cache(new_entry)
+
+    def update_active_entries(self, p_ip_address: str, p_blocked_ip_addresses: list[str],
+                              p_forward_entries: dict[str, FirewallEntry], p_comment:str):
+
+        # Use the devices blocked ip addresses if they defined else use the globally defined addresses
+        effective_list_of_ip_addresses = self._config.target_ip \
+            if len(p_blocked_ip_addresses) == 0 else p_blocked_ip_addresses
+
+        for target_ip in effective_list_of_ip_addresses:
+            entry_key = key(p_source=p_ip_address, p_destination=target_ip)
+
+            if entry_key not in p_forward_entries:
+                self.add_missing_entry(p_ip_address=p_ip_address, p_target_ip=target_ip, p_comment=p_comment)
+
+        for forward_entry in p_forward_entries.values():
+            if forward_entry.destination not in p_blocked_ip_addresses:
+                self.remove_entry(p_entry=forward_entry)
+
+
+    def set_usage_permission_for_ip(self, p_ip_address: str, p_blocked_ip_addresses: list[str],
+                                    p_usage_permitted: bool):
+
+        self.read_forward_entries()
+
+        forward_entries = self.get_active_forward_entries(p_ip_address=p_ip_address)
+
+        if p_usage_permitted and len(forward_entries) > 0:
+            self.remove_entries(p_forward_entries=forward_entries)
+
+        elif not p_usage_permitted:
+            self.update_active_entries(p_ip_address=p_ip_address, p_blocked_ip_addresses=p_blocked_ip_addresses,
+                                       p_forward_entries=forward_entries, p_comment=DEFAULT_COMMENT)
+
+    def read_forward_entries(self):
+
+        if self._entries is not None and self._last_table_scan is not None:
+            if datetime.datetime.now() < self._last_table_scan + datetime.timedelta(seconds=self._config.cache_ttl):
+                return
+
+            self._logger.debug("Clearing iptables cache.")
+            self._entries = []
+
+        command = shlex.split(self._config.sudo_command + " " + self._config.iptables_list_forward_command)
+
+        self._logger.debug(f"Executing command {command} in subprocess")
+        proc = subprocess.run(command, stdout=subprocess.PIPE)
+
+        if proc.returncode >= 1:
+            raise ConfigurationException(f"{command} returns exit code {proc.returncode}")
+
+        stdout_string = proc.stdout.decode("UTF-8")
+
+        line_regex = re.compile(self._config.iptables_list_forward_entry_pattern)
+
+        self._entries = []
+
+        for line in stdout_string.split("\n"):
+            self._logger.debug(f"iptables output: {line}")
+
+            result = line_regex.match(line)
+
+            if result:
+                entry = FirewallEntry()
+                entry.index = int(result.group(self._config.iptables_list_forward_entry_pattern_index_group))
+                entry.target = result.group(self._config.iptables_list_forward_entry_pattern_target_group)
+                entry.protocol = result.group(self._config.iptables_list_forward_entry_pattern_protocol_group)
+                entry.option = result.group(self._config.iptables_list_forward_entry_pattern_option_group)
+                entry.source = result.group(self._config.iptables_list_forward_entry_pattern_source_group)
+                entry.destination = \
+                    result.group(self._config.iptables_list_forward_entry_pattern_destination_group)
+                entry.comment = \
+                    result.group(self._config.iptables_list_forward_entry_pattern_comment_group)
+                self._entries.append(entry)
+
+        self._last_table_scan = datetime.datetime.now()
diff --git a/little_brother/devices/firewall_handler_config_model.py b/little_brother/devices/firewall_handler_config_model.py
new file mode 100644
index 0000000..79335fc
--- /dev/null
+++ b/little_brother/devices/firewall_handler_config_model.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2019-2022  Marcus Rickert
+#
+# See https://github.com/marcus67/little_brother
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from typing import Optional
+
+from python_base_app import configuration
+from python_base_app.tools import PATTERN_IP_ADDRESS
+
+SECTION_NAME = "FirewallHandler"
+
+DEFAULT_SUDO_COMMAND = "/usr/bin/sudo"
+DEFAULT_CHAIN = "FORWARD"
+DEFAULT_TARGET = "DROP"
+DEFAULT_PROTOCOL = "all"
+DEFAULT_CACHE_TTL = 300  # seconds
+DEFAULT_COMMENT = "rule generated by LittleBrother"
+
+DEFAULT_IPTABLES_LIST_FORWARD_COMMAND = "/usr/sbin/iptables -n --line-numbers  -L " + DEFAULT_CHAIN
+DEFAULT_IPTABLES_ADD_FORWARD_COMMAND_PATTERN = "iptables -I " + DEFAULT_CHAIN + " -p " + DEFAULT_PROTOCOL + " -j " + \
+                                               DEFAULT_TARGET + " -s {source_ip} -d {destination_ip} " \
+                                               "-m comment --comment '{comment}'"
+DEFAULT_IPTABLES_REMOVE_FORWARD_COMMAND_PATTERN = "iptables -D " + DEFAULT_CHAIN + " {index}"
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN = \
+    r"^([0-9]+)\s+(\w+)\s+(\w+)\s+(\S+)\s+(" + PATTERN_IP_ADDRESS + \
+    r")(/[0-9]+)?\s+(" + PATTERN_IP_ADDRESS + ")(/[0-9]+)?\s*(.*)"
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_INDEX_GROUP = 1
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_TARGET_GROUP = 2
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_PROTOCOL_GROUP = 3
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_OPTION_GROUP = 4
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_SOURCE_GROUP = 5
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_DESTINATION_GROUP = 7
+DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_COMMENT_GROUP = 9
+
+
+class FirewallHandlerConfigModel(configuration.ConfigModel):
+
+    def __init__(self):
+        super().__init__(p_section_name=SECTION_NAME)
+
+        self.sudo_command = DEFAULT_SUDO_COMMAND
+        self.cache_ttl = DEFAULT_CACHE_TTL
+
+        self.target_ip: Optional[list[str]] = [configuration.NONE_STRING]
+
+        self.iptables_list_forward_command: str = \
+            DEFAULT_IPTABLES_LIST_FORWARD_COMMAND
+        self.iptables_add_forward_command_pattern: str = \
+            DEFAULT_IPTABLES_ADD_FORWARD_COMMAND_PATTERN
+        self.iptables_remove_forward_command_pattern: str = \
+            DEFAULT_IPTABLES_REMOVE_FORWARD_COMMAND_PATTERN
+
+        self.iptables_list_forward_entry_pattern: str = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN
+        self.iptables_list_forward_entry_pattern_index_group: int = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_INDEX_GROUP
+        self.iptables_list_forward_entry_pattern_target_group: int = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_TARGET_GROUP
+        self.iptables_list_forward_entry_pattern_protocol_group: int = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_PROTOCOL_GROUP
+        self.iptables_list_forward_entry_pattern_option_group: int = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_OPTION_GROUP
+        self.iptables_list_forward_entry_pattern_source_group: int = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_SOURCE_GROUP
+        self.iptables_list_forward_entry_pattern_destination_group: int = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_DESTINATION_GROUP
+        self.iptables_list_forward_entry_pattern_comment_group: int = \
+            DEFAULT_IPTABLES_LIST_FORWARD_ENTRY_PATTERN_COMMENT_GROUP
+
+    def is_active(self):
+        return len(self.target_ip) > 0
diff --git a/little_brother/entity_forms.py b/little_brother/entity_forms.py
index cdcbb92..fa99853 100644
--- a/little_brother/entity_forms.py
+++ b/little_brother/entity_forms.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (C) 2019-2021  Marcus Rickert
+# Copyright (C) 2019-2022  Marcus Rickert
 #
 # See https://github.com/marcus67/little_brother
 # This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,10 @@
 
 _ = lambda x: x
 
+def set_get_text(p_get_text):
+    global _
+    _ = p_get_text
+
 a_pinger = pinger.Pinger()
 
 
@@ -42,10 +46,9 @@ class UserForm(custom_form.ModelForm):
     first_name = wtforms.StringField("FirstName")
     last_name = wtforms.StringField("LastName")
     locale = wtforms.SelectField("Locale")
-    process_name_pattern = wtforms.TextAreaField("ProcessNamePattern",
-                                                 validators=[regex_validator])
-    prohibited_process_name_pattern = wtforms.TextAreaField("ProhibitedProcessNamePattern",
-                                                 validators=[regex_validator])
+    process_name_pattern = custom_fields.TextArea("ProcessNamePattern", validators=[regex_validator])
+    prohibited_process_name_pattern = custom_fields.TextArea("ProhibitedProcessNamePattern",
+                                                             validators=[regex_validator])
     active = custom_fields.BooleanField("Active")
 
 
@@ -66,8 +69,13 @@ class DummyAdminForm(custom_form.ModelForm):
 
 
 def dns_validator(_form, field):
-    if not a_pinger.is_valid_ping(field.data):
-        raise wtforms.validators.ValidationError(_("Not a valid host address"))
+    urls = field.data.splitlines()
+
+    for url in urls:
+        if url.strip() != "":
+            if not a_pinger.is_valid_ping(url):
+                msg = _("Not a valid host address: {url}")
+                raise wtforms.validators.ValidationError(msg.format(url=url))
 
 
 class DeviceForm(custom_form.ModelForm):
@@ -92,6 +100,9 @@ class DeviceForm(custom_form.ModelForm):
                                        validators=[wtforms.validators.NumberRange(
                                            min=constants.DEVICE_MIN_SAMPLE_SIZE,
                                            max=constants.DEVICE_MAX_SAMPLE_SIZE)])
+    blocked_urls = custom_fields.TextArea("BlockedUrls",
+                                          validators=[wtforms.validators.length(max=2048),
+                                                      dns_validator])
 
 
 def create_rulesets_form(prefix, p_localized_context_details, p_context_choices, p_context_details_filters):
@@ -114,3 +125,4 @@ class RulesetForm(custom_form.ModelForm):
 class User2DeviceForm(custom_form.ModelForm):
     percent = wtforms.IntegerField("Percent", validators=[wtforms.validators.NumberRange(min=1, max=100)])
     active = custom_fields.BooleanField("Active")
+    blockable = custom_fields.BooleanField("Blockable")
diff --git a/little_brother/persistence/persistent_device.py b/little_brother/persistence/persistent_device.py
index de35e08..50b7dcb 100644
--- a/little_brother/persistence/persistent_device.py
+++ b/little_brother/persistence/persistent_device.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (C) 2019-2021  Marcus Rickert
+# Copyright (C) 2019-2022  Marcus Rickert
 #
 # See https://github.com/marcus67/little_brother
 # This program is free software; you can redistribute it and/or modify
@@ -37,6 +37,7 @@ class Device(Base, BaseEntity):
     max_active_ping_delay = Column(Integer)
     sample_size = Column(Integer)
     users = relationship("User2Device", back_populates="device", lazy="joined")
+    blocked_urls = Column(String(2048))
 
     def __init__(self):
         super(BaseEntity).__init__()
@@ -45,6 +46,7 @@ def __init__(self):
         self.sample_size = constants.DEFAULT_DEVICE_SAMPLE_SIZE
         self.min_activity_duration = constants.DEFAULT_DEVICE_MIN_ACTIVITY_DURATION
         self.max_active_ping_delay = constants.DEFAULT_DEVICE_MAX_ACTIVE_PING_DELAY
+        self.blocked_urls = None
 
     def populate_test_data(self, p_session_context: SessionContext):
         self.device_name = "SomeDeviceName"
@@ -66,6 +68,17 @@ def list_of_users(self, p_exclude=None):
                           for user2device in self.users
                           if p_exclude is None or not user2device.user is p_exclude])
 
+    @property
+    def ip_address(self) -> str:
+        return tools.get_ip_address_by_dns_name(self.hostname)
+
+    @property
+    def list_of_blocked_ip_addresses(self) -> list[str]:
+        if self.blocked_urls is None:
+            return []
+
+        return [ ip for url in self.blocked_urls.splitlines() if url.strip() != '' for ip in tools.get_ip_addresses_by_dns_name(url) ]
+
     @property
     def summary(self):
         texts = []
diff --git a/little_brother/persistence/persistent_user_2_device.py b/little_brother/persistence/persistent_user_2_device.py
index 23d77f6..e867c15 100644
--- a/little_brother/persistence/persistent_user_2_device.py
+++ b/little_brother/persistence/persistent_user_2_device.py
@@ -35,6 +35,7 @@ class User2Device(Base, BaseEntity):
     id = Column(Integer, primary_key=True)
     active = Column(Boolean)
     percent = Column(Integer)
+    blockable = Column(Boolean, default=False)
 
     user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
     user = relationship("User", back_populates="devices", lazy="joined")
diff --git a/little_brother/process_handler_manager.py b/little_brother/process_handler_manager.py
index b81e8a7..4902b3f 100644
--- a/little_brother/process_handler_manager.py
+++ b/little_brother/process_handler_manager.py
@@ -59,7 +59,7 @@
 MINIMUM_VERSION_WITH_CLIENT_STAT_SUPPORT = "0.3.9"
 
 CSS_CLASS_MAXIMUM_PING_EXCEEDED = "node_inactive"
-CSS_CLASS_SLAVE_VERSION_OUTDATED = "node_outdated"
+CSS_CLASS_CLIENT_VERSION_OUTDATED = "node_outdated"
 
 # Dummy function to trigger extraction by pybabel...
 _ = lambda x, y=None: x
@@ -385,7 +385,7 @@ def send_historic_process_infos(self):
                 self.queue_event_historic_process_start(p_pinfo=pinfo)
                 counter = counter + 1
 
-        fmt = "Sent %d historic process infos to slaves" % counter
+        fmt = "Sent %d historic process infos to clients" % counter
         self._logger.info(fmt)
 
     def queue_event_historic_process_start(self, p_pinfo):
diff --git a/little_brother/settings.py b/little_brother/settings.py
index 414d66a..728c24a 100644
--- a/little_brother/settings.py
+++ b/little_brother/settings.py
@@ -18,7 +18,7 @@
 settings = {
     "name": "little-brother",
     "url": "https://github.com/marcus67/little_brother",
-    "version": "0.4.19",
+    "version": "0.4.26",
     "description": "Simple parental control application monitoring specific processes on Linux hosts "
                    "to monitor and limit the play time of (young) children.",
     "author": "Marcus Rickert",
@@ -27,7 +27,7 @@
 
 extended_settings = {
     "display_url": "github.com/marcus67/little_brother",
-    "debian_package_revision": "121",
+    "debian_package_revision": "133",
     "debian_package_architecture": "all",
     "babel_rel_directory": "translations",
     "analyze_extra_coverage_exclusions": "run_python_base_app_test_suite_no_venv.py",
diff --git a/little_brother/static/default.css b/little_brother/static/default.css
index fb0d3d1..121c990 100644
--- a/little_brother/static/default.css
+++ b/little_brother/static/default.css
@@ -1,5 +1,5 @@
 /*
-#    Copyright (C) 2019  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -387,12 +387,17 @@ ul {
   80% { opacity: 0.5; }
 }
 
-/* https://stackoverflow.com/questions/41081275/bootstrap-left-align-checkbox-in-horizontal-form */
+/* See https://stackoverflow.com/questions/41081275/bootstrap-left-align-checkbox-in-horizontal-form */
 .move-left {
   width: auto;
   box-shadow: none;
 }
 
+/* See https://de.w3docs.com/snippets/css/wie-kann-man-die-grossenanderung-des-elements-textarea-deaktivieren.html */
+.text-field-lock-width {
+  resize: vertical;
+}
+
 .fas {
   width: 20px;
 }
diff --git a/little_brother/templates/devices.template.html b/little_brother/templates/devices.template.html
index 68e2f1a..6e323f1 100644
--- a/little_brother/templates/devices.template.html
+++ b/little_brother/templates/devices.template.html
@@ -1,5 +1,5 @@
 {#<!--
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -83,8 +83,8 @@
 					{% call accordion.details() %}{{device.html_key}}{% endcall %}
 				<DIV CLASS="row ALTERNATE2_ROWLEVEL_3 TITLE_ROW">
 					<DIV CLASS="col-xs-1 "></DIV>
-					<DIV CLASS="col-xs-4  col-md-2  col-lg-3 localetext">{{_('Name')}}</DIV>
-					<DIV CLASS="col-xs-7  col-md-3  col-lg-5 localetext">{{_('Host Name')}}</DIV>
+					<DIV CLASS="col-xs-4  col-md-2  col-lg-2 localetext">{{_('Name')}}</DIV>
+					<DIV CLASS="col-xs-7  col-md-3  col-lg-3 localetext">{{_('Host Name')}}</DIV>
 					<!-- line break for xs -->
 					<DIV CLASS="clearfix  visible-xs-block visible-sm-block"></DIV>
 					<DIV CLASS="col-xs-1  hidden-md hidden-lg SEPERATOR_ROW"></DIV>
@@ -94,20 +94,29 @@
 					<DIV CLASS="col-xs-4  col-md-2  col-lg-1 localetext">{{_('Min Activity Duration [s]')}}</DIV>
 					<DIV CLASS="col-xs-3  col-md-2  col-lg-1 localetext">{{_('Sample Size')}}</DIV>
 					<DIV CLASS="col-xs-4  col-md-2  col-lg-1 localetext">{{_('Max Active Response Delay [ms]')}}</DIV>
+
+					<!-- line break for xs START -->
+					<DIV CLASS="clearfix  visible-xs-block visible-sm-block"></DIV>
+					<DIV CLASS="col-xs-1                                    hidden-lg SEPERATOR_ROW"></DIV>
+					<DIV CLASS="col-xs-11                                   hidden-lg SEPERATOR_ROW_LINE"></DIV>
+					<!-- line break for xs END -->
+
+					<DIV CLASS="col-xs-1            hidden-lg  "></DIV>
+					<DIV CLASS="col-xs-11           col-lg-3 localetext">{{_('Blocked URLs')}}</DIV>
 				</DIV>
 				<DIV CLASS="row ALTERNATE2_ROWLEVEL_3 TITLE_ROW">
 					<DIV CLASS="col-xs-1 "></DIV>
-					<DIV CLASS="col-xs-4  col-md-2  col-lg-3 localetext">{{
+					<DIV CLASS="col-xs-4  col-md-2  col-lg-2 localetext">{{
 						helper.render_field(forms[device.device_name].device_name) }}
 					</DIV>
-					<DIV CLASS="col-xs-7  col-md-3  col-lg-5 localetext">{{
+					<DIV CLASS="col-xs-7  col-md-3  col-lg-3 localetext">{{
 						helper.render_field(forms[device.device_name].hostname) }}
 					</DIV>
-					<!-- line break for xs -->
+					<!-- line break for xs START -->
 					<DIV CLASS="clearfix  visible-xs-block visible-sm-block"></DIV>
 					<DIV CLASS="col-xs-1  hidden-md hidden-lg SEPERATOR_ROW"></DIV>
 					<DIV CLASS="col-xs-11 hidden-md hidden-lg SEPERATOR_ROW_LINE"></DIV>
-					<!-- line break for xs -->
+					<!-- line break for xs END -->
 					<DIV CLASS="col-xs-1 hidden-md hidden-lg  "></DIV>
 					<DIV CLASS="col-xs-4  col-md-2  col-lg-1 localetext">{{
 						helper.render_field(forms[device.device_name].min_activity_duration) }}
@@ -118,6 +127,15 @@
 					<DIV CLASS="col-xs-4  col-md-2  col-lg-1 localetext">{{
 						helper.render_field(forms[device.device_name].max_active_ping_delay) }}
 					</DIV>
+					<!-- line break for xs START -->
+					<DIV CLASS="clearfix  visible-xs-block visible-sm-block"></DIV>
+					<DIV CLASS="col-xs-1                                    hidden-lg SEPERATOR_ROW"></DIV>
+					<DIV CLASS="col-xs-11                                   hidden-lg SEPERATOR_ROW_LINE"></DIV>
+					<!-- line break for xs END -->
+					<DIV CLASS="col-xs-1            hidden-lg  "></DIV>
+					<DIV CLASS="col-xs-11            col-lg-3 localetext">{{
+						helper.render_field(forms[device.device_name].blocked_urls, rows=5) }}
+					</DIV>
 				</DIV>
 					{{ accordion.end() }}
 				{% endfor %}
diff --git a/little_brother/templates/users.template.html b/little_brother/templates/users.template.html
index 3db6a9b..1ebbaa6 100644
--- a/little_brother/templates/users.template.html
+++ b/little_brother/templates/users.template.html
@@ -292,14 +292,16 @@
 									<DIV CLASS="row ALTERNATE2_ROWLEVEL_3 TITLE_ROW dense-font">
 										<DIV CLASS="col-xs-1 "></DIV>
 										<DIV CLASS="col-xs-3  col-sm-2  col-md-2  col-lg-2  localetext">{{_('Monitored')}}</DIV>
+										<DIV CLASS="col-xs-3  col-sm-2  col-md-2  col-lg-2  localetext">{{_('Blockable')}}</DIV>
 										<DIV CLASS="col-xs-3  col-sm-2  col-md-1  col-lg-1  localetext">{{_('Percent')}}</DIV>
-										<DIV CLASS="col-xs-5  col-sm-2  col-md-8  col-lg-8  text-right"></DIV>
+										<DIV CLASS="col-xs-2  col-sm-5  col-md-6  col-lg-6  text-right"></DIV>
 									</DIV>
 									<DIV CLASS="row ALTERNATE2_ROWLEVEL_3">
 										<DIV CLASS="col-xs-1 "></DIV>
 										<DIV CLASS="col-xs-3  col-sm-2  col-md-2 col-lg-2  ">{{ helper.render_field(forms[user2device.html_key].active) }}</DIV>
+										<DIV CLASS="col-xs-3  col-sm-2  col-md-2 col-lg-2  ">{{ helper.render_field(forms[user2device.html_key].blockable) }}</DIV>
 										<DIV CLASS="col-xs-3  col-sm-2  col-md-1 col-lg-1  ">{{ helper.render_field(forms[user2device.html_key].percent) }}</DIV>
-										<DIV CLASS="col-xs-5  col-sm-7  col-md-8 col-lg-8 text-right ">
+										<DIV CLASS="col-xs-2  col-sm-5  col-md-6 col-lg-6 text-right ">
 											<BUTTON type="submit" name="submit" class="btn" title="{{_('Save')}}"><I class="fas fa-save"></I></BUTTON>
 										</DIV>
 									</DIV>
diff --git a/little_brother/translations/bn/LC_MESSAGES/messages.po b/little_brother/translations/bn/LC_MESSAGES/messages.po
index df85ce7..68b34f3 100644
--- a/little_brother/translations/bn/LC_MESSAGES/messages.po
+++ b/little_brother/translations/bn/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version:  LittleBrother\n"
 "Report-Msgid-Bugs-To: little-brother@web.de\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-20 10:00+0200\n"
 "Last-Translator: Rownak Jyoti Zaman <jyotizaman@yahoo.com>\n"
 "Language: bn\n"
@@ -68,7 +68,7 @@ msgstr "ব্যবহারকারী"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "সেইভ"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -79,8 +79,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -316,11 +316,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "ব্যবহারকারীর নাম '{username}' মনিটর করা হচ্ছে না"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -331,7 +331,7 @@ msgid "Username"
 msgstr "ব্যবহারকারীর নাম"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -346,7 +346,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -483,7 +483,7 @@ msgstr "ওভাররাইড"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "সেইভ"
 
@@ -524,18 +524,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -746,7 +750,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -775,23 +779,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -858,3 +866,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/da/LC_MESSAGES/messages.po b/little_brother/translations/da/LC_MESSAGES/messages.po
index 070ebe0..61454ab 100644
--- a/little_brother/translations/da/LC_MESSAGES/messages.po
+++ b/little_brother/translations/da/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version:  LittleBrother\n"
 "Report-Msgid-Bugs-To: little-brother@web.de\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-05-11 23:49+0200\n"
 "Last-Translator: Erik Husmark\n"
 "Language: da\n"
@@ -68,7 +68,7 @@ msgstr "Bruger"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "Gem"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -79,8 +79,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -301,11 +301,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "bruger '{username}' overvåges ikke"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -316,7 +316,7 @@ msgid "Username"
 msgstr "Brugernavn"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -331,7 +331,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -468,7 +468,7 @@ msgstr "særregel"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Gem"
 
@@ -509,18 +509,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -729,7 +733,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -758,23 +762,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -835,3 +843,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/de/LC_MESSAGES/messages.po b/little_brother/translations/de/LC_MESSAGES/messages.po
index 82d29b9..8788b2b 100644
--- a/little_brother/translations/de/LC_MESSAGES/messages.po
+++ b/little_brother/translations/de/LC_MESSAGES/messages.po
@@ -16,7 +16,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version:  little-brother\n"
 "Report-Msgid-Bugs-To: marcus.rickert@web.de\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2019-02-27 23:25+0100\n"
 "Last-Translator: Marcus Rickert <marcus.rickert@web.de>\n"
 "Language: de\n"
@@ -75,8 +75,8 @@ msgid "Master"
 msgstr "Master"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
-msgstr "Slave"
+msgid "Client"
+msgstr "Client"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
 msgid "Details"
@@ -86,9 +86,9 @@ msgstr "Details"
 msgid "Sub pattern must be longer than one character"
 msgstr "Teilmuster muss länger als ein Zeichen sein"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
-msgstr "Keine gültige Computer-Adresse"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
+msgstr "Keine gültige Computer-Adresse: {url}"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
 msgid "vacation"
@@ -321,11 +321,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "Benutzer '{username}' wird nicht überwacht"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr "Zugewiesene Benutzer"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr "Computername"
@@ -336,7 +336,7 @@ msgid "Username"
 msgstr "Benutzername"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -351,7 +351,7 @@ msgstr "Unbekannt"
 msgid "Locale"
 msgstr "Lokalisierung"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr "Geteilt mit"
 
@@ -488,7 +488,7 @@ msgstr "ersetzt"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Sichern"
 
@@ -529,18 +529,22 @@ msgstr "Stichproben- größe"
 msgid "Max Active Response Delay [ms]"
 msgstr "Max. Verzögerung der Antwort [ms]"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr "Blockierte URLs"
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr "Möchten Sie die Beobachtung des Geräts permanent beenden?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr "Beobachtung beenden"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr "Abbruch"
 
@@ -749,7 +753,7 @@ msgid "Edit ruleset details"
 msgstr "Ändere Details des Regelsatzes"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr "Regelsatz entfernen"
 
@@ -778,23 +782,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr "Beobachtung dieses Geräts für den Benutzer beenden"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr "Blockierbar"
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr "Prozent"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr "Möchten Sie die Beobachtung des Benutzers permanent beenden?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr "Beobachtung beenden"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr "Möchten Sie den Regelsatz permanent entfernen?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -808,11 +816,13 @@ msgid ""
 "HREF=\"{changes_url}\">here</A> for changes. Download Debian package <a "
 "href=\"{download_url}\">here</a>."
 msgstr ""
-"Neue Version von LittleBrother für den"
-" Update-Kanal <CODE>{channel}</CODE> verfügbar:"
-" Version <CODE>{suggested_version}</CODE> in "
-"Revision <CODE>{suggested_revision}</CODE>. "
-"Siehe <A HREF=\"{changes_url}\">hier</A> für eine Liste der Änderungen. "
-"Debian-Paket <a href=\"{download_url}\">hier</a> "
-"herunterladen."
+"Neue Version von LittleBrother für den Update-Kanal "
+"<CODE>{channel}</CODE> verfügbar: Version "
+"<CODE>{suggested_version}</CODE> in Revision "
+"<CODE>{suggested_revision}</CODE>. Siehe <A "
+"HREF=\"{changes_url}\">hier</A> für eine Liste der Änderungen. Debian-"
+"Paket <a href=\"{download_url}\">hier</a> herunterladen."
+
+#~ msgid "Not a valid host address"
+#~ msgstr "Keine gültige Computer-Adresse"
 
diff --git a/little_brother/translations/es/LC_MESSAGES/messages.po b/little_brother/translations/es/LC_MESSAGES/messages.po
index a31ef69..eddb129 100644
--- a/little_brother/translations/es/LC_MESSAGES/messages.po
+++ b/little_brother/translations/es/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-14 21:34+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: es\n"
@@ -66,7 +66,7 @@ msgid "Master"
 msgstr "Usuario"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
+msgid "Client"
 msgstr "Asegura"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -77,8 +77,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -311,11 +311,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "nombre de usuario '{username}' no está observado"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -326,7 +326,7 @@ msgid "Username"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -341,7 +341,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -478,7 +478,7 @@ msgstr "sobrevira"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Asegura"
 
@@ -519,18 +519,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -741,7 +745,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -770,23 +774,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -853,3 +861,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/fi/LC_MESSAGES/messages.po b/little_brother/translations/fi/LC_MESSAGES/messages.po
index b8dcb6f..bb9c07f 100644
--- a/little_brother/translations/fi/LC_MESSAGES/messages.po
+++ b/little_brother/translations/fi/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-19 11:47+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: fi\n"
@@ -66,7 +66,7 @@ msgid "Master"
 msgstr "Käyttäjä"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
+msgid "Client"
 msgstr "Tallenna"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -77,8 +77,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -312,11 +312,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "Käyttäjätunnusta '{username}' ei tarkkailla"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -327,7 +327,7 @@ msgid "Username"
 msgstr "Käyttäjätunnus"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -342,7 +342,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -479,7 +479,7 @@ msgstr "override"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Tallenna"
 
@@ -520,18 +520,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -742,7 +746,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -771,23 +775,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -854,3 +862,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/fr/LC_MESSAGES/messages.po b/little_brother/translations/fr/LC_MESSAGES/messages.po
index 86dcf7c..7a6d419 100644
--- a/little_brother/translations/fr/LC_MESSAGES/messages.po
+++ b/little_brother/translations/fr/LC_MESSAGES/messages.po
@@ -6,7 +6,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version:  LittleBrother\n"
 "Report-Msgid-Bugs-To: little-brother@web.de\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2021-02-21 10:30+0100\n"
 "Last-Translator: albano battistella <albano_battistella@hotmail.com>\n"
 "Language: fr\n"
@@ -65,8 +65,8 @@ msgid "Master"
 msgstr "Master"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
-msgstr "Slave"
+msgid "Client"
+msgstr "Client"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
 msgid "Details"
@@ -76,9 +76,9 @@ msgstr "Détails"
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
-msgstr "Pas une adresse hôte valide"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
+msgstr "Pas une adresse hôte valide: {url}"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
 msgid "vacation"
@@ -317,11 +317,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "le nom d'utilisateur '{username}' n'est pas surveillé"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr "Utilisateurs attribués"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr "Nom Hôte"
@@ -332,7 +332,7 @@ msgid "Username"
 msgstr "Nom d'utilisateur"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -347,7 +347,7 @@ msgstr "Inconnu"
 msgid "Locale"
 msgstr "Local"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr "Partagé avec"
 
@@ -484,7 +484,7 @@ msgstr "réécriture"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Sauver"
 
@@ -525,20 +525,24 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr "Délai de réponse actif maximum [ms]"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 "Voulez-vous vraiment supprimer définitivement le dispositif de "
 "surveillance?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr "Retirer de la surveillance"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr "Annuler"
 
@@ -747,7 +751,7 @@ msgid "Edit ruleset details"
 msgstr "Modifier les détails de l'ensemble de règles"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr "Supprimer l'ensemble de règles"
 
@@ -776,25 +780,30 @@ msgid "Remove monitoring of this device for the user"
 msgstr "Supprimer la surveillance de cet dispositif pour l'utilisateur"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+#, fuzzy
+msgid "Blockable"
+msgstr "Local"
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr "Pour cent"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 "Voulez-vous vraiment supprimer définitivement l'utilisateur de la "
 "surveillance?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr "Retirer de la surveillance"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr "Voulez-vous vraiment supprimer définitivement l'ensemble de règles?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -863,3 +872,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr "Pas une adresse hôte valide"
+
diff --git a/little_brother/translations/hr/LC_MESSAGES/messages.po b/little_brother/translations/hr/LC_MESSAGES/messages.po
index 9999102..b2d07ad 100644
--- a/little_brother/translations/hr/LC_MESSAGES/messages.po
+++ b/little_brother/translations/hr/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-09-06 22:56+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: hr\n"
@@ -69,7 +69,7 @@ msgstr "Korisnik"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "Spremiti"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -80,8 +80,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -315,11 +315,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "Korisnik '{username}' se ne nadzire"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -330,7 +330,7 @@ msgid "Username"
 msgstr "Ime korisnika"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -345,7 +345,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -482,7 +482,7 @@ msgstr "prekoracenje"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Spremiti"
 
@@ -523,18 +523,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -745,7 +749,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -774,23 +778,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -833,3 +841,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/hu/LC_MESSAGES/messages.po b/little_brother/translations/hu/LC_MESSAGES/messages.po
index 6f5bd75..c98c956 100644
--- a/little_brother/translations/hu/LC_MESSAGES/messages.po
+++ b/little_brother/translations/hu/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-20 09:48+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: hu\n"
@@ -66,7 +66,7 @@ msgid "Master"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
+msgid "Client"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -77,8 +77,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -287,11 +287,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -302,7 +302,7 @@ msgid "Username"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -317,7 +317,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -454,7 +454,7 @@ msgstr ""
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr ""
 
@@ -495,18 +495,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -714,7 +718,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -743,23 +747,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -832,3 +840,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/it/LC_MESSAGES/messages.po b/little_brother/translations/it/LC_MESSAGES/messages.po
index b6bd9f7..8c145fa 100644
--- a/little_brother/translations/it/LC_MESSAGES/messages.po
+++ b/little_brother/translations/it/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2021-12-25 15:00+0100\n"
 "Last-Translator: albano battistella <albano_battistella@hotmail.com>\n"
 "Language: it\n"
@@ -66,8 +66,8 @@ msgid "Master"
 msgstr "Master"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
-msgstr "Slave"
+msgid "Client"
+msgstr "Client"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
 msgid "Details"
@@ -77,9 +77,9 @@ msgstr "Dettagli"
 msgid "Sub pattern must be longer than one character"
 msgstr "Il sotto-modello deve essere più lungo di un carattere"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
-msgstr "Non è un indirizzo host valido"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
+msgstr "Non è un indirizzo host valido: {url}"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
 msgid "vacation"
@@ -307,11 +307,11 @@ msgstr "parametro '{parameter_name}' con valore '{value}' ha un formato sbagliat
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "nome utente '{username}' non esiste o non viene monitorato"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr "Utenti assegnati"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr "Nome Host"
@@ -322,7 +322,7 @@ msgid "Username"
 msgstr "Nome Utente"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -337,7 +337,7 @@ msgstr "Sconosciuto"
 msgid "Locale"
 msgstr "Locale"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr "Condiviso con"
 
@@ -474,7 +474,7 @@ msgstr "annullare"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Salva"
 
@@ -515,18 +515,22 @@ msgstr "Misura di prova"
 msgid "Max Active Response Delay [ms]"
 msgstr "Ritardo risposta attiva massima [ms]"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr "Vuoi veramente rimuovere definitivamente il dispositivo dal monitoraggio?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr "Rimuovi dal monitoraggio"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr "Cancella"
 
@@ -735,7 +739,7 @@ msgid "Edit ruleset details"
 msgstr "Aggiungi dettaglio regola"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr "Rimuovi regola"
 
@@ -764,23 +768,28 @@ msgid "Remove monitoring of this device for the user"
 msgstr "Rimuovere il monitoraggio di questo dispositivo per l'utente"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+#, fuzzy
+msgid "Blockable"
+msgstr "Locale"
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr "Percentuale"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr "Vuoi veramente rimuovere permanentemente l'utente dal monitoraggio?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr "Rimuovi dal monitoraggio"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr "Vuoi veramente rimuovere definitivamente il set di regole?"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -852,3 +861,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr "Non è un indirizzo host valido"
+
diff --git a/little_brother/translations/ja/LC_MESSAGES/messages.po b/little_brother/translations/ja/LC_MESSAGES/messages.po
index 5d133df..42d5940 100644
--- a/little_brother/translations/ja/LC_MESSAGES/messages.po
+++ b/little_brother/translations/ja/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-20 09:49+0200\n"
 "Last-Translator: Arik Mahbub <arikmahbub@gmail.com>\n"
 "Language: ja\n"
@@ -68,7 +68,7 @@ msgstr "ユーザー"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "セーブ"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -79,8 +79,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -302,11 +302,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "ユーザー名 '{username}' は監視されていません"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -317,7 +317,7 @@ msgid "Username"
 msgstr "ユーザー名"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -332,7 +332,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -469,7 +469,7 @@ msgstr "オーバーライド"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "セーブ"
 
@@ -510,18 +510,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -732,7 +736,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -761,23 +765,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -844,3 +852,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/lt/LC_MESSAGES/messages.po b/little_brother/translations/lt/LC_MESSAGES/messages.po
index 7176d76..aa8e893 100644
--- a/little_brother/translations/lt/LC_MESSAGES/messages.po
+++ b/little_brother/translations/lt/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-20 09:48+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: lt\n"
@@ -67,7 +67,7 @@ msgid "Master"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
+msgid "Client"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -78,8 +78,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -288,11 +288,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -303,7 +303,7 @@ msgid "Username"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -318,7 +318,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -455,7 +455,7 @@ msgstr ""
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr ""
 
@@ -496,18 +496,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -715,7 +719,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -744,23 +748,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -833,3 +841,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/nl/LC_MESSAGES/messages.po b/little_brother/translations/nl/LC_MESSAGES/messages.po
index 5a907f7..d29cae0 100644
--- a/little_brother/translations/nl/LC_MESSAGES/messages.po
+++ b/little_brother/translations/nl/LC_MESSAGES/messages.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LittleBrother 0.2\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-14 20:21+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: nl\n"
@@ -69,7 +69,7 @@ msgstr "Gebruikers"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "Opslaan"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -80,8 +80,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -298,11 +298,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "gebruikersnaam '{username}' niet gecontroleerd worden"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -313,7 +313,7 @@ msgid "Username"
 msgstr "Gebruikersnaam"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -328,7 +328,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -465,7 +465,7 @@ msgstr "Zet uit"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Opslaan"
 
@@ -506,18 +506,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -728,7 +732,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -757,23 +761,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -840,3 +848,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/ru/LC_MESSAGES/messages.po b/little_brother/translations/ru/LC_MESSAGES/messages.po
index ab13013..9495a84 100644
--- a/little_brother/translations/ru/LC_MESSAGES/messages.po
+++ b/little_brother/translations/ru/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version:  LittleBrother\n"
 "Report-Msgid-Bugs-To: little-brother@web.de\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-20 09:53+0200\n"
 "Last-Translator: J. Moldawski\n"
 "Language: ru\n"
@@ -69,7 +69,7 @@ msgstr "Пользователь"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "Сохранить"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -80,8 +80,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -318,11 +318,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "Пользователь '{username}' не отслеживается"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -333,7 +333,7 @@ msgid "Username"
 msgstr "Имя пользователя"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -348,7 +348,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -485,7 +485,7 @@ msgstr "изменение"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Сохранить"
 
@@ -526,18 +526,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -748,7 +752,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -777,23 +781,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -860,3 +868,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/sr/LC_MESSAGES/messages.po b/little_brother/translations/sr/LC_MESSAGES/messages.po
index ab84116..5e74845 100644
--- a/little_brother/translations/sr/LC_MESSAGES/messages.po
+++ b/little_brother/translations/sr/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-21 00:21+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: sr\n"
@@ -67,7 +67,7 @@ msgid "Master"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
-msgid "Slave"
+msgid "Client"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -78,8 +78,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -288,11 +288,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -303,7 +303,7 @@ msgid "Username"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -318,7 +318,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -455,7 +455,7 @@ msgstr ""
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr ""
 
@@ -496,18 +496,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -715,7 +719,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -744,23 +748,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -833,3 +841,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/th/LC_MESSAGES/messages.po b/little_brother/translations/th/LC_MESSAGES/messages.po
index 78b5bf9..3d90429 100644
--- a/little_brother/translations/th/LC_MESSAGES/messages.po
+++ b/little_brother/translations/th/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-19 23:19+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: th\n"
@@ -68,7 +68,7 @@ msgstr "ผู้ใช้"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "บันทึก"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -79,8 +79,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -313,11 +313,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "ชื่อผู้ใช้'{username}'ไม่ถูกเฝ้าระวัง"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -328,7 +328,7 @@ msgid "Username"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -343,7 +343,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -480,7 +480,7 @@ msgstr "ยกเลิก"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "บันทึก"
 
@@ -521,18 +521,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -743,7 +747,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -772,23 +776,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -855,3 +863,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/translations/tr/LC_MESSAGES/messages.po b/little_brother/translations/tr/LC_MESSAGES/messages.po
index c382320..9d6baf7 100644
--- a/little_brother/translations/tr/LC_MESSAGES/messages.po
+++ b/little_brother/translations/tr/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2022-01-30 21:24+0100\n"
+"POT-Creation-Date: 2022-06-19 18:18+0200\n"
 "PO-Revision-Date: 2020-04-19 23:19+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: tr\n"
@@ -68,7 +68,7 @@ msgstr "Kullanıcı"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/client_info.py:68
 #, fuzzy
-msgid "Slave"
+msgid "Client"
 msgstr "Kaydet"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/context_rule_handler.py:62
@@ -79,8 +79,8 @@ msgstr ""
 msgid "Sub pattern must be longer than one character"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:70
-msgid "Not a valid host address"
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/entity_forms.py:74
+msgid "Not a valid host address: {url}"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/german_vacation_context_rule_handler.py:33
@@ -313,11 +313,11 @@ msgstr ""
 msgid "username '{username}' does not exist or is not being monitored"
 msgstr "kullanıcı adı '{username}' gözetlenmiyor"
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:74
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:87
 msgid "Assigned users"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:76
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_device.py:89
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:87
 msgid "Host Name"
 msgstr ""
@@ -328,7 +328,7 @@ msgid "Username"
 msgstr "Kullanıcı adı"
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user.py:192
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:48
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:49
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:101
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:294
 msgid "Monitored"
@@ -343,7 +343,7 @@ msgstr ""
 msgid "Locale"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:52
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/persistence/persistent_user_2_device.py:53
 msgid "Shared with"
 msgstr ""
 
@@ -480,7 +480,7 @@ msgstr "Geçersiz kıl"
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:80
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:106
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:198
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:303
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:305
 msgid "Save"
 msgstr "Kaydet"
 
@@ -521,18 +521,22 @@ msgstr ""
 msgid "Max Active Response Delay [ms]"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:99
+msgid "Blocked URLs"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Do you really want to permanently remove the device from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
 msgid "Remove from Monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:132
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/devices.template.html:140
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Cancel"
 msgstr ""
 
@@ -743,7 +747,7 @@ msgid "Edit ruleset details"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:182
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Remove ruleset"
 msgstr ""
 
@@ -772,23 +776,27 @@ msgid "Remove monitoring of this device for the user"
 msgstr ""
 
 #: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:295
+msgid "Blockable"
+msgstr ""
+
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:296
 msgid "Percent"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
 msgid "Do you really want to permanently remove the user from monitoring?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:319
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:321
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid "Remove from monitoring"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:324
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:326
 msgid "Do you really want to permanently remove the ruleset?"
 msgstr ""
 
-#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:329
+#: /home/mr/projects/LittleBrotherGitHub/little_brother/templates/users.template.html:331
 msgid ""
 "Do you really want to permanently remove monitoring of the device for "
 "this user?"
@@ -855,3 +863,6 @@ msgstr ""
 #~ "href=\"{download_url}\">here</a>."
 #~ msgstr ""
 
+#~ msgid "Not a valid host address"
+#~ msgstr ""
+
diff --git a/little_brother/user_manager.py b/little_brother/user_manager.py
index 725d499..ffec586 100644
--- a/little_brother/user_manager.py
+++ b/little_brother/user_manager.py
@@ -151,7 +151,7 @@ def queue_event_update_login_mapping(self, p_hostname, p_login_mapping):
             p_payload=p_login_mapping)
         self.event_handler.queue_event(p_event=event, p_is_action=True)
 
-    def send_login_mapping_to_slave(self, p_hostname):
+    def send_login_mapping_to_client(self, p_hostname):
 
         self.queue_event_update_login_mapping(p_hostname=p_hostname,
                                               p_login_mapping=self._login_mapping.to_json())
diff --git a/little_brother/web/users_view_handler.py b/little_brother/web/users_view_handler.py
index 5fbbe59..5ffc8e0 100644
--- a/little_brother/web/users_view_handler.py
+++ b/little_brother/web/users_view_handler.py
@@ -124,7 +124,7 @@ def handle_button_press_for_user(self, p_forms, p_session_context, p_submit_id,
                 p_session_context=p_session_context, p_username=p_user.username)
             self.persistence.clear_cache()
             self.user_manager.reset_users(p_session_context=p_session_context)
-            self.app_control.send_config_to_all_slaves()
+            self.app_control.send_config_to_all_clients()
             self.app_control.reset_process_patterns()
 
         elif p_submit_id == p_user.new_ruleset_html_key:
@@ -256,7 +256,7 @@ def save_users_data(self, p_users, p_forms):
             if changed:
                 session.commit()
                 self._persistence.clear_cache()
-                self.app_control.send_config_to_all_slaves()
+                self.app_control.send_config_to_all_clients()
                 self.user_manager.reset_users(p_session_context=session_context)
                 self.app_control.reset_process_patterns()
 
diff --git a/little_brother/web/web_server.py b/little_brother/web/web_server.py
index 05b75d4..3a19c24 100644
--- a/little_brother/web/web_server.py
+++ b/little_brother/web/web_server.py
@@ -27,6 +27,7 @@
 import little_brother
 from little_brother import app_control
 from little_brother import constants
+from little_brother import entity_forms
 from little_brother.api import api_view_handler
 from little_brother.persistence.persistent_dependency_injection_mix_in import PersistenceDependencyInjectionMixIn
 from little_brother.web.about_view_handler import AboutViewHandler
@@ -136,12 +137,14 @@ def __init__(self,
         self._app.jinja_env.filters['seconds_as_humanized_duration'] = self.format_seconds_as_humanized_duration
         self._app.jinja_env.filters['_base'] = self._base_gettext
 
-        self._babel = flask_babel.Babel(self._app)
-        self._babel.localeselector(self._locale_helper.locale_selector)
+        self._babel = flask_babel.Babel()
+        #self._babel.localeselector(self._locale_helper.locale_selector)
+        self._babel.init_app(app=self._app, locale_selector=self._locale_helper.locale_selector)
         gettext.bindtextdomain("messages", "little_brother/translations")
 
-    def invert(self, rel_font_size):
+        entity_forms.set_get_text(self.gettext)
 
+    def invert(self, rel_font_size):
         return str(int(1.0 / float(rel_font_size) * 10000.0))
 
     def format_datetime(self, value):
diff --git a/requirements.txt b/requirements.txt
index 643d3d6..947d50a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,21 +1,24 @@
-alembic>=1.7.5
-lagom>=1.3.0
-requests>=2.25.0
-psutil>=5.7.3
-python-dateutil>=2.8.1
-python-ldap>=3.3.1
-SQLAlchemy>=1.3.18
-PyMySQL>=0.10.1
-pytest>=6.2.1
-Flask>=1.1.2
-Flask-Login>=0.5.0
-Flask-Babel>=2.0.0
-Flask-Migrate>=2.5.3
-Flask-WTF>=0.14.3
+Flask-Babel>=3.1.0
+Flask-Migrate>=4.0.4
+Flask-WTF>=1.1.1
+Flask>=2.3.2
+PyMySQL>=1.1.0
+SQLAlchemy>=2.0.19
+WTForms>=3.0.1
+alembic>=1.11.1
+distro>=1.8.0
+flask-login>=0.6.2
+humanize>=4.7.0
+lagom>=2.4.1
+mockito>=1.4.0
+packaging>=23.1
+prometheus_client>=0.17.1
+psutil>=5.9.5
+pytest>=7.4.0
+python-dateutil>=2.8.2
+requests>=2.31.0
 secure>=0.3.0
-selenium>=3.141.0
-urllib3>=1.26.2
-prometheus_client>=0.9.0
-humanize>=3.2.0
-wtforms>=3.0.0a1 # not directly required, pinned by Snyk to avoid a vulnerability
-distro>=1.6.0
+selenium>=4.10.0
+urllib3>=2.0.4
+
+
diff --git a/setup.py b/setup.py
index fb339ab..bde1f13 100755
--- a/setup.py
+++ b/setup.py
@@ -36,10 +36,13 @@
 
     "packages": ['little_brother',
                  'little_brother.api',
+                 'little_brother.devices',
                  'little_brother.persistence',
                  'little_brother.web',
                  'little_brother.test',
                  'little_brother.test.persistence',
+                 'little_brother.test.pytests.devices',
+                 'little_brother.test.pytests',
                  'little_brother.test.web',
                  'little_brother.test.api',
                  ],
@@ -51,25 +54,30 @@
         "run_little_brother_test_suite.py",
     ],
     "long_description": "Tool to monitor login time of users on Debian hosts and terminate processes if usage times "
-                        "are exceeded.",
+                        "are exceeded. Note that this package is not meant as a simple install with PIP since it "
+                        "also requires additional work in the operating system (e.g. add a user, create directories, "
+                        "define a startup service). This is all done by the Debian package provided at "
+                        "https://sourceforge.net/projects/little-brother/ .",
 }
 
 extended_setup_params = {
 
     # Target version to be used to upgrade the database
-    "target_alembic_version": "9713cef84918",
+    "target_alembic_version": "ed5e0310d209",
 
     "docker_registry_user": "marcusrickert",
     # Docker image contexts to be built. The second entry of the tuple denotes if the resulting image is to be uploaded
     "docker_contexts": [ ('little-brother-base', False),
-                         #'docker/little-brother-master',
                          ('little-brother-client', True),
                          ('little-brother-ubuntu-base', False),
-                         # 'docker/little-brother-master',
                          ('little-brother-ubuntu-client', True),
+#                         ('little-brother-arch-linux-base', False),
+#                         ('little-brother-arch-linux-client', True),
+                         ('little-brother-alpine-client', True),
                          ],
 
     # additional setup configuration used by CI stages
+    "owasp": True,
 
     # technical name used for e.g. directories, PIP-package, and users
     "create_user": True,
@@ -79,21 +87,25 @@
     # "deploy_tmpfile_conf": True,
     "deploy_sudoers_file": True,
     "deploy_apparmor_file": True,
-    "contributing_setups": ["python_base_app", "some_flask_helpers"],
+    "contributing_setups": [
+        "python_base_app",
+        "some_flask_helpers",
+    ],
     "publish_debian_package": little_brother.settings.SOURCEFORGE_CHANNELS,
     "publish_docker_images": little_brother.settings.DOCKER_CHANNELS,
     "publish_latest_docker_image": little_brother.settings.RELEASE_BRANCH_NAME,
     "debian_extra_files": [
-        ("etc/slave.config", "etc/little-brother/slave.config"),
+        ("etc/client.config", "etc/little-brother/client.config"),
         ("etc/master.config", "etc/little-brother/master.config"),
     ],
     "debian_templates": [
         ("/etc/little-brother/master.config", "/etc/little-brother/little-brother.config")
     ],
     "build_pypi_package": True,
-    "publish_pypi_package": { little_brother.settings.RELEASE_BRANCH_NAME: ('PYPI_API_URL', 'PYPI_API_TOKEN', 'TEST_PYPI_API_USER'),
-                              little_brother.settings.MASTER_BRANCH_NAME: ('TEST_PYPI_API_URL', 'TEST_PYPI_API_TOKEN', 'TEST_PYPI_API_USER')
-                              },
+    "publish_pypi_package": {
+        little_brother.settings.RELEASE_BRANCH_NAME: ('PYPI_API_URL', 'PYPI_API_TOKEN', 'TEST_PYPI_API_USER'),
+        little_brother.settings.MASTER_BRANCH_NAME: ('TEST_PYPI_API_URL', 'TEST_PYPI_API_TOKEN', 'TEST_PYPI_API_USER')
+    },
     "generate_generic_install": True,
     "analyze": True,
     "analyze_extra_exclusions" : "vagrant/**",
diff --git a/vagrant/.gitignore b/vagrant/.gitignore
deleted file mode 100644
index 4456848..0000000
--- a/vagrant/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.settings
-.vagrant
diff --git a/vagrant/Vagrantfile b/vagrant/Vagrantfile
deleted file mode 100644
index 44fdf39..0000000
--- a/vagrant/Vagrantfile
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- mode: ruby -*-
-# vi: set ft=ruby :
-#
-# Copyright (C) 2019  Marcus Rickert
-#
-# See https://github.com/marcus67/little_brother
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
-VAGRANTFILE_API_VERSION = "2"
-
-# See https://stackoverflow.com/questions/26811089/vagrant-how-to-have-host-platform-specific-provisioning-steps
-module OS
-    def OS.windows
-        (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
-    end
-
-    def OS.mac
-        (/darwin/ =~ RUBY_PLATFORM) != nil
-    end
-
-    def OS.unix
-        !OS.windows
-    end
-
-    def OS.linux
-        OS.unix and not OS.mac
-    end
-end
-
-require './settings.rb'
-
-# Require the reboot plugin.
-#require './assets/vagrant-provision-reboot-plugin'
-
-
-# VM Settings
-VAGRANT_HOME_PARTITION_FILE="drives/home.dvi"
-
-Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
-
-  config.vm.box = VAGRANT_BOX
-  config.vm.box_version = VAGRANT_BOX_VERSION
-  config.ssh.insert_key = false
-
-  config.vm.provider :virtualbox do |vb,override|
-    vb.name = VM_NAME
-    vb.gui = true
-
-    vb.memory = VM_MEMORY
-    vb.cpus = VM_CPUS
-
-    # Siehe http://stackoverflow.com/questions/24231620/how-to-set-vagrant-virtualbox-video-memory
-    vb.customize ["modifyvm", :id, "--vram", VM_VRAM]
-
-    # enable shared clipboard and drag'n'drop
-    vb.customize ["modifyvm", :id, "--clipboard", "bidirectional"]
-    vb.customize ["modifyvm", :id, "--draganddrop", "bidirectional"]
-
-    override.vm.network "private_network", :type => 'dhcp', :adapter => 2
-  end
-
-  # Create groups and users "seth" and "jennifer" ein
-  config.vm.provision "shell", path: "assets/setup-users.sh"
-
-end
diff --git a/vagrant/assets/setup-groups-and-users.sh b/vagrant/assets/setup-groups-and-users.sh
deleted file mode 100644
index 9ed1142..0000000
--- a/vagrant/assets/setup-groups-and-users.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#! /bin/bash
-
-echo "Create users and groups..."
-for user in jennifer, seth; do
-    adduser --disabled-login --gecos "${user}, ACME, 123, 123, 123" ${user}
-done


Test patch:
diff --git a/little_brother/test/api/test_api_view_handler.py b/little_brother/test/api/test_api_view_handler.py
index 85c6b5a..c2f76cf 100644
--- a/little_brother/test/api/test_api_view_handler.py
+++ b/little_brother/test/api/test_api_view_handler.py
@@ -19,9 +19,10 @@
 #    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 import datetime
 import os
-import time
 import unittest
 
+import time
+
 # from little_brother import constants, dependency_injection
 from little_brother import dependency_injection, admin_event, constants
 from little_brother.admin_event import AdminEvent
@@ -64,7 +65,7 @@ def test_dummy_event(self):
 
         master_connector = MasterConnector(p_config=master_connector_config)
 
-        hostname = "SLAVE"
+        hostname = "CLIENT"
         process_name = "MY_PROCESS"
 
         event = AdminEvent(p_event_type=admin_event.EVENT_TYPE_DUMMY_1,
diff --git a/little_brother/test/api/test_master_connector.py b/little_brother/test/api/test_master_connector.py
index a37dfeb..fdc7136 100644
--- a/little_brother/test/api/test_master_connector.py
+++ b/little_brother/test/api/test_master_connector.py
@@ -53,11 +53,11 @@ def test_encode_and_decode(self):
         self.assertIsNotNone(received_message)
         self.assertEqual(3, len(received_message))
 
-        hostname, json_events, json_slave_stats = received_message
+        hostname, json_events, json_client_stats = received_message
 
         self.assertEqual(HOSTNAME, hostname)
         self.assertEqual(1, len(json_events))
-        self.assertEqual(1, len(json_slave_stats))
+        self.assertEqual(1, len(json_client_stats))
 
 
 if __name__ == "__main__":
diff --git a/little_brother/test/persistence/test_persistence.py b/little_brother/test/persistence/test_persistence.py
index 244cf8b..61b73ba 100644
--- a/little_brother/test/persistence/test_persistence.py
+++ b/little_brother/test/persistence/test_persistence.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -119,8 +119,6 @@ def test_get_create_table_session(self):
         self.assertIsNotNone(session2)
         self.assertEqual(session, session2)
 
-
-
     @staticmethod
     def create_pinfo(p_age_in_days, p_include_end_time=False):
 
diff --git a/little_brother/test/persistence/test_persistent_device.py b/little_brother/test/persistence/test_persistent_device.py
new file mode 100644
index 0000000..d17a09c
--- /dev/null
+++ b/little_brother/test/persistence/test_persistent_device.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+#    Copyright (C) 2019-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from little_brother import dependency_injection
+from little_brother.persistence.persistence import Persistence
+from little_brother.persistence.persistent_device import Device
+from little_brother.persistence.persistent_device_entity_manager import DeviceEntityManager
+from little_brother.persistence.session_context import SessionContext
+from little_brother.test.persistence import test_persistence
+from python_base_app import tools
+from python_base_app.test import base_test
+
+SPECIFIC_DNS_NAME = "welt.de"
+SPECIFIC_DNS_NAMES = [ "welt.de", "ikea.de" ]
+SPECIFIC_DNS_NAMES_STRING = "\n".join(SPECIFIC_DNS_NAMES)
+
+class TestDevice(base_test.BaseTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+
+    def setUp(self):
+        dependency_injection.reset()
+
+    def test_list_of_ip_addresses(self):
+
+        test_persistence.TestPersistence.create_dummy_persistence(self._logger)
+        dummy_persistence = dependency_injection.container[Persistence]
+
+        with SessionContext(p_persistence=dummy_persistence) as session_context:
+            device = Device()
+            device.populate_test_data(p_session_context=session_context)
+            device.blocked_urls = SPECIFIC_DNS_NAMES_STRING
+            session = session_context.get_session()
+            session.add(device)
+            session.commit()
+            new_id = device.id
+
+        with SessionContext(p_persistence=dummy_persistence) as session_context:
+            entity_manager = DeviceEntityManager()
+            saved_entity : Device = entity_manager.get_by_id(p_session_context=session_context, p_id=new_id)
+            self.assertIsNotNone(saved_entity)
+
+            ip_addresses = saved_entity.list_of_blocked_ip_addresses
+
+            for dns_name in SPECIFIC_DNS_NAMES:
+                ip_address = tools.get_ip_address_by_dns_name(dns_name)
+                self.assertIn(ip_address, ip_addresses)
+
+
+    def test_ip_address(self):
+
+        test_persistence.TestPersistence.create_dummy_persistence(self._logger)
+        dummy_persistence = dependency_injection.container[Persistence]
+
+        with SessionContext(p_persistence=dummy_persistence) as session_context:
+            device = Device()
+            device.populate_test_data(p_session_context=session_context)
+            device.hostname = SPECIFIC_DNS_NAME
+            session = session_context.get_session()
+            session.add(device)
+            session.commit()
+            new_id = device.id
+
+        with SessionContext(p_persistence=dummy_persistence) as session_context:
+            entity_manager = DeviceEntityManager()
+            saved_entity : Device = entity_manager.get_by_id(p_session_context=session_context, p_id=new_id)
+            self.assertIsNotNone(saved_entity)
+
+            self.assertEqual(tools.get_ip_address_by_dns_name(SPECIFIC_DNS_NAME), saved_entity.ip_address)
diff --git a/little_brother/test/persistence/test_suite.py b/little_brother/test/persistence/test_suite.py
index cf45e91..39ee370 100755
--- a/little_brother/test/persistence/test_suite.py
+++ b/little_brother/test/persistence/test_suite.py
@@ -21,10 +21,11 @@
 
 import unittest
 
-from little_brother.test.persistence import test_persistence
+from little_brother.test.persistence.test_persistence import TestPersistence
 from little_brother.test.persistence.test_persistent_admin_event_entity_manager import TestAdminEventEntityManager
 from little_brother.test.persistence.test_persistent_daily_user_status_entity_manager import \
     TestDailyUserStatusEntityManager
+from little_brother.test.persistence.test_persistent_device import TestDevice
 from little_brother.test.persistence.test_persistent_device_entity_manager import TestDeviceEntityManager
 from little_brother.test.persistence.test_persistent_process_info_entity_manager import TestProcessInfoEntityManager
 from little_brother.test.persistence.test_persistent_rule_override_entity_manager import TestRuleOverrideEntityManager
@@ -38,7 +39,7 @@
 
 def add_test_cases(p_test_suite, p_config_filename=None):
     base_test.add_tests_in_test_unit(
-        p_test_suite=p_test_suite, p_test_unit_class=test_persistence.TestPersistence,
+        p_test_suite=p_test_suite, p_test_unit_class=TestPersistence,
         p_config_filename=p_config_filename)
 
     base_test.add_tests_in_test_unit(
@@ -49,6 +50,10 @@ def add_test_cases(p_test_suite, p_config_filename=None):
         p_test_suite=p_test_suite, p_test_unit_class=TestDeviceEntityManager,
         p_config_filename=p_config_filename)
 
+    base_test.add_tests_in_test_unit(
+        p_test_suite=p_test_suite, p_test_unit_class=TestDevice,
+        p_config_filename=p_config_filename)
+
     base_test.add_tests_in_test_unit(
         p_test_suite=p_test_suite, p_test_unit_class=TestProcessInfoEntityManager,
         p_config_filename=p_config_filename)
diff --git a/little_brother/test/pytests/__init__.py b/little_brother/test/pytests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/little_brother/test/pytests/devices/__init__.py b/little_brother/test/pytests/devices/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/little_brother/test/pytests/devices/test_device_activation_manager.py b/little_brother/test/pytests/devices/test_device_activation_manager.py
new file mode 100644
index 0000000..714bceb
--- /dev/null
+++ b/little_brother/test/pytests/devices/test_device_activation_manager.py
@@ -0,0 +1,417 @@
+# -*- coding: utf-8 -*-
+
+#    Copyright (C) 2021-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+
+import pytest
+from mockito import when
+
+from little_brother import dependency_injection
+from little_brother.app_control_config_model import AppControlConfigModel
+from little_brother.devices.device_activation_manager import DeviceActivationManager
+from little_brother.devices.device_activation_manager_config_model import DeviceActivationManagerConfigModel
+from little_brother.devices.firewall_device_activation_handler import FirewallDeviceActivationHandler
+from little_brother.devices.firewall_entry import key
+from little_brother.devices.firewall_handler import FirewallHandler
+from little_brother.devices.firewall_handler_config_model import FirewallHandlerConfigModel, \
+    DEFAULT_IPTABLES_ADD_FORWARD_COMMAND_PATTERN, DEFAULT_COMMENT
+from little_brother.login_mapping import LoginMapping
+from little_brother.persistence.persistence import Persistence
+from little_brother.persistence.persistent_device import Device
+from little_brother.persistence.persistent_user import User
+from little_brother.persistence.persistent_user_2_device import User2Device
+from little_brother.persistence.session_context import SessionContext
+from little_brother.test.persistence.test_persistence import TestPersistence
+from little_brother.user_manager import UserManager
+from little_brother.user_status import UserStatus
+from python_base_app import log_handling, tools
+from python_base_app.configuration import ConfigurationException
+
+DEFAULT_SERVER_GROUP = "default-group"
+DEFAULT_TARGET_IP = "0.0.0.0"
+DEFAULT_SOURCE_IP = "192.1.0.254"
+
+DEFAULT_SPECIFIC_TARGET_IPS = [ "1.2.3.4", "4.3.2.1" ]
+
+
+@pytest.fixture
+def default_device_activation_manager_config():
+    config = DeviceActivationManagerConfigModel()
+    return config
+
+
+@pytest.fixture
+def logger():
+    return log_handling.get_logger("pytest-executor")
+
+
+def setup_function():
+    dependency_injection.reset()
+
+
+@pytest.fixture
+def dummy_persistence(logger):
+    TestPersistence.create_dummy_persistence(p_logger=logger, p_delete=True)
+    yield dependency_injection.container[Persistence]
+
+
+@pytest.fixture
+def user_manager_with_activity_forbidden() -> UserManager:
+    app_control_config = AppControlConfigModel()
+    login_mapping = LoginMapping()
+
+    user_status = UserStatus()
+    user_status.activity_allowed = False
+
+    with when(UserManager).get_current_user_status(...).thenReturn(user_status):
+        user_manager = UserManager(p_config=app_control_config, p_is_master=True, p_login_mapping=login_mapping,
+                                   p_server_group=DEFAULT_SERVER_GROUP)
+        dependency_injection.container[UserManager] = user_manager
+        yield user_manager
+
+
+@pytest.fixture
+def user_manager_with_activity_permitted() -> UserManager:
+    app_control_config = AppControlConfigModel()
+    login_mapping = LoginMapping()
+
+    user_status = UserStatus()
+    user_status.activity_allowed = True
+
+    with when(UserManager).get_current_user_status(...).thenReturn(user_status):
+        user_manager = UserManager(p_config=app_control_config, p_is_master=True, p_login_mapping=login_mapping,
+                                   p_server_group=DEFAULT_SERVER_GROUP)
+        dependency_injection.container[UserManager] = user_manager
+        yield user_manager
+
+
+@pytest.fixture
+def default_firewall_handler_config():
+    config = FirewallHandlerConfigModel()
+    config.target_ip = [DEFAULT_TARGET_IP]
+    return config
+
+@pytest.fixture
+def firewall_handler_config_with_several_ips():
+    config = FirewallHandlerConfigModel()
+    config.target_ip = DEFAULT_SPECIFIC_TARGET_IPS
+    return config
+
+
+@pytest.fixture
+def firewall_device_activation_handler(default_firewall_handler_config):
+    handler = FirewallDeviceActivationHandler()
+    firewall_handler = FirewallHandler(p_config=default_firewall_handler_config)
+    dependency_injection.container[FirewallHandler] = firewall_handler
+    return handler
+
+@pytest.fixture
+def firewall_device_activation_handler_with_several_ips(firewall_handler_config_with_several_ips):
+    handler = FirewallDeviceActivationHandler()
+    firewall_handler = FirewallHandler(p_config=firewall_handler_config_with_several_ips)
+    dependency_injection.container[FirewallHandler] = firewall_handler
+    return handler
+
+
+def populate_user_and_device(p_session_context : SessionContext, p_blocked_urls : list[str]=None):
+    session = p_session_context.get_session()
+    user = User()
+    session.add(user)
+    user.populate_test_data(p_session_context=p_session_context)
+    device = Device()
+    session.add(device)
+    device.populate_test_data(p_session_context=p_session_context)
+
+    if p_blocked_urls is not None:
+        device.blocked_urls = "\n".join(p_blocked_urls)
+
+    device.hostname = "localhost"
+    user2device = User2Device()
+    session.add(user2device)
+    user2device.user = user
+    user2device.device = device
+    user2device.active = True
+    user2device.blockable = True
+    session.commit()
+    return device
+
+
+def test_create_device_activation_manager(default_device_activation_manager_config):
+    manager = DeviceActivationManager(p_config=default_device_activation_manager_config)
+    assert manager is not None
+
+
+def test_get_recurring_task_without_handlers(default_device_activation_manager_config):
+    manager = DeviceActivationManager(p_config=default_device_activation_manager_config)
+
+    task = manager.get_recurring_task()
+    assert task is None
+
+
+def test_get_recurring_task_with_handlers(default_device_activation_manager_config):
+    manager = DeviceActivationManager(p_config=default_device_activation_manager_config)
+
+    handler = FirewallDeviceActivationHandler()
+    manager.add_handler(p_handler=handler)
+
+    task = manager.get_recurring_task()
+    assert task is not None
+    assert "check_device_activation_status" in task.name
+    assert task.interval == manager._config.check_interval
+
+
+def test_user_manager(default_device_activation_manager_config, user_manager_with_activity_forbidden):
+    manager = DeviceActivationManager(p_config=default_device_activation_manager_config)
+
+    local_user_manager = manager.user_manager
+    assert local_user_manager is not None
+    assert user_manager_with_activity_forbidden == local_user_manager
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_set_usage_permission_status_for_device_with_activity_forbidden(dummy_persistence,
+                                                                        user_manager_with_activity_forbidden,
+                                                                        default_device_activation_manager_config,
+                                                                        firewall_device_activation_handler):
+    with SessionContext(p_persistence=dummy_persistence) as session_context:
+
+        device = populate_user_and_device(p_session_context=session_context)
+
+        manager = DeviceActivationManager(default_device_activation_manager_config)
+
+        manager.add_handler(p_handler=firewall_device_activation_handler)
+        handler = dependency_injection.container[FirewallHandler]
+
+        try:
+
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+
+            entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=DEFAULT_TARGET_IP)
+            assert entry_key not in forward_entries
+
+            entries = handler.entries
+            number_of_entries = len(entries)
+
+            manager.check_device_activation_status()
+
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+            assert entry_key in forward_entries
+
+            assert len(entries) == number_of_entries + 1
+
+        finally:
+            manager.shutdown()
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_set_usage_permission_status_for_device_with_activity_forbidden_check_comment(
+        dummy_persistence,
+        user_manager_with_activity_forbidden,
+        default_device_activation_manager_config,
+        firewall_device_activation_handler):
+    with SessionContext(p_persistence=dummy_persistence) as session_context:
+
+        device = populate_user_and_device(p_session_context=session_context)
+
+        manager = DeviceActivationManager(default_device_activation_manager_config)
+
+        manager.add_handler(p_handler=firewall_device_activation_handler)
+        handler = dependency_injection.container[FirewallHandler]
+
+        try:
+            entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=DEFAULT_TARGET_IP)
+
+            manager.check_device_activation_status()
+
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+            assert entry_key in forward_entries
+
+            forward_entry = forward_entries.get(entry_key)
+
+            assert DEFAULT_COMMENT in forward_entry.comment
+
+        finally:
+            manager.shutdown()
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_set_usage_permission_status_for_device_with_activity_forbidden_several_ip_addresses(
+        dummy_persistence,
+        user_manager_with_activity_forbidden,
+        default_device_activation_manager_config,
+        firewall_device_activation_handler_with_several_ips):
+    with SessionContext(p_persistence=dummy_persistence) as session_context:
+
+        device = populate_user_and_device(p_session_context=session_context)
+
+        firewall_device_activation_handler.target_ip = DEFAULT_SPECIFIC_TARGET_IPS
+
+        manager = DeviceActivationManager(default_device_activation_manager_config)
+
+        manager.add_handler(p_handler=firewall_device_activation_handler_with_several_ips)
+        handler = dependency_injection.container[FirewallHandler]
+
+        try:
+
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+
+            for ip_address in DEFAULT_SPECIFIC_TARGET_IPS:
+                entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=ip_address)
+                assert entry_key not in forward_entries
+
+            entries = handler.entries
+            number_of_entries = len(entries)
+
+            manager.check_device_activation_status()
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+
+            entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=DEFAULT_TARGET_IP)
+            assert entry_key not in forward_entries
+
+            for ip_address in DEFAULT_SPECIFIC_TARGET_IPS:
+                entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=ip_address)
+                assert entry_key in forward_entries
+
+            assert len(entries) == number_of_entries + len(DEFAULT_SPECIFIC_TARGET_IPS)
+
+        finally:
+            manager.shutdown()
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_set_usage_permission_status_for_device_with_activity_forbidden_device_ip_addresses_override(
+        dummy_persistence,
+        user_manager_with_activity_forbidden,
+        default_device_activation_manager_config,
+        firewall_device_activation_handler):
+    with SessionContext(p_persistence=dummy_persistence) as session_context:
+
+        device = populate_user_and_device(p_session_context=session_context, p_blocked_urls=DEFAULT_SPECIFIC_TARGET_IPS)
+
+        firewall_device_activation_handler.target_ip = DEFAULT_SPECIFIC_TARGET_IPS
+
+        manager = DeviceActivationManager(default_device_activation_manager_config)
+
+        manager.add_handler(p_handler=firewall_device_activation_handler)
+        handler = dependency_injection.container[FirewallHandler]
+
+        try:
+
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+
+            for ip_address in DEFAULT_SPECIFIC_TARGET_IPS:
+                entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=ip_address)
+                assert entry_key not in forward_entries
+
+            entries = handler.entries
+            number_of_entries = len(entries)
+
+            manager.check_device_activation_status()
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+
+            entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=DEFAULT_TARGET_IP)
+            assert entry_key not in forward_entries
+
+            for ip_address in DEFAULT_SPECIFIC_TARGET_IPS:
+                entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=ip_address)
+                assert entry_key in forward_entries
+
+            assert len(entries) == number_of_entries + len(DEFAULT_SPECIFIC_TARGET_IPS)
+
+        finally:
+            manager.shutdown()
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_set_usage_permission_status_for_device_with_activity_allowed(dummy_persistence,
+                                                                      user_manager_with_activity_permitted,
+                                                                      default_device_activation_manager_config,
+                                                                      firewall_device_activation_handler):
+    with SessionContext(p_persistence=dummy_persistence) as session_context:
+
+        device = populate_user_and_device(p_session_context=session_context)
+
+        manager = DeviceActivationManager(default_device_activation_manager_config)
+
+        manager.add_handler(p_handler=firewall_device_activation_handler)
+        handler = dependency_injection.container[FirewallHandler]
+
+        try:
+
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+
+            entry_key = key(p_source=tools.get_ip_address_by_dns_name(device.hostname), p_destination=DEFAULT_TARGET_IP)
+            assert entry_key not in forward_entries
+
+            entries = handler.entries
+            number_of_entries = len(entries)
+
+            manager.check_device_activation_status()
+
+            forward_entries = handler.get_active_forward_entries(p_ip_address=device.hostname)
+            assert entry_key not in forward_entries
+
+            assert len(entries) == number_of_entries
+
+        finally:
+            manager.shutdown()
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_set_usage_permission_status_for_device_invalid_binary(dummy_persistence,
+                                                               user_manager_with_activity_forbidden,
+                                                               default_device_activation_manager_config,
+                                                               firewall_device_activation_handler):
+    with SessionContext(p_persistence=dummy_persistence) as session_context:
+
+        manager = DeviceActivationManager(default_device_activation_manager_config)
+
+        populate_user_and_device(p_session_context=session_context)
+
+        manager.add_handler(p_handler=firewall_device_activation_handler)
+        handler = dependency_injection.container[FirewallHandler]
+        handler._config.iptables_add_forward_command_pattern = "x" + DEFAULT_IPTABLES_ADD_FORWARD_COMMAND_PATTERN
+
+        try:
+            with pytest.raises(ConfigurationException) as e:
+                manager.check_device_activation_status()
+
+            assert "returns exit code" in str(e)
+
+        finally:
+            manager.shutdown()
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_iptables_insert_entry_invalid_binary(default_firewall_handler_config):
+    handler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+
+    handler.read_forward_entries()
+
+    try:
+        handler._config.iptables_add_forward_command_pattern = "x" + DEFAULT_IPTABLES_ADD_FORWARD_COMMAND_PATTERN
+
+        with pytest.raises(ConfigurationException) as e:
+            handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                                p_usage_permitted=False)
+
+        assert "returns exit code" in str(e)
+
+    finally:
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=True)
diff --git a/little_brother/test/pytests/devices/test_device_activation_manager_config_model.py b/little_brother/test/pytests/devices/test_device_activation_manager_config_model.py
new file mode 100644
index 0000000..f88cb89
--- /dev/null
+++ b/little_brother/test/pytests/devices/test_device_activation_manager_config_model.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+#    Copyright (C) 2021-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from little_brother.devices.device_activation_manager_config_model import DeviceActivationManagerConfigModel
+
+def test_create_device_activation_manager_config_model():
+    config = DeviceActivationManagerConfigModel()
+    assert config is not None
+    assert config.check_interval is not None
diff --git a/little_brother/test/pytests/devices/test_firewall_device_activation_handler.py b/little_brother/test/pytests/devices/test_firewall_device_activation_handler.py
new file mode 100644
index 0000000..3e63fe9
--- /dev/null
+++ b/little_brother/test/pytests/devices/test_firewall_device_activation_handler.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+#    Copyright (C) 2021-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import pytest
+from mockito import patch
+
+from little_brother import dependency_injection
+from little_brother.devices.firewall_device_activation_handler import FirewallDeviceActivationHandler
+from little_brother.devices.firewall_handler import FirewallHandler
+from little_brother.devices.firewall_handler_config_model import FirewallHandlerConfigModel
+from little_brother.persistence.persistence import Persistence
+from little_brother.persistence.persistent_device import Device
+from little_brother.persistence.session_context import SessionContext
+from little_brother.test.persistence.test_persistence import TestPersistence
+from python_base_app import log_handling, tools
+
+DEFAULT_TARGET_IP = "8.8.8.8"
+DEFAULT_HOSTNAME = "localhost"
+
+
+class CallResult:
+
+    def __init__(self):
+        self.ip_address = None
+        self.usage_permitted = None
+        self.blocked_ip_addresses = []
+
+    def set(self, p_ip_address, p_blocked_ip_addresses, p_usage_permitted):
+        self.ip_address = p_ip_address
+        self.blocked_ip_addresses = p_blocked_ip_addresses
+        self.usage_permitted = p_usage_permitted
+
+
+@pytest.fixture
+def default_firewall_handler_config():
+    config = FirewallHandlerConfigModel()
+    config.target_ip = [DEFAULT_TARGET_IP]
+    return config
+
+
+@pytest.fixture
+def default_firewall_handler(default_firewall_handler_config):
+    handler = FirewallHandler(p_config=default_firewall_handler_config)
+    dependency_injection.container[FirewallHandler] = handler
+    return handler
+
+
+@pytest.fixture
+def patched_firewall_handler_test_result(default_firewall_handler_config):
+    handler = FirewallHandler(p_config=default_firewall_handler_config)
+    test_result = CallResult()
+    with patch(handler.set_usage_permission_for_ip,
+               lambda p_ip_address, p_blocked_ip_addresses, p_usage_permitted:
+               test_result.set(p_ip_address=p_ip_address,
+                               p_blocked_ip_addresses=p_blocked_ip_addresses,
+                               p_usage_permitted=p_usage_permitted)):
+        dependency_injection.container[FirewallHandler] = handler
+        yield test_result
+
+
+@pytest.fixture
+def logger():
+    return log_handling.get_logger("pytest-executor")
+
+
+def setup_function():
+    dependency_injection.reset()
+
+
+@pytest.fixture
+def dummy_persistence(logger):
+    TestPersistence.create_dummy_persistence(p_logger=logger, p_delete=True)
+    return dependency_injection.container[Persistence]
+
+
+def test_create_firewall_device_activation_handler(default_firewall_handler):
+    handler = FirewallDeviceActivationHandler()
+    assert handler is not None
+    assert handler.firewall_handler == default_firewall_handler
+
+
+def test_create_firewall_device_activation_handler_set_usage_permission_for_ip(
+        patched_firewall_handler_test_result, dummy_persistence):
+    with SessionContext(p_persistence=dummy_persistence) as session_context:
+        handler = FirewallDeviceActivationHandler()
+        assert handler is not None
+        device = Device()
+        device.populate_test_data(p_session_context=session_context)
+        device.hostname = DEFAULT_HOSTNAME
+        session = session_context.get_session()
+        session.add(device)
+        session.commit()
+
+        handler.set_usage_permission_for_device(p_device=device, p_usage_permitted=False)
+
+        assert patched_firewall_handler_test_result.ip_address == tools.get_ip_address_by_dns_name(
+            p_dns_name=DEFAULT_HOSTNAME)
+        assert not patched_firewall_handler_test_result.usage_permitted
+
+        device.hostname = tools.get_ip_address_by_dns_name(p_dns_name=DEFAULT_HOSTNAME)
+        session.commit()
+
+        handler.set_usage_permission_for_device(p_device=device, p_usage_permitted=True)
+
+        assert patched_firewall_handler_test_result.ip_address == device.hostname
+        assert patched_firewall_handler_test_result.usage_permitted
diff --git a/little_brother/test/pytests/devices/test_firewall_entry.py b/little_brother/test/pytests/devices/test_firewall_entry.py
new file mode 100644
index 0000000..e8d1185
--- /dev/null
+++ b/little_brother/test/pytests/devices/test_firewall_entry.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+#    Copyright (C) 2021-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from little_brother.devices.firewall_entry import FirewallEntry
+
+def test_create_firewall_entry():
+
+    assert FirewallEntry() is not None
diff --git a/little_brother/test/pytests/devices/test_firewall_handler.py b/little_brother/test/pytests/devices/test_firewall_handler.py
new file mode 100644
index 0000000..0631be3
--- /dev/null
+++ b/little_brother/test/pytests/devices/test_firewall_handler.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+
+#    Copyright (C) 2021-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import time
+
+import pytest
+
+from little_brother.devices.firewall_entry import key, FirewallEntry
+from little_brother.devices.firewall_handler import FirewallHandler
+from little_brother.devices.firewall_handler_config_model import FirewallHandlerConfigModel, \
+    DEFAULT_IPTABLES_ADD_FORWARD_COMMAND_PATTERN, DEFAULT_IPTABLES_REMOVE_FORWARD_COMMAND_PATTERN, \
+    DEFAULT_IPTABLES_LIST_FORWARD_COMMAND
+from python_base_app.configuration import ConfigurationException
+
+DEFAULT_SOURCE_IP = "192.1.0.254"
+DEFAULT_SOURCE_IP_2 = "192.1.0.253"
+DEFAULT_TARGET_IP = "8.8.8.8"
+
+
+@pytest.fixture
+def default_firewall_handler_config():
+    config = FirewallHandlerConfigModel()
+    config.target_ip = [DEFAULT_TARGET_IP]
+    return config
+
+
+def test_create_firewall_handler(default_firewall_handler_config):
+    handler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+    assert handler._config.target_ip is not None
+    assert len(handler._config.target_ip) == 1
+    assert handler._config.target_ip[0] == DEFAULT_TARGET_IP
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_iptables_read_table(default_firewall_handler_config):
+    handler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+
+    handler.read_forward_entries()
+    assert handler._entries is not None
+
+    entry_key = key(p_source=DEFAULT_SOURCE_IP, p_destination=DEFAULT_TARGET_IP)
+    assert entry_key not in handler._entries
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_iptables_insert_and_delete_entry(default_firewall_handler_config):
+    handler: FirewallHandler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+
+    handler.read_forward_entries()
+    assert handler._entries is not None
+
+    entries = handler._entries
+
+    number_of_entries = len(entries)
+
+    forward_entries = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP)
+
+    try:
+        entry_key = key(p_source=DEFAULT_SOURCE_IP, p_destination=DEFAULT_TARGET_IP)
+        assert entry_key not in forward_entries
+
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=False)
+        forward_entries = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP)
+        assert entry_key in forward_entries
+
+        assert len(entries) == number_of_entries + 1
+
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP_2, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=False)
+        assert entry_key in forward_entries
+        assert len(entries) == number_of_entries + 2
+
+        entry_key_2 = key(p_source=DEFAULT_SOURCE_IP_2, p_destination=DEFAULT_TARGET_IP)
+        forward_entries = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP)
+        forward_entries_2 = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP_2)
+        assert entry_key in forward_entries
+        assert entry_key_2 in forward_entries_2
+
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=True)
+        forward_entries = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP)
+        forward_entries_2 = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP_2)
+        assert entry_key not in forward_entries
+        assert entry_key_2 in forward_entries_2
+        assert len(entries) == number_of_entries + 1
+
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP_2, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=True)
+        forward_entries = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP)
+        forward_entries_2 = handler.get_active_forward_entries(p_ip_address=DEFAULT_SOURCE_IP_2)
+        assert entry_key not in forward_entries
+        assert entry_key_2 not in forward_entries_2
+        assert len(entries) == number_of_entries
+
+    finally:
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=True)
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP_2, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=True)
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_iptables_insert_entry_invalid_binary(default_firewall_handler_config):
+    handler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+
+    handler.read_forward_entries()
+
+    try:
+        handler._config.iptables_add_forward_command_pattern = "x" + DEFAULT_IPTABLES_ADD_FORWARD_COMMAND_PATTERN
+
+        with pytest.raises(ConfigurationException) as e:
+            handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                                p_usage_permitted=False)
+
+        assert "returns exit code" in str(e)
+
+    finally:
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=True)
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_iptables_remove_entry_invalid_binary(default_firewall_handler_config):
+    handler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+
+    handler.read_forward_entries()
+    old_pattern = handler._config.iptables_remove_forward_command_pattern
+
+    try:
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=False)
+
+        handler._config.iptables_remove_forward_command_pattern = "x" + DEFAULT_IPTABLES_REMOVE_FORWARD_COMMAND_PATTERN
+
+        with pytest.raises(ConfigurationException) as e:
+            handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                                p_usage_permitted=True)
+
+        assert "returns exit code" in str(e)
+
+    finally:
+        handler._config.iptables_remove_forward_command_pattern = old_pattern
+        handler.set_usage_permission_for_ip(p_ip_address=DEFAULT_SOURCE_IP, p_blocked_ip_addresses=[],
+                                            p_usage_permitted=True)
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_iptables_list_entries_invalid_binary(default_firewall_handler_config):
+    handler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+
+    handler._config.iptables_list_forward_command = "x" + DEFAULT_IPTABLES_LIST_FORWARD_COMMAND
+
+    with pytest.raises(ConfigurationException) as e:
+        handler.read_forward_entries()
+
+    assert "returns exit code" in str(e)
+
+
+@pytest.mark.skipif(os.getenv("NO_IPTABLES"), reason="no iptables allowed")
+def test_iptables_list_entries_with_cache_timeout(default_firewall_handler_config):
+    default_firewall_handler_config.cache_ttl = 1
+    handler = FirewallHandler(default_firewall_handler_config)
+    assert handler is not None
+
+    entries = handler.entries
+
+    assert id(entries) == id(handler.entries)
+
+    entries.append(FirewallEntry())
+
+    assert id(entries) == id(handler.entries)
+
+    time.sleep(default_firewall_handler_config.cache_ttl + 1)
+
+    assert id(entries) != id(handler.entries)
diff --git a/little_brother/test/pytests/devices/test_firewall_handler_config_model.py b/little_brother/test/pytests/devices/test_firewall_handler_config_model.py
new file mode 100644
index 0000000..09304a0
--- /dev/null
+++ b/little_brother/test/pytests/devices/test_firewall_handler_config_model.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+#    Copyright (C) 2021-2022  Marcus Rickert
+#
+#    See https://github.com/marcus67/little_brother
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from little_brother.devices.firewall_handler_config_model import FirewallHandlerConfigModel
+
+DEFAULT_TARGET_IP = "8.8.8.8"
+
+
+def test_create_firewall_handler_config_model():
+    config = FirewallHandlerConfigModel()
+    assert config is not None
+    assert not config.is_active()
+
+
+def test_create_active_firewall_handler_config_model():
+    config = FirewallHandlerConfigModel()
+    assert config is not None
+    config.target_ip = DEFAULT_TARGET_IP
+    assert config.is_active()
diff --git a/little_brother/test/test_app.py b/little_brother/test/test_app.py
index b998d49..eb79921 100644
--- a/little_brother/test/test_app.py
+++ b/little_brother/test/test_app.py
@@ -20,9 +20,10 @@
 
 import os
 import os.path
-import sys
 import unittest
 
+import sys
+
 from little_brother import dependency_injection
 from little_brother.api.master_connector import MasterConnector
 from little_brother.app import App, ProcessIteratorFactory, APP_NAME, get_argument_parser
@@ -76,7 +77,8 @@ def test_prepare_configuration(self):
 
         self.assertIsNotNone(config)
 
-        self.assertEqual(14, len(configuration._sections))
+        self.assertEqual(15, len(configuration._sections))
+        self.assertEqual(1, len(configuration._optional_section_handler_definitions))
 
     @classmethod
     def create_dummy_app(cls, p_logger):
diff --git a/little_brother/test/test_app_control.py b/little_brother/test/test_app_control.py
index ef7926f..45dbaa0 100644
--- a/little_brother/test/test_app_control.py
+++ b/little_brother/test/test_app_control.py
@@ -98,7 +98,7 @@ def test_retrieve_user_mappings(self):
 
         user_manager.retrieve_user_mappings()
 
-    def test_is_slave(self):
+    def test_is_client(self):
         mc_config = master_connector.MasterConnectorConfigModel()
         mc_config.host_url = "htt" + "p://master.domain/"
         config = app_control.AppControlConfigModel()
diff --git a/little_brother/test/test_client_device_handler.py b/little_brother/test/test_client_device_handler.py
index febf8f4..8b7572e 100644
--- a/little_brother/test/test_client_device_handler.py
+++ b/little_brother/test/test_client_device_handler.py
@@ -41,7 +41,7 @@ def setUp(self):
     def check_list_has_n_elements(self, p_list, p_n):
         self.assertIsNotNone(p_list)
         self.assertIsInstance(p_list, list)
-        self.assertEqual(len(p_list), p_n)
+        self.assertEqual(p_n, len(p_list))
 
     @base_test.skip_if_env("NO_PING")
     def test_existing_host(self):
diff --git a/little_brother/test/test_client_info.py b/little_brother/test/test_client_info.py
index e815fa4..c595ba7 100644
--- a/little_brother/test/test_client_info.py
+++ b/little_brother/test/test_client_info.py
@@ -58,7 +58,7 @@ def test_property_node_type(self):
 
         ci = client_info.ClientInfo(p_is_master=False, p_host_name=HOSTNAME, p_client_stats=None)
 
-        self.assertEqual(ci.node_type, "Slave")
+        self.assertEqual(ci.node_type, "Client")
 
     def test_property_seconds_without_ping(self):
 
@@ -117,7 +117,7 @@ def test_property_version_class(self):
         ci = client_info.ClientInfo(p_is_master=True, p_host_name=HOSTNAME, p_client_stats=None,
                                     p_master_version=MASTER_VERSION)
 
-        self.assertEqual(ci.version_class, client_info.CSS_CLASS_SLAVE_VERSION_OUTDATED)
+        self.assertEqual(ci.version_class, client_info.CSS_CLASS_CLIENT_VERSION_OUTDATED)
 
         ci.client_stats = ClientStats(p_version=MASTER_VERSION)
 
@@ -125,7 +125,7 @@ def test_property_version_class(self):
 
         ci.client_stats = ClientStats(p_version=OLD_CLIENT_VERSION)
 
-        self.assertEqual(ci.version_class, client_info.CSS_CLASS_SLAVE_VERSION_OUTDATED)
+        self.assertEqual(ci.version_class, client_info.CSS_CLASS_CLIENT_VERSION_OUTDATED)
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/little_brother/test/test_pytest.py b/little_brother/test/test_pytest.py
new file mode 100644
index 0000000..2525ebe
--- /dev/null
+++ b/little_brother/test/test_pytest.py
@@ -0,0 +1,27 @@
+#    Copyright (C) 2019  Marcus Rickert
+#
+#    See https://github.com/marcus67/python_base_app
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import os
+
+from python_base_app.test import base_test
+
+
+class TestPytest(base_test.BaseTestCase):
+
+    def test_pytest(self):
+        base_dir = os.path.dirname(__file__)
+        self.execute_pytest(p_base_dir=base_dir)
diff --git a/little_brother/test/test_suite.py b/little_brother/test/test_suite.py
index 0f8158b..55ad4ef 100755
--- a/little_brother/test/test_suite.py
+++ b/little_brother/test/test_suite.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -20,7 +20,7 @@
 
 import unittest
 
-from little_brother.test import test_app, test_client_info
+from little_brother.test import test_app, test_client_info, test_pytest
 from little_brother.test import test_app_control
 from little_brother.test import test_client_device_handler
 from little_brother.test import test_client_process_handler
@@ -42,6 +42,10 @@
 
 
 def add_test_cases(p_test_suite, p_config_filename=None):
+    base_test.add_tests_in_test_unit(
+        p_test_suite=p_test_suite,
+        p_test_unit_class=test_pytest.TestPytest, p_config_filename=p_config_filename)
+
     base_test.add_tests_in_test_unit(
         p_test_suite=p_test_suite,
         p_test_unit_class=test_process_info.TestProcessInfo, p_config_filename=p_config_filename)
diff --git a/little_brother/test/web/base_test_status_server.py b/little_brother/test/web/base_test_status_server.py
index 083021c..08814e8 100644
--- a/little_brother/test/web/base_test_status_server.py
+++ b/little_brother/test/web/base_test_status_server.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -23,6 +23,7 @@
 import unittest
 
 import selenium
+from selenium.webdriver.common.by import By
 from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.remote.webelement import WebElement
 
@@ -48,6 +49,7 @@
 from little_brother.web import web_server
 from python_base_app import locale_helper
 from python_base_app.base_user_handler import BaseUserHandler
+from python_base_app.configuration import ConfigurationException
 from python_base_app.test import base_test
 from python_base_app.test import test_unix_user_handler
 
@@ -159,7 +161,7 @@ def create_selenium_driver(self):
             self._driver = selenium.webdriver.Chrome(options=options)
 
         else:
-            self._driver = selenium.webdriver.PhantomJS()
+            raise ConfigurationException("No valid Selenium driver selected! Use SELENIUM_CHROME_DRIVER=1.")
 
     def create_status_server_using_ruleset_configs(self, p_ruleset_configs):
 
@@ -213,17 +215,17 @@ def login_admin(self):
         assert "Administration" in self._driver.title
 
     def check_empty_user_list(self):
-        self._driver.find_element_by_xpath(XPATH_EMPTY_USER_LIST)
+        self._driver.find_element(By.XPATH, XPATH_EMPTY_USER_LIST)
 
     def check_empty_device_list(self):
-        self._driver.find_element_by_xpath(XPATH_EMPTY_DEVICE_LIST)
+        self._driver.find_element(By.XPATH, XPATH_EMPTY_DEVICE_LIST)
 
     def login(self):
-        elem = self._driver.find_element_by_name("username")
+        elem = self._driver.find_element(By.NAME, "username")
         elem.clear()
         elem.send_keys(test_unix_user_handler.ADMIN_USER)
 
-        elem = self._driver.find_element_by_name("password")
+        elem = self._driver.find_element(By.NAME, "password")
         elem.clear()
         elem.send_keys(test_unix_user_handler.ADMIN_PASSWORD)
         elem.send_keys(Keys.RETURN)
@@ -249,7 +251,7 @@ def add_new_user(self, p_user_entity_manager: UserEntityManager) -> int:
         user.active = True
         session.commit()
 
-        user_manager : UserManager = dependency_injection.container[UserManager]
+        user_manager: UserManager = dependency_injection.container[UserManager]
 
         user_manager.add_monitored_user(p_username=self.get_new_user_name())
         user_manager.retrieve_user_mappings()
diff --git a/little_brother/test/web/test_status_server.py b/little_brother/test/web/test_status_server.py
index dd98154..ae49331 100644
--- a/little_brother/test/web/test_status_server.py
+++ b/little_brother/test/web/test_status_server.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
diff --git a/little_brother/test/web/test_status_server_about.py b/little_brother/test/web/test_status_server_about.py
index e087aea..9471471 100644
--- a/little_brother/test/web/test_status_server_about.py
+++ b/little_brother/test/web/test_status_server_about.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -20,6 +20,8 @@
 
 import unittest
 
+from selenium.webdriver.common.by import By
+
 from little_brother import constants
 from little_brother import settings
 from little_brother.test.web.base_test_status_server import BaseTestStatusServer
@@ -39,11 +41,12 @@ def test_page_about(self):
         assert "About" in self._driver.title
 
         xpath = "//DIV[DIV[1] = 'Version' and DIV[2] = '{version}']"
-        self._driver.find_element_by_xpath(xpath.format(version=settings.settings['version']))
+        self._driver.find_element(By.XPATH, xpath.format(version=settings.settings['version']))
 
         xpath = "//DIV[DIV[1] = 'Debian Package Revision' and DIV[2] = '{debian_package_revision}']"
-        self._driver.find_element_by_xpath(
-            xpath.format(debian_package_revision=settings.extended_settings['debian_package_revision']))
+        self._driver.find_element(By.XPATH,
+                                  xpath.format(
+                                      debian_package_revision=settings.extended_settings['debian_package_revision']))
 
 
 if __name__ == "__main__":
diff --git a/little_brother/test/web/test_status_server_admin.py b/little_brother/test/web/test_status_server_admin.py
index 1eb2b0d..1a869b3 100644
--- a/little_brother/test/web/test_status_server_admin.py
+++ b/little_brother/test/web/test_status_server_admin.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -21,6 +21,8 @@
 import unittest
 from typing import List
 
+from selenium.webdriver.common.by import By
+
 from little_brother import constants
 from little_brother import dependency_injection
 from little_brother.persistence.persistent_rule_override import RuleOverride
@@ -113,25 +115,25 @@ def test_page_admin_edit(self):
 
         elem_name_prefix = self.get_admin_elem_name_prefix(p_reference_date=reference_date)
 
-        elem = self._driver.find_element_by_id(elem_name_prefix + "min_time_of_day")
+        elem = self._driver.find_element(By.ID, elem_name_prefix + "min_time_of_day")
         self.set_value(p_value=NEW_MIN_TIME_OF_DAY, p_elem=elem)
 
-        elem = self._driver.find_element_by_id(elem_name_prefix + "max_time_of_day")
+        elem = self._driver.find_element(By.ID, elem_name_prefix + "max_time_of_day")
         self.set_value(p_value=NEW_MAX_TIME_OF_DAY, p_elem=elem)
 
-        elem = self._driver.find_element_by_id(elem_name_prefix + "max_time_per_day")
+        elem = self._driver.find_element(By.ID, elem_name_prefix + "max_time_per_day")
         self.set_value(p_value=NEW_MAX_TIME_PER_DAY, p_elem=elem)
 
-        elem = self._driver.find_element_by_id(elem_name_prefix + "max_activity_duration")
+        elem = self._driver.find_element(By.ID, elem_name_prefix + "max_activity_duration")
         self.set_value(p_value=NEW_MAX_ACTIVITY_DURATION, p_elem=elem)
 
-        elem = self._driver.find_element_by_id(elem_name_prefix + "min_break")
+        elem = self._driver.find_element(By.ID, elem_name_prefix + "min_break")
         self.set_value(p_value=NEW_MIN_BREAK, p_elem=elem)
 
-        elem = self._driver.find_element_by_id(elem_name_prefix + "free_play")
+        elem = self._driver.find_element(By.ID, elem_name_prefix + "free_play")
         self.click(elem)
 
-        save_button = self._driver.find_element_by_id("save")
+        save_button = self._driver.find_element(By.ID, "save")
         self.click(save_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -173,7 +175,7 @@ def test_page_add_time_extension(self):
         elem_name = "time_extension_{username}_{extension}".format(
             username=self.get_new_user_name(), extension=EXTENSION_IN_MINUTES)
 
-        elem = self._driver.find_element_by_id(elem_name)
+        elem = self._driver.find_element(By.ID, elem_name)
         self.click(elem)
 
         with SessionContext(self._persistence) as session_context:
@@ -212,7 +214,7 @@ def test_page_delete_time_extension(self):
 
         elem_name = "time_extension_{username}_0".format(username=self.get_new_user_name())
 
-        elem = self._driver.find_element_by_id(elem_name)
+        elem = self._driver.find_element(By.ID, elem_name)
         self.click(elem)
 
         with SessionContext(self._persistence) as session_context:
@@ -248,7 +250,7 @@ def test_page_extend_time_extension(self):
         elem_name = "time_extension_{username}_{extension}".format(username=self.get_new_user_name(),
                                                                    extension=SECOND_EXTENSION_IN_MINUTES)
 
-        elem = self._driver.find_element_by_id(elem_name)
+        elem = self._driver.find_element(By.ID, elem_name)
         self.click(elem)
 
         with SessionContext(self._persistence) as session_context:
@@ -281,14 +283,14 @@ def test_page_admin_edit_invalid_duration_format(self):
         elem_name_prefix = self.get_admin_elem_name_prefix(p_reference_date=reference_date)
 
         elem_name = elem_name_prefix + "max_activity_duration"
-        elem = self._driver.find_element_by_id(elem_name)
+        elem = self._driver.find_element(By.ID, elem_name)
         self.set_value(p_value=NEW_INVALID_DURATION, p_elem=elem)
 
-        save_button = self._driver.find_element_by_id("save")
+        save_button = self._driver.find_element(By.ID, "save")
         self.click(save_button)
 
         xpath = "//LABEL[@CLASS = 'error-label' and @FOR = '{elem_name}']".format(elem_name=elem_name)
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
         # Data was not saved!
         with SessionContext(self._persistence) as session_context:
@@ -315,14 +317,14 @@ def _test_page_admin_edit_invalid_data(self, p_elem_name: str, p_invalid_data: s
         elem_name_prefix = self.get_admin_elem_name_prefix(p_reference_date=reference_date)
 
         elem_name = elem_name_prefix + p_elem_name
-        elem = self._driver.find_element_by_id(elem_name)
+        elem = self._driver.find_element(By.ID, elem_name)
         self.set_value(p_value=p_invalid_data, p_elem=elem)
 
-        save_button = self._driver.find_element_by_id("save")
+        save_button = self._driver.find_element(By.ID, "save")
         self.click(save_button)
 
         xpath = "//LABEL[@CLASS = 'error-label' and @FOR = '{elem_name}']".format(elem_name=elem_name)
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
         # Data was not saved!
         with SessionContext(self._persistence) as session_context:
diff --git a/little_brother/test/web/test_status_server_devices.py b/little_brother/test/web/test_status_server_devices.py
index 7d3a88d..8785ed5 100644
--- a/little_brother/test/web/test_status_server_devices.py
+++ b/little_brother/test/web/test_status_server_devices.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -20,6 +20,8 @@
 
 import unittest
 
+from selenium.webdriver.common.by import By
+
 from little_brother import constants
 from little_brother import dependency_injection
 from little_brother.persistence.persistent_device import Device
@@ -74,7 +76,7 @@ def test_page_devices_add_and_delete_device(self):
             new_device_name = device_entity_manager.get_new_device_name(
                 p_session_context=session_context, p_name_pattern=constants.DEFAULT_DEVICE_NEW_NAME_PATTERN)
 
-            add_button = self._driver.find_element_by_id("add_device")
+            add_button = self._driver.find_element(By.ID, "add_device")
             add_button.click()
 
             device: Device = device_entity_manager.get_by_device_name(
@@ -84,12 +86,12 @@ def test_page_devices_add_and_delete_device(self):
         device_id = device.id
 
         xpath = "//DIV/A[@aria-controls='detailsdevice_1']"
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
-        delete_button = self._driver.find_element_by_id("delete_device_1")
+        delete_button = self._driver.find_element(By.ID, "delete_device_1")
         delete_button.click()
 
-        delete_button = self._driver.find_element_by_id("delete_device_1-modal-confirm")
+        delete_button = self._driver.find_element(By.ID, "delete_device_1-modal-confirm")
 
         self.click(delete_button)
 
@@ -115,22 +117,22 @@ def test_page_devices_edit_device(self):
 
         elem_prefix = "device_{id}_".format(id=device_id)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "device_name")
+        elem = self._driver.find_element(By.ID, elem_prefix + "device_name")
         self.set_value(p_elem=elem, p_value=NEW_DEVICE_NAME)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "hostname")
+        elem = self._driver.find_element(By.ID, elem_prefix + "hostname")
         self.set_value(p_elem=elem, p_value=NEW_DEVICE_HOST_NAME)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "min_activity_duration")
+        elem = self._driver.find_element(By.ID, elem_prefix + "min_activity_duration")
         self.set_value(p_elem=elem, p_value=NEW_DEVICE_MIN_ACTIVITY_DURATION)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "max_active_ping_delay")
+        elem = self._driver.find_element(By.ID, elem_prefix + "max_active_ping_delay")
         self.set_value(p_elem=elem, p_value=NEW_DEVICE_MAX_ACTIVE_PING_DELAY)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "sample_size")
+        elem = self._driver.find_element(By.ID, elem_prefix + "sample_size")
         self.set_value(p_elem=elem, p_value=NEW_DEVICE_SAMPLE_SIZE)
 
-        save_button = self._driver.find_element_by_id("save")
+        save_button = self._driver.find_element(By.ID, "save")
         self.click(save_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -163,14 +165,14 @@ def _test_page_devices_edit_invalid_data(self, p_elem_name: str, p_invalid_data:
         elem_name_prefix = "device_{id}_".format(id=device_id)
 
         elem_name = elem_name_prefix + p_elem_name
-        elem = self._driver.find_element_by_id(elem_name)
+        elem = self._driver.find_element(By.ID, elem_name)
         self.set_value(p_value=p_invalid_data, p_elem=elem)
 
-        save_button = self._driver.find_element_by_id("save")
+        save_button = self._driver.find_element(By.ID, "save")
         self.click(save_button)
 
         xpath = "//LABEL[@CLASS = 'error-label' and @FOR = '{elem_name}']".format(elem_name=elem_name)
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
         # Data was not saved!
         with SessionContext(self._persistence) as session_context:
diff --git a/little_brother/test/web/test_status_server_index.py b/little_brother/test/web/test_status_server_index.py
index 5eb0dc3..ad545e3 100644
--- a/little_brother/test/web/test_status_server_index.py
+++ b/little_brother/test/web/test_status_server_index.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -20,6 +20,8 @@
 
 import unittest
 
+from selenium.webdriver.common.by import By
+
 from little_brother import constants
 from little_brother.test import test_data
 from little_brother.test.web.base_test_status_server import BaseTestStatusServer
@@ -30,7 +32,7 @@ class TestStatusServerIndex(BaseTestStatusServer):
 
     def check_index_page_visible(self):
         xpath = "//DIV[DIV[1] = 'User' and DIV[2] = 'Context' and DIV[12] = 'Reasons']"
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
     @base_test.skip_if_env("NO_SELENIUM_TESTS")
     def test_page_index_with_process_no_restrictions(self):
diff --git a/little_brother/test/web/test_status_server_topology.py b/little_brother/test/web/test_status_server_topology.py
index 0779281..1ef37e7 100644
--- a/little_brother/test/web/test_status_server_topology.py
+++ b/little_brother/test/web/test_status_server_topology.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -20,6 +20,8 @@
 
 import unittest
 
+from selenium.webdriver.common.by import By
+
 from little_brother import constants
 from little_brother.test.web.base_test_status_server import BaseTestStatusServer
 from python_base_app.test import base_test
@@ -44,7 +46,7 @@ def test_page_topology(self):
         # After logging in we are on the users page
         xpath = "//DIV/DIV[DIV[1] = 'Node Type' and DIV[2] = 'Node Name']"
         assert "Topology" in self._driver.title
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
         # The second time we call the users page.
         self._driver.get(self._status_server.get_url(p_internal=False, p_rel_url=constants.TOPOLOGY_REL_URL))
@@ -53,7 +55,7 @@ def test_page_topology(self):
 
         # we are on the users page right away...
         xpath = "//DIV/DIV[DIV[1] = 'Node Type' and DIV[2] = 'Node Name']"
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
 
 if __name__ == "__main__":
diff --git a/little_brother/test/web/test_status_server_users.py b/little_brother/test/web/test_status_server_users.py
index fc46915..66d6ffb 100644
--- a/little_brother/test/web/test_status_server_users.py
+++ b/little_brother/test/web/test_status_server_users.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-#    Copyright (C) 2019-2021  Marcus Rickert
+#    Copyright (C) 2019-2022  Marcus Rickert
 #
 #    See https://github.com/marcus67/little_brother
 #
@@ -21,6 +21,7 @@
 import unittest
 
 import selenium.webdriver.support.ui
+from selenium.webdriver.common.by import By
 
 from little_brother import constants
 from little_brother import dependency_injection
@@ -70,11 +71,11 @@ def test_page_users_add_and_delete_user(self):
 
         self.login_users()
 
-        elem = self._driver.find_element_by_id("username")
+        elem = self._driver.find_element(By.ID, "username")
         dropdown = selenium.webdriver.support.ui.Select(elem)
         dropdown.select_by_value(test_unix_user_handler.USER_2_UID)
 
-        add_button = self._driver.find_element_by_id("add_user")
+        add_button = self._driver.find_element(By.ID, "add_user")
         add_button.click()
 
         user_entity_manager: UserEntityManager = dependency_injection.container[UserEntityManager]
@@ -87,12 +88,12 @@ def test_page_users_add_and_delete_user(self):
         user_id = user.id
 
         xpath = "//DIV/A[@aria-controls='detailsuser_1']"
-        self._driver.find_element_by_xpath(xpath)
+        self._driver.find_element(By.XPATH, xpath)
 
-        delete_button = self._driver.find_element_by_id("delete_user_1")
+        delete_button = self._driver.find_element(By.ID, "delete_user_1")
         self.click(delete_button)
 
-        delete_button = self._driver.find_element_by_id("delete_user_1-modal-confirm")
+        delete_button = self._driver.find_element(By.ID, "delete_user_1-modal-confirm")
         self.click(delete_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -115,22 +116,22 @@ def test_page_users_edit_user(self):
 
         elem_prefix = "user_{id}_".format(id=user_id)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "first_name")
+        elem = self._driver.find_element(By.ID, elem_prefix + "first_name")
         self.set_value(p_elem=elem, p_value=NEW_USER_FIRST_NAME)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "last_name")
+        elem = self._driver.find_element(By.ID, elem_prefix + "last_name")
         self.set_value(p_elem=elem, p_value=NEW_USER_LAST_NAME)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "process_name_pattern")
+        elem = self._driver.find_element(By.ID, elem_prefix + "process_name_pattern")
         self.set_value(p_elem=elem, p_value=NEW_USER_PROCESS_NAME_PATTERN)
 
-        elem = self._driver.find_element_by_id(elem_prefix + "locale")
+        elem = self._driver.find_element(By.ID, elem_prefix + "locale")
         self.set_value(p_elem=elem, p_value=NEW_USER_LOCALE)
 
-        check_box = self._driver.find_element_by_id(elem_prefix + "active")
+        check_box = self._driver.find_element(By.ID, elem_prefix + "active")
         self.click(check_box)
 
-        save_button = self._driver.find_element_by_id("save")
+        save_button = self._driver.find_element(By.ID, "save")
         self.click(save_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -157,7 +158,7 @@ def test_page_users_assign_rule_set(self):
         self.login_users()
 
         elem_name = "new_ruleset_user_{id}".format(id=user_id)
-        add_button = self._driver.find_element_by_id(elem_name)
+        add_button = self._driver.find_element(By.ID, elem_name)
         self.click(add_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -185,10 +186,10 @@ def test_page_users_unassign_rule_set(self):
         self.login_users()
 
         elem_name = "delete_ruleset_{id}".format(id=rule_set_id)
-        unassign_button = self._driver.find_element_by_id(elem_name)
+        unassign_button = self._driver.find_element(By.ID, elem_name)
         self.click(unassign_button)
 
-        confirm_button = self._driver.find_element_by_id(elem_name + "-modal-confirm")
+        confirm_button = self._driver.find_element(By.ID, elem_name + "-modal-confirm")
         self.click(confirm_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -218,7 +219,7 @@ def test_page_users_move_rule_set_down(self):
         self.login_users()
 
         elem_name = "move_down_ruleset_{id}".format(id=rule_set_2_id)
-        add_button = self._driver.find_element_by_id(elem_name)
+        add_button = self._driver.find_element(By.ID, elem_name)
         self.click(add_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -246,7 +247,7 @@ def test_page_users_move_rule_set_up(self):
         self.login_users()
 
         elem_name = "move_up_ruleset_{id}".format(id=rule_set_1_id)
-        add_button = self._driver.find_element_by_id(elem_name)
+        add_button = self._driver.find_element(By.ID, elem_name)
         self.click(add_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -292,11 +293,11 @@ def test_page_users_assign_device(self):
         self.login_users()
 
         elem_name = "user_{id}_device_id".format(id=user_id)
-        select_device = self._driver.find_element_by_id(elem_name)
+        select_device = self._driver.find_element(By.ID, elem_name)
         self.set_value(p_elem=select_device, p_value=device_id)
 
         elem_name = "new_device_user_{id}".format(id=user_id)
-        add_button = self._driver.find_element_by_id(elem_name)
+        add_button = self._driver.find_element(By.ID, elem_name)
         self.click(add_button)
 
         with SessionContext(self._persistence) as session_context:
@@ -334,10 +335,10 @@ def test_page_users_unassign_device(self):
         self.login_users()
 
         elem_name = "delete_user2device_{id}".format(id=user_2_device_id)
-        unassign_button = self._driver.find_element_by_id(elem_name)
+        unassign_button = self._driver.find_element(By.ID, elem_name)
         self.click(unassign_button)
 
-        confirm_button = self._driver.find_element_by_id(elem_name + "-modal-confirm")
+        confirm_button = self._driver.find_element(By.ID, elem_name + "-modal-confirm")
         self.click(confirm_button)
 
         with SessionContext(self._persistence) as session_context: