Expand UI for notifications.

This commit is contained in:
James Cole
2024-12-07 08:05:51 +01:00
parent 1220564f30
commit 26948a058a
13 changed files with 2834 additions and 2622 deletions

View File

@@ -67,22 +67,24 @@ class HomeController extends Controller
// admin notification settings: // admin notification settings:
$notifications = []; $notifications = [];
foreach (config('firefly.admin_notifications') as $item) { foreach (config('notifications.notifications.owner') as $key => $info) {
$notifications[$item] = app('fireflyconfig')->get(sprintf('notification_%s', $item), true)->data; if($info['enabled']) {
$notifications[$key] = app('fireflyconfig')->get(sprintf('notification_%s', $key), true)->data;
} }
$slackUrl = app('fireflyconfig')->get('slack_webhook_url', '')->data; }
//
return view('admin.index', compact('title', 'mainTitleIcon', 'email', 'notifications', 'slackUrl')); return view('admin.index', compact('title', 'mainTitleIcon', 'email', 'notifications'));
} }
public function notifications(Request $request): RedirectResponse public function notifications(Request $request): RedirectResponse
{ {
foreach (config('firefly.admin_notifications') as $item) { foreach (config('notifications.notifications.owner') as $key => $info) {
$value = false; $value = false;
if ($request->has(sprintf('notification_%s', $item))) { if ($request->has(sprintf('notification_%s', $key))) {
$value = true; $value = true;
} }
app('fireflyconfig')->set(sprintf('notification_%s', $item), $value); app('fireflyconfig')->set(sprintf('notification_%s', $key), $value);
} }
$url = (string)$request->get('slackUrl'); $url = (string)$request->get('slackUrl');
if ('' === $url) { if ('' === $url) {

View File

@@ -0,0 +1,44 @@
<?php
/*
* NotificationController.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Http\Controllers\Controller;
use Illuminate\Support\Facades\Log;
class NotificationController extends Controller
{
public function index()
{
Log::channel('audit')->info('User visits notifications index.');
$title = (string) trans('firefly.administration');
$mainTitleIcon = 'fa-hand-spock-o';
$subTitle = (string) trans('firefly.title_owner_notifications');
$subTitleIcon = 'envelope-o';
$slackUrl = app('fireflyconfig')->get('slack_webhook_url', '')->data;
$discordUrl = app('fireflyconfig')->get('discord_webhook_url', '')->data;
$channels = config('notifications.channels');
return view('admin.notifications.index', compact('title', 'subTitle', 'mainTitleIcon', 'subTitleIcon', 'channels', 'slackUrl','discordUrl'));
}
}

View File

@@ -111,6 +111,7 @@ class PreferencesController extends Controller
// notification preferences (single value for each): // notification preferences (single value for each):
$notifications = []; $notifications = [];
die('fix the reference to the available notifications.');
foreach (config('firefly.available_notifications') as $notification) { foreach (config('firefly.available_notifications') as $notification) {
$notifications[$notification] = app('preferences')->get(sprintf('notification_%s', $notification), true)->data; $notifications[$notification] = app('preferences')->get(sprintf('notification_%s', $notification), true)->data;
} }
@@ -165,6 +166,7 @@ class PreferencesController extends Controller
// extract notifications: // extract notifications:
$all = $request->all(); $all = $request->all();
die('fix the reference to the available notifications.');
foreach (config('firefly.available_notifications') as $option) { foreach (config('firefly.available_notifications') as $option) {
$key = sprintf('notification_%s', $option); $key = sprintf('notification_%s', $option);
if (array_key_exists($key, $all)) { if (array_key_exists($key, $all)) {

View File

@@ -147,9 +147,7 @@ return [
'update_endpoint' => 'https://version.firefly-iii.org/index.json', 'update_endpoint' => 'https://version.firefly-iii.org/index.json',
'update_minimum_age' => 7, 'update_minimum_age' => 7,
// notifications
'available_notifications' => ['bill_reminder', 'new_access_token', 'transaction_creation', 'user_login', 'rule_action_failures'],
'admin_notifications' => ['admin_new_reg', 'user_new_reg', 'new_version', 'invite_created', 'invite_redeemed'],
// enabled languages // enabled languages
'languages' => [ 'languages' => [

54
config/notifications.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
/*
* notifications.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
return [
'channels' => [
'email' => ['enabled' => true, 'ui_configurable' => 0,],
'slack' => ['enabled' => true, 'ui_configurable' => 1,],
'discord' => ['enabled' => true, 'ui_configurable' => 1,],
'nfty' => ['enabled' => false, 'ui_configurable' => 0,],
'pushover' => ['enabled' => false, 'ui_configurable' => 0,],
'gotify' => ['enabled' => false, 'ui_configurable' => 0,],
'pushbullet' => ['enabled' => false, 'ui_configurable' => 0,],
],
'notifications' => [
'user' => [
'some_notification' => [
'enabled' => true,
'email' => '',
'slack' => '',
],
],
'owner' => [
//'invitation_created' => ['enabled' => true],
// 'some_notification' => ['enabled' => true],
'admin_new_reg' => ['enabled' => true],
'user_new_reg' => ['enabled' => true],
'new_version' => ['enabled' => true],
'invite_created' => ['enabled' => true],
'invite_redeemed' => ['enabled' => true],
],
],
// // notifications
// 'available_notifications' => ['bill_reminder', 'new_access_token', 'transaction_creation', 'user_login', 'rule_action_failures'],
// 'admin_notifications' => ['admin_new_reg', 'user_new_reg', 'new_version', 'invite_created', 'invite_redeemed'],
];

View File

@@ -86,4 +86,7 @@ return [
'mfa_enableMFA' => 'Enable multi-factor authentication', 'mfa_enableMFA' => 'Enable multi-factor authentication',
'mfa_backup_codes' => 'Backup codes', 'mfa_backup_codes' => 'Backup codes',
'mfa_disableMFA' => 'Disable multi-factor authentication', 'mfa_disableMFA' => 'Disable multi-factor authentication',
// notifications
'notification_index' => 'Owner notifications',
]; ];

View File

@@ -1382,6 +1382,7 @@ return [
'slack_webhook_url' => 'Slack Webhook URL', 'slack_webhook_url' => 'Slack Webhook URL',
'slack_webhook_url_help' => 'If you want Firefly III to notify you using Slack, enter the webhook URL here. Otherwise leave the field blank. If you are an admin, you need to set this URL in the administration as well.', 'slack_webhook_url_help' => 'If you want Firefly III to notify you using Slack, enter the webhook URL here. Otherwise leave the field blank. If you are an admin, you need to set this URL in the administration as well.',
'slack_url_label' => 'Slack "incoming webhook" URL', 'slack_url_label' => 'Slack "incoming webhook" URL',
'discord_url_label' => 'Discord webhook URL',
// Financial administrations // Financial administrations
'administration_index' => 'Financial administration', 'administration_index' => 'Financial administration',
@@ -1909,6 +1910,8 @@ return [
'select_at_least_one_tag' => 'Please select at least one tag', 'select_at_least_one_tag' => 'Please select at least one tag',
'select_at_least_one_expense' => 'Please select at least one combination of expense/revenue accounts. If you have none (the list is empty) this report is not available.', 'select_at_least_one_expense' => 'Please select at least one combination of expense/revenue accounts. If you have none (the list is empty) this report is not available.',
'account_default_currency' => 'This will be the default currency associated with this account.', 'account_default_currency' => 'This will be the default currency associated with this account.',
'piggy_default_currency' => 'Piggy banks can only save money in a single currency.',
'piggy_account_currency_match' => 'Only accounts that use the previously selected currency will be accepted.',
'reconcile_has_more' => 'Your Firefly III ledger has more money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".', 'reconcile_has_more' => 'Your Firefly III ledger has more money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".',
'reconcile_has_less' => 'Your Firefly III ledger has less money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".', 'reconcile_has_less' => 'Your Firefly III ledger has less money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".',
'reconcile_is_equal' => 'Your Firefly III ledger and your bank statements match. There is nothing to do. Please press "Confirm reconciliation" to confirm your input.', 'reconcile_is_equal' => 'Your Firefly III ledger and your bank statements match. There is nothing to do. Please press "Confirm reconciliation" to confirm your input.',
@@ -2477,16 +2480,33 @@ return [
'admin_maintanance_title' => 'Maintenance', 'admin_maintanance_title' => 'Maintenance',
'admin_maintanance_expl' => 'Some nifty buttons for Firefly III maintenance', 'admin_maintanance_expl' => 'Some nifty buttons for Firefly III maintenance',
'admin_maintenance_clear_cache' => 'Clear cache', 'admin_maintenance_clear_cache' => 'Clear cache',
'admin_notifications' => 'Admin notifications', 'owner_notifications' => 'Admin notifications',
'admin_notifications_expl' => 'The following notifications can be enabled or disabled by the administrator. If you want to get these messages over Slack as well, set the "incoming webhook" URL.', 'owner_notifications_expl' => 'The following notifications can be enabled or disabled by the administrator. It will be sent over ALL configured channels. Some channels are configured in your environment variables, others can be set in your <a href="#">notifications settings</a>.',
'admin_notification_check_user_new_reg' => 'User gets post-registration welcome message', 'settings_notifications' => 'Settings for notifications',
'admin_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification', 'title_owner_notifications' => 'Owner notifications',
'admin_notification_check_new_version' => 'A new version is available', 'owner_notification_check_user_new_reg' => 'User gets post-registration welcome message',
'admin_notification_check_invite_created' => 'A user is invited to Firefly III', 'owner_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification',
'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed', 'owner_notification_check_new_version' => 'A new version is available',
'owner_notification_check_invite_created' => 'A user is invited to Firefly III',
'owner_notification_check_invite_redeemed' => 'A user invitation is redeemed',
'all_invited_users' => 'All invited users', 'all_invited_users' => 'All invited users',
'save_notification_settings' => 'Save settings', 'save_notification_settings' => 'Save settings',
'notification_settings' => 'Settings for notifications',
'notification_settings_saved' => 'The notification settings have been saved', 'notification_settings_saved' => 'The notification settings have been saved',
'available_channels_title' => 'Available channels',
'available_channels_expl' => 'These channels are available to send notifications over. To test your confiuration, use the buttons below. Please note that the buttons have no spam control.',
'notification_channel_name_email' => 'Email',
'notification_channel_name_slack' => 'Slack',
'notification_channel_name_discord' => 'Discord',
'notification_channel_name_nfty' => 'Nfty',
'notification_channel_name_pushover' => 'Pushover',
'notification_channel_name_gotify' => 'Gotify',
'notification_channel_name_pushbullet' => 'Pushbullet',
'channel_not_available' => 'not available yet',
'configure_channel_in_env' => 'needs environment variables',
'test_notification_channel_name_email' => 'Test email',
'test_notification_channel_name_slack' => 'Test Slack',
'test_notification_channel_name_discord' => 'Test Discord',
'split_transaction_title' => 'Description of the split transaction', 'split_transaction_title' => 'Description of the split transaction',
'split_transaction_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', 'split_transaction_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.',

View File

@@ -17,6 +17,7 @@
</li> </li>
<li><a href="{{ route('admin.links.index') }}">{{ 'journal_link_configuration'|_ }}</a></li> <li><a href="{{ route('admin.links.index') }}">{{ 'journal_link_configuration'|_ }}</a></li>
<li><a href="{{ route('admin.update-check') }}">{{ 'update_check_title'|_ }}</a></li> <li><a href="{{ route('admin.update-check') }}">{{ 'update_check_title'|_ }}</a></li>
<li><a href="{{ route('admin.notifications') }}">{{ 'settings_notifications'|_ }}</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -34,20 +35,20 @@
<input type="hidden" name="_token" value="{{ csrf_token() }}"> <input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="box box-default"> <div class="box box-default">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">{{ 'admin_notifications'|_ }}</h3> <h3 class="box-title">{{ 'owner_notifications'|_ }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
<p> <p>
{{ 'admin_notifications_expl'|_ }} {{ 'owner_notifications_expl'|_ }}
</p> </p>
{% for notification, value in notifications %} {% for notification, value in notifications %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input value="1" {% if true == value %}checked{% endif %} type="checkbox" name="notification_{{ notification }}"> {{ trans('firefly.admin_notification_check_'~notification) }} <input value="1" {% if true == value %}checked{% endif %} type="checkbox" name="notification_{{ notification }}"> {{ trans('firefly.owner_notification_check_'~notification) }}
</label> </label>
</div> </div>
{% endfor %} {% endfor %}
{{ ExpandedForm.text('slackUrl', slackUrl, {'label' : 'slack_url_label'|_}) }} {# {{ ExpandedForm.text('slackUrl', slackUrl, {'label' : 'slack_url_label'|_}) }} #}
</div> </div>
<div class="box-footer"> <div class="box-footer">
<button type="submit" class="btn btn-success"> <button type="submit" class="btn btn-success">

View File

@@ -0,0 +1,68 @@
{% extends './layout/default' %}
{% block breadcrumbs %}
{{ Breadcrumbs.render }}
{% endblock %}
{% block content %}
<div class="row" xmlns="http://www.w3.org/1999/html">
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<form action="{{ route('admin.notification.post') }}" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ 'notification_settings'|_ }}</h3>
</div>
<div class="box-body">
{{ ExpandedForm.text('slackUrl', slackUrl, {'label' : 'slack_url_label'|_}) }}
{{ ExpandedForm.text('discordUrl', discordUrl, {'label' : 'discord_url_label'|_}) }}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-success">
<span class="fa fa-check-circle"></span> {{ ('save_notification_settings')|_ }}
</button>
</div>
</div>
</form>
</div>
<form action="{{ route('admin.notification.test') }}" method="post">
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ 'available_channels_title'|_ }}</h3>
</div>
<div class="box-body">
<p>
{{ 'available_channels_expl'|_ }}
</p>
<ul>
{% for name,info in channels %}
<li>
{% if info.enabled %}
☑️ {{ trans('firefly.notification_channel_name_'~name) }}
{% if 0 == info.ui_configurable %}({{ 'configure_channel_in_env'|_ }}) {% endif %}
{% endif %}
{% if not info.enabled %}
⚠️ {{ trans('firefly.notification_channel_name_'~name) }} ({{ 'channel_not_available'|_ }})
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<div class="box-footer">
<div class="btn-group">
{% for name,info in channels %}
{% if info.enabled %}
<button type="submit" name="test_submit" value="{{ name }}" class="btn btn-default">
{{ trans('firefly.test_notification_channel_name_'~name) }}
</button>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
</form>
</div>
{% endblock %}

View File

@@ -2,7 +2,7 @@
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label> <label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
<div class="col-sm-8"> <div class="col-sm-8">
{{ Html.select(name~"[]", list, selected).id(options.id).class('form-control').attribute('multiple').attribute('autocomplete','off').attribute('spellcheck','false').attribute('placeholder', options.placeholder) }} {{ Html.multiselect(name~"[]", list, selected).id(options.id).class('form-control').attribute('autocomplete','off').attribute('spellcheck','false').attribute('placeholder', options.placeholder) }}
{% include 'form.help' %} {% include 'form.help' %}
{% include 'form.feedback' %} {% include 'form.feedback' %}

View File

@@ -56,7 +56,13 @@
{% endif %} {% endif %}
{# SINGLE INFO MESSAGE #} {# SINGLE INFO MESSAGE #}
{% if session('info') is not iterable %} {% if session('info') is not iterable %}
{% if session_has('info_url') %}
<a href="{{ session('info_url') }}">
{% endif %}
<strong>{{ 'flash_info'|_ }}:</strong> {{ session('info')|raw }} <strong>{{ 'flash_info'|_ }}:</strong> {{ session('info')|raw }}
{% if session_has('info_url') %}
</a>
{% endif %}
{% endif %} {% endif %}
</div> </div>

View File

@@ -179,6 +179,15 @@ Breadcrumbs::for(
} }
); );
Breadcrumbs::for(
'admin.notification.index',
static function (Generator $breadcrumbs): void {
$breadcrumbs->parent('home');
$breadcrumbs->push(trans('firefly.administration'), route('admin.index'));
$breadcrumbs->push(trans('breadcrumbs.notification_index'), route('admin.notification.index'));
}
);
Breadcrumbs::for( Breadcrumbs::for(
'admin.users', 'admin.users',
static function (Generator $breadcrumbs): void { static function (Generator $breadcrumbs): void {

View File

@@ -1398,6 +1398,11 @@ Route::group(
// FF configuration: // FF configuration:
Route::get('configuration', ['uses' => 'ConfigurationController@index', 'as' => 'configuration.index']); Route::get('configuration', ['uses' => 'ConfigurationController@index', 'as' => 'configuration.index']);
Route::post('configuration', ['uses' => 'ConfigurationController@postIndex', 'as' => 'configuration.index.post']); Route::post('configuration', ['uses' => 'ConfigurationController@postIndex', 'as' => 'configuration.index.post']);
// routes for notifications settings.
Route::get('notifications', ['uses' => 'NotificationController@index', 'as' => 'notification.index']);
Route::post('notifications', ['uses' => 'NotificationController@postIndex', 'as' => 'notification.post']);
Route::post('notifications/test', ['uses' => 'NotificationController@testNotification', 'as' => 'notification.test']);
} }
); );