mirror of
https://github.com/caronc/apprise-api.git
synced 2025-01-07 22:48:53 +01:00
Logging details available via Apprise/API/Website (#34)
This commit is contained in:
parent
b29ddf15c0
commit
a57b621c29
@ -164,7 +164,7 @@ The use of environment variables allow you to provide over-rides to default sett
|
|||||||
| `SECRET_KEY` | A Django variable acting as a *salt* for most things that require security. This API uses it for the hash sequences when writing the configuration files to disk (`hash` mode only).
|
| `SECRET_KEY` | A Django variable acting as a *salt* for most things that require security. This API uses it for the hash sequences when writing the configuration files to disk (`hash` mode only).
|
||||||
| `ALLOWED_HOSTS` | A list of strings representing the host/domain names that this API can serve. This is a security measure to prevent HTTP Host header attacks, which are possible even under many seemingly-safe web server configurations. By default this is set to `*` allowing any host. Use space to delimit more than one host.
|
| `ALLOWED_HOSTS` | A list of strings representing the host/domain names that this API can serve. This is a security measure to prevent HTTP Host header attacks, which are possible even under many seemingly-safe web server configurations. By default this is set to `*` allowing any host. Use space to delimit more than one host.
|
||||||
| `BASE_URL` | Those who are hosting the API behind a proxy that requires a subpath to gain access to this API should specify this path here as well. By default this is not set at all.
|
| `BASE_URL` | Those who are hosting the API behind a proxy that requires a subpath to gain access to this API should specify this path here as well. By default this is not set at all.
|
||||||
| `LOG_LEVEL` | Adjust the log level to the console; the default value is `DEBUG` if the `DEBUG` is set below otherwise the default is `INFO`. Possible values are `ERROR`, `WARNING`, `INFO`, and `DEBUG`.
|
| `LOG_LEVEL` | Adjust the log level to the console. Possible values are `CRITICAL`, `ERROR`, `WARNING`, `INFO`, and `DEBUG`.
|
||||||
| `DEBUG` | This defaults to `False` however can be set to `True` if defined with a non-zero value (such as `1`).
|
| `DEBUG` | This defaults to `False` however can be set to `True` if defined with a non-zero value (such as `1`).
|
||||||
|
|
||||||
|
|
||||||
|
@ -267,11 +267,18 @@ document.querySelector('#addconfig').onsubmit = function(event) {
|
|||||||
'{% trans "Successfully saved the specified URL(s)." %}',
|
'{% trans "Successfully saved the specified URL(s)." %}',
|
||||||
'success'
|
'success'
|
||||||
);
|
);
|
||||||
|
} else if(response.status > 500) {
|
||||||
|
// Disk issue
|
||||||
|
Swal.fire(
|
||||||
|
'{% trans "Save" %}',
|
||||||
|
'{% trans "There was an issue writing the configuration to your filesystem. Check your file permissions and try again." %}',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// user notification
|
// user notification
|
||||||
Swal.fire(
|
Swal.fire(
|
||||||
'{% trans "Save" %}',
|
'{% trans "Save" %}',
|
||||||
'{% trans "Failed to save the specified URL(s)." %}',
|
'{% trans "Failed to save the specified URL(s). Check your syntax and try again." %}',
|
||||||
'error'
|
'error'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -338,24 +345,37 @@ document.querySelector('#donotify').onsubmit = function(event) {
|
|||||||
let response = fetch('{% url "notify" key %}', {
|
let response = fetch('{% url "notify" key %}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: body,
|
body: body,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'text/html',
|
||||||
|
'X-Apprise-Log-Level': 'info'
|
||||||
|
}
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
|
response.text().then(function (html) {
|
||||||
if(response.status == 200)
|
if(response.status == 200)
|
||||||
{
|
{
|
||||||
// user notification
|
// user notification
|
||||||
Swal.fire(
|
Swal.fire(
|
||||||
'{% trans "Notification" %}',
|
'{% trans "Notification" %}',
|
||||||
'{% trans "Successfully sent the notification(s)." %}',
|
'{% trans "Successfully sent the notification(s)." %}' + html,
|
||||||
'success'
|
'success'
|
||||||
);
|
);
|
||||||
|
} else if(response.status == 424) {
|
||||||
|
// user notification
|
||||||
|
Swal.fire(
|
||||||
|
'{% trans "Notification" %}',
|
||||||
|
'{% trans "One or more of the notification(s) were not sent." %}' + html,
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// user notification
|
// user notification
|
||||||
Swal.fire(
|
Swal.fire(
|
||||||
'{% trans "Notification" %}',
|
'{% trans "Notification" %}',
|
||||||
'{% trans "Failed to send the notification(s)." %}',
|
'{% trans "Failed to send the notification(s)." %}' + html,
|
||||||
'error'
|
'error'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -241,7 +241,7 @@ class NotifyTests(SimpleTestCase):
|
|||||||
'format': 'invalid'
|
'format': 'invalid'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test referencing a key that doesn't exist
|
# Test case with format set to invalid
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
'/notify/{}'.format(key),
|
'/notify/{}'.format(key),
|
||||||
data=json.dumps(json_data),
|
data=json.dumps(json_data),
|
||||||
@ -261,7 +261,7 @@ class NotifyTests(SimpleTestCase):
|
|||||||
'format': None,
|
'format': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test referencing a key that doesn't exist
|
# Test case with format changed
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
'/notify/{}'.format(key),
|
'/notify/{}'.format(key),
|
||||||
data=json.dumps(json_data),
|
data=json.dumps(json_data),
|
||||||
@ -284,3 +284,61 @@ class NotifyTests(SimpleTestCase):
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert mock_notify.call_count == 1
|
assert mock_notify.call_count == 1
|
||||||
|
|
||||||
|
# Reset our count
|
||||||
|
mock_notify.reset_mock()
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'HTTP_X_APPRISE_LOG_LEVEL': 'debug',
|
||||||
|
'HTTP_ACCEPT': 'text/plain',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test referencing a key that doesn't exist
|
||||||
|
response = self.client.post(
|
||||||
|
'/notify/{}'.format(key),
|
||||||
|
data=json.dumps(json_data),
|
||||||
|
content_type='application/json',
|
||||||
|
**headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert mock_notify.call_count == 1
|
||||||
|
assert response['content-type'] == 'text/plain'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'HTTP_X_APPRISE_LOG_LEVEL': 'debug',
|
||||||
|
'HTTP_ACCEPT': 'text/html',
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_notify.reset_mock()
|
||||||
|
|
||||||
|
# Test referencing a key that doesn't exist
|
||||||
|
response = self.client.post(
|
||||||
|
'/notify/{}'.format(key),
|
||||||
|
data=json.dumps(json_data),
|
||||||
|
content_type='application/json',
|
||||||
|
**headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert mock_notify.call_count == 1
|
||||||
|
assert response['content-type'] == 'text/html'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'HTTP_X_APPRISE_LOG_LEVEL': 'invalid',
|
||||||
|
'HTTP_ACCEPT': 'text/*',
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_notify.reset_mock()
|
||||||
|
|
||||||
|
# Test referencing a key that doesn't exist
|
||||||
|
response = self.client.post(
|
||||||
|
'/notify/{}'.format(key),
|
||||||
|
data=json.dumps(json_data),
|
||||||
|
content_type='application/json',
|
||||||
|
**headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert mock_notify.call_count == 1
|
||||||
|
assert response['content-type'] == 'text/html'
|
||||||
|
@ -27,6 +27,7 @@ from django.http import HttpResponse
|
|||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.views.decorators.gzip import gzip_page
|
from django.views.decorators.gzip import gzip_page
|
||||||
@ -422,6 +423,39 @@ class NotifyView(View):
|
|||||||
# Add our configuration
|
# Add our configuration
|
||||||
a_obj.add(ac_obj)
|
a_obj.add(ac_obj)
|
||||||
|
|
||||||
|
# Our return content type can be controlled by the Accept keyword
|
||||||
|
# If it includes /* or /html somewhere then we return html, otherwise
|
||||||
|
# we return the logs as they're processed in their text format.
|
||||||
|
# The HTML response type has a bit of overhead where as it's not
|
||||||
|
# the case with text/plain
|
||||||
|
content_type = \
|
||||||
|
'text/html' if re.search(r'text\/(\*|html)',
|
||||||
|
request.headers.get('Accept', ''),
|
||||||
|
re.IGNORECASE) \
|
||||||
|
else 'text/plain'
|
||||||
|
|
||||||
|
# Acquire our log level from headers if defined, otherwise use
|
||||||
|
# the global one set in the settings
|
||||||
|
level = request.headers.get(
|
||||||
|
'X-Apprise-Log-Level',
|
||||||
|
settings.LOGGING['loggers']['apprise']['level']).upper()
|
||||||
|
|
||||||
|
# Initialize our response object
|
||||||
|
response = None
|
||||||
|
|
||||||
|
if level in ('CRITICAL', 'ERROR' 'WARNING', 'INFO', 'DEBUG'):
|
||||||
|
level = getattr(apprise.logging, level)
|
||||||
|
|
||||||
|
esc = '<!!-!ESC!-!!>'
|
||||||
|
fmt = '<li class="log_%(levelname)s">' \
|
||||||
|
'<div class="log_time">%(asctime)s</div>' \
|
||||||
|
'<div class="log_level">%(levelname)s</div>' \
|
||||||
|
f'<div class="log_msg">{esc}%(message)s{esc}</div></li>' \
|
||||||
|
if content_type == 'text/html' else \
|
||||||
|
settings.LOGGING['formatters']['standard']['format']
|
||||||
|
|
||||||
|
# Now specify our format (and over-ride the default):
|
||||||
|
with apprise.LogCapture(level=level, fmt=fmt) as logs:
|
||||||
# Perform our notification at this point
|
# Perform our notification at this point
|
||||||
result = a_obj.notify(
|
result = a_obj.notify(
|
||||||
content.get('body'),
|
content.get('body'),
|
||||||
@ -430,17 +464,47 @@ class NotifyView(View):
|
|||||||
tag=content.get('tag'),
|
tag=content.get('tag'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if content_type == 'text/html':
|
||||||
|
# Iterate over our entries so that we can prepare to escape
|
||||||
|
# things to be presented as HTML
|
||||||
|
esc = re.escape(esc)
|
||||||
|
entries = re.findall(
|
||||||
|
r'(?P<head><li .+?){}(?P<to_escape>.*?)'
|
||||||
|
r'{}(?P<tail>.+li>$)(?=$|<li .+{})'.format(
|
||||||
|
esc, esc, esc), logs.getvalue(),
|
||||||
|
re.DOTALL)
|
||||||
|
|
||||||
|
# Wrap logs in `<ul>` tag and escape our message body:
|
||||||
|
response = '<ul class="logs">{}</ul>'.format(
|
||||||
|
''.join([e[0] + escape(e[1]) + e[2] for e in entries]))
|
||||||
|
|
||||||
|
else: # content_type == 'text/plain'
|
||||||
|
response = logs.getvalue()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Perform our notification at this point without logging
|
||||||
|
result = a_obj.notify(
|
||||||
|
content.get('body'),
|
||||||
|
title=content.get('title', ''),
|
||||||
|
notify_type=content.get('type', apprise.NotifyType.INFO),
|
||||||
|
tag=content.get('tag'),
|
||||||
|
)
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
# If at least one notification couldn't be sent; change up
|
# If at least one notification couldn't be sent; change up
|
||||||
# the response to a 424 error code
|
# the response to a 424 error code
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
|
response if response is not None else
|
||||||
_('One or more notification could not be sent.'),
|
_('One or more notification could not be sent.'),
|
||||||
|
content_type=content_type,
|
||||||
status=ResponseCode.failed_dependency)
|
status=ResponseCode.failed_dependency)
|
||||||
|
|
||||||
# Return our retrieved content
|
# Return our retrieved content
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
|
response if response is not None else
|
||||||
_('Notification(s) sent.'),
|
_('Notification(s) sent.'),
|
||||||
status=ResponseCode.okay
|
content_type=content_type,
|
||||||
|
status=ResponseCode.okay,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,7 +41,10 @@ SECRET_KEY = os.environ.get(
|
|||||||
#
|
#
|
||||||
# export DJANGO_SETTINGS_MODULE=core.settings.debug
|
# export DJANGO_SETTINGS_MODULE=core.settings.debug
|
||||||
# ./manage.py runserver
|
# ./manage.py runserver
|
||||||
DEBUG = bool(os.environ.get("DEBUG", False))
|
#
|
||||||
|
# Support 'yes', '1', 'true', 'enable', 'active', and +
|
||||||
|
DEBUG = os.environ.get("DEBUG", 'No')[0].lower() in (
|
||||||
|
'a', 'y', '1', 't', 'e', '+')
|
||||||
|
|
||||||
# allow all hosts by default otherwise read from the
|
# allow all hosts by default otherwise read from the
|
||||||
# ALLOWED_HOSTS environment variable
|
# ALLOWED_HOSTS environment variable
|
||||||
|
@ -75,3 +75,47 @@ textarea {
|
|||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
border: 1px solid #e4e4e4;
|
border: 1px solid #e4e4e4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Notification Details */
|
||||||
|
ul.logs {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
height: 60%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li {
|
||||||
|
display: flex;
|
||||||
|
text-align: left;
|
||||||
|
width: 50em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li div.log_time {
|
||||||
|
font-weight: normal;
|
||||||
|
flex: 0 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li div.log_level {
|
||||||
|
font-weight: bold;
|
||||||
|
align: right;
|
||||||
|
flex: 0 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li div.log_msg {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li.log_INFO {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li.log_DEBUG {
|
||||||
|
color: #606060;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li.log_WARNING {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.logs li.log_ERROR {
|
||||||
|
color: #8B0000;
|
||||||
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
django
|
django
|
||||||
apprise >= 0.8.8
|
apprise >= 0.9.0
|
||||||
|
Loading…
Reference in New Issue
Block a user