Admin and Discord Notification Improvements (#1407)
* Backend changes Sorting for : Airlines, Subfleets, Type Ratings and exported Flights Blade : Added WYSIWYG editor to flight remarks/notes field Notifications : Mails > Added enabled/disabled settings for mails. Discord > Dashed out PirepPreFiled, re-enabled PirepStatusChanged with reduced messages * Update PirepStatusChanged.php * Update NotificationEventsHandler.php * in_array fix * Fix Discord Notifications * Discord Notifications Removed the pirep url from message as it is mostly private at phpvms side. Also removed the Flight Ident from fields 'cause it is being used in the title. Added the user avatar as thumbnail, and pirep filed message uses the airline logo as the main image. Even though the outgoing pirep status messages are reduced, it is still possible to enable/disable them from admin settings. Pirep Filed is always being sent (if the webhook is defined) * StyleFix
This commit is contained in:
@@ -410,6 +410,41 @@
|
||||
options: ''
|
||||
type: text
|
||||
description: The Discord Webhook URL for private notifications
|
||||
- key: notifications.discord_pirep_status
|
||||
name: Discord Pirep Messages (Public)
|
||||
group: notifications
|
||||
value: true
|
||||
options: ''
|
||||
type: boolean
|
||||
description: Pirep status messages (Only key events are being sent)
|
||||
- key: notifications.mail_pirep_admin
|
||||
name: Pirep Filed (Admin)
|
||||
group: notifications
|
||||
value: true
|
||||
options: ''
|
||||
type: boolean
|
||||
description: Pirep filed mails sent to admins
|
||||
- key: notifications.mail_pirep_user_ack
|
||||
name: Pirep Accepted (Pilot)
|
||||
group: notifications
|
||||
value: true
|
||||
options: ''
|
||||
type: boolean
|
||||
description: Pirep Accepted mails sent to pilots
|
||||
- key: notifications.mail_pirep_user_rej
|
||||
name: Pirep Rejected (Pilot)
|
||||
group: notifications
|
||||
value: true
|
||||
options: ''
|
||||
type: boolean
|
||||
description: Pirep Rejected mails sent to pilots
|
||||
- key: notifications.mail_news
|
||||
name: News Mails
|
||||
group: notifications
|
||||
value: true
|
||||
options: ''
|
||||
type: boolean
|
||||
description: News mails sent to all members
|
||||
- key: 'cron.random_id'
|
||||
name: 'Cron Randomized ID'
|
||||
group: 'cron'
|
||||
|
||||
@@ -38,7 +38,7 @@ class AirlinesController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->airlineRepo->pushCriteria(new RequestCriteria($request));
|
||||
$airlines = $this->airlineRepo->all();
|
||||
$airlines = $this->airlineRepo->orderby('name', 'asc')->get();
|
||||
|
||||
return view('admin.airlines.index', [
|
||||
'airlines' => $airlines,
|
||||
|
||||
@@ -308,7 +308,7 @@ class FlightController extends Controller
|
||||
$where['airline_id'] = $airline_id;
|
||||
$file_name = 'flights-'.$airline_id.'.csv';
|
||||
}
|
||||
$flights = $this->flightRepo->where($where)->get();
|
||||
$flights = $this->flightRepo->where($where)->orderBy('airline_id')->orderBy('flight_number')->orderBy('route_code')->orderBy('route_leg')->get();
|
||||
|
||||
$path = $exporter->exportFlights($flights);
|
||||
return response()->download($path, $file_name, ['content-type' => 'text/csv'])->deleteFileAfterSend(true);
|
||||
|
||||
@@ -83,7 +83,7 @@ class SubfleetController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->subfleetRepo->with(['airline'])->pushCriteria(new RequestCriteria($request));
|
||||
$subfleets = $this->subfleetRepo->all();
|
||||
$subfleets = $this->subfleetRepo->orderby('name', 'asc')->get();
|
||||
|
||||
return view('admin.subfleets.index', [
|
||||
'subfleets' => $subfleets,
|
||||
|
||||
@@ -32,7 +32,7 @@ class TypeRatingController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->typeratingRepo->pushCriteria(new RequestCriteria($request));
|
||||
$typeratings = $this->typeratingRepo->all();
|
||||
$typeratings = $this->typeratingRepo->orderby('type', 'asc')->get();
|
||||
|
||||
return view('admin.typeratings.index', [
|
||||
'typeratings' => $typeratings,
|
||||
@@ -50,7 +50,6 @@ class TypeRatingController extends Controller
|
||||
|
||||
$model = $this->typeratingRepo->create($input);
|
||||
Flash::success('Type Rating saved successfully.');
|
||||
// Cache::forget(config('cache.keys.RANKS_PILOT_LIST.key'));
|
||||
|
||||
return redirect(route('admin.typeratings.edit', [$model->id]));
|
||||
}
|
||||
|
||||
@@ -11,14 +11,16 @@ use Carbon\Carbon;
|
||||
*/
|
||||
class DiscordMessage
|
||||
{
|
||||
const COLOR_SUCCESS = '#0B6623';
|
||||
const COLOR_WARNING = '#FD6A02';
|
||||
const COLOR_ERROR = '#ED2939';
|
||||
const COLOR_SUCCESS = '0B6623';
|
||||
const COLOR_WARNING = 'FD6A02';
|
||||
const COLOR_ERROR = 'ED2939';
|
||||
|
||||
public $webhook_url;
|
||||
|
||||
protected $title;
|
||||
protected $url;
|
||||
protected $thumbnail = [];
|
||||
protected $image = [];
|
||||
protected $description;
|
||||
protected $timestamp;
|
||||
protected $footer;
|
||||
@@ -26,30 +28,43 @@ class DiscordMessage
|
||||
protected $author = [];
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* Supply the webhook URL that this should be going to
|
||||
*/
|
||||
// Supply the webhook URL that this should be going to
|
||||
public function webhook(string $url): self
|
||||
{
|
||||
$this->webhook_url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of the notification
|
||||
*/
|
||||
// Title of the embed
|
||||
public function title(string $title): self
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// URL of the Title
|
||||
public function url(string $url): self
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Thumbnail image (placed right side of embed)
|
||||
// ['url' => '']
|
||||
public function thumbnail(array $thumbnail): self
|
||||
{
|
||||
$this->thumbnail = $thumbnail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Main image (placed bottom of embed)
|
||||
// ['url' => '']
|
||||
public function image(array $image): self
|
||||
{
|
||||
$this->image = $image;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $descriptionLines
|
||||
*/
|
||||
@@ -63,22 +78,15 @@ class DiscordMessage
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the author information:
|
||||
* [
|
||||
* 'name' => '',
|
||||
* 'url' => '',
|
||||
* 'icon_url' => '',
|
||||
*/
|
||||
// Author details
|
||||
// ['name' => '', 'url' => '', 'icon_url' => '']
|
||||
public function author(array $author): self
|
||||
{
|
||||
$this->author = $author;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fields
|
||||
*/
|
||||
// Fields
|
||||
public function fields(array $fields): self
|
||||
{
|
||||
$this->fields = [];
|
||||
@@ -99,32 +107,45 @@ class DiscordMessage
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Fixed Success Color
|
||||
public function success(): self
|
||||
{
|
||||
$this->color = static::COLOR_SUCCESS;
|
||||
$this->color = hexdec('0B6623'); // static::COLOR_SUCCESS;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Fixed Warning
|
||||
public function warning(): self
|
||||
{
|
||||
$this->color = static::COLOR_WARNING;
|
||||
$this->color = hexdec('FD6A02'); // static::COLOR_WARNING;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Fixed Error
|
||||
public function error(): self
|
||||
{
|
||||
$this->color = static::COLOR_ERROR;
|
||||
$this->color = hexdec('ED2939'); // static::COLOR_ERROR;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Custom Color
|
||||
public function color(string $embed_color): self
|
||||
{
|
||||
$this->color = hexdec($embed_color);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$embeds = [
|
||||
'type' => 'rich',
|
||||
'color' => $this->color,
|
||||
'title' => $this->title,
|
||||
'url' => $this->url,
|
||||
'type' => 'rich',
|
||||
'thumbnail' => $this->thumbnail,
|
||||
'description' => $this->description,
|
||||
'author' => $this->author,
|
||||
'image' => $this->image,
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
];
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class NewsAdded extends Notification implements ShouldQueue
|
||||
public function toDiscordChannel($news): ?DiscordMessage
|
||||
{
|
||||
$dm = new DiscordMessage();
|
||||
return $dm->url(setting('notifications.discord_public_webhook_url'))
|
||||
return $dm->webhook(setting('notifications.discord_public_webhook_url'))
|
||||
->success()
|
||||
->title('News: '.$news->subject)
|
||||
->author([
|
||||
|
||||
@@ -43,11 +43,10 @@ class PirepFiled extends Notification implements ShouldQueue
|
||||
{
|
||||
$title = 'Flight '.$pirep->ident.' Filed';
|
||||
$fields = [
|
||||
'Flight' => $pirep->ident,
|
||||
'Departure Airport' => $pirep->dpt_airport_id,
|
||||
'Arrival Airport' => $pirep->arr_airport_id,
|
||||
'Equipment' => $pirep->aircraft->ident,
|
||||
'Flight Time' => Time::minutesToTimeString($pirep->flight_time),
|
||||
'Dep.Airport' => $pirep->dpt_airport_id,
|
||||
'Arr.Airport' => $pirep->arr_airport_id,
|
||||
'Equipment' => $pirep->aircraft->ident,
|
||||
'Flight Time' => Time::minutesToTimeString($pirep->flight_time),
|
||||
];
|
||||
|
||||
if ($pirep->distance) {
|
||||
@@ -63,16 +62,19 @@ class PirepFiled extends Notification implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// User avatar, somehow $pirep->user->resolveAvatarUrl() is not being accepted by Discord as thumbnail
|
||||
$user_avatar = !empty($pirep->user->avatar) ? $pirep->user->avatar->url : $pirep->user->gravatar(256);
|
||||
|
||||
$dm = new DiscordMessage();
|
||||
return $dm->url(setting('notifications.discord_public_webhook_url'))
|
||||
return $dm->webhook(setting('notifications.discord_public_webhook_url'))
|
||||
->success()
|
||||
->title($title)
|
||||
->description($pirep->user->discord_id ? 'Flight by <@'.$pirep->user->discord_id.'>' : '')
|
||||
->url(route('frontend.pireps.show', [$pirep->id]))
|
||||
->thumbnail(['url' => $user_avatar])
|
||||
->image(['url' => $pirep->airline->logo])
|
||||
->author([
|
||||
'name' => $pirep->user->ident.' - '.$pirep->user->name_private,
|
||||
'url' => route('frontend.profile.show', [$pirep->user_id]),
|
||||
'icon_url' => $pirep->user->resolveAvatarUrl(),
|
||||
'name' => $pirep->user->ident.' - '.$pirep->user->name_private,
|
||||
'url' => route('frontend.profile.show', [$pirep->user_id]),
|
||||
])
|
||||
->fields($fields);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ use App\Notifications\Channels\Discord\DiscordMessage;
|
||||
use App\Notifications\Channels\Discord\DiscordWebhook;
|
||||
use App\Support\Units\Distance;
|
||||
use App\Support\Units\Time;
|
||||
use function config;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use PhpUnitsOfMeasure\Exception\NonNumericValue;
|
||||
use PhpUnitsOfMeasure\Exception\NonStringUnitName;
|
||||
use function route;
|
||||
|
||||
/**
|
||||
* Send the PIREP accepted message to a particular user, can also be sent to Discord
|
||||
@@ -75,14 +73,16 @@ class PirepStatusChanged extends Notification implements ShouldQueue
|
||||
*/
|
||||
public function toDiscordChannel($pirep): ?DiscordMessage
|
||||
{
|
||||
$title = 'Flight '.$pirep->ident.' '.self::$verbs[$pirep->status];
|
||||
if (empty(setting('notifications.discord_public_webhook_url'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$title = 'Flight '.$pirep->ident.' '.self::$verbs[$pirep->status];
|
||||
$fields = [
|
||||
'Flight' => $pirep->ident,
|
||||
'Departure Airport' => $pirep->dpt_airport_id,
|
||||
'Arrival Airport' => $pirep->arr_airport_id,
|
||||
'Equipment' => $pirep->aircraft->ident,
|
||||
'Flight Time' => Time::minutesToTimeString($pirep->flight_time),
|
||||
'Dep.Airport' => $pirep->dpt_airport_id,
|
||||
'Arr.Airport' => $pirep->arr_airport_id,
|
||||
'Equipment' => $pirep->aircraft->ident,
|
||||
'Flight Time' => Time::minutesToTimeString($pirep->flight_time),
|
||||
];
|
||||
|
||||
// Show the distance, but include the planned distance if it's been set
|
||||
@@ -109,16 +109,29 @@ class PirepStatusChanged extends Notification implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// User avatar, somehow $pirep->user->resolveAvatarUrl() is not being accepted by Discord as thumbnail
|
||||
$user_avatar = !empty($pirep->user->avatar) ? $pirep->user->avatar->url : $pirep->user->gravatar(256);
|
||||
|
||||
// Proper coloring for the messages
|
||||
// Pirep Filed > success, normals > warning, non-normals > error
|
||||
$danger_types = [
|
||||
PirepStatus::GRND_RTRN,
|
||||
PirepStatus::DIVERTED,
|
||||
PirepStatus::CANCELLED,
|
||||
PirepStatus::PAUSED,
|
||||
PirepStatus::EMERG_DESCENT,
|
||||
];
|
||||
$color = in_array($pirep->status, $danger_types, true) ? 'ED2939' : 'FD6A02';
|
||||
|
||||
$dm = new DiscordMessage();
|
||||
return $dm->url(setting('notifications.discord_public_webhook_url'))
|
||||
->success()
|
||||
return $dm->webhook(setting('notifications.discord_public_webhook_url'))
|
||||
->color($color)
|
||||
->title($title)
|
||||
->description($pirep->user->discord_id ? 'Flight by <@'.$pirep->user->discord_id.'>' : '')
|
||||
->url(route('frontend.pireps.show', [$pirep->id]))
|
||||
->thumbnail(['url' => $user_avatar])
|
||||
->author([
|
||||
'name' => $pirep->user->ident.' - '.$pirep->user->name_private,
|
||||
'url' => route('frontend.profile.show', [$pirep->user_id]),
|
||||
'icon_url' => $pirep->user->resolveAvatarUrl(),
|
||||
'name' => $pirep->user->ident.' - '.$pirep->user->name_private,
|
||||
'url' => route('frontend.profile.show', [$pirep->user_id]),
|
||||
])
|
||||
->fields($fields);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class UserRegistered extends Notification implements ShouldQueue
|
||||
public function toDiscordChannel($notifiable): ?DiscordMessage
|
||||
{
|
||||
$dm = new DiscordMessage();
|
||||
return $dm->url(setting('notifications.discord_private_webhook_url'))
|
||||
return $dm->webhook(setting('notifications.discord_private_webhook_url'))
|
||||
->success()
|
||||
->title('New User Registered: '.$this->user->ident)
|
||||
->author([
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Events\PirepRejected;
|
||||
use App\Events\PirepStatusChange;
|
||||
use App\Events\UserRegistered;
|
||||
use App\Events\UserStateChanged;
|
||||
use App\Models\Enums\PirepStatus;
|
||||
use App\Models\Enums\UserState;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Messages\UserRejected;
|
||||
@@ -161,7 +162,7 @@ class NotificationEventsHandler extends Listener
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefile notification
|
||||
* Prefile notification. Disabled intentionally, No need to send it to Discord
|
||||
*/
|
||||
public function onPirepPrefile(PirepPrefiled $event): void
|
||||
{
|
||||
@@ -170,16 +171,33 @@ class NotificationEventsHandler extends Listener
|
||||
/*
|
||||
* Broadcast notifications
|
||||
*/
|
||||
Notification::send([$event->pirep], new Messages\Broadcast\PirepPrefiled($event->pirep));
|
||||
// Notification::send([$event->pirep], new Messages\Broadcast\PirepPrefiled($event->pirep));
|
||||
}
|
||||
|
||||
/**
|
||||
* Status Change notification. Disabled for now, too noisy
|
||||
* Status Change notification.
|
||||
* Reduced the messages (Boarding, Pushback, TakeOff, Landing and non-normals only)
|
||||
* If needed array can be tied to a setting at admin side for further customization
|
||||
*/
|
||||
public function onPirepStatusChange(PirepStatusChange $event): void
|
||||
{
|
||||
// Log::info('NotificationEvents::onPirepStatusChange: '.$event->pirep->id.' prefiled');
|
||||
// Notification::send([$event->pirep], new Messages\Discord\PirepStatusChanged($event->pirep));
|
||||
Log::info('NotificationEvents::onPirepStatusChange: '.$event->pirep->id.' status changed');
|
||||
|
||||
$message_types = [
|
||||
PirepStatus::BOARDING,
|
||||
PirepStatus::PUSHBACK_TOW,
|
||||
PirepStatus::GRND_RTRN,
|
||||
PirepStatus::TAKEOFF,
|
||||
PirepStatus::LANDED,
|
||||
PirepStatus::DIVERTED,
|
||||
PirepStatus::CANCELLED,
|
||||
PirepStatus::PAUSED,
|
||||
PirepStatus::EMERG_DESCENT,
|
||||
];
|
||||
|
||||
if (setting('notifications.discord_pirep_status', true) && in_array($event->pirep->status, $message_types, true)) {
|
||||
Notification::send([$event->pirep], new Messages\Broadcast\PirepStatusChanged($event->pirep));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +208,9 @@ class NotificationEventsHandler extends Listener
|
||||
public function onPirepFile(PirepFiled $event): void
|
||||
{
|
||||
Log::info('NotificationEvents::onPirepFile: '.$event->pirep->id.' filed');
|
||||
$this->notifyAdmins(new Messages\PirepFiled($event->pirep));
|
||||
if (setting('notifications.mail_pirep_admin', true)) {
|
||||
$this->notifyAdmins(new Messages\PirepFiled($event->pirep));
|
||||
}
|
||||
|
||||
/*
|
||||
* Broadcast notifications
|
||||
@@ -205,8 +225,10 @@ class NotificationEventsHandler extends Listener
|
||||
*/
|
||||
public function onPirepAccepted(PirepAccepted $event): void
|
||||
{
|
||||
Log::info('NotificationEvents::onPirepAccepted: '.$event->pirep->id.' accepted');
|
||||
$this->notifyUser($event->pirep->user, new Messages\PirepAccepted($event->pirep));
|
||||
if (setting('notifications.mail_pirep_user_ack', true)) {
|
||||
Log::info('NotificationEvents::onPirepAccepted: '.$event->pirep->id.' accepted');
|
||||
$this->notifyUser($event->pirep->user, new Messages\PirepAccepted($event->pirep));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,8 +238,10 @@ class NotificationEventsHandler extends Listener
|
||||
*/
|
||||
public function onPirepRejected(PirepRejected $event): void
|
||||
{
|
||||
Log::info('NotificationEvents::onPirepRejected: '.$event->pirep->id.' rejected');
|
||||
$this->notifyUser($event->pirep->user, new Messages\PirepRejected($event->pirep));
|
||||
if (setting('notifications.mail_pirep_user_rej', true)) {
|
||||
Log::info('NotificationEvents::onPirepRejected: '.$event->pirep->id.' rejected');
|
||||
$this->notifyUser($event->pirep->user, new Messages\PirepRejected($event->pirep));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +252,9 @@ class NotificationEventsHandler extends Listener
|
||||
public function onNewsAdded(NewsAdded $event): void
|
||||
{
|
||||
Log::info('NotificationEvents::onNewsAdded');
|
||||
$this->notifyAllUsers(new Messages\NewsAdded($event->news));
|
||||
if (setting('notifications.mail_news', true)) {
|
||||
$this->notifyAllUsers(new Messages\NewsAdded($event->news));
|
||||
}
|
||||
|
||||
/*
|
||||
* Broadcast notifications
|
||||
|
||||
@@ -174,7 +174,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="form-container">
|
||||
@@ -260,8 +259,9 @@
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-12">
|
||||
{{ Form::textarea('notes', null, [
|
||||
'class' => 'form-control input-text',
|
||||
'style' => 'padding: 10px',
|
||||
'id' => 'editor',
|
||||
'class' => 'editor',
|
||||
'style' => 'padding: 5px',
|
||||
]) }}
|
||||
<p class="text-danger">{{ $errors->first('notes') }}</p>
|
||||
</div>
|
||||
@@ -288,3 +288,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@section('scripts')
|
||||
@parent
|
||||
<script src="{{ public_asset('assets/vendor/ckeditor4/ckeditor.js') }}"></script>
|
||||
<script>
|
||||
$(document).ready(function () { CKEDITOR.replace('editor'); });
|
||||
</script>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user