mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Merge pull request #901 from bunkerity/dev
Merge branch "dev" into branch "staging"
This commit is contained in:
commit
3fe07c8c85
140 changed files with 2610 additions and 2530 deletions
2
.github/workflows/container-build.yml
vendored
2
.github/workflows/container-build.yml
vendored
|
|
@ -86,7 +86,7 @@ jobs:
|
|||
# Compute metadata
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: bunkerity/${{ inputs.IMAGE }}
|
||||
# Build cached image
|
||||
|
|
|
|||
2
.github/workflows/linux-build.yml
vendored
2
.github/workflows/linux-build.yml
vendored
|
|
@ -137,7 +137,7 @@ jobs:
|
|||
- name: Extract metadata
|
||||
if: inputs.TEST == true
|
||||
id: meta
|
||||
uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: ghcr.io/bunkerity/${{ inputs.LINUX }}-tests:${{ inputs.RELEASE }}
|
||||
- name: Build test image
|
||||
|
|
|
|||
2
.github/workflows/push-docker.yml
vendored
2
.github/workflows/push-docker.yml
vendored
|
|
@ -65,7 +65,7 @@ jobs:
|
|||
# Compute metadata
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: bunkerity/${{ inputs.IMAGE }}
|
||||
# Build and push
|
||||
|
|
|
|||
2
.github/workflows/push-packagecloud.yml
vendored
2
.github/workflows/push-packagecloud.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
- name: Check out repository code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Install ruby
|
||||
uses: ruby/setup-ruby@bd03e04863f52d169e18a2b190e8fa6b84938215 # v1.170.0
|
||||
uses: ruby/setup-ruby@22fdc77bf4148f810455b226c90fb81b5cbc00a7 # v1.171.0
|
||||
with:
|
||||
ruby-version: "3.0"
|
||||
- name: Install packagecloud
|
||||
|
|
|
|||
2
.github/workflows/test-core-linux.yml
vendored
2
.github/workflows/test-core-linux.yml
vendored
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
' | sudo tee /etc/apt/preferences.d/mozilla-firefox
|
||||
sudo apt install --no-install-recommends -y openssl git nodejs tar bzip2 wget curl grep libx11-xcb1 libappindicator3-1 libasound2 libdbus-glib-1-2 libxtst6 libxt6 php-fpm unzip firefox
|
||||
- name: Download geckodriver
|
||||
uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0
|
||||
with:
|
||||
max_attempts: 3
|
||||
timeout_minutes: 20
|
||||
|
|
|
|||
2
.github/workflows/tests-ui-linux.yml
vendored
2
.github/workflows/tests-ui-linux.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
' | sudo tee /etc/apt/preferences.d/mozilla-firefox
|
||||
sudo apt install --no-install-recommends -y openssl git nodejs tar bzip2 wget curl grep libx11-xcb1 libappindicator3-1 libasound2 libdbus-glib-1-2 libxtst6 libxt6 php-fpm unzip firefox
|
||||
- name: Download geckodriver
|
||||
uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0
|
||||
with:
|
||||
max_attempts: 3
|
||||
timeout_minutes: 20
|
||||
|
|
|
|||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -2,12 +2,27 @@
|
|||
|
||||
## v1.5.6 - YYYY/MM/DD
|
||||
|
||||
- [BUGFIX] Fix issues with the antibot feature ([#866](https://github.com/bunkerity/bunkerweb/issues/866), [#870](https://github.com/bunkerity/bunkerweb/issues/870))
|
||||
- [UI] Add bans management page in the web UI
|
||||
- [UI] Add blocked requests page in the web UI
|
||||
- [UI] Add the possibility to clone a service in the web UI
|
||||
- [UI] Add the possibility to set a service as draft in the web UI
|
||||
- [FEATURE] Add setting REDIS_SSL_VERIFY to activate/disable the SSL certificate verification when using Redis
|
||||
- [FEATURE] Add Redis Sentinel fallback to master automatically if no slaves are available
|
||||
- [FEATURE] Add Redis Sentinel support for bwcli
|
||||
- [FEATURE] Add new Metrics core plugin that will allow metrics collection and retrieval of internal metrics
|
||||
- [FEATURE] Add setting DATABASE_LOG_LEVEL to control SQLAlchemy loggers separately from the main one
|
||||
- [MISC] Add a better custom certificate cache handling
|
||||
- [MISC] Updated Linux base images in Dockerfiles
|
||||
- [MISC] Add recommended dialects to databases string
|
||||
- [DOCUMENTATION] Update web UI's setup wizard instructions in the documentation
|
||||
- [DEPS] Updated stream-lua-nginx-module to v0.0.14
|
||||
- [DEPS] Updated lua-nginx-module version to v0.10.26
|
||||
- [DEPS] Updated libmaxminddb version to v1.9.1
|
||||
- [DEPS] Updated lua-resty-core to v0.1.28
|
||||
- [DEPS] Updated zlib version to v1.3.1
|
||||
- [DEPS] Updated ModSecurity version to v3.0.12
|
||||
- [DEPS] Updated lua-resty-mlcache version to v2.6.1
|
||||
|
||||
## v1.5.5 - 2024/01/12
|
||||
|
||||
|
|
|
|||
|
|
@ -260,10 +260,39 @@ The first step is to install the plugin by putting the plugin files inside the c
|
|||
|
||||
## Writing a plugin
|
||||
|
||||
### Structure
|
||||
|
||||
!!! tip "Existing plugins"
|
||||
|
||||
If the documentation is not enough, you can have a look at the existing source code of [official plugins](https://github.com/bunkerity/bunkerweb-plugins) and the [core plugins](https://github.com/bunkerity/bunkerweb/tree/v1.5.6/src/common/core) (already included in BunkerWeb but they are plugins, technically speaking).
|
||||
|
||||
What a plugin structure looks like :
|
||||
```
|
||||
plugin /
|
||||
confs / conf_type.conf
|
||||
ui / actions.py
|
||||
template.html
|
||||
plugin.lua
|
||||
plugin.json
|
||||
```
|
||||
|
||||
- **conf_type.conf** : add a [custom NGINX configurations.](/quickstart-guide/#custom-configurations)
|
||||
|
||||
- **actions.py** : script to execute on flask server.
|
||||
This script is running on flask context, you have access to lib and utils like `jinja2`, `requests`, etc...
|
||||
|
||||
- **template.html** : custom plugin page you can access from ui.
|
||||
|
||||
- **plugin.lua** : code to execute on NGINX using [NGING LUA modile.](https://github.com/openresty/lua-nginx-module)
|
||||
|
||||
- **plugin.json** : metadata, settings and jobs for your settings.
|
||||
|
||||
!!! info "Optional files"
|
||||
|
||||
Files like `confs` and `ui` ones are optional. Add them only to fit your needs.
|
||||
|
||||
### Getting started
|
||||
|
||||
The first step is to create a folder that will contain the plugin :
|
||||
|
||||
```shell
|
||||
|
|
@ -515,9 +544,9 @@ BunkerWeb uses an internal job scheduler for periodic tasks like renewing certif
|
|||
|
||||
### Plugin page
|
||||
|
||||
Plugin pages are used to display information about your plugin and interact with the user inside the plugins section of the [web UI](web-ui.md).
|
||||
Everything related to the web UI is located inside the subfolder **ui** as we seen in the [previous structure section.](#structure)
|
||||
|
||||
Everything related to the web UI is located inside a subfolder named **ui** at the root directory of your plugin. A template file named **template.html** and located inside the **ui** subfolder contains the client code and logic to display your page. Another file named **actions.py** and also located inside the **ui** subfolder contains code that will be executed when the user is interacting with your page (filling a form for example).
|
||||
#### Basic example
|
||||
|
||||
!!! info "Jinja 2 template"
|
||||
The **template.html** file is a Jinja2 template, please refer to the [Jinja2 documentation](https://jinja.palletsprojects.com) if needed.
|
||||
|
|
@ -555,3 +584,40 @@ def myplugin() :
|
|||
!!! info "Python libraries"
|
||||
You can use Python libraries that are already available like :
|
||||
`Flask`, `Flask-Login`, `Flask-WTF`, `beautifulsoup4`, `docker`, `Jinja2`, `python-magic` and `requests`. To see the full list, you can have a look at the Web UI [requirements.txt](https://github.com/bunkerity/bunkerweb/blobsrc/ui/requirements.txt). If you need external libraries, you can install them inside the **ui** folder of your plugin and then use the classical **import** directive.
|
||||
|
||||
#### Utils
|
||||
|
||||
To easily update the content of a template inside the UI with JSON, a **SetupPlugin class** is available in `src/ui/static/js/plugins/setup.js` and will be executed when the plugin template is load.
|
||||
|
||||
For example, in case **actions.py** return this JSON :
|
||||
```python
|
||||
def plugin():
|
||||
return {"message": "ok", "data": {"name": "test"}}
|
||||
```
|
||||
|
||||
I need to add this on my **template.html** :
|
||||
```html
|
||||
<script>
|
||||
new SetupPlugin({
|
||||
name: {
|
||||
el: document.querySelector('[data-name]'),
|
||||
value: '',
|
||||
type: 'text',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
!!! info "Check core plugins"
|
||||
|
||||
Core plugins are using this utils. Feel free to look at them in order to see in details how each `key` is working.
|
||||
|
||||
|
||||
| key | Type | Description |
|
||||
| :--------: | :----: | :------------------------------------------------------------------------------------------- |
|
||||
| `name ` | string | Replace `name` by the JSON key to extract the related value. |
|
||||
| `el` | element| Select element you want the value to be updated. |
|
||||
| `value` | any | Default value on template load or in case retrieving JSON failed. |
|
||||
| `type` | string | Define the script behavior with the incoming value. Available : `text`, `list` and `status`. |
|
||||
| `textEl` | element| Optional additionnal text content when type is `status`. |
|
||||
| `listNames`| string | List of data keys when type is `list`. |
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
mike==2.0.0
|
||||
mkdocs==1.5.3
|
||||
mkdocs-material[imaging]==9.5.5
|
||||
mkdocs-material[imaging]==9.5.6
|
||||
mkdocs-print-site-plugin==2.3.6
|
||||
pytablewriter==1.2.0
|
||||
|
|
|
|||
|
|
@ -311,9 +311,9 @@ mkdocs==1.5.3 \
|
|||
# -r requirements.in
|
||||
# mike
|
||||
# mkdocs-material
|
||||
mkdocs-material==9.5.5 \
|
||||
--hash=sha256:4480d9580faf42fed0123d0465502bfc1c0c239ecc9c4d66159cf0459ea1b4ae \
|
||||
--hash=sha256:ac50b2431a79a3b160fdefbba37c9132485f1a69166aba115ad49fafdbbbc5df
|
||||
mkdocs-material==9.5.6 \
|
||||
--hash=sha256:5b24df36d8ac6cecd611241ce6f6423ccde3e1ad89f8360c3f76d5565fc2d82a \
|
||||
--hash=sha256:e115b90fccf5cd7f5d15b0c2f8e6246b21041628b8f590630e7fca66ed7fcf6c
|
||||
# via
|
||||
# -r requirements.in
|
||||
# mkdocs-material
|
||||
|
|
@ -415,9 +415,9 @@ pillow==10.2.0 \
|
|||
# via
|
||||
# cairosvg
|
||||
# mkdocs-material
|
||||
platformdirs==4.1.0 \
|
||||
--hash=sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380 \
|
||||
--hash=sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420
|
||||
platformdirs==4.2.0 \
|
||||
--hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \
|
||||
--hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768
|
||||
# via mkdocs
|
||||
pycparser==2.21 \
|
||||
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
|
||||
|
|
@ -445,9 +445,9 @@ python-dateutil==2.8.2 \
|
|||
# via
|
||||
# ghp-import
|
||||
# typepy
|
||||
pytz==2023.3.post1 \
|
||||
--hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \
|
||||
--hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7
|
||||
pytz==2023.4 \
|
||||
--hash=sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40 \
|
||||
--hash=sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a
|
||||
# via typepy
|
||||
pyyaml==6.0.1 \
|
||||
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
|
||||
|
|
@ -643,9 +643,9 @@ typepy==1.3.2 \
|
|||
# pytablewriter
|
||||
# tabledata
|
||||
# typepy
|
||||
urllib3==2.1.0 \
|
||||
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
|
||||
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
|
||||
urllib3==2.2.0 \
|
||||
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
|
||||
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
|
||||
# via requests
|
||||
verspec==0.1.0 \
|
||||
--hash=sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 \
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ Here is how you can access the logs, depending on your integration :
|
|||
|
||||
Don't forget that BunkerWeb runs as an unprivileged user for obvious security reasons. Double-check the permissions of files and folders used by BunkerWeb, especially if you use custom configurations (more info [here](quickstart-guide.md#custom-configurations)). You will need to set at least **RW** rights on files and **_RWX_** on folders.
|
||||
|
||||
## Disable security checks
|
||||
|
||||
For debugging purposes, you may need to temporarily disable the security checks made by BunkerWeb. One quick way of doing it is by adding everyone in the whitelist (e.g. `WHITELIST_IP=0.0.0.0/0`).
|
||||
|
||||
## ModSecurity
|
||||
|
||||
The default BunkerWeb configuration of ModSecurity is to load the Core Rule Set in anomaly scoring mode with a paranoia level (PL) of 1 :
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ shopt -u globstar
|
|||
# linux
|
||||
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" src/linux/scripts/*.sh
|
||||
# db
|
||||
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" src/common/db/Database.py
|
||||
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" src/common/db/model.py
|
||||
# github
|
||||
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" .github/ISSUE_TEMPLATE/bug_report.yml
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ class IngressController(Controller):
|
|||
obj = event["object"]
|
||||
metadata = obj.metadata if obj else None
|
||||
annotations = metadata.annotations if metadata else None
|
||||
data = obj.data if obj else None
|
||||
if not obj:
|
||||
return False
|
||||
if obj.kind == "Pod":
|
||||
|
|
@ -242,6 +243,8 @@ class IngressController(Controller):
|
|||
return annotations and "bunkerweb.io/CONFIG_TYPE" in annotations
|
||||
if obj.kind == "Service":
|
||||
return True
|
||||
if obj.kind == "Secret":
|
||||
return data and "tls.crt" in data and "tls.key" in data
|
||||
return False
|
||||
|
||||
def __watch(self, watch_type):
|
||||
|
|
@ -255,6 +258,8 @@ class IngressController(Controller):
|
|||
what = self.__corev1.list_config_map_for_all_namespaces
|
||||
elif watch_type == "service":
|
||||
what = self.__corev1.list_service_for_all_namespaces
|
||||
elif watch_type == "secret":
|
||||
what = self.__corev1.list_secret_for_all_namespaces
|
||||
else:
|
||||
raise Exception(f"Unsupported watch_type {watch_type}")
|
||||
|
||||
|
|
@ -328,7 +333,7 @@ class IngressController(Controller):
|
|||
|
||||
def process_events(self):
|
||||
self._set_autoconf_load_db()
|
||||
watch_types = ("pod", "ingress", "configmap", "service")
|
||||
watch_types = ("pod", "ingress", "configmap", "service", "secret")
|
||||
threads = [Thread(target=self.__watch, args=(watch_type,)) for watch_type in watch_types]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ helpers.fill_ctx = function(no_ref)
|
|||
end
|
||||
data.remote_addr = var.remote_addr
|
||||
data.server_name = var.server_name
|
||||
data.local_time = var.local_time
|
||||
data.time_local = var.time_local
|
||||
if data.kind == "http" then
|
||||
data.uri = var.uri
|
||||
data.request_uri = var.request_uri
|
||||
|
|
@ -196,6 +196,7 @@ helpers.fill_ctx = function(no_ref)
|
|||
data.http_content_length = var.http_content_length
|
||||
data.http_origin = var.http_origin
|
||||
data.http_version = req.http_version()
|
||||
data.start_time = req.start_time()
|
||||
data.scheme = var.scheme
|
||||
end
|
||||
-- IP data : global
|
||||
|
|
|
|||
|
|
@ -93,4 +93,19 @@ function plugin:ret(ret, msg, status, redirect, data)
|
|||
return { ret = ret, msg = msg, status = status, redirect = redirect, data = data }
|
||||
end
|
||||
|
||||
function plugin:set_metric(kind, key, value)
|
||||
if self.ctx and self.ctx.bw then
|
||||
if not self.ctx.bw.metrics then
|
||||
self.ctx.bw.metrics = {}
|
||||
end
|
||||
if not self.ctx.bw.metrics[self.id] then
|
||||
self.ctx.bw.metrics[self.id] = {}
|
||||
end
|
||||
if not self.ctx.bw.metrics[self.id][kind] then
|
||||
self.ctx.bw.metrics[self.id][kind] = {}
|
||||
end
|
||||
self.ctx.bw.metrics[self.id][kind][key] = value
|
||||
end
|
||||
end
|
||||
|
||||
return plugin
|
||||
|
|
|
|||
|
|
@ -786,6 +786,7 @@ utils.get_phases = function()
|
|||
"preread",
|
||||
"log_stream",
|
||||
"log_default",
|
||||
"timer"
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,15 @@ init_worker_by_lua_block {
|
|||
local ngx = ngx
|
||||
local INFO = ngx.INFO
|
||||
local ERR = ngx.ERR
|
||||
local WARN = ngx.WARN
|
||||
local NOTICE = ngx.NOTICE
|
||||
local worker = ngx.worker
|
||||
local worker_id = worker.id
|
||||
local timer_at = ngx.timer.at
|
||||
local require_plugin = helpers.require_plugin
|
||||
local new_plugin = helpers.new_plugin
|
||||
local call_plugin = helpers.call_plugin
|
||||
local tostring = tostring
|
||||
|
||||
-- Don't go further we are in loading state
|
||||
local is_loading, err = require "bunkerweb.utils".get_variable("IS_LOADING", false)
|
||||
|
|
@ -27,6 +32,71 @@ init_worker_by_lua_block {
|
|||
return
|
||||
end
|
||||
|
||||
-- Recurrent timer
|
||||
local recurrent_timer
|
||||
recurrent_timer = function(premature)
|
||||
|
||||
local worker_id = tostring(worker.id())
|
||||
local logger = require "bunkerweb.logger":new("TIMER-" .. worker_id)
|
||||
|
||||
-- Worker exiting
|
||||
if premature then
|
||||
logger:log(WARN, "worker is exiting, disabling timer")
|
||||
return
|
||||
end
|
||||
logger:log(INFO, "timer started")
|
||||
|
||||
-- Get plugins order
|
||||
local order, err = datastore:get("plugins_order", true)
|
||||
if not order then
|
||||
logger:log(ERR, "can't get plugins order from datastore : " .. err)
|
||||
return
|
||||
end
|
||||
|
||||
-- Call timer() methods
|
||||
logger:log(INFO, "calling timer() methods of plugins ...")
|
||||
for i, plugin_id in ipairs(order.timer) do
|
||||
-- Require call
|
||||
local plugin_lua, err = require_plugin(plugin_id)
|
||||
if plugin_lua == false then
|
||||
logger:log(ERR, err)
|
||||
elseif plugin_lua == nil then
|
||||
logger:log(INFO, err)
|
||||
else
|
||||
-- Check if plugin has timer method
|
||||
if plugin_lua.timer ~= nil then
|
||||
-- New call
|
||||
local ok, plugin_obj = new_plugin(plugin_lua)
|
||||
if not ok then
|
||||
logger:log(ERR, plugin_obj)
|
||||
else
|
||||
local ok, ret = call_plugin(plugin_obj, "timer")
|
||||
if not ok then
|
||||
logger:log(ERR, ret)
|
||||
elseif not ret.ret then
|
||||
logger:log(ERR, plugin_id .. ":timer() call failed : " .. ret.msg)
|
||||
else
|
||||
logger:log(INFO, plugin_id .. ":timer() call successful : " .. ret.msg)
|
||||
end
|
||||
end
|
||||
else
|
||||
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method timer() is not defined")
|
||||
end
|
||||
end
|
||||
end
|
||||
logger:log(INFO, "called timer() methods of plugins")
|
||||
local hdl
|
||||
hdl, err = timer_at(5, recurrent_timer)
|
||||
if not hdl then
|
||||
logger:log(ERR, "can't create timer : " .. err)
|
||||
end
|
||||
end
|
||||
local hdl
|
||||
hdl, err = timer_at(0, recurrent_timer)
|
||||
if not hdl then
|
||||
logger:log(ERR, "can't create timer : " .. err)
|
||||
end
|
||||
|
||||
-- Instantiate lock
|
||||
local lock = require "resty.lock":new("worker_lock", { timeout = 10 })
|
||||
if not lock then
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ function antibot:access()
|
|||
if ok == nil then
|
||||
return self:ret(false, "check challenge error : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
elseif not ok then
|
||||
self:set_metric("counters", "failed_challenges", 1)
|
||||
self.logger:log(ngx.WARN, "client failed challenge : " .. err)
|
||||
end
|
||||
if redirect then
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
# Spoofing an action file
|
||||
def antibot(**kwargs):
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("antibot")
|
||||
|
||||
if data.get("counter_failed_challenges") is None:
|
||||
data["counter_failed_challenges"] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
return {"counter_failed_challenges": 0}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
|
|
@ -9,12 +16,9 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ antibot_info or "Anti-bot technology is designed to detect and
|
||||
mitigate suspicious or malicious bots, preventing them from reaching an
|
||||
organization's websites or IT ecosystem." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
|
@ -29,14 +33,12 @@
|
|||
>
|
||||
Challenges
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ antibot_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count class="mb-1 font-bold dark:text-white/90">"unknown"</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5"
|
||||
>total number</span
|
||||
>
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
|
||||
>total failed
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
|
|
@ -66,4 +68,20 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_failed_challenges: {
|
||||
el: document.querySelector("[data-count]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# Spoofing an action file
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ authbasic_info or "Basic Auth is a method for an HTTP user agent
|
||||
(e.g. a web browser) to provide a user name and password when making a
|
||||
request." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
AUTH BASIC
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ authbasic_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
|
||||
>passed credentials</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-green-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="stroke-none scale-[0.6] leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.75 1.5a6.75 6.75 0 0 0-6.651 7.906c.067.39-.032.717-.221.906l-6.5 6.499a3 3 0 0 0-.878 2.121v2.818c0 .414.336.75.75.75H6a.75.75 0 0 0 .75-.75v-1.5h1.5A.75.75 0 0 0 9 19.5V18h1.5a.75.75 0 0 0 .53-.22l2.658-2.658c.19-.189.517-.288.906-.22A6.75 6.75 0 1 0 15.75 1.5Zm0 3a.75.75 0 0 0 0 1.5A2.25 2.25 0 0 1 18 8.25a.75.75 0 0 0 1.5 0 3.75 3.75 0 0 0-3.75-3.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -49,6 +49,7 @@ function badbehavior:log()
|
|||
if not ok then
|
||||
return self:ret(false, "can't create increase timer : " .. err)
|
||||
end
|
||||
self:set_metric("counters", tostring(ngx.status), 1)
|
||||
return self:ret(true, "success")
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,13 @@
|
|||
# Spoofing an action file
|
||||
def badbehavior(**kwargs):
|
||||
try:
|
||||
# Here we will have a list { 'counter_403': X, 'counter_401': Y ... }
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("badbehavior")
|
||||
format_data = []
|
||||
# Format to fit [{code: 403, count: X}, {code: 401, count: Y} ...]
|
||||
for key, value in data.items():
|
||||
format_data[key] = {"code": int(key.split("_")[1]), "count": value}
|
||||
|
||||
return {"items": format_data}
|
||||
|
||||
except:
|
||||
return {"items": []}
|
||||
|
|
|
|||
|
|
@ -1,72 +1,31 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [{"code" : 400,
|
||||
"count" : 24}, {"code" : 403, "count" : 845}, {"code" : 402, "count" : 12}]%}
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ bad_behavior_info or "Ban IP generating too much 'bad' HTTP status
|
||||
code in a period of time." }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
BAD BEHAVIOR
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ bad_behavior_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
|
||||
>total ip bans</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
aria-label="version"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="stroke-none scale-75 leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="m6.72 5.66 11.62 11.62A8.25 8.25 0 0 0 6.72 5.66Zm10.56 12.68L5.66 6.72a8.25 8.25 0 0 0 11.62 11.62ZM5.105 5.106c3.807-3.808 9.98-3.808 13.788 0 3.808 3.807 3.808 9.98 0 13.788-3.807 3.808-9.98 3.808-13.788 0-3.808-3.807-3.808-9.98 0-13.788Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
{% if items|length != 0 %}
|
||||
<div
|
||||
class="2xl:col-span-4 3xl:col-span-3 w-full md:max-w-[350px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
data-fetch-success-show
|
||||
class="hidden 2xl:col-span-4 3xl:col-span-3 w-full md:max-w-[350px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
|
||||
|
|
@ -91,29 +50,41 @@
|
|||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
data-item
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
data-name="code"
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-6 m-0 my-1"
|
||||
>
|
||||
{{item['code']}}
|
||||
</p>
|
||||
></p>
|
||||
<p
|
||||
data-name="count"
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-6 m-0 my-1"
|
||||
>
|
||||
{{item['count']}}
|
||||
</p>
|
||||
></p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
items: {
|
||||
el: document.querySelector("[data-item]"),
|
||||
value: [],
|
||||
type: "list",
|
||||
listNames: ["code", "count"],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -132,12 +132,14 @@ function blacklist:access()
|
|||
if not ok then
|
||||
self.logger:log(ERR, "error while checking cache : " .. cached)
|
||||
elseif cached and cached ~= "ok" then
|
||||
local data = self:get_data(cached)
|
||||
self:set_metric("counters", "failed_" .. data.id, 1)
|
||||
return self:ret(
|
||||
true,
|
||||
k .. " is in cached blacklist (info : " .. cached .. ")",
|
||||
get_deny_status(),
|
||||
nil,
|
||||
self:get_data(cached)
|
||||
data
|
||||
)
|
||||
end
|
||||
if ok and cached then
|
||||
|
|
@ -161,12 +163,14 @@ function blacklist:access()
|
|||
self.logger:log(ERR, "error while adding element to cache : " .. err)
|
||||
end
|
||||
if blacklisted ~= "ok" then
|
||||
local data = self:get_data(blacklisted)
|
||||
self:set_metric("counters", "failed_" .. data.id, 1)
|
||||
return self:ret(
|
||||
true,
|
||||
k .. " is blacklisted (info : " .. blacklisted .. ")",
|
||||
get_deny_status(),
|
||||
nil,
|
||||
self:get_data(blacklisted)
|
||||
data
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1 +1,23 @@
|
|||
# Spoofing an action file
|
||||
def blacklist(**kwargs):
|
||||
keys = [
|
||||
"counter_blacklist_url",
|
||||
"counter_blacklist_ip",
|
||||
"counter_blacklist_rdns",
|
||||
"counter_blacklist_asn",
|
||||
"counter_blacklist_usa",
|
||||
]
|
||||
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("blacklist")
|
||||
|
||||
for key in keys:
|
||||
if data.get(key) is None:
|
||||
data[key] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
data = {}
|
||||
for key in keys:
|
||||
data[key] = 0
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
|
|
@ -10,11 +17,9 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ blacklist_info or "Deny access based on internal and external
|
||||
IP/network/rDNS/ASN blacklists." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
|
@ -30,9 +35,7 @@
|
|||
>
|
||||
URL
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ blacklist_url_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count-url class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
|
|
@ -72,9 +75,7 @@
|
|||
>
|
||||
IP
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ blacklist_ip_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count-ip class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
|
|
@ -112,9 +113,7 @@
|
|||
>
|
||||
RDNS
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ blacklist_rdns_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count-rdns class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
|
|
@ -160,9 +159,7 @@
|
|||
>
|
||||
ASN
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ blacklist_asn_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count-asn class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
|
|
@ -200,9 +197,7 @@
|
|||
>
|
||||
User Agent
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ blacklist_user_agent_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count-user-agent class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
|
|
@ -231,6 +226,41 @@
|
|||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_blacklist_url: {
|
||||
el: document.querySelector("[data-count-url]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
counter_blacklist_ip: {
|
||||
el: document.querySelector("[data-count-ip]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
counter_blacklist_rdns: {
|
||||
el: document.querySelector("[data-count-rdns]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
counter_blacklist_asn: {
|
||||
el: document.querySelector("[data-count-asn]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
counter_blacklist_ua: {
|
||||
el: document.querySelector("[data-count-user-agent]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ local ngx = ngx
|
|||
local ERR = ngx.ERR
|
||||
local NOTICE = ngx.NOTICE
|
||||
local WARN = ngx.WARN
|
||||
local HTTP_INTERNAL_SERVER_ERROR = ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
local HTTP_OK = ngx.HTTP_OK
|
||||
local timer_at = ngx.timer.at
|
||||
local get_phase = ngx.get_phase
|
||||
local get_version = utils.get_version
|
||||
|
|
@ -28,6 +30,7 @@ local open = io.open
|
|||
local encode = cjson.encode
|
||||
local decode = cjson.decode
|
||||
local http_new = http.new
|
||||
local match = string.match
|
||||
|
||||
function bunkernet:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
|
|
@ -308,4 +311,28 @@ function bunkernet:report(ip, reason, reason_data, method, url, headers)
|
|||
return self:request("POST", "/report", data)
|
||||
end
|
||||
|
||||
function bunkernet:api()
|
||||
-- Match request
|
||||
if not match(self.ctx.bw.uri, "^/bunkernet/ping$") or self.ctx.bw.request_method ~= "POST" then
|
||||
return self:ret(false, "success")
|
||||
end
|
||||
-- Check id
|
||||
if not self.bunkernet_id then
|
||||
return self:ret(true, "missing instance ID", HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
-- Send ping request
|
||||
local ok, err, status, _ = self:ping()
|
||||
if not ok then
|
||||
return self:ret(true, "error while sending request to API : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
if status ~= 200 then
|
||||
return self:ret(
|
||||
true,
|
||||
"received status " .. tostring(status) .. " from API using instance ID " .. self.bunkernet_id,
|
||||
HTTP_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
end
|
||||
return self:ret(true, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful", HTTP_OK)
|
||||
end
|
||||
|
||||
return bunkernet
|
||||
|
|
|
|||
|
|
@ -1 +1,8 @@
|
|||
# Spoofing an action file
|
||||
def bunkernet():
|
||||
return {
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"info": "test",
|
||||
"status": "active",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,34 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- status -->
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
{% if bunkernet_status %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="mx-1 flex justify-start items-center">
|
||||
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
|
||||
<svg
|
||||
data-status-svg
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 100 100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-8 h-8 fill-green-500"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<circle cx="50" cy="50" r="50" />
|
||||
</svg>
|
||||
</div>
|
||||
<p
|
||||
data-status-text
|
||||
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
Active
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="mx-1 flex justify-start items-center">
|
||||
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-8 h-8 fill-red-500"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p
|
||||
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
Inactive
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- end status -->
|
||||
</div>
|
||||
|
|
@ -66,11 +41,9 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ bunkernet_info or "BunkerNet is a crowdsourced database of malicious
|
||||
requests shared between all BunkerWeb instances over the world. " }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
|
@ -180,6 +153,25 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
// value : active / inactive / unknown
|
||||
status: {
|
||||
el: document.querySelector("[data-status-svg]"),
|
||||
value: "unknown",
|
||||
type: "status",
|
||||
textEl: document.querySelector("[data-status-text]"),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- end test -->
|
||||
<script async>
|
||||
function ping() {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ function cors:header()
|
|||
and self.variables["CORS_ALLOW_ORIGIN"] ~= "*"
|
||||
and not regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"])
|
||||
then
|
||||
self:set_metric("counters", "failed_cors", 1)
|
||||
self.logger:log(WARN, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
|
||||
return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
# Spoofing an action file
|
||||
def cors(**kwargs):
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("cors")
|
||||
|
||||
if data.get("counter_failed_cors") is None:
|
||||
data["counter_failed_cors"] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
return {"counter_failed_cors": 0}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
|
|
@ -9,11 +17,9 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ cors_info or "Cross-Origin Resource Sharing lets you manage how your
|
||||
service can be contacted from different origins." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
|
@ -28,9 +34,7 @@
|
|||
>
|
||||
CORS
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ cors_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
|
||||
|
|
@ -55,12 +59,27 @@
|
|||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_failed_cors: {
|
||||
el: document.querySelector("[data-count]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ function country:access()
|
|||
.. ")"
|
||||
)
|
||||
end
|
||||
self:set_metric("counters", "failed_country", 1)
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP "
|
||||
|
|
@ -85,6 +86,7 @@ function country:access()
|
|||
if not ok then
|
||||
return self:ret(false, "error while adding item to cache : " .. err)
|
||||
end
|
||||
self:set_metric("counters", "failed_country", 1)
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country_data .. ")",
|
||||
|
|
@ -105,6 +107,7 @@ function country:access()
|
|||
if not ok then
|
||||
return self:ret(false, "error while adding item to cache : " .. err)
|
||||
end
|
||||
self:set_metric("counters", "failed_country", 1)
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country_data .. ")",
|
||||
|
|
|
|||
|
|
@ -1 +1,22 @@
|
|||
# Spoofing an action file
|
||||
def country():
|
||||
return {
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"info": "test",
|
||||
"blacklist_count": 3,
|
||||
"whitelist_count": 23,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def country(**kwargs):
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("country")
|
||||
|
||||
if data.get("counter_failed_country") is None:
|
||||
data["counter_failed_country"] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
return {"counter_failed_country": 0}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ country_info or "The country security feature allows you to apply
|
||||
policy based on the country of the IP address of clients (blacklist /
|
||||
whitelist)." }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
|
@ -31,13 +33,11 @@
|
|||
>
|
||||
Country
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ country_blacklist_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
|
||||
>blacklist request blocked</span
|
||||
>request blocked</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -58,54 +58,27 @@
|
|||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
Country
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ country_blacklist_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
|
||||
>whitelist request passed</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-green-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-75 leading-none text-lg relative stroke-green-700 fill-white"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_failed_country: {
|
||||
el: document.querySelector("[data-count]"),
|
||||
value: "",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# Spoofing an action file
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
|
||||
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
|
||||
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
|
||||
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-4 2xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ custom_certificate_info or "Custom certificates allow you to get
|
||||
HTTPS / SSL / TLS on your requests." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
{% if items|length != 0 %}
|
||||
|
||||
<div
|
||||
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
|
||||
CUSTOM CERTIFICATE LIST
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-span-12 overflow-y-auto overflow-x-auto">
|
||||
<!-- list container-->
|
||||
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
|
||||
<!-- header-->
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
Server name
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
CN
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
Expiry date
|
||||
</p>
|
||||
|
||||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['server_name']}}
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['cn']}}
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['expire']}}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1 +1,10 @@
|
|||
# Spoofing an action file
|
||||
def db():
|
||||
return {
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"info": "test",
|
||||
"driver": "SQLite",
|
||||
"version": "13.2",
|
||||
"size": "14.8",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
|
|
@ -10,50 +17,47 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- driver-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">DB</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center mt-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ db_info or "BunkerWeb securely stores its current configuration in
|
||||
a backend database, which contains essential data for smooth
|
||||
operation." }}
|
||||
DRIVER
|
||||
<span
|
||||
data-driver
|
||||
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex justify-start items-center mt-1 mb-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
VERSION
|
||||
<span
|
||||
data-version
|
||||
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end driver -->
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
<!-- driver-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">DB</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center mt-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
DRIVER
|
||||
<span
|
||||
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ db_driver or "unknown" }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex justify-start items-center mt-1 mb-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
VERSION
|
||||
<span
|
||||
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ db_version or "unknown" }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end driver -->
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
|
|
@ -64,9 +68,7 @@
|
|||
>
|
||||
SIZE
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ db_count or "unknown" }}
|
||||
</h5>
|
||||
<h5 data-size class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
|
||||
|
|
@ -102,6 +104,31 @@
|
|||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
driver: {
|
||||
el: document.querySelector("[data-driver]"),
|
||||
value: "",
|
||||
type: "text",
|
||||
},
|
||||
version: {
|
||||
el: document.querySelector("[data-version]"),
|
||||
value: "",
|
||||
type: "text",
|
||||
},
|
||||
size: {
|
||||
el: document.querySelector("[data-size]"),
|
||||
value: "",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ function dnsbl:access()
|
|||
if cached == "ok" then
|
||||
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (not blacklisted)")
|
||||
end
|
||||
self:set_metric("counters", "failed_dnsbl", 1)
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
# Spoofing an action file
|
||||
def dnsbl(**kwargs):
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("dnsbl")
|
||||
|
||||
if data.get("counter_failed_dnsbl") is None:
|
||||
data["counter_failed_dnsbl"] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
return {"counter_failed_dnsbl": 0}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
|
||||
"www.example.com", "status" : "ok"}, {"server_name" : "app1.com", "status" :
|
||||
"ok"}, {"server_name" : "test.2.fr", "status" : "ko"} ]%}
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
|
|
@ -11,87 +16,70 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ dnsbl_info or "Deny access based on external DNSBL servers." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
{% if items|length != 0 %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">DNSBL LIST</h5>
|
||||
</div>
|
||||
<div class="col-span-12 overflow-y-auto overflow-x-auto">
|
||||
<!-- list container-->
|
||||
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
|
||||
<!-- header-->
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-8 m-0 pb-2 border-b border-gray-400"
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
DNSBL
|
||||
</p>
|
||||
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
|
||||
>request blocked</span
|
||||
>
|
||||
Server name
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
Status
|
||||
</p>
|
||||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
|
||||
>
|
||||
{{item['server_name']}}
|
||||
</p>
|
||||
{% if item['status'] == "ko"%}
|
||||
<div class="col-span-4 ml-2 m-0 my-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6 fill-red-500"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-span-4 ml-2 m-0 my-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6 fill-green-500"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="scale-75 leading-none text-lg relative fill-red-700 stroke-white"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_failed_dnsbl: {
|
||||
el: document.querySelector("[data-count]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ local plugin = require "bunkerweb.plugin"
|
|||
|
||||
local ngx = ngx
|
||||
local subsystem = ngx.config.subsystem
|
||||
local tostring = tostring
|
||||
|
||||
local template
|
||||
local render = nil
|
||||
|
|
@ -69,6 +70,11 @@ function errors:initialize(ctx)
|
|||
}
|
||||
end
|
||||
|
||||
function errors:log()
|
||||
self:set_metric("counters", tostring(ngx.status), 1)
|
||||
return self:ret(true, "success")
|
||||
end
|
||||
|
||||
function errors:render_template(code)
|
||||
-- Render template
|
||||
render("error.html", {
|
||||
|
|
|
|||
|
|
@ -1 +1,13 @@
|
|||
# Spoofing an action file
|
||||
def errors(**kwargs):
|
||||
try:
|
||||
# Here we will have a list { 'counter_403': X, 'counter_401': Y ... }
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("errors")
|
||||
format_data = []
|
||||
# Format to fit [{code: 403, count: X}, {code: 401, count: Y} ...]
|
||||
for key, value in data.items():
|
||||
format_data[key] = {"code": int(key.split("_")[1]), "count": value}
|
||||
|
||||
return {"items": format_data}
|
||||
|
||||
except:
|
||||
return {"items": []}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [ {"count" : 74,
|
||||
"code" : "403"}, {"count" : 82, "code" : "404"}, {"count" : "32", "code" :
|
||||
"400"} ]%}
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
|
|
@ -11,17 +16,16 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ dnsbl_info or "Deny access based on external DNSBL servers." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
{% if items|length != 0 %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full md:max-w-[400px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
data-fetch-success-show
|
||||
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full md:max-w-[400px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">ERRORS LIST</h5>
|
||||
|
|
@ -43,29 +47,42 @@
|
|||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
data-item
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
data-name="code"
|
||||
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
|
||||
>
|
||||
{{item['code']}}
|
||||
</p>
|
||||
></p>
|
||||
<p
|
||||
data-name="count"
|
||||
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['count']}}
|
||||
</p>
|
||||
></p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
items: {
|
||||
el: document.querySelector("[data-item]"),
|
||||
value: [],
|
||||
type: "list",
|
||||
listNames: ["code", "count"],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ function greylist:access()
|
|||
end
|
||||
|
||||
-- Return
|
||||
self:set_metric("counters", "failed_greylist", 1)
|
||||
return self:ret(true, "not in greylist", get_deny_status())
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
# Spoofing an action file
|
||||
def greylist(**kwargs):
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("greylist")
|
||||
|
||||
if data.get("counter_failed_greylist") is None:
|
||||
data["counter_failed_greylist"] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
return {"counter_failed_greylist": 0}
|
||||
|
|
|
|||
|
|
@ -1,236 +1,83 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
GREYLIST
|
||||
</p>
|
||||
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
|
||||
>request blocked</span
|
||||
>
|
||||
{{ greylist_info or "Allow access while keeping security features
|
||||
based on internal and external IP/network/rDNS/ASN greylists. " }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
URL
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ greylist_url_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
denied
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-600"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-[0.6] leading-none text-lg relative fill-white"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="scale-75 leading-none text-lg relative fill-red-700 stroke-white"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M19.902 4.098a3.75 3.75 0 0 0-5.304 0l-4.5 4.5a3.75 3.75 0 0 0 1.035 6.037.75.75 0 0 1-.646 1.353 5.25 5.25 0 0 1-1.449-8.45l4.5-4.5a5.25 5.25 0 1 1 7.424 7.424l-1.757 1.757a.75.75 0 1 1-1.06-1.06l1.757-1.757a3.75 3.75 0 0 0 0-5.304Zm-7.389 4.267a.75.75 0 0 1 1-.353 5.25 5.25 0 0 1 1.449 8.45l-4.5 4.5a5.25 5.25 0 1 1-7.424-7.424l1.757-1.757a.75.75 0 1 1 1.06 1.06l-1.757 1.757a3.75 3.75 0 1 0 5.304 5.304l4.5-4.5a3.75 3.75 0 0 0-1.035-6.037.75.75 0 0 1-.354-1Z"
|
||||
clip-rule="evenodd"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
IP
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ greylist_ip_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
denied
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-lime-600"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-50 leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
d="M3.53 2.47a.75.75 0 0 0-1.06 1.06l18 18a.75.75 0 1 0 1.06-1.06l-18-18ZM20.25 5.507v11.561L5.853 2.671c.15-.043.306-.075.467-.094a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93ZM3.75 21V6.932l14.063 14.063L12 18.088l-7.165 3.583A.75.75 0 0 1 3.75 21Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
RDNS
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ greylist_rdns_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
denied
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-indigo-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-[0.6] leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
d="M11.625 16.5a1.875 1.875 0 1 0 0-3.75 1.875 1.875 0 0 0 0 3.75Z"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm6 16.5c.66 0 1.277-.19 1.797-.518l1.048 1.048a.75.75 0 0 0 1.06-1.06l-1.047-1.048A3.375 3.375 0 1 0 11.625 18Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
ASN
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ greylist_asn_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
denied
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-blue-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-[0.6] leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
User Agent
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ greylist_user_agent_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
|
||||
denied
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-amber-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-50 leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_failed_greylist: {
|
||||
el: document.querySelector("[data-count]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# Spoofing an action file
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
|
||||
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
|
||||
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
|
||||
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-4 2xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ lets_encrypt_info or "Let's Encrypt certificates for secure HTTP
|
||||
requests." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
{% if items|length != 0 %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
|
||||
LET'S ENCRYPT LIST
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-span-12 overflow-y-auto overflow-x-auto">
|
||||
<!-- list container-->
|
||||
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
|
||||
<!-- header-->
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
Server name
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
CN
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
Expiry date
|
||||
</p>
|
||||
|
||||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['server_name']}}
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['cn']}}
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['expire']}}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -158,6 +158,7 @@ function limit:access()
|
|||
end
|
||||
-- Limit reached
|
||||
if limited then
|
||||
self:set_metric("counters", "limited_uri_" .. self.ctx.bw.uri, 1)
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP "
|
||||
|
|
|
|||
|
|
@ -1 +1,13 @@
|
|||
# Spoofing an action file
|
||||
def limit(**kwargs):
|
||||
try:
|
||||
# Here we will have a list { 'limit_uri_url1': X, 'limit_uri_url2': Y ... }
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("limit")
|
||||
format_data = []
|
||||
# Format to fit [{url: "url1", count: X}, {url: "url2", count: Y} ...]
|
||||
for key, value in data.items():
|
||||
format_data[key] = {"url": key.replace("limit_uri_", ""), "count": value}
|
||||
|
||||
return {"items": format_data}
|
||||
|
||||
except:
|
||||
return {"items": []}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [ {"url" :
|
||||
"http://www.example.com", "count" : 24},{"url" : "http://www.example.com",
|
||||
"count" : 24} ]%}
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
|
|
@ -11,17 +16,16 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ limit_info or "Limit maximum number of requests and connections." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
{% if items|length != 0 %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
data-fetch-success-show
|
||||
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
|
||||
|
|
@ -46,29 +50,41 @@
|
|||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
data-item
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
data-name="url"
|
||||
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
|
||||
>
|
||||
{{item['url']}}
|
||||
</p>
|
||||
></p>
|
||||
<p
|
||||
data-name="count"
|
||||
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['count']}}
|
||||
</p>
|
||||
></p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
items: {
|
||||
el: document.querySelector("[data-item]"),
|
||||
value: [],
|
||||
type: "list",
|
||||
listNames: ["url", "count"],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,36 @@ local class = require "middleclass"
|
|||
local datastore = require "bunkerweb.datastore"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local lrucache = require "resty.lrucache"
|
||||
|
||||
local metrics = class("metrics", plugin)
|
||||
|
||||
local lru, err_lru = lrucache.new(100000)
|
||||
if not lru then
|
||||
require "bunkerweb.logger":new("METRICS"):log(ERR, "failed to instantiate LRU cache : " .. err_lru)
|
||||
end
|
||||
|
||||
local ngx = ngx
|
||||
local shared = ngx.shared
|
||||
local subsystem = ngx.config.subsystem
|
||||
local ERR = ngx.ERR
|
||||
local HTTP_INTERNAL_SERVER_ERROR = ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
local HTTP_OK = ngx.HTTP_OK
|
||||
local worker = ngx.worker
|
||||
local worker_id = worker.id
|
||||
|
||||
local get_reason = utils.get_reason
|
||||
local get_country = utils.get_country
|
||||
local has_variable = utils.has_variable
|
||||
local encode = cjson.encode
|
||||
local decode = cjson.decode
|
||||
|
||||
local match = string.match
|
||||
local time = os.time
|
||||
local tonumber = tonumber
|
||||
local tostring = tostring
|
||||
local table_insert = table.insert
|
||||
local table_remove = table.remove
|
||||
|
||||
function metrics:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
|
|
@ -32,9 +46,9 @@ function metrics:initialize(ctx)
|
|||
self.metrics_datastore = datastore:new(dict)
|
||||
end
|
||||
|
||||
function metrics:log()
|
||||
function metrics:log(bypass_checks)
|
||||
-- Don't go further if metrics is not enabled
|
||||
if self.variables["USE_METRICS"] == "no" then
|
||||
if not bypass_checks and self.variables["USE_METRICS"] == "no" then
|
||||
return self:ret(true, "metrics are disabled")
|
||||
end
|
||||
-- Store blocked requests
|
||||
|
|
@ -50,58 +64,169 @@ function metrics:log()
|
|||
end
|
||||
end
|
||||
local request = {
|
||||
date = os.time(),
|
||||
date = self.ctx.bw.start_time or time(),
|
||||
ip = self.ctx.bw.remote_addr,
|
||||
country = country,
|
||||
method = self.ctx.bw.request_method,
|
||||
url = self.ctx.bw.request_uri,
|
||||
status = ngx.status,
|
||||
["user-agent"] = self.ctx.bw.http_user_agent or "",
|
||||
user_agent = self.ctx.bw.http_user_agent or "",
|
||||
reason = reason,
|
||||
data = data,
|
||||
}
|
||||
local ok
|
||||
ok, err = self.metrics_datastore:safe_rpush("metrics_requests", encode(request))
|
||||
if not ok then
|
||||
self.logger:log(ERR, "can't save request to datastore : " .. err)
|
||||
-- Get current requests
|
||||
local requests = lru:get("requests")
|
||||
if not requests then
|
||||
requests = {}
|
||||
end
|
||||
-- Add current request
|
||||
table_insert(requests, request)
|
||||
-- Remove old requests
|
||||
local nb_delete = #requests - tonumber(self.variables["METRICS_MAX_BLOCKED_REQUESTS"])
|
||||
while nb_delete > 0 do
|
||||
table_remove(requests, 1)
|
||||
nb_delete = nb_delete - 1
|
||||
end
|
||||
-- Update worker cache
|
||||
lru:set("requests", requests)
|
||||
end
|
||||
-- Get metrics from plugins
|
||||
local all_metrics = self.ctx.bw.metrics
|
||||
if all_metrics then
|
||||
-- Loop on plugins
|
||||
for plugin, plugin_metrics in pairs(all_metrics) do
|
||||
-- Loop on kinds
|
||||
for kind, kind_metrics in pairs(plugin_metrics) do
|
||||
-- Increment counters
|
||||
if kind == "counters" then
|
||||
for metric_key, metric_value in pairs(kind_metrics) do
|
||||
local lru_key = plugin .. "_counter_" .. metric_key
|
||||
local metric_counter = lru:get(lru_key)
|
||||
if not metric_counter then
|
||||
metric_counter = metric_value
|
||||
else
|
||||
metric_counter = metric_counter + metric_value
|
||||
end
|
||||
lru:set(lru_key, metric_counter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self:ret(true, "success")
|
||||
end
|
||||
|
||||
function metrics:log_default()
|
||||
return self:log()
|
||||
local is_needed, err = has_variable("USE_METRICS", "yes")
|
||||
if is_needed == nil then
|
||||
return self:ret(false, "can't check USE_METRICS variable : " .. err)
|
||||
end
|
||||
if is_needed then
|
||||
return self:log(true)
|
||||
end
|
||||
return self:ret(true, "metrics not used")
|
||||
end
|
||||
|
||||
function metrics:timer()
|
||||
-- Check if metrics is used
|
||||
local is_needed, err = has_variable("USE_METRICS", "yes")
|
||||
if is_needed == nil then
|
||||
return self:ret(false, "can't check USE_METRICS variable : " .. err)
|
||||
end
|
||||
if not is_needed then
|
||||
return self:ret(true, "metrics not used")
|
||||
end
|
||||
|
||||
local ret = true
|
||||
local ret_err = "metrics updated"
|
||||
local wid = tostring(worker_id())
|
||||
|
||||
-- Purpose of following code is to populate the LRU cache.
|
||||
-- In case of a reload, everything in LRU cache is removed
|
||||
-- so we need to copy it from SHM cache if it exists.
|
||||
local setup = lru:get("setup")
|
||||
if not setup then
|
||||
for _, key in ipairs(self.metrics_datastore:keys()) do
|
||||
if key:match("_" .. wid .. "$") then
|
||||
local value, err = self.metrics_datastore:get(key)
|
||||
if not value and err ~= "not found" then
|
||||
ret = false
|
||||
ret_err = err
|
||||
self.logger:log(ERR, "error while checking " .. key .. " : " .. err)
|
||||
end
|
||||
if value then
|
||||
local ok, decoded = pcall(decode, value)
|
||||
if ok then
|
||||
value = decoded
|
||||
end
|
||||
lru:set(key:gsub("_" .. wid .. "$", ""), value)
|
||||
end
|
||||
end
|
||||
end
|
||||
lru:set("setup", true)
|
||||
end
|
||||
-- Loop on all keys
|
||||
for _, key in ipairs(lru:get_keys()) do
|
||||
-- Get LRU data
|
||||
local value = lru:get(key)
|
||||
if type(value) == "table" then
|
||||
value = encode(value)
|
||||
end
|
||||
-- Push to dict
|
||||
local ok, err = self.metrics_datastore:set(key .. "_" .. wid, value)
|
||||
if not ok then
|
||||
ret = false
|
||||
ret_err = err
|
||||
self.logger:log(ERR, "can't update " .. key .. "_" .. wid .. " : " .. err)
|
||||
end
|
||||
end
|
||||
-- Done
|
||||
return self:ret(ret, ret_err)
|
||||
end
|
||||
|
||||
function metrics:api()
|
||||
-- Match request
|
||||
if not match(self.ctx.bw.uri, "^/metrics/requests$") or self.ctx.bw.request_method ~= "GET" then
|
||||
if not match(self.ctx.bw.uri, "^/metrics/.+$") or self.ctx.bw.request_method ~= "GET" then
|
||||
return self:ret(false, "success")
|
||||
end
|
||||
|
||||
-- Get requests metrics
|
||||
local len, err = self.metrics_datastore:llen("metrics_requests")
|
||||
if not len then
|
||||
return self:ret(true, "error while getting length of metrics_requests : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
local i = 0
|
||||
local data = {}
|
||||
while i < len do
|
||||
local request
|
||||
request, err = self.metrics_datastore:lpop("metrics_requests")
|
||||
if request then
|
||||
table.insert(data, decode(request))
|
||||
else
|
||||
return self:ret(true, "error while getting metrics_requests : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
-- Extract filter parameter
|
||||
local filter = self.ctx.bw.uri:gsub("^/metrics/", "")
|
||||
-- Loop on keys
|
||||
local metrics_data = {}
|
||||
for _, key in ipairs(self.metrics_datastore:keys()) do
|
||||
-- Check if key starts with our filter
|
||||
if key:match("^" .. filter .. "_") then
|
||||
-- Get the value
|
||||
local data, err = self.metrics_datastore:get(key)
|
||||
if not data then
|
||||
return self:ret(true, "error while fetching requests : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
local metric_key = key:gsub("_[0-9]+$", ""):gsub("^" .. filter .. "_", "")
|
||||
if metric_key == "" then
|
||||
metric_key = filter
|
||||
end
|
||||
-- Table case
|
||||
local ok, decoded = pcall(decode, data)
|
||||
if ok then
|
||||
data = decoded
|
||||
end
|
||||
if type(data) == "table" then
|
||||
if not metrics_data[metric_key] then
|
||||
metrics_data[metric_key] = {}
|
||||
end
|
||||
for _, metric_value in ipairs(data) do
|
||||
table_insert(metrics_data[metric_key], metric_value)
|
||||
end
|
||||
-- Counter case
|
||||
else
|
||||
if not metrics_data[metric_key] then
|
||||
metrics_data[metric_key] = 0
|
||||
end
|
||||
metrics_data[metric_key] = metrics_data[metric_key] + data
|
||||
end
|
||||
end
|
||||
local ok
|
||||
ok, err = self.metrics_datastore:safe_rpush("metrics_requests", request)
|
||||
if not ok then
|
||||
self.logger:log(ERR, "can't save request to datastore : " .. err)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return self:ret(true, data, HTTP_OK)
|
||||
return self:ret(true, metrics_data, HTTP_OK)
|
||||
end
|
||||
|
||||
return metrics
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"stream": "partial",
|
||||
"settings": {
|
||||
"USE_METRICS": {
|
||||
"context": "global",
|
||||
"context": "multisite",
|
||||
"default": "yes",
|
||||
"help": "Enable collection and retrieval of internal metrics.",
|
||||
"id": "use-metrics",
|
||||
|
|
@ -22,6 +22,15 @@
|
|||
"label": "Metrics memory size",
|
||||
"regex": "^\\d+[kKmMgG]?$",
|
||||
"type": "text"
|
||||
},
|
||||
"METRICS_MAX_BLOCKED_REQUESTS": {
|
||||
"context": "global",
|
||||
"default": "100",
|
||||
"help": "Maximum number of blocked requests to store (per worker).",
|
||||
"id": "metrics-max-blocked-requests",
|
||||
"label": "Metrics max blocked requests",
|
||||
"regex": "^\\d+$",
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,18 @@ function misc:access()
|
|||
-- Check if method is allowed
|
||||
for allowed_method in self.variables["ALLOWED_METHODS"]:gmatch("[^|]+") do
|
||||
if method == allowed_method then
|
||||
self:set_metric("counters", "failed_method", 1)
|
||||
return self:ret(true, "method " .. method .. " is allowed")
|
||||
end
|
||||
end
|
||||
return self:ret(true, "method " .. method .. " is not allowed", HTTP_NOT_ALLOWED)
|
||||
end
|
||||
|
||||
function misc:log_default()
|
||||
if self.variables["DISABLE_DEFAULT_SERVER"] == "yes" then
|
||||
self:set_metric("counters", "failed_default", 1)
|
||||
end
|
||||
return self:ret(true, "success")
|
||||
end
|
||||
|
||||
return misc
|
||||
|
|
|
|||
|
|
@ -1 +1,14 @@
|
|||
# Spoofing an action file
|
||||
def misc(**kwargs):
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("misc")
|
||||
|
||||
if "counter_failed_default" not in data:
|
||||
data["counter_failed_default"] = 0
|
||||
|
||||
if "counter_failed_method" not in data:
|
||||
data["counter_failed_method"] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
return {"counter_failed_default": 0, "counter_failed_method": 0}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
|
|
@ -10,10 +17,9 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ misc_info or "Miscellaneous settings (methods, servers...)." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
|
@ -29,9 +35,10 @@
|
|||
>
|
||||
DEFAULT SERVER DISABLED
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ default_server_count or "unknown" }}
|
||||
</h5>
|
||||
<h5
|
||||
data-count-server-disabled
|
||||
class="mb-1 font-bold dark:text-white/90"
|
||||
></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
|
||||
|
|
@ -74,9 +81,10 @@
|
|||
>
|
||||
DISALLOWED METHODS
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ disallowed_methods_count or "unknown" }}
|
||||
</h5>
|
||||
<h5
|
||||
data-count-disallowed-methods
|
||||
class="mb-1 font-bold dark:text-white/90"
|
||||
></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
|
||||
|
|
@ -103,6 +111,26 @@
|
|||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_failed_default: {
|
||||
el: document.querySelector("[data-count-server-disabled]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
counter_failed_method: {
|
||||
el: document.querySelector("[data-count-disallowed-methods]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# Spoofing an action file
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ modsec_info or "ModSecurity is an open source, cross platform web application firewall (WAF) engine for Apache, IIS and Nginx that is developed by Trustwave's SpiderLabs." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
MODSECURITY
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">{{ modsec_count or "unknown" }}</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
|
||||
>request blocked
|
||||
</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="scale-75 leading-none text-lg relative fill-red-700 stroke-white"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
"antibot"
|
||||
],
|
||||
"headers": ["headers", "cors", "reverseproxy", "clientcache", "antibot"],
|
||||
"log": ["badbehavior", "bunkernet", "metrics"],
|
||||
"log": ["badbehavior", "bunkernet", "errors", "metrics"],
|
||||
"preread": [
|
||||
"whitelist",
|
||||
"blacklist",
|
||||
|
|
@ -42,5 +42,6 @@
|
|||
"reversescan"
|
||||
],
|
||||
"log_stream": ["badbehavior", "bunkernet"],
|
||||
"log_default": ["badbehavior", "bunkernet", "metrics"]
|
||||
"log_default": ["badbehavior", "bunkernet", "errors", "misc", "metrics"],
|
||||
"timer": ["metrics"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ local redis = class("redis", plugin)
|
|||
|
||||
local ngx = ngx
|
||||
local NOTICE = ngx.NOTICE
|
||||
local HTTP_INTERNAL_SERVER_ERROR = ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
local HTTP_OK = ngx.HTTP_OK
|
||||
local match = string.match
|
||||
|
||||
function redis:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
|
|
@ -34,4 +37,46 @@ function redis:init_worker()
|
|||
return self:ret(true, "success")
|
||||
end
|
||||
|
||||
function redis:api()
|
||||
if self.ctx.bw.uri == "/redis/ping" and self.ctx.bw.request_method == "POST" then
|
||||
-- Check redis connection
|
||||
local ok, err = self.clusterstore:connect(true)
|
||||
if not ok then
|
||||
return self:ret(true, "redis connect error : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
-- Send ping
|
||||
local ok, err = self.clusterstore:call("ping")
|
||||
self.clusterstore:close()
|
||||
if err then
|
||||
return self:ret(true, "error while sending ping command to redis server : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
if not ok then
|
||||
return self:ret(true, "redis ping command failed", HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
return self:ret(true, "success", HTTP_OK)
|
||||
end
|
||||
if self.ctx.bw.uri == "/redis/stats" and self.ctx.bw.request_method == "GET" then
|
||||
-- Connect to redis
|
||||
local ok, err = self.clusterstore:connect(true)
|
||||
if not ok then
|
||||
return self:ret(true, "redis connect error : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
-- Get number of keys
|
||||
local nb_keys, err = self.clusterstore:call("dbsize")
|
||||
self.clusterstore:close()
|
||||
if err then
|
||||
return self:ret(true, "error while sending dbsize command to redis server : " .. err, HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
if not ok then
|
||||
return self:ret(true, "redis dbsize command failed", HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
-- Return data
|
||||
local data = {
|
||||
redis_nb_keys = nb_keys
|
||||
}
|
||||
return self:ret(true, data, HTTP_OK)
|
||||
end
|
||||
return self:ret(false, "success")
|
||||
end
|
||||
|
||||
return redis
|
||||
|
|
|
|||
|
|
@ -1 +1,8 @@
|
|||
# Spoofing an action file
|
||||
def redis():
|
||||
return {
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"info": "test",
|
||||
"status": "active",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,34 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- status -->
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
{% if redis_status %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="mx-1 flex justify-start items-center">
|
||||
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
|
||||
<svg
|
||||
data-status-svg
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 100 100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-8 h-8 fill-green-500"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<circle cx="50" cy="50" r="50" />
|
||||
</svg>
|
||||
</div>
|
||||
<p
|
||||
data-status-text
|
||||
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
Active
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="mx-1 flex justify-start items-center">
|
||||
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-8 h-8 fill-red-500"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p
|
||||
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
Inactive
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- end status -->
|
||||
</div>
|
||||
|
||||
|
|
@ -66,11 +40,9 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ redis_info or "Redis server configuration when using BunkerWeb in
|
||||
cluster mode. " }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
|
@ -179,7 +151,25 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end test -->
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
// value : active / inactive / unknown
|
||||
status: {
|
||||
el: document.querySelector("[data-status-svg]"),
|
||||
value: "unknown",
|
||||
type: "status",
|
||||
textEl: document.querySelector("[data-status-text]"),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<script async>
|
||||
function ping() {
|
||||
let data = new FormData();
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ function reversescan:access()
|
|||
elseif cached == "open" then
|
||||
ret_threads = true
|
||||
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
|
||||
self:set_metric("counters", "failed_" .. port, 1)
|
||||
break
|
||||
-- Perform scan in a thread
|
||||
elseif not cached then
|
||||
|
|
@ -99,6 +100,7 @@ function reversescan:access()
|
|||
if open then
|
||||
ret_threads = true
|
||||
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
|
||||
self:set_metric("counters", "failed_" .. port, 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1 +1,13 @@
|
|||
# Spoofing an action file
|
||||
def reversescan(**kwargs):
|
||||
try:
|
||||
# Here we will have a list { 'counter_403': X, 'counter_401': Y ... }
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("reversescan")
|
||||
format_data = []
|
||||
# Format to fit [{code: 403, count: X}, {code: 401, count: Y} ...]
|
||||
for key, value in data.items():
|
||||
format_data[key] = {"port": int(key.split("_")[1]), "count": value}
|
||||
|
||||
return {"items": format_data}
|
||||
|
||||
except:
|
||||
return {"items": []}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [ {"port" : 4000,
|
||||
"count" : 400}, {"port" : 4400, "count" : 780}, {"port" : 5000, "count" : 40},
|
||||
]%}
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
|
|
@ -11,21 +16,16 @@
|
|||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ reversescan_info or "Reverse scan is a feature designed to detect
|
||||
open ports by establishing TCP connections with clients' IP addresses.
|
||||
Consider adding this feature if you want to detect possible open proxies
|
||||
or connections from servers." }}
|
||||
</p>
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
{% if items|length != 0 %}
|
||||
|
||||
<div
|
||||
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[500px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
data-fetch-success-show
|
||||
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[500px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
|
||||
|
|
@ -50,29 +50,41 @@
|
|||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
data-item
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
data-name="port"
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-5 m-0 my-1"
|
||||
>
|
||||
{{item['port']}}
|
||||
</p>
|
||||
></p>
|
||||
<p
|
||||
data-name="count"
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-7 m-0 my-1"
|
||||
>
|
||||
{{item['count']}}
|
||||
</p>
|
||||
></p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
items: {
|
||||
el: document.querySelector("[data-item]"),
|
||||
value: [],
|
||||
type: "list",
|
||||
listNames: ["port", "count"],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# Spoofing an action file
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
|
||||
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
|
||||
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
|
||||
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-4 2xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
{{ lets_encrypt_info or "Selfsigned certificates for secure HTTP
|
||||
requests." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
{% if items|length != 0 %}
|
||||
<div
|
||||
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div class="col-span-12">
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
|
||||
SELFSIGNED LIST
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-span-12 overflow-y-auto overflow-x-auto">
|
||||
<!-- list container-->
|
||||
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
|
||||
<!-- header-->
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
Server name
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
CN
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
|
||||
>
|
||||
Expiry date
|
||||
</p>
|
||||
|
||||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full">
|
||||
{% for item in items %}
|
||||
<li
|
||||
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
|
||||
>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['server_name']}}
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['cn']}}
|
||||
</p>
|
||||
<p
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
|
||||
>
|
||||
{{item['expire']}}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- end list-->
|
||||
</div>
|
||||
<!-- end list container-->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{% if USE_UI == "yes" +%}
|
||||
SecRule REQUEST_FILENAME "@rx /services$" "id:7771,ctl:ruleRemoveByTag=attack-rce,ctl:ruleRemoveByTag=attack-xss,ctl:ruleRemoveByTag=attack-generic,nolog"
|
||||
SecRule REQUEST_FILENAME "@rx /global_config$" "id:7772,ctl:ruleRemoveByTag=platform-pgsql,nolog"
|
||||
SecRule REQUEST_FILENAME "@rx /configs$" "id:7773,ctl:ruleRemoveByTag=language-shell,ctl:ruleRemoveByTag=attack-lfi,nolog"
|
||||
{% endif +%}
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
# Spoofing an action file
|
||||
def whitelist(**kwargs):
|
||||
try:
|
||||
data = kwargs["app"].config["INSTANCES"].get_metrics("whitelist")
|
||||
|
||||
if "counter_passed_whitelist" not in data:
|
||||
data["counter_passed_whitelist"] = 0
|
||||
|
||||
return data
|
||||
|
||||
except:
|
||||
return {"counter_passed_whitelist": 0}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,76 @@
|
|||
{% extends "base.html" %} {% block content %}
|
||||
<input
|
||||
type="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token }}"
|
||||
class="hidden"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<div class="col-span-12 grid grid-cols-12 gap-4">
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
<!-- info-->
|
||||
<div
|
||||
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
|
||||
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
<div class="mx-1 flex justify-start items-center my-4">
|
||||
<p
|
||||
data-info
|
||||
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
WHITELIST
|
||||
</p>
|
||||
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
|
||||
>request passed</span
|
||||
>
|
||||
{{ whitelist_info or "Allow access based on internal and external
|
||||
IP/network/rDNS/ASN whitelists." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
URL
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ whitelist_url_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
|
||||
passed
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-600"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-green-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m4.5 12.75 6 6 9-13.5"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-[0.6] leading-none text-lg relative fill-white"
|
||||
class="scale-75 leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M19.902 4.098a3.75 3.75 0 0 0-5.304 0l-4.5 4.5a3.75 3.75 0 0 0 1.035 6.037.75.75 0 0 1-.646 1.353 5.25 5.25 0 0 1-1.449-8.45l4.5-4.5a5.25 5.25 0 1 1 7.424 7.424l-1.757 1.757a.75.75 0 1 1-1.06-1.06l1.757-1.757a3.75 3.75 0 0 0 0-5.304Zm-7.389 4.267a.75.75 0 0 1 1-.353 5.25 5.25 0 0 1 1.449 8.45l-4.5 4.5a5.25 5.25 0 1 1-7.424-7.424l1.757-1.757a.75.75 0 1 1 1.06 1.06l-1.757 1.757a3.75 3.75 0 1 0 5.304 5.304l4.5-4.5a3.75 3.75 0 0 0-1.035-6.037.75.75 0 0 1-.354-1Z"
|
||||
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
|
@ -62,175 +78,20 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
IP
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ whitelist_ip_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
|
||||
passed
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-lime-600"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-50 leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
d="M3.53 2.47a.75.75 0 0 0-1.06 1.06l18 18a.75.75 0 1 0 1.06-1.06l-18-18ZM20.25 5.507v11.561L5.853 2.671c.15-.043.306-.075.467-.094a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93ZM3.75 21V6.932l14.063 14.063L12 18.088l-7.165 3.583A.75.75 0 0 1 3.75 21Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
RDNS
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ whitelist_rdns_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
|
||||
passed
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-indigo-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-[0.6] leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
d="M11.625 16.5a1.875 1.875 0 1 0 0-3.75 1.875 1.875 0 0 0 0 3.75Z"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm6 16.5c.66 0 1.277-.19 1.797-.518l1.048 1.048a.75.75 0 0 0 1.06-1.06l-1.047-1.048A3.375 3.375 0 1 0 11.625 18Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
ASN
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ whitelist_asn_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
|
||||
passed
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-blue-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-[0.6] leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<!-- text -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
|
||||
>
|
||||
User Agent
|
||||
</p>
|
||||
<h5 class="mb-1 font-bold dark:text-white/90">
|
||||
{{ whitelist_user_agent_count or "unknown" }}
|
||||
</h5>
|
||||
|
||||
<p class="mb-0 dark:text-white dark:opacity-60">
|
||||
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
|
||||
passed
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div
|
||||
role="img"
|
||||
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-amber-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="scale-50 leading-none text-lg relative fill-white"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</div>
|
||||
<script>
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: "{{ plugin['description'] or ''}}",
|
||||
type: "text",
|
||||
},
|
||||
counter_passed_whitelist: {
|
||||
el: document.querySelector("[data-count]"),
|
||||
value: "unknown",
|
||||
type: "text",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ function whitelist:access()
|
|||
ngx_var.is_whitelisted = "yes"
|
||||
self.ctx.bw.is_whitelisted = "yes"
|
||||
env_set("is_whitelisted", "yes")
|
||||
self:set_metric("counters", "passed_whitelist", 1)
|
||||
return self:ret(true, err, OK)
|
||||
end
|
||||
-- Perform checks
|
||||
|
|
@ -155,7 +156,8 @@ function whitelist:access()
|
|||
ngx_var.is_whitelisted = "yes"
|
||||
self.ctx.bw.is_whitelisted = "yes"
|
||||
env_set("is_whitelisted", "yes")
|
||||
return self:ret(true, k .. " is whitelisted (info : " .. whitelisted .. ")", ngx.OK)
|
||||
self:set_metric("counters", "passed_whitelist", 1)
|
||||
return self:ret(true, k .. " is whitelisted (info : " .. whitelisted .. ")", OK)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ class Database:
|
|||
|
||||
def get_metadata(self) -> Dict[str, str]:
|
||||
"""Get the metadata from the database"""
|
||||
data = {"version": "1.5.4", "integration": "unknown"}
|
||||
data = {"version": "1.5.6", "integration": "unknown"}
|
||||
with self.__db_session() as session:
|
||||
with suppress(ProgrammingError, OperationalError):
|
||||
metadata = session.query(Metadata).with_entities(Metadata.version, Metadata.integration).filter_by(id=1).first()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
cryptography==42.0.1
|
||||
cryptography==42.0.2
|
||||
psycopg[binary,pool]==3.1.17
|
||||
PyMySQL==1.1.0
|
||||
sqlalchemy==2.0.25
|
||||
|
|
|
|||
|
|
@ -58,39 +58,39 @@ cffi==1.16.0 \
|
|||
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
|
||||
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
|
||||
# via cryptography
|
||||
cryptography==42.0.1 \
|
||||
--hash=sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4 \
|
||||
--hash=sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6 \
|
||||
--hash=sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e \
|
||||
--hash=sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881 \
|
||||
--hash=sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323 \
|
||||
--hash=sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77 \
|
||||
--hash=sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824 \
|
||||
--hash=sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407 \
|
||||
--hash=sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60 \
|
||||
--hash=sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411 \
|
||||
--hash=sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14 \
|
||||
--hash=sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0 \
|
||||
--hash=sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04 \
|
||||
--hash=sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77 \
|
||||
--hash=sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302 \
|
||||
--hash=sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c \
|
||||
--hash=sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986 \
|
||||
--hash=sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa \
|
||||
--hash=sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11 \
|
||||
--hash=sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8 \
|
||||
--hash=sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc \
|
||||
--hash=sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49 \
|
||||
--hash=sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035 \
|
||||
--hash=sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca \
|
||||
--hash=sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7 \
|
||||
--hash=sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2 \
|
||||
--hash=sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f \
|
||||
--hash=sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404 \
|
||||
--hash=sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453 \
|
||||
--hash=sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009 \
|
||||
--hash=sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa \
|
||||
--hash=sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7
|
||||
cryptography==42.0.2 \
|
||||
--hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \
|
||||
--hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \
|
||||
--hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \
|
||||
--hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \
|
||||
--hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \
|
||||
--hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \
|
||||
--hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \
|
||||
--hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \
|
||||
--hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \
|
||||
--hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \
|
||||
--hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \
|
||||
--hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \
|
||||
--hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \
|
||||
--hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \
|
||||
--hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \
|
||||
--hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \
|
||||
--hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \
|
||||
--hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \
|
||||
--hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \
|
||||
--hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \
|
||||
--hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \
|
||||
--hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \
|
||||
--hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \
|
||||
--hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \
|
||||
--hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \
|
||||
--hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \
|
||||
--hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \
|
||||
--hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \
|
||||
--hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \
|
||||
--hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \
|
||||
--hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \
|
||||
--hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f
|
||||
# via -r requirements.in
|
||||
greenlet==3.0.3 \
|
||||
--hash=sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67 \
|
||||
|
|
|
|||
|
|
@ -298,9 +298,9 @@ six==1.16.0 \
|
|||
# via
|
||||
# kubernetes
|
||||
# python-dateutil
|
||||
urllib3==2.1.0 \
|
||||
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
|
||||
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
|
||||
urllib3==2.2.0 \
|
||||
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
|
||||
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
|
||||
# via
|
||||
# docker
|
||||
# kubernetes
|
||||
|
|
|
|||
|
|
@ -181,9 +181,9 @@ toposort==1.10 \
|
|||
--hash=sha256:bfbb479c53d0a696ea7402601f4e693c97b0367837c8898bc6471adfca37a6bd \
|
||||
--hash=sha256:cbdbc0d0bee4d2695ab2ceec97fe0679e9c10eab4b2a87a9372b929e70563a87
|
||||
# via pip-compile-multi
|
||||
urllib3==2.1.0 \
|
||||
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
|
||||
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
|
||||
urllib3==2.2.0 \
|
||||
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
|
||||
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
|
||||
# via requests
|
||||
wheel==0.42.0 \
|
||||
--hash=sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d \
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
certbot==2.8.0
|
||||
configobj==5.0.8
|
||||
cryptography==42.0.1
|
||||
cryptography==42.0.2
|
||||
maxminddb==2.5.2
|
||||
python-magic==0.4.27
|
||||
schedule==1.2.1
|
||||
|
|
|
|||
|
|
@ -172,39 +172,39 @@ configobj==5.0.8 \
|
|||
# via
|
||||
# -r requirements.in
|
||||
# certbot
|
||||
cryptography==42.0.1 \
|
||||
--hash=sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4 \
|
||||
--hash=sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6 \
|
||||
--hash=sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e \
|
||||
--hash=sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881 \
|
||||
--hash=sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323 \
|
||||
--hash=sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77 \
|
||||
--hash=sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824 \
|
||||
--hash=sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407 \
|
||||
--hash=sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60 \
|
||||
--hash=sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411 \
|
||||
--hash=sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14 \
|
||||
--hash=sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0 \
|
||||
--hash=sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04 \
|
||||
--hash=sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77 \
|
||||
--hash=sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302 \
|
||||
--hash=sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c \
|
||||
--hash=sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986 \
|
||||
--hash=sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa \
|
||||
--hash=sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11 \
|
||||
--hash=sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8 \
|
||||
--hash=sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc \
|
||||
--hash=sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49 \
|
||||
--hash=sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035 \
|
||||
--hash=sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca \
|
||||
--hash=sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7 \
|
||||
--hash=sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2 \
|
||||
--hash=sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f \
|
||||
--hash=sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404 \
|
||||
--hash=sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453 \
|
||||
--hash=sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009 \
|
||||
--hash=sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa \
|
||||
--hash=sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7
|
||||
cryptography==42.0.2 \
|
||||
--hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \
|
||||
--hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \
|
||||
--hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \
|
||||
--hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \
|
||||
--hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \
|
||||
--hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \
|
||||
--hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \
|
||||
--hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \
|
||||
--hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \
|
||||
--hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \
|
||||
--hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \
|
||||
--hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \
|
||||
--hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \
|
||||
--hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \
|
||||
--hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \
|
||||
--hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \
|
||||
--hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \
|
||||
--hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \
|
||||
--hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \
|
||||
--hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \
|
||||
--hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \
|
||||
--hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \
|
||||
--hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \
|
||||
--hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \
|
||||
--hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \
|
||||
--hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \
|
||||
--hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \
|
||||
--hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \
|
||||
--hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \
|
||||
--hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \
|
||||
--hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \
|
||||
--hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f
|
||||
# via
|
||||
# -r requirements.in
|
||||
# acme
|
||||
|
|
@ -303,9 +303,9 @@ python-magic==0.4.27 \
|
|||
--hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
|
||||
--hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
|
||||
# via -r requirements.in
|
||||
pytz==2023.3.post1 \
|
||||
--hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \
|
||||
--hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7
|
||||
pytz==2023.4 \
|
||||
--hash=sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40 \
|
||||
--hash=sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a
|
||||
# via
|
||||
# acme
|
||||
# certbot
|
||||
|
|
@ -328,9 +328,9 @@ six==1.16.0 \
|
|||
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
|
||||
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
|
||||
# via configobj
|
||||
urllib3==2.1.0 \
|
||||
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
|
||||
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
|
||||
urllib3==2.2.0 \
|
||||
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
|
||||
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
|
||||
# via requests
|
||||
zipp==3.17.0 \
|
||||
--hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \
|
||||
|
|
|
|||
116
src/ui/main.py
116
src/ui/main.py
|
|
@ -41,7 +41,6 @@ from tarfile import CompressionError, HeaderError, ReadError, TarError, open as
|
|||
from threading import Thread
|
||||
from tempfile import NamedTemporaryFile
|
||||
from time import sleep, time
|
||||
from traceback import format_exc
|
||||
from zipfile import BadZipFile, ZipFile
|
||||
|
||||
from src.Instances import Instances
|
||||
|
|
@ -191,7 +190,6 @@ try:
|
|||
WTF_CSRF_SSL_STRICT=False,
|
||||
USER=USER,
|
||||
SEND_FILE_MAX_AGE_DEFAULT=86400,
|
||||
PLUGIN_ARGS={},
|
||||
RELOADING=False,
|
||||
LAST_RELOAD=0,
|
||||
TO_FLASH=[],
|
||||
|
|
@ -1188,21 +1186,6 @@ def plugins():
|
|||
|
||||
return redirect(url_for("loading", next=url_for("plugins"), message="Reloading plugins"))
|
||||
|
||||
plugin_args = app.config["PLUGIN_ARGS"]
|
||||
app.config["PLUGIN_ARGS"] = {}
|
||||
|
||||
if request.args.get("plugin_id", False):
|
||||
plugin_id = request.args.get("plugin_id")
|
||||
page = db.get_plugin_template(plugin_id)
|
||||
|
||||
if page:
|
||||
return render_template(
|
||||
Environment(loader=FileSystemLoader(join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")).from_string(page.decode("utf-8")),
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
username=current_user.get_id(),
|
||||
**(app.jinja_env.globals | (plugin_args["args"] if plugin_args.get("plugin", None) == plugin_id else {})),
|
||||
)
|
||||
|
||||
plugins = app.config["CONFIG"].get_plugins()
|
||||
plugins_internal = 0
|
||||
plugins_external = 0
|
||||
|
|
@ -1218,7 +1201,6 @@ def plugins():
|
|||
plugins=plugins,
|
||||
plugins_internal=plugins_internal,
|
||||
plugins_external=plugins_external,
|
||||
plugins_errors=db.get_plugins_errors(),
|
||||
username=current_user.get_id(),
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
)
|
||||
|
|
@ -1276,22 +1258,44 @@ def upload_plugin():
|
|||
|
||||
@app.route("/plugins/<plugin>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def custom_plugin(plugin):
|
||||
def custom_plugin(plugin: str):
|
||||
plugins = app.config["CONFIG"].get_plugins()
|
||||
|
||||
curr_plugin = {}
|
||||
for plug in plugins:
|
||||
if plug["id"] == plugin:
|
||||
curr_plugin = plug
|
||||
break
|
||||
|
||||
message = ""
|
||||
if not plugin_id_rx.match(plugin):
|
||||
flash(
|
||||
f"Invalid plugin id, <b>{plugin}</b> (must be between 1 and 64 characters, only letters, numbers, underscores and hyphens)",
|
||||
"error",
|
||||
)
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
message = f'Invalid plugin id, "{plugin}" (must be between 1 and 64 characters, only letters, numbers, underscores and hyphens)'
|
||||
app.logger.error(message)
|
||||
if request.method == "GET":
|
||||
return message, 400
|
||||
return {"message": f'Invalid plugin id, "{plugin}" (must be between 1 and 64 characters, only letters, numbers, underscores and hyphens)'}, 400
|
||||
|
||||
if request.method == "GET":
|
||||
page = db.get_plugin_template(plugin)
|
||||
|
||||
if page:
|
||||
return render_template(
|
||||
Environment(loader=FileSystemLoader(join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")).from_string(page.decode("utf-8")),
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
username=current_user.get_id(),
|
||||
current_endpoint=plugin,
|
||||
plugin=curr_plugin,
|
||||
**app.jinja_env.globals,
|
||||
)
|
||||
|
||||
message = f'The plugin "{plugin}" does not have a template'
|
||||
app.logger.error(message)
|
||||
return message, 404
|
||||
|
||||
module = db.get_plugin_actions(plugin)
|
||||
|
||||
if module is None:
|
||||
flash(
|
||||
f"The <i>actions.py</i> file for the plugin <b>{plugin}</b> does not exist",
|
||||
"error",
|
||||
)
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
return {"message": f'The actions.py file for the plugin "{plugin}" does not exist'}, 404
|
||||
|
||||
try:
|
||||
# Try to import the custom plugin
|
||||
|
|
@ -1302,32 +1306,28 @@ def custom_plugin(plugin):
|
|||
loader = SourceFileLoader("actions", temp.name)
|
||||
actions = loader.load_module()
|
||||
except:
|
||||
flash(
|
||||
f"An error occurred while importing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
|
||||
"error",
|
||||
)
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
message = f'An error occurred while importing the plugin "{plugin}", see logs for more details'
|
||||
app.logger.exception(message)
|
||||
return {"message": message}, 500
|
||||
|
||||
error = False
|
||||
error = None
|
||||
res = None
|
||||
|
||||
try:
|
||||
# Try to get the custom plugin custom function and call it
|
||||
method = getattr(actions, plugin)
|
||||
res = method()
|
||||
if request.args:
|
||||
res = method(app=app, args=request.args.to_dict())
|
||||
elif request.is_json:
|
||||
res = method(app=app, args=request.json)
|
||||
else:
|
||||
res = method(app=app)
|
||||
except AttributeError:
|
||||
flash(
|
||||
f"The plugin <b>{plugin}</b> does not have a <i>{plugin}</i> method",
|
||||
"error",
|
||||
)
|
||||
error = True
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
message = f'The plugin "{plugin}" does not have a "{plugin}" method, see logs for more details'
|
||||
error = 404
|
||||
except:
|
||||
flash(
|
||||
f"An error occurred while executing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
|
||||
"error",
|
||||
)
|
||||
error = True
|
||||
message = f'An error occurred while executing the plugin "{plugin}", see logs for more details'
|
||||
error = 500
|
||||
finally:
|
||||
if sbin_nginx_path.is_file():
|
||||
# Remove the custom plugin from the shared library
|
||||
|
|
@ -1335,13 +1335,15 @@ def custom_plugin(plugin):
|
|||
sys_modules.pop("actions")
|
||||
del actions
|
||||
|
||||
if request.method != "POST" or error is True or res is None or isinstance(res, dict) is False:
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
if message or not isinstance(res, dict) and not res:
|
||||
message = message or f'The plugin "{plugin}" did not return a valid response'
|
||||
if error:
|
||||
app.logger.exception(message)
|
||||
else:
|
||||
app.logger.error(message)
|
||||
|
||||
app.config["PLUGIN_ARGS"] = {"plugin": plugin, "args": res}
|
||||
|
||||
flash(f"Your action <b>{plugin}</b> has been executed")
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
app.logger.info(f"Plugin {plugin} action executed successfully")
|
||||
return jsonify({"message": "ok", "data": res}), 200
|
||||
|
||||
|
||||
@app.route("/cache", methods=["GET"])
|
||||
|
|
@ -1580,9 +1582,11 @@ def logs_container(container_id):
|
|||
logs.append(
|
||||
{
|
||||
"content": log,
|
||||
"type": "error"
|
||||
if "[error]" in log_lower or "[crit]" in log_lower or "[alert]" in log_lower or "❌" in log_lower
|
||||
else ("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "ℹ️" in log_lower else "message")),
|
||||
"type": (
|
||||
"error"
|
||||
if "[error]" in log_lower or "[crit]" in log_lower or "[alert]" in log_lower or "❌" in log_lower
|
||||
else ("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "ℹ️" in log_lower else "message"))
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from os import sep
|
||||
from os.path import join
|
||||
from pathlib import Path
|
||||
|
|
@ -118,6 +117,9 @@ class Instance:
|
|||
def reports(self) -> Tuple[bool, dict[str, Any]]:
|
||||
return self.apiCaller.send_to_apis("GET", "/metrics/requests", response=True)
|
||||
|
||||
def metrics(self, plugin_id) -> Tuple[bool, dict[str, Any]]:
|
||||
return self.apiCaller.send_to_apis("GET", f"/metrics/{plugin_id}", response=True)
|
||||
|
||||
|
||||
class Instances:
|
||||
def __init__(self, docker_client, kubernetes_client, integration: str):
|
||||
|
|
@ -352,15 +354,72 @@ class Instances:
|
|||
resp, instance_reports = instance.reports()
|
||||
if not resp:
|
||||
return []
|
||||
return instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", [])
|
||||
return instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", {"requests": []})["requests"]
|
||||
|
||||
reports: List[dict[str, Any]] = []
|
||||
for instance in self.get_instances():
|
||||
resp, instance_reports = instance.reports()
|
||||
try:
|
||||
resp, instance_reports = instance.reports()
|
||||
except:
|
||||
continue
|
||||
|
||||
if not resp:
|
||||
continue
|
||||
reports.extend(instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", []))
|
||||
reports.extend(instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", {"requests": []})["requests"])
|
||||
|
||||
reports.sort(key=lambda x: x["date"], reverse=True)
|
||||
|
||||
return reports
|
||||
|
||||
def get_metrics(self, plugin_id: str):
|
||||
# Get metrics from all instances
|
||||
metrics = {}
|
||||
for instance in self.get_instances():
|
||||
try:
|
||||
resp, instance_metrics = instance.metrics(plugin_id)
|
||||
except:
|
||||
continue
|
||||
|
||||
# filters
|
||||
if not resp:
|
||||
continue
|
||||
|
||||
if instance.name not in instance_metrics or instance_metrics[instance.name]["msg"] is None or instance_metrics[instance.name]["msg"] is not dict or instance_metrics[instance.name]["status"] != "success":
|
||||
continue
|
||||
|
||||
metric_data = instance_metrics[instance.name]["msg"]
|
||||
|
||||
# Update metrics looking for value type
|
||||
for key, value in metric_data.items():
|
||||
if key not in metrics:
|
||||
metrics[key] = value
|
||||
continue
|
||||
|
||||
# Case value is number, add it to the existing value
|
||||
if isinstance(value, (int, float)):
|
||||
metrics[key] += value
|
||||
continue
|
||||
# Case value is string, replace the existing value
|
||||
elif isinstance(value, str):
|
||||
metrics[key] = value
|
||||
continue
|
||||
# Case value is list, extend it to the existing value
|
||||
if isinstance(value, list):
|
||||
metrics[key].extend(value)
|
||||
continue
|
||||
# Case value is a dict, loop on it and update the existing value
|
||||
if isinstance(value, dict):
|
||||
for k, v in value.items():
|
||||
if k not in metrics[key]:
|
||||
metrics[key][k] = v
|
||||
continue
|
||||
if isinstance(v, (int, float)):
|
||||
metrics[key][k] += v
|
||||
continue
|
||||
if isinstance(v, list):
|
||||
metrics[key][k].extend(v)
|
||||
continue
|
||||
if isinstance(v, str):
|
||||
metrics[key][k] = v
|
||||
continue
|
||||
return metrics
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
256
src/ui/static/js/plugins/setup.js
Normal file
256
src/ui/static/js/plugins/setup.js
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
class SetupPlugin {
|
||||
constructor(data) {
|
||||
// Set data defaults elements and variables
|
||||
// Key of this.data need to match key of fetch data json object to update values
|
||||
// type<str> : text (target el), list (el need to be first element of list)
|
||||
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
|
||||
this.data = data;
|
||||
/* EXAMPLE
|
||||
{
|
||||
info: {
|
||||
el: document.querySelector("[data-info]"),
|
||||
value: `Anti-bot technology is designed to detect and mitigate suspicious or
|
||||
malicious bots, preventing them from reaching an organization's websites
|
||||
or IT ecosystem.`,
|
||||
type: "text",
|
||||
},
|
||||
|
||||
items: {
|
||||
el: document.querySelector("[data-item]"),
|
||||
value: [],
|
||||
type: "list",
|
||||
listNames: ["server_name", "cn", "expire"],
|
||||
},
|
||||
// value : active / inactive / unknown
|
||||
status: {
|
||||
el: document.querySelector("[data-status]"),
|
||||
value: "unknown",
|
||||
type: "status",
|
||||
textEl: document.querySelector("[data-status-text]"),
|
||||
},
|
||||
*/
|
||||
// Hidden elements that will be shown on success, like ping buttons or list rendering
|
||||
this.showOnSuccessEls = document.querySelectorAll(
|
||||
"[data-fetch-success-show]"
|
||||
);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
this.createAlertEl();
|
||||
// Set default values and fetch
|
||||
this.updateDataDOM();
|
||||
this.updateAlert("fetch");
|
||||
|
||||
fetch(location.href, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRFToken": document.querySelector('input[name="csrf_token"]')
|
||||
.value,
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
// Update data and DOM
|
||||
this.getFetchDataByKey(res.data);
|
||||
this.updateDataDOM();
|
||||
// Show hidden elements
|
||||
this.showSuccessEls();
|
||||
// Feedback
|
||||
this.updateAlert("success");
|
||||
})
|
||||
.catch((error) => {
|
||||
this.updateAlert("error");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createAlertEl() {
|
||||
// Container
|
||||
this.alertEl = this.createEl(
|
||||
"div",
|
||||
[
|
||||
["data-fetch", ""],
|
||||
["role", "alert"],
|
||||
],
|
||||
"bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border",
|
||||
"",
|
||||
""
|
||||
);
|
||||
|
||||
this.alertCloseEl = this.createEl(
|
||||
"button",
|
||||
[["data-fetch-close", ""]],
|
||||
"absolute right-7 top-1.5",
|
||||
"",
|
||||
this.alertEl
|
||||
);
|
||||
|
||||
this.alertCloseIconEl = this.createEl(
|
||||
"svg",
|
||||
[
|
||||
["xmlns", "http://www.w3.org/2000/svg"],
|
||||
["viewBox", "0 0 320 512"],
|
||||
],
|
||||
"cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5",
|
||||
"",
|
||||
this.alertCloseEl
|
||||
);
|
||||
|
||||
// Close icon paths
|
||||
const paths = [
|
||||
"M11.7 2.805a.75.75 0 0 1 .6 0A60.65 60.65 0 0 1 22.83 8.72a.75.75 0 0 1-.231 1.337 49.948 49.948 0 0 0-9.902 3.912l-.003.002c-.114.06-.227.119-.34.18a.75.75 0 0 1-.707 0A50.88 50.88 0 0 0 7.5 12.173v-.224c0-.131.067-.248.172-.311a54.615 54.615 0 0 1 4.653-2.52.75.75 0 0 0-.65-1.352 56.123 56.123 0 0 0-4.78 2.589 1.858 1.858 0 0 0-.859 1.228 49.803 49.803 0 0 0-4.634-1.527.75.75 0 0 1-.231-1.337A60.653 60.653 0 0 1 11.7 2.805Z",
|
||||
,
|
||||
"M13.06 15.473a48.45 48.45 0 0 1 7.666-3.282c.134 1.414.22 2.843.255 4.284a.75.75 0 0 1-.46.711 47.87 47.87 0 0 0-8.105 4.342.75.75 0 0 1-.832 0 47.87 47.87 0 0 0-8.104-4.342.75.75 0 0 1-.461-.71c.035-1.442.121-2.87.255-4.286.921.304 1.83.634 2.726.99v1.27a1.5 1.5 0 0 0-.14 2.508c-.09.38-.222.753-.397 1.11.452.213.901.434 1.346.66a6.727 6.727 0 0 0 .551-1.607 1.5 1.5 0 0 0 .14-2.67v-.645a48.549 48.549 0 0 1 3.44 1.667 2.25 2.25 0 0 0 2.12 0Z",
|
||||
,
|
||||
"M4.462 19.462c.42-.419.753-.89 1-1.395.453.214.902.435 1.347.662a6.742 6.742 0 0 1-1.286 1.794.75.75 0 0 1-1.06-1.06Z",
|
||||
];
|
||||
paths.forEach((path) => {
|
||||
this.createEl("path", [["d", path]], "", "", this.alertCloseIconEl);
|
||||
});
|
||||
// Status
|
||||
this.alertStatusEl = this.createEl(
|
||||
"h5",
|
||||
[["data-fetch-status", ""]],
|
||||
"text-lg mb-0 text-white dark:text-gray-300",
|
||||
"Fetching",
|
||||
this.alertEl
|
||||
);
|
||||
|
||||
this.alertMsgEl = this.createEl(
|
||||
"p",
|
||||
[["data-fetch-msg", ""]],
|
||||
"text-white dark:text-gray-300 mb-0 text-sm",
|
||||
"Please wait...",
|
||||
this.alertEl
|
||||
);
|
||||
|
||||
document.body.appendChild(this.alertEl);
|
||||
|
||||
this.alertCloseEl.addEventListener("click", () => {
|
||||
this.alertEl.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
createEl(tag, attArr, className, text, parent) {
|
||||
const el = document.createElement(tag);
|
||||
attArr.forEach((att) => {
|
||||
el.setAttribute(att[0], att[1]);
|
||||
});
|
||||
if (className) el.className = className;
|
||||
if (text) el.textContent = text;
|
||||
if (parent) parent.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
showSuccessEls() {
|
||||
this.showOnSuccessEls.forEach((el) => {
|
||||
el.classList.remove("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
// Key of fetch data need to match key of this.data
|
||||
getFetchDataByKey(fetchDataObj) {
|
||||
for (const [key, value] of Object.entries(this.data)) {
|
||||
// Case list
|
||||
if (Array.isArray(fetchDataObj[key])) {
|
||||
value["value"] = fetchDataObj[key] || value["value"] || "";
|
||||
continue;
|
||||
}
|
||||
// Case number
|
||||
if (!isNaN(fetchDataObj[key])) {
|
||||
value["value"] = fetchDataObj[key] == 0 ? "0" : fetchDataObj[key];
|
||||
continue;
|
||||
}
|
||||
// Others
|
||||
value["value"] = fetchDataObj[key] || value["value"] || "";
|
||||
}
|
||||
}
|
||||
|
||||
updateDataDOM() {
|
||||
for (const [key, val] of Object.entries(this.data)) {
|
||||
const el = val["el"];
|
||||
const type = val["type"];
|
||||
const value = val["value"];
|
||||
|
||||
// Case text
|
||||
if (type === "text") {
|
||||
el.textContent = value || "";
|
||||
continue;
|
||||
}
|
||||
// Case status
|
||||
if (type === "status") {
|
||||
const textEl = val["textEl"] || null;
|
||||
|
||||
if (value === "active")
|
||||
this.setStatus(el, textEl, "fill-green-500", "Active");
|
||||
if (value === "inactive")
|
||||
this.setStatus(el, textEl, "fill-red-500", "Inactive");
|
||||
if (value === "unknown")
|
||||
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Case list, we will render elements after the selected elements
|
||||
if (type === "list") {
|
||||
// Case no list to render
|
||||
if (!value || value.length <= 0) continue;
|
||||
|
||||
// Clone item element
|
||||
const itemEl = el.cloneNode(true);
|
||||
itemEl.classList.remove("hidden");
|
||||
const parentEl = el.parentNode;
|
||||
// Add item element after selected element
|
||||
const items = value.forEach((item) => {
|
||||
const newItemEl = itemEl.cloneNode(true);
|
||||
// Update item element values
|
||||
for (const [nameKey, nameValue] of Object.entries(item)) {
|
||||
newItemEl.querySelector(`[data-name="${nameKey}"]`).textContent =
|
||||
nameValue;
|
||||
}
|
||||
// Add item element after selected element
|
||||
parentEl.appendChild(newItemEl);
|
||||
});
|
||||
|
||||
// Delete schema
|
||||
el.remove();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setStatus(el, textEl, colorClass, text) {
|
||||
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
|
||||
el ? el.classList.add(colorClass) : null;
|
||||
textEl ? (textEl.textContent = text) : null;
|
||||
}
|
||||
|
||||
// Show fetch state alert
|
||||
// type<str> : fetch, success, error
|
||||
updateAlert(type) {
|
||||
if (!type) return;
|
||||
|
||||
const [status, msg, color] = this.getAlertType(type);
|
||||
|
||||
this.alertEl.classList.remove("bg-sky-500", "bg-green-500", "bg-red-500");
|
||||
|
||||
this.alertStatusEl.textContent = status;
|
||||
this.alertMsgEl.textContent = msg;
|
||||
this.alertEl.classList.add(color);
|
||||
|
||||
this.alertEl.classList.remove("hidden");
|
||||
|
||||
if (type !== "fetch")
|
||||
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
|
||||
}
|
||||
|
||||
getAlertType(type) {
|
||||
if (type === "fetch") return ["Fetching", "Please wait...", "bg-sky-500"];
|
||||
if (type === "error")
|
||||
return ["Error", "Something went wrong", "bg-red-500"];
|
||||
if (type === "success")
|
||||
return ["Success", "Data fetched successfully", "bg-green-500"];
|
||||
}
|
||||
}
|
||||
|
|
@ -19,10 +19,10 @@ class ServiceModal {
|
|||
//modal forms
|
||||
this.formNewEdit = this.modal.querySelector("[data-services-modal-form]");
|
||||
this.formDelete = this.modal.querySelector(
|
||||
"[data-services-modal-form-delete]"
|
||||
"[data-services-modal-form-delete]",
|
||||
);
|
||||
this.submitBtn = document.querySelector(
|
||||
"button[data-services-modal-submit]"
|
||||
"button[data-services-modal-submit]",
|
||||
);
|
||||
//container
|
||||
this.container = document.querySelector("main");
|
||||
|
|
@ -92,7 +92,7 @@ class ServiceModal {
|
|||
) {
|
||||
//set form info and right form
|
||||
const [action, serviceName, isDraft, method] = this.getActionData(
|
||||
e.target
|
||||
e.target,
|
||||
);
|
||||
const oldServName = e.target
|
||||
.closest("[data-services-service]")
|
||||
|
|
@ -104,7 +104,7 @@ class ServiceModal {
|
|||
oldServName,
|
||||
this.formNewEdit,
|
||||
isDraft,
|
||||
method
|
||||
method,
|
||||
);
|
||||
//get service data and parse it
|
||||
//multiple type logic is launch at same time on relate class
|
||||
|
|
@ -116,7 +116,7 @@ class ServiceModal {
|
|||
this.updateModalData(obj);
|
||||
//show modal
|
||||
this.resetFilterInp();
|
||||
this.changeSubmitBtn("EDIT", "edit-btn");
|
||||
this.changeSubmitBtn("SAVE", "valid-btn");
|
||||
this.openModal();
|
||||
}
|
||||
} catch (err) {}
|
||||
|
|
@ -128,7 +128,7 @@ class ServiceModal {
|
|||
) {
|
||||
//set form info and right form
|
||||
const [action, serviceName, isDraft, method] = this.getActionData(
|
||||
e.target
|
||||
e.target,
|
||||
);
|
||||
this.setForm(
|
||||
action,
|
||||
|
|
@ -136,7 +136,7 @@ class ServiceModal {
|
|||
serviceName,
|
||||
this.formNewEdit,
|
||||
isDraft,
|
||||
method
|
||||
method,
|
||||
);
|
||||
//set default value with method default
|
||||
//get service data and parse it
|
||||
|
|
@ -168,7 +168,7 @@ class ServiceModal {
|
|||
) {
|
||||
//set form info and right form
|
||||
const [action, serviceName, isDraft, method] = this.getActionData(
|
||||
e.target
|
||||
e.target,
|
||||
);
|
||||
this.setForm(
|
||||
action,
|
||||
|
|
@ -176,7 +176,7 @@ class ServiceModal {
|
|||
serviceName,
|
||||
this.formNewEdit,
|
||||
isDraft,
|
||||
method
|
||||
method,
|
||||
);
|
||||
//set default value with method default
|
||||
this.setSettingsDefault();
|
||||
|
|
@ -200,7 +200,7 @@ class ServiceModal {
|
|||
) {
|
||||
//set form info and right form
|
||||
const [action, serviceName, isDraft, method] = this.getActionData(
|
||||
e.target
|
||||
e.target,
|
||||
);
|
||||
this.setForm(
|
||||
action,
|
||||
|
|
@ -208,7 +208,7 @@ class ServiceModal {
|
|||
serviceName,
|
||||
this.formDelete,
|
||||
isDraft,
|
||||
method
|
||||
method,
|
||||
);
|
||||
//show modal
|
||||
this.openModal();
|
||||
|
|
@ -229,7 +229,7 @@ class ServiceModal {
|
|||
"delete-btn",
|
||||
"valid-btn",
|
||||
"edit-btn",
|
||||
"info-btn"
|
||||
"info-btn",
|
||||
);
|
||||
this.submitBtn.classList.add(btnType);
|
||||
}
|
||||
|
|
@ -290,15 +290,15 @@ class ServiceModal {
|
|||
//click the custom select dropdown to update select value
|
||||
select.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
|
||||
)
|
||||
.click();
|
||||
|
||||
//set state to custom visible el
|
||||
const btnCustom = document.querySelector(
|
||||
`[data-setting-select=${select.getAttribute(
|
||||
"data-setting-select-default"
|
||||
)}]`
|
||||
"data-setting-select-default",
|
||||
)}]`,
|
||||
);
|
||||
|
||||
this.setDisabledDefault(btnCustom, defaultMethod);
|
||||
|
|
@ -374,9 +374,8 @@ class ServiceModal {
|
|||
|
||||
if (action === "delete") {
|
||||
this.showDeleteForm();
|
||||
formEl.querySelector(
|
||||
`[data-services-modal-text]`
|
||||
).textContent = `Are you sure you want to delete ${serviceName} ?`;
|
||||
formEl.querySelector(`[data-services-modal-text]`).textContent =
|
||||
`Are you sure you want to delete ${serviceName} ?`;
|
||||
const nameInp = formEl.querySelector(`input[name="SERVER_NAME"]`);
|
||||
nameInp.setAttribute("value", serviceName);
|
||||
nameInp.value = serviceName;
|
||||
|
|
@ -487,7 +486,7 @@ class ServiceModal {
|
|||
if (inp.tagName === "SELECT") {
|
||||
inp.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${value}']`
|
||||
`button[data-setting-select-dropdown-btn][value='${value}']`,
|
||||
)
|
||||
.click();
|
||||
inp.setAttribute("data-method", method);
|
||||
|
|
@ -599,7 +598,7 @@ class Multiple {
|
|||
const attName = btn.getAttribute(`data-${this.prefix}-multiple-add`);
|
||||
//get all multiple groups
|
||||
const multipleEls = document.querySelectorAll(
|
||||
`[data-${this.prefix}-settings-multiple*="${attName}"]`
|
||||
`[data-${this.prefix}-settings-multiple*="${attName}"]`,
|
||||
);
|
||||
//case no schema
|
||||
if (multipleEls.length <= 0) return;
|
||||
|
|
@ -611,7 +610,7 @@ class Multiple {
|
|||
//and keep the highest num
|
||||
multipleEls.forEach((container) => {
|
||||
const ctnrName = container.getAttribute(
|
||||
"data-services-settings-multiple"
|
||||
"data-services-settings-multiple",
|
||||
);
|
||||
const num = this.getSuffixNumOrFalse(ctnrName);
|
||||
if (!isNaN(num) && num > topNum) topNum = num;
|
||||
|
|
@ -622,7 +621,7 @@ class Multiple {
|
|||
const setNum = +currNum === 0 ? `` : `_${currNum}`;
|
||||
//the default (schema) group is the last group
|
||||
const schema = document.querySelector(
|
||||
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`
|
||||
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`,
|
||||
);
|
||||
//clone schema to create a group with new num
|
||||
const schemaClone = schema.cloneNode(true);
|
||||
|
|
@ -660,7 +659,7 @@ class Multiple {
|
|||
.hasAttribute(`data-${this.prefix}-multiple-delete`)
|
||||
) {
|
||||
const multContainer = e.target.closest(
|
||||
"[data-services-settings-multiple]"
|
||||
"[data-services-settings-multiple]",
|
||||
);
|
||||
multContainer.remove();
|
||||
}
|
||||
|
|
@ -682,13 +681,13 @@ class Multiple {
|
|||
? name.replace(`_${splitName[splitName.length - 1]}`, "").trim()
|
||||
: name.trim();
|
||||
const relateSetting = document.querySelector(
|
||||
`[data-setting-container=${nameSuffixLess}_SCHEMA]`
|
||||
`[data-setting-container=${nameSuffixLess}_SCHEMA]`,
|
||||
);
|
||||
const relateCtnr = relateSetting.closest(
|
||||
"[data-services-settings-multiple]"
|
||||
"[data-services-settings-multiple]",
|
||||
);
|
||||
const relateCtnrName = relateCtnr.getAttribute(
|
||||
"data-services-settings-multiple"
|
||||
"data-services-settings-multiple",
|
||||
);
|
||||
//then we sort the setting on the right container name by suffixe number
|
||||
if (!(relateCtnrName in sortMultiples)) {
|
||||
|
|
@ -706,7 +705,7 @@ class Multiple {
|
|||
addOneMultGroup() {
|
||||
const settings = document.querySelector("[data-services-modal-form]");
|
||||
const multAddBtns = settings.querySelectorAll(
|
||||
"[data-services-multiple-add]"
|
||||
"[data-services-multiple-add]",
|
||||
);
|
||||
multAddBtns.forEach((btn) => {
|
||||
//check if already one (SCHEMA exclude so length >= 2)
|
||||
|
|
@ -721,7 +720,7 @@ class Multiple {
|
|||
|
||||
showMultByAtt(att) {
|
||||
const multContainers = document.querySelectorAll(
|
||||
`[data-services-settings-multiple^=${att}]`
|
||||
`[data-services-settings-multiple^=${att}]`,
|
||||
);
|
||||
multContainers.forEach((container) => {
|
||||
if (
|
||||
|
|
@ -735,7 +734,7 @@ class Multiple {
|
|||
|
||||
toggleMultByAtt(att) {
|
||||
const multContainers = document.querySelectorAll(
|
||||
`[data-services-settings-multiple^=${att}]`
|
||||
`[data-services-settings-multiple^=${att}]`,
|
||||
);
|
||||
multContainers.forEach((container) => {
|
||||
if (
|
||||
|
|
@ -751,7 +750,7 @@ class Multiple {
|
|||
//get schema settings
|
||||
const multiples = {};
|
||||
const schemaSettings = document.querySelectorAll(
|
||||
`[data-setting-container$="SCHEMA"]`
|
||||
`[data-setting-container$="SCHEMA"]`,
|
||||
);
|
||||
// loop on every schema settings
|
||||
schemaSettings.forEach((schema) => {
|
||||
|
|
@ -777,11 +776,11 @@ class Multiple {
|
|||
setMultipleToDOM(sortMultObj, setMethodUI = false) {
|
||||
//we loop on each multiple that contains values to render to DOM
|
||||
for (const [schemaCtnrName, multGroupBySuffix] of Object.entries(
|
||||
sortMultObj
|
||||
sortMultObj,
|
||||
)) {
|
||||
//we need to access the DOM schema container
|
||||
const schemaCtnr = document.querySelector(
|
||||
`[data-services-settings-multiple="${schemaCtnrName}"]`
|
||||
`[data-services-settings-multiple="${schemaCtnrName}"]`,
|
||||
);
|
||||
//now we have to loop on each multiple settings group
|
||||
for (const [suffix, settings] of Object.entries(multGroupBySuffix)) {
|
||||
|
|
@ -797,14 +796,14 @@ class Multiple {
|
|||
for (const [name, data] of Object.entries(settings)) {
|
||||
//get setting container of clone container
|
||||
const settingContainer = schemaCtnrClone.querySelector(
|
||||
`[data-setting-container="${name}"]`
|
||||
`[data-setting-container="${name}"]`,
|
||||
);
|
||||
//replace input info and disabled state
|
||||
this.setSetting(
|
||||
data["value"],
|
||||
setMethodUI ? "ui" : data["method"],
|
||||
data["global"],
|
||||
settingContainer
|
||||
settingContainer,
|
||||
);
|
||||
}
|
||||
//send schema clone to DOM and show it
|
||||
|
|
@ -819,7 +818,7 @@ class Multiple {
|
|||
"data-services-settings-multiple",
|
||||
schemaCtnrClone
|
||||
.getAttribute("data-services-settings-multiple")
|
||||
.replace("_SCHEMA", suffix)
|
||||
.replace("_SCHEMA", suffix),
|
||||
);
|
||||
|
||||
//rename title
|
||||
|
|
@ -833,18 +832,18 @@ class Multiple {
|
|||
|
||||
//rename setting container
|
||||
const settingCtnrs = schemaCtnrClone.querySelectorAll(
|
||||
"[data-setting-container]"
|
||||
"[data-setting-container]",
|
||||
);
|
||||
settingCtnrs.forEach((settingCtnr) => {
|
||||
settingCtnr.setAttribute(
|
||||
"data-setting-container",
|
||||
settingCtnr
|
||||
.getAttribute("data-setting-container")
|
||||
.replace("_SCHEMA", suffix)
|
||||
.replace("_SCHEMA", suffix),
|
||||
);
|
||||
settingCtnr.setAttribute(
|
||||
"id",
|
||||
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix)
|
||||
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -922,15 +921,15 @@ class Multiple {
|
|||
//click the custom select dropdown btn value to update select value
|
||||
select.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
|
||||
)
|
||||
.click();
|
||||
|
||||
//set state to custom visible el
|
||||
const btnCustom = document.querySelector(
|
||||
`[data-setting-select=${select.getAttribute(
|
||||
"data-setting-select-default"
|
||||
)}]`
|
||||
"data-setting-select-default",
|
||||
)}]`,
|
||||
);
|
||||
|
||||
this.setDisabledMultServ(btnCustom, method, global);
|
||||
|
|
@ -966,10 +965,10 @@ class Multiple {
|
|||
selects.forEach((select) => {
|
||||
const method = select.getAttribute("data-default-method");
|
||||
const name = select.getAttribute(
|
||||
"data-services-setting-select-default"
|
||||
"data-services-setting-select-default",
|
||||
);
|
||||
const selDOM = document.querySelector(
|
||||
`button[data-services-setting-select='${name}']`
|
||||
`button[data-services-setting-select='${name}']`,
|
||||
);
|
||||
if (method === "ui" || method === "default") {
|
||||
selDOM.removeAttribute("disabled", "");
|
||||
|
|
@ -1004,7 +1003,7 @@ class Multiple {
|
|||
hiddenIfNoMultiples() {
|
||||
//hide multiple btn if no multiple exist on a plugin
|
||||
const multiples = document.querySelectorAll(
|
||||
`[data-${this.prefix}-settings-multiple]`
|
||||
`[data-${this.prefix}-settings-multiple]`,
|
||||
);
|
||||
multiples.forEach((container) => {
|
||||
if (container.querySelectorAll(`[data-setting-container]`).length <= 0)
|
||||
|
|
@ -1016,7 +1015,7 @@ class Multiple {
|
|||
|
||||
removePrevMultiples() {
|
||||
const multiPlugins = document.querySelectorAll(
|
||||
`[data-${this.prefix}-settings-multiple]`
|
||||
`[data-${this.prefix}-settings-multiple]`,
|
||||
);
|
||||
multiPlugins.forEach((multiGrp) => {
|
||||
if (
|
||||
|
|
@ -1054,7 +1053,7 @@ const setModal = new ServiceModal();
|
|||
const format = new FormatValue();
|
||||
const setFilterGlobal = new FilterSettings(
|
||||
"settings-filter",
|
||||
"[data-service-content='settings']"
|
||||
"[data-service-content='settings']",
|
||||
);
|
||||
|
||||
const setMultiple = new Multiple("services");
|
||||
|
|
|
|||
|
|
@ -8,16 +8,15 @@
|
|||
}
|
||||
|
||||
* {
|
||||
font-family: "Open Sans", sans-serif !important;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
}
|
||||
|
||||
.ace_editor,
|
||||
.ace_editor div,
|
||||
.ace_content {
|
||||
font-family: "Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro",
|
||||
.ace_editor * {
|
||||
font-family: "Monaco", "Menlo", "Ubuntu Mono", "Droid Sans Mono", "Consolas",
|
||||
monospace !important;
|
||||
font-size: 16px !important;
|
||||
font-weight: normal !important;
|
||||
font-weight: 400 !important;
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
|
|
|
|||
2
src/ui/templates/file_manager.html
vendored
2
src/ui/templates/file_manager.html
vendored
|
|
@ -429,7 +429,7 @@ data-{{current_endpoint}}-modal
|
|||
<!-- editor-->
|
||||
|
||||
<div class="mt-4 w-full justify-end flex">
|
||||
<button
|
||||
<button type="button"
|
||||
data-{{current_endpoint}}-modal-close
|
||||
|
||||
class="close-btn text-xs mr-2"
|
||||
|
|
|
|||
3
src/ui/templates/head.html
vendored
3
src/ui/templates/head.html
vendored
|
|
@ -1,4 +1,4 @@
|
|||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip()
|
||||
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip()
|
||||
%}
|
||||
|
||||
<head>
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="./css/dashboard.css" />
|
||||
|
||||
<script type="module" src="./js/global.js"></script>
|
||||
<script src="./js/plugins/setup.js"></script>
|
||||
|
||||
<script async src="./js/utils/purify/purify.min.js"></script>
|
||||
<script src="./js/editor/ace.js"></script>
|
||||
|
|
|
|||
2
src/ui/templates/header.html
vendored
2
src/ui/templates/header.html
vendored
|
|
@ -1,4 +1,4 @@
|
|||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', ' ').replace('-', ' ')
|
||||
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', ' ').replace('-', ' ')
|
||||
%}
|
||||
|
||||
<!-- header -->
|
||||
|
|
|
|||
7
src/ui/templates/menu.html
vendored
7
src/ui/templates/menu.html
vendored
|
|
@ -1,6 +1,5 @@
|
|||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip()
|
||||
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip()
|
||||
%}
|
||||
|
||||
<!-- float button-->
|
||||
<button
|
||||
aria-controls="sidebar-menu"
|
||||
|
|
@ -440,7 +439,7 @@
|
|||
<a
|
||||
class="leading-8 font-bold hover:brightness-75"
|
||||
target="_blank"
|
||||
href="https://docs.bunkerweb.io/1.5.4/plugins/?utm_campaign=self&utm_source=ui#writing-a-plugin"
|
||||
href="https://docs.bunkerweb.io/latest/plugins/?utm_campaign=self&utm_source=ui#writing-a-plugin"
|
||||
>check doc</a
|
||||
>
|
||||
</h6>
|
||||
|
|
@ -450,7 +449,7 @@
|
|||
<a
|
||||
target="_blank"
|
||||
class="{% if current_endpoint == 'logs' %}font-semibold text-slate-700 dark:bg-primary/50 rounded-lg dark:hover:bg-primary/60 bg-primary/20 hover:bg-primary/30{% else %}dark:hover:bg-primary/20 hover:bg-primary/5 {% endif %} hover:rounded-lg dark:text-white dark:opacity-80 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition"
|
||||
href="{{request.url_root}}plugins?plugin_id={{plugin['id']}}"
|
||||
href="{{request.url_root}}plugins/{{plugin['id']}}"
|
||||
>
|
||||
<div
|
||||
class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5"
|
||||
|
|
|
|||
3
src/ui/templates/navbar.html
vendored
3
src/ui/templates/navbar.html
vendored
|
|
@ -1,6 +1,5 @@
|
|||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip()
|
||||
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip()
|
||||
%}
|
||||
|
||||
<div class="navigation-wrap bg-custom start-header start-style">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
|
|
|||
14
src/ui/templates/plugins.html
vendored
14
src/ui/templates/plugins.html
vendored
|
|
@ -43,18 +43,6 @@ include "plugins_modal.html" %}
|
|||
{{plugins_external}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
PLUGINS ERRORS
|
||||
</p>
|
||||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
{{plugins_errors}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
|
|
@ -229,7 +217,7 @@ include "plugins_modal.html" %}
|
|||
<a
|
||||
aria-label="plugin page link"
|
||||
class="hover:-translate-y-px mx-1"
|
||||
href="{{request.url_root}}plugins?plugin_id={{plugin['id']}}"
|
||||
href="{{request.url_root}}plugins/{{plugin['id']}}"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 fill-sky-500 dark dark:brightness-90"
|
||||
|
|
|
|||
2
src/ui/templates/plugins_modal.html
vendored
2
src/ui/templates/plugins_modal.html
vendored
|
|
@ -50,7 +50,7 @@
|
|||
</div>
|
||||
<!-- action button -->
|
||||
<div class="w-full justify-center flex mt-10">
|
||||
<button
|
||||
<button type="button"
|
||||
data-plugins-modal-close
|
||||
class="close-btn mb-4 mr-3 text-base"
|
||||
>
|
||||
|
|
|
|||
4
src/ui/templates/reports.html
vendored
4
src/ui/templates/reports.html
vendored
|
|
@ -363,7 +363,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
<div
|
||||
class="col-span-12 overflow-y-auto overflow-x-auto"
|
||||
> <!-- list container-->
|
||||
<div class="min-w-[1200px] w-full grid grid-cols-12 rounded p-2">
|
||||
<div class="min-w-[1300px] w-full grid grid-cols-12 rounded p-2">
|
||||
<!-- header-->
|
||||
<p
|
||||
class="dark:text-gray-300 flex justify-center h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
|
||||
|
|
@ -420,7 +420,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
>
|
||||
<p
|
||||
|
||||
class="flex justify-center dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
|
||||
class="text-center flex justify-center dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
|
||||
data-{{current_endpoint}}-date="{{report['date']}}"
|
||||
>
|
||||
{{report['date']}}
|
||||
|
|
|
|||
2
src/ui/templates/settings_plugins.html
vendored
2
src/ui/templates/settings_plugins.html
vendored
|
|
@ -1,4 +1,4 @@
|
|||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
|
||||
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
|
||||
%}
|
||||
{% set global_config = config["CONFIG"].get_config() %}
|
||||
{% set plugins = config["CONFIG"].get_plugins() %}
|
||||
|
|
|
|||
3
src/ui/templates/settings_tabs.html
vendored
3
src/ui/templates/settings_tabs.html
vendored
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
|
||||
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
|
||||
%}
|
||||
{% set plugins = config["CONFIG"].get_plugins() %}
|
||||
<div data-{{current_endpoint}}-tabs class="col-span-12 grid grid-cols-12 {% if current_endpoint == 'services' %}mb-4{% endif %}">
|
||||
|
|
|
|||
|
|
@ -159,9 +159,9 @@ trio-websocket==0.11.1 \
|
|||
--hash=sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f \
|
||||
--hash=sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638
|
||||
# via selenium
|
||||
urllib3==2.1.0 \
|
||||
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
|
||||
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
|
||||
urllib3==2.2.0 \
|
||||
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
|
||||
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
|
||||
# via
|
||||
# requests
|
||||
# selenium
|
||||
|
|
|
|||
|
|
@ -159,9 +159,9 @@ trio-websocket==0.11.1 \
|
|||
--hash=sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f \
|
||||
--hash=sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638
|
||||
# via selenium
|
||||
urllib3==2.1.0 \
|
||||
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
|
||||
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
|
||||
urllib3==2.2.0 \
|
||||
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
|
||||
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
|
||||
# via
|
||||
# requests
|
||||
# selenium
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue