mirror of
https://github.com/eduardogsilva/routerfleet.git
synced 2025-06-20 17:15:39 +02:00
Message center settings and channel configuration. Telegram and callmebot added
This commit is contained in:
parent
eeeead26cc
commit
dfb4285ab3
8 changed files with 411 additions and 5 deletions
|
@ -1,3 +1,42 @@
|
|||
from django.contrib import admin
|
||||
from .models import Notification, MessageChannel, Message, MessageSettings
|
||||
|
||||
# Register your models here.
|
||||
|
||||
class NotificationAdmin(admin.ModelAdmin):
|
||||
list_display = ('notification_type', 'router', 'router_backup', 'created', 'updated', 'uuid')
|
||||
list_filter = ('notification_type', 'router', 'router_backup', 'created', 'updated', 'uuid')
|
||||
search_fields = ('notification_type', 'router', 'router_backup', 'created', 'updated', 'uuid')
|
||||
readonly_fields = ('created', 'updated', 'uuid')
|
||||
|
||||
|
||||
admin.site.register(Notification, NotificationAdmin)
|
||||
|
||||
|
||||
class MessageChannelAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'enabled', 'channel_type', 'destination', 'token', 'status_change_offline', 'status_change_online', 'backup_fail', 'daily_status_report', 'daily_backup_report', 'created', 'updated', 'uuid')
|
||||
list_filter = ('name', 'enabled', 'channel_type', 'destination', 'token', 'status_change_offline', 'status_change_online', 'backup_fail', 'daily_status_report', 'daily_backup_report', 'created', 'updated', 'uuid')
|
||||
search_fields = ('name', 'enabled', 'channel_type', 'destination', 'token', 'status_change_offline', 'status_change_online', 'backup_fail', 'daily_status_report', 'daily_backup_report', 'created', 'updated', 'uuid')
|
||||
readonly_fields = ('created', 'updated', 'uuid')
|
||||
|
||||
|
||||
admin.site.register(MessageChannel, MessageChannelAdmin)
|
||||
|
||||
|
||||
class MessageAdmin(admin.ModelAdmin):
|
||||
list_display = ('channel', 'subject', 'message', 'status', 'retry_count', 'error_message', 'completed', 'created', 'updated', 'uuid')
|
||||
list_filter = ('channel', 'subject', 'message', 'status', 'retry_count', 'error_message', 'completed', 'created', 'updated', 'uuid')
|
||||
search_fields = ('channel', 'subject', 'message', 'status', 'retry_count', 'error_message', 'completed', 'created', 'updated', 'uuid')
|
||||
readonly_fields = ('created', 'updated', 'uuid')
|
||||
|
||||
|
||||
admin.site.register(Message, MessageAdmin)
|
||||
|
||||
|
||||
class MessageSettingsAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'max_length', 'max_retry', 'retry_interval', 'concatenate_status_change', 'created', 'updated', 'uuid')
|
||||
list_filter = ('name', 'max_length', 'max_retry', 'retry_interval', 'concatenate_status_change', 'created', 'updated', 'uuid')
|
||||
search_fields = ('name', 'max_length', 'max_retry', 'retry_interval', 'concatenate_status_change', 'created', 'updated', 'uuid')
|
||||
readonly_fields = ('created', 'updated', 'uuid')
|
||||
|
||||
|
||||
admin.site.register(MessageSettings, MessageSettingsAdmin)
|
||||
|
|
173
message_center/forms.py
Normal file
173
message_center/forms.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
import requests
|
||||
from .models import MessageSettings, MessageChannel
|
||||
|
||||
from django import forms
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Fieldset, ButtonHolder, Submit, Div, Field, HTML
|
||||
from crispy_forms.bootstrap import FormActions, StrictButton
|
||||
from django.core.exceptions import ValidationError
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class MessageSettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = MessageSettings
|
||||
fields = [
|
||||
'max_length', 'max_retry', 'retry_interval', 'concatenate_status_change', 'status_change_delay',
|
||||
'concatenate_backup_fails', 'backup_fails_delay', 'daily_report_time'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MessageSettingsForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
'Message Settings',
|
||||
),
|
||||
Div(
|
||||
Div(Field('max_length'), css_class='col-md-6'),
|
||||
Div(Field('daily_report_time'), css_class='col-md-6'),
|
||||
css_class='row'),
|
||||
Div(
|
||||
Div(Field('max_retry'), css_class='col-md-6'),
|
||||
Div(Field('retry_interval'), css_class='col-md-6'),
|
||||
css_class='row'),
|
||||
Div(
|
||||
Div(
|
||||
Div(
|
||||
Div(Field('concatenate_status_change'), css_class='col-md-12'),
|
||||
Div(Field('status_change_delay'), css_class='col-md-12'),
|
||||
css_class='row'),
|
||||
css_class='col-md-6'),
|
||||
Div(
|
||||
Div(
|
||||
Div(Field('concatenate_backup_fails'), css_class='col-md-12'),
|
||||
Div(Field('backup_fails_delay'), css_class='col-md-12'),
|
||||
css_class='row'),
|
||||
css_class='col-md-6'),
|
||||
css_class='row'),
|
||||
Div(
|
||||
Div(
|
||||
Submit('submit', 'Save', css_class='btn btn-success'),
|
||||
HTML(' <a class="btn btn-secondary" href="/message_center/channel_list/">Back</a> '),
|
||||
css_class='col-md-12'
|
||||
),
|
||||
css_class='row')
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
max_length = cleaned_data.get('max_length')
|
||||
if max_length is not None and (max_length < 500 or max_length > 2000):
|
||||
self.add_error('max_length', 'Max length must be between 500 and 2000.')
|
||||
|
||||
daily_report_time = cleaned_data.get('daily_report_time')
|
||||
if daily_report_time is not None:
|
||||
try:
|
||||
datetime.strptime(daily_report_time, '%H:%M')
|
||||
except ValueError:
|
||||
self.add_error('daily_report_time', 'Invalid time format. Use HH:MM.')
|
||||
|
||||
max_retry = cleaned_data.get('max_retry')
|
||||
if max_retry is not None and (max_retry < 0 or max_retry > 5):
|
||||
self.add_error('max_retry', 'Max retry must be between 0 and 5.')
|
||||
|
||||
retry_interval = cleaned_data.get('retry_interval')
|
||||
if retry_interval is not None and (retry_interval < 30 or retry_interval > 600):
|
||||
self.add_error('retry_interval', 'Retry interval must be between 30 and 600 seconds.')
|
||||
|
||||
status_change_delay = cleaned_data.get('status_change_delay')
|
||||
if status_change_delay is not None and (status_change_delay < 60 or status_change_delay > 600):
|
||||
self.add_error('status_change_delay', 'Status change delay must be between 60 and 600 seconds.')
|
||||
|
||||
backup_fails_delay = cleaned_data.get('backup_fails_delay')
|
||||
if backup_fails_delay is not None and (backup_fails_delay < 60 or backup_fails_delay > 3600):
|
||||
self.add_error('backup_fails_delay', 'Backup fails delay must be between 60 and 3600 seconds.')
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class MessageChannelForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = MessageChannel
|
||||
fields = [
|
||||
'name', 'enabled', 'channel_type', 'destination', 'token', 'status_change_offline', 'status_change_online',
|
||||
'backup_fail', 'daily_status_report', 'daily_backup_report'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MessageChannelForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.fields['enabled'].label = 'Channel Enabled'
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
'Message Channel',
|
||||
),
|
||||
Div(
|
||||
Div(Field('name'), css_class='col-md-6'),
|
||||
Div(Field('channel_type'), css_class='col-md-6'),
|
||||
css_class='row'),
|
||||
Div(
|
||||
Div(Field('destination'), css_class='col-md-6'),
|
||||
Div(Field('token'), css_class='col-md-6'),
|
||||
css_class='row'),
|
||||
|
||||
Div(
|
||||
Div(
|
||||
Div(HTML('<h4>Notification Settings</h4>'), css_class='col-md-12'),
|
||||
|
||||
Div(Field('status_change_offline'), css_class='col-md-6'),
|
||||
Div(Field('status_change_online'), css_class='col-md-6'),
|
||||
Div(Field('daily_status_report'), css_class='col-md-6'),
|
||||
Div(Field('daily_backup_report'), css_class='col-md-6'),
|
||||
Div(Field('backup_fail'), css_class='col-md-6'),
|
||||
Div(Field('enabled'), css_class='col-md-6'),
|
||||
css_class='row'),
|
||||
Div(
|
||||
Submit('submit', 'Save', css_class='btn btn-success'),
|
||||
HTML(' <a class="btn btn-secondary" href="/message_center/channel_list/">Back</a> '),
|
||||
css_class='col-md-12'
|
||||
),
|
||||
css_class='row')
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
name = cleaned_data.get('name')
|
||||
enabled = cleaned_data.get('enabled')
|
||||
|
||||
destination = cleaned_data.get('destination')
|
||||
if destination is not None and len(destination) > 100:
|
||||
self.add_error('destination', 'Destination must be less than 100 characters.')
|
||||
|
||||
token = cleaned_data.get('token')
|
||||
if token is not None and len(token) > 100:
|
||||
self.add_error('token', 'Token must be less than 100 characters.')
|
||||
|
||||
channel_type = cleaned_data.get('channel_type')
|
||||
if channel_type == 'ntfy':
|
||||
self.add_error('channel_type', 'This channel type is not supported. Support will be added soon.')
|
||||
|
||||
test_message = 'Test message from RouterFleet'
|
||||
remote_error = 'No error message received'
|
||||
|
||||
if channel_type == 'callmebot' and enabled:
|
||||
if not token or not destination:
|
||||
raise forms.ValidationError('CallMeBot requires a token and destination.')
|
||||
|
||||
message = requests.get(f'https://api.callmebot.com/whatsapp.php?phone={destination}&text={test_message}&apikey={token}')
|
||||
if message.status_code != 200:
|
||||
if message.text:
|
||||
remote_error = message.text[:200]
|
||||
raise forms.ValidationError(f'Test message failed. CallMeBot API status code {message.status_code}. Error: {remote_error}')
|
||||
|
||||
elif channel_type == 'telegram':
|
||||
if not token or not destination:
|
||||
raise forms.ValidationError('Telegram requires a token and destination.')
|
||||
message = requests.get(f'https://api.telegram.org/bot{token}/sendMessage?chat_id={destination}&text={test_message}')
|
||||
if message.status_code != 200:
|
||||
if message.text:
|
||||
remote_error = message.text[:200]
|
||||
raise forms.ValidationError(f'Test message failed. Telegram API status code {message.status_code}. Error: {remote_error}')
|
||||
|
||||
return cleaned_data
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.0.4 on 2024-04-16 17:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('message_center', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='messagechannel',
|
||||
name='channel_type',
|
||||
field=models.CharField(choices=[('callmebot', 'CallMeBot (WhatsApp)'), ('telegram', 'Telegram')], max_length=100),
|
||||
),
|
||||
]
|
|
@ -19,7 +19,7 @@ class MessageChannel(models.Model):
|
|||
enabled = models.BooleanField(default=True)
|
||||
channel_type = models.CharField(
|
||||
max_length=100, choices=(
|
||||
('callmebot', 'CallMeBot'), ('ntfy', 'ntfy'), ('telegram', 'telegram'),
|
||||
('callmebot', 'CallMeBot (WhatsApp)'), ('telegram', 'Telegram'),
|
||||
)
|
||||
)
|
||||
destination = models.CharField(max_length=100, blank=True, null=True)
|
||||
|
|
|
@ -1,3 +1,66 @@
|
|||
from django.shortcuts import render
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from user_manager.models import UserAcl
|
||||
from .forms import MessageSettingsForm, MessageChannelForm
|
||||
from .models import Notification, MessageChannel, Message, MessageSettings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
# Create your views here.
|
||||
|
||||
@login_required()
|
||||
def view_message_channel_list(request):
|
||||
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=20).exists():
|
||||
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
|
||||
message_settings, _ = MessageSettings.objects.get_or_create(name='message_settings')
|
||||
message_channels = MessageChannel.objects.all()
|
||||
context = {
|
||||
'message_settings': message_settings,
|
||||
'message_channels': message_channels,
|
||||
}
|
||||
return render(request, 'message_center/message_channel_list.html', context=context)
|
||||
|
||||
|
||||
@login_required()
|
||||
def view_manage_message_settings(request):
|
||||
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists():
|
||||
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
|
||||
message_settings, _ = MessageSettings.objects.get_or_create(name='message_settings')
|
||||
form = MessageSettingsForm(request.POST or None, instance=message_settings)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Message Settings saved successfully')
|
||||
return redirect('/message_center/channel_list/')
|
||||
context = {
|
||||
'message_settings': message_settings,
|
||||
'form': form,
|
||||
}
|
||||
return render(request, 'generic_form.html', context=context)
|
||||
|
||||
|
||||
@login_required()
|
||||
def view_manage_message_channel(request):
|
||||
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists():
|
||||
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
|
||||
message_settings, _ = MessageSettings.objects.get_or_create(name='message_settings')
|
||||
if request.GET.get('uuid'):
|
||||
message_channel = MessageChannel.objects.get(uuid=request.GET.get('uuid'))
|
||||
if request.GET.get('action') == 'delete':
|
||||
if request.GET.get('confirmation') == 'delete':
|
||||
message_channel.delete()
|
||||
messages.success(request, 'Message Channel deleted successfully')
|
||||
return redirect('/message_center/channel_list/')
|
||||
else:
|
||||
messages.warning(request, 'Message Channel not deleted|Invalid confirmation')
|
||||
return redirect('/message_center/channel_list/')
|
||||
else:
|
||||
message_channel = None
|
||||
|
||||
form = MessageChannelForm(request.POST or None, instance=message_channel)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Message Channel saved successfully')
|
||||
return redirect('/message_center/channel_list/')
|
||||
context = {
|
||||
'message_settings': message_settings,
|
||||
'form': form,
|
||||
}
|
||||
return render(request, 'generic_form.html', context=context)
|
||||
|
|
|
@ -9,6 +9,7 @@ from backup.views import view_backup_profile_list, view_manage_backup_profile, v
|
|||
from monitoring.views import view_export_router_list, view_update_router_status, view_router_config_timestamp, view_router_last_status_change
|
||||
from backup_data.views import view_generate_backup_schedule, view_create_backup_tasks, view_perform_backup_tasks, view_housekeeping
|
||||
from routerfleet_tools.views import cron_check_updates
|
||||
from message_center.views import view_message_channel_list, view_manage_message_settings, view_manage_message_channel
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -48,5 +49,8 @@ urlpatterns = [
|
|||
path('cron/check_updates/', cron_check_updates, name='check_updates'),
|
||||
path('wireguard_webadmin/', view_wireguard_webadmin_launcher, name='wireguard_webadmin_launcher'),
|
||||
path('wireguard_webadmin/manage/', view_manage_wireguard_integration, name='manage_wireguard_integration'),
|
||||
path('wireguard_webadmin/launch/', view_launch_wireguard_webadmin, name='launch_wireguard_webadmin')
|
||||
path('wireguard_webadmin/launch/', view_launch_wireguard_webadmin, name='launch_wireguard_webadmin'),
|
||||
path('message_center/channel_list/', view_message_channel_list, name='message_channel_list'),
|
||||
path('message_center/manage_settings/', view_manage_message_settings, name='manage_message_settings'),
|
||||
path('message_center/manage_channel/', view_manage_message_channel, name='manage_message_channel'),
|
||||
]
|
||||
|
|
|
@ -143,6 +143,15 @@
|
|||
</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="/message_center/channel_list/" class="nav-link {% if '/message_center/' in request.path %}active{% endif %}">
|
||||
<i class="far fa-envelope nav-icon"></i>
|
||||
<p>
|
||||
Message Center
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="/wireguard_webadmin/" class="nav-link {% if '/wireguard_webadmin/' in request.path %}active{% endif %}">
|
||||
|
|
100
templates/message_center/message_channel_list.html
Normal file
100
templates/message_center/message_channel_list.html
Normal file
|
@ -0,0 +1,100 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class='row'>
|
||||
<div class='col-lg-12'>
|
||||
<div class="card card-primary card-outline">
|
||||
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Notification Channels</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body row">
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-hover datatables-no-export">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Enabled</th>
|
||||
<th>Channel</th>
|
||||
<th style="text-decoration: underline" title="Status change to Online">Online</th>
|
||||
<th style="text-decoration: underline" title="Status change to Offline">Offline</th>
|
||||
<th style="text-decoration: underline" title="Backup failed">Backup</th>
|
||||
<th style="text-decoration: underline" title="Daily report for host status">Status Report</th>
|
||||
<th style="text-decoration: underline" title="Daily backup report">Backup Report</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for channel in message_channels %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/message_center/manage_channel/?uuid={{ channel.uuid }}">
|
||||
{{ channel.name }}
|
||||
</a>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{% if channel.enabled %}
|
||||
<span style="display: none">enabled</span><i class="far fa-check-circle text-success"></i>
|
||||
{% else %}
|
||||
<span style="display: none">disabled</span><i class="far fa-times-circle text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ channel.get_channel_type_display }}</td>
|
||||
<td>
|
||||
{% if channel.status_change_online %}
|
||||
<span style="display: none">enabled</span><i class="far fa-check-circle text-success"></i>
|
||||
{% else %}
|
||||
<span style="display: none">disabled</span><i class="far fa-times-circle text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if channel.status_change_offline %}
|
||||
<span style="display: none">enabled</span><i class="far fa-check-circle text-success"></i>
|
||||
{% else %}
|
||||
<span style="display: none">disabled</span><i class="far fa-times-circle text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if channel.backup_fail %}
|
||||
<span style="display: none">enabled</span><i class="far fa-check-circle text-success"></i>
|
||||
{% else %}
|
||||
<span style="display: none">disabled</span><i class="far fa-times-circle text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if channel.daily_status_report %}
|
||||
<span style="display: none">enabled</span><i class="far fa-check-circle text-success"></i>
|
||||
{% else %}
|
||||
<span style="display: none">disabled</span><i class="far fa-times-circle text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if channel.daily_backup_report %}
|
||||
<span style="display: none">enabled</span><i class="far fa-check-circle text-success"></i>
|
||||
{% else %}
|
||||
<span style="display: none">disabled</span><i class="far fa-times-circle text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<a href="/message_center/manage_channel/" class="btn btn-primary">Create Channel</a>
|
||||
<a href="/message_center/manage_settings/" class="btn btn-primary">Message Settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue