Added system polling
This commit is contained in:
parent
c23b5ebfc2
commit
4e44e2e266
@ -28,7 +28,9 @@ abstract class Protocol
|
||||
|
||||
// Our sessions Types
|
||||
public const SESSION_AUTO = 0;
|
||||
/** @deprecate Use mailers:class */
|
||||
public const SESSION_EMSI = 1;
|
||||
/** @deprecate Use mailers:class */
|
||||
public const SESSION_BINKP = 2;
|
||||
public const SESSION_ZMODEM = 3;
|
||||
|
||||
@ -447,6 +449,7 @@ abstract class Protocol
|
||||
$slo->sessiontype = $type;
|
||||
$slo->sessiontime = $this->node->session_time;
|
||||
$slo->result = ($rc & self::S_MASK);
|
||||
$slo->originate = $this->originate;
|
||||
|
||||
$so->logs()->save($slo);
|
||||
}
|
||||
|
@ -1420,8 +1420,6 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
$trys++;
|
||||
}
|
||||
|
||||
dump(['ls_rxHdr'=>hex_dump(join('',$this->ls_rxHdr))]);
|
||||
|
||||
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout))) {
|
||||
/* Send ZRINIT again */
|
||||
case self::ZRQINIT:
|
||||
|
@ -34,14 +34,14 @@ class CommBinkpSend extends Command
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Log::info('CBS:- Call BINKP send');
|
||||
|
||||
$mo = Mailer::where('name',self::ID)->singleOrFail();
|
||||
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
if (! $ao)
|
||||
throw new ModelNotFoundException('Unknown node: '.$this->argument('ftn'));
|
||||
|
||||
Job::dispatchSync($ao,$mo);
|
||||
Log::info(sprintf('CBS:- Call BINKP send for %s',$ao->ftn));
|
||||
|
||||
$mo = Mailer::where('name',self::ID)->singleOrFail();
|
||||
|
||||
Job::dispatch($ao,$mo);
|
||||
}
|
||||
}
|
||||
|
@ -34,14 +34,14 @@ class CommEMSISend extends Command
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Log::info('CES:- Call EMSI send');
|
||||
|
||||
$mo = Mailer::where('name',self::ID)->singleOrFail();
|
||||
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
if (! $ao)
|
||||
throw new ModelNotFoundException('Unknown node: '.$this->argument('ftn'));
|
||||
|
||||
Job::dispatchSync($ao,$mo);
|
||||
Log::info(sprintf('CES:- Call EMSI send for %s',$ao->ftn));
|
||||
|
||||
$mo = Mailer::where('name',self::ID)->singleOrFail();
|
||||
|
||||
Job::dispatch($ao,$mo);
|
||||
}
|
||||
}
|
||||
|
50
app/Console/Commands/JobList.php
Normal file
50
app/Console/Commands/JobList.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\Job;
|
||||
|
||||
class JobList extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'job:list';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Detail list of items in the queue';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$lastq = NULL;
|
||||
|
||||
foreach (Job::orderBy('queue')->orderBy('created_at')->cursor() as $o) {
|
||||
if ($o->queue !== $lastq) {
|
||||
$this->alert(sprintf('Queue: %s',$o->queue));
|
||||
$lastq = $o->queue;
|
||||
}
|
||||
|
||||
$this->info(sprintf('%s-%d: %s[%s] - %d tries (Created: %s,Timeout: %s,Next: %s)',
|
||||
$o->uuid,
|
||||
$o->id,
|
||||
$o->display_name,
|
||||
$o->command->subject,
|
||||
$o->attempts,
|
||||
$o->created_at,
|
||||
$o->retry_until ?: '-',
|
||||
$o->reserved_at ?: '-',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
52
app/Console/Commands/MailSend.php
Normal file
52
app/Console/Commands/MailSend.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Jobs\MailSend as Job;
|
||||
|
||||
class MailSend extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'mail:send'
|
||||
.' {--T|type=normal : Send crash, normal or both mail}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Trigger a poll to each node with mail queued';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
switch ($this->option('type')) {
|
||||
case 'crash':
|
||||
Log::info('CML:- Triggering polls to send CRASH mail');
|
||||
Job::dispatchSync(TRUE);
|
||||
break;
|
||||
|
||||
case 'normal':
|
||||
Log::info('CML:- Triggering polls to send NORMAL mail');
|
||||
Job::dispatchSync(FALSE);
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
Log::info('CML:- Triggering polls to send ALL mail');
|
||||
Job::dispatchSync(NULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->error('Specify -T crash, normal or all');
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\MailSend;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
@ -24,8 +25,8 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// $schedule->command('inspire')
|
||||
// ->hourly();
|
||||
$schedule->job(new MailSend(TRUE))->everyFiveMinutes()->withoutOverlapping();
|
||||
$schedule->job(new MailSend(FALSE))->twiceDaily(1,13);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\ViewErrorBag;
|
||||
|
||||
use App\Http\Requests\SystemRegister;
|
||||
use App\Jobs\AddressPoll;
|
||||
use App\Models\{Address,Echoarea,Filearea,Setup,System,SystemZone,Zone};
|
||||
use App\Notifications\Netmails\AddressLink;
|
||||
use App\Rules\{FidoInteger,TwoByteInteger};
|
||||
@ -279,6 +280,13 @@ class SystemController extends Controller
|
||||
foreach (['name','location','sysop','hold','phone','address','port','active','method','notes','zt_id','pkt_type'] as $key)
|
||||
$o->{$key} = $request->post($key);
|
||||
|
||||
switch ($request->post('pollmode')) {
|
||||
case 1: $o->pollmode = FALSE; break;
|
||||
case 2: $o->pollmode = TRUE; break;
|
||||
default: $o->pollmode = NULL;
|
||||
}
|
||||
|
||||
$o->autohold = FALSE;
|
||||
$o->save();
|
||||
|
||||
$mailers = collect($request->post('mailer_details'))
|
||||
@ -653,8 +661,10 @@ class SystemController extends Controller
|
||||
break;
|
||||
}
|
||||
|
||||
if ($ca->count() && $la=$ca->pop())
|
||||
if ($ca->count() && $la=$ca->pop()) {
|
||||
Notification::route('netmail',$la)->notify(new AddressLink(Auth::user()));
|
||||
AddressPoll::dispatch($la)->delay(15);
|
||||
}
|
||||
|
||||
return view('user.system.register_send')
|
||||
->with('la',$la)
|
||||
|
@ -27,6 +27,14 @@ class SystemRegister extends FormRequest
|
||||
return Gate::allows($this->so->users->count() ? 'update' : 'register',$this->so);
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'hold' => 'Must be Yes or No',
|
||||
'pollmode' => 'Must be Hold, Normal or Crash',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@ -64,6 +72,7 @@ class SystemRegister extends FormRequest
|
||||
$this->so->exists ? [
|
||||
'active' => 'required|boolean',
|
||||
'hold' => 'required|boolean',
|
||||
'pollmode' => 'required|integer|min:0|max:2',
|
||||
] : [],
|
||||
));
|
||||
}
|
||||
|
@ -3,24 +3,37 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\ManuallyFailedException;
|
||||
use Illuminate\Queue\MaxAttemptsExceededException;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
use App\Classes\Protocol;
|
||||
use App\Classes\Protocol\{Binkp,EMSI};
|
||||
use App\Classes\Sock\SocketClient;
|
||||
use App\Classes\Sock\SocketException;
|
||||
use App\Models\{Address,Mailer,Setup};
|
||||
use App\Notifications\Netmails\PollingFailed;
|
||||
|
||||
class AddressPoll implements ShouldQueue
|
||||
class AddressPoll implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
private const LOGKEY = 'JAP';
|
||||
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 5;
|
||||
|
||||
public $maxExceptions = 1;
|
||||
|
||||
public $failOnTimeout = TRUE;
|
||||
|
||||
public const QUEUE = 'poll';
|
||||
|
||||
private Address $ao;
|
||||
private ?Mailer $mo;
|
||||
|
||||
@ -28,6 +41,50 @@ class AddressPoll implements ShouldQueue
|
||||
{
|
||||
$this->ao = $ao;
|
||||
$this->mo = $mo;
|
||||
|
||||
$this->onQueue(self::QUEUE);
|
||||
}
|
||||
|
||||
public function __get($key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'address':
|
||||
return $this->ao;
|
||||
|
||||
case 'subject':
|
||||
return $this->ao->ftn;
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Because pluck doesnt return __get() defined vars
|
||||
*
|
||||
* @param $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($key): bool
|
||||
{
|
||||
$keys = ['address'];
|
||||
|
||||
return in_array($key,$keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time to wait between tries
|
||||
*
|
||||
* @return int[] in seconds
|
||||
*/
|
||||
public function backoff(): array
|
||||
{
|
||||
return [
|
||||
60*5, // 5 mins
|
||||
60*60, // 1 hr
|
||||
60*60*6, // 6 hrs
|
||||
60*60*12 // 12 hrs
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,24 +92,19 @@ class AddressPoll implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->ao->system->mailer_preferred->count() || ($this->mo && (! $this->ao->system->mailer_preferred->find($this->mo))))
|
||||
throw new \Exception(sprintf('Unable to poll [%s] missing mailer details',$this->ao->ftn));
|
||||
if (! $this->ao->system->mailer_preferred->count() || ($this->mo && (! $this->ao->system->mailer_preferred->find($this->mo)))) {
|
||||
$this->fail('Missing mailer details');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::info(sprintf('%s:- Polling [%s] - attempt [%d]',self::LOGKEY,$this->ao->ftn,$this->attempts()));
|
||||
|
||||
foreach ($this->ao->system->mailer_preferred as $o) {
|
||||
// If we chose a protocol, skip to find the mailer details for it
|
||||
if ($this->mo && ($o->id !== $this->mo->id))
|
||||
continue;
|
||||
|
||||
Log::info(sprintf('%s:- Starting a [%s] session to [%s:%d]',self::LOGKEY,$o->name,$this->ao->system->address,$o->pivot->port));
|
||||
|
||||
try {
|
||||
$client = SocketClient::create($this->ao->system->address,$o->pivot->port);
|
||||
|
||||
} catch (SocketException $e) {
|
||||
Log::error(sprintf('%s:! Unable to connect to [%s]: %s',self::LOGKEY,$this->ao->ftn,$e->getMessage()));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
switch ($o->name) {
|
||||
case 'BINKP':
|
||||
$s = new Binkp(Setup::findOrFail(config('app.id')));
|
||||
@ -67,16 +119,59 @@ class AddressPoll implements ShouldQueue
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Exception(sprintf('Node [%s] has a mailer type that is unhandled',$this->ao->ftn));
|
||||
$this->fail('Mailer type unhandled');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (($s->session($session,$client,$this->ao) & Protocol::S_MASK) === Protocol::S_OK) {
|
||||
Log::info(sprintf('%s:= Connection ended successfully with [%s]',self::LOGKEY,$client->address_remote));
|
||||
break;
|
||||
Log::info(sprintf('%s:- Trying a [%s] session to [%s:%d] (%s)',
|
||||
self::LOGKEY,$o->name,$this->ao->system->address,$o->pivot->port,$this->ao->ftn));
|
||||
|
||||
} else {
|
||||
Log::alert(sprintf('%s:! Connection failed to [%s]',self::LOGKEY,$client->address_remote));
|
||||
try {
|
||||
$client = SocketClient::create($this->ao->system->address,$o->pivot->port);
|
||||
|
||||
if (($s->session($session,$client,$this->ao) & Protocol::S_MASK) === Protocol::S_OK) {
|
||||
Log::info(sprintf('%s:= Connection ended successfully with [%s] (%s)',self::LOGKEY,$client->address_remote,$this->ao->ftn));
|
||||
return;
|
||||
|
||||
} else {
|
||||
Log::alert(sprintf('%s:! Connection failed to [%s] (%s)',self::LOGKEY,$client->address_remote,$this->ao->ftn));
|
||||
}
|
||||
|
||||
} catch (SocketException $e) {
|
||||
Log::error(sprintf('%s:! Unable to connect to [%s]: %s',self::LOGKEY,$this->ao->ftn,$e->getMessage()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$delay = (int)($this->backoff()[$this->attempts()-1] ?? last($this->backoff()));
|
||||
Log::info(sprintf('%s:= Retrying poll in %d seconds',self::LOGKEY,$delay));
|
||||
$this->release($delay);
|
||||
}
|
||||
|
||||
public function failed(\Throwable $exception): void
|
||||
{
|
||||
switch (get_class($exception)) {
|
||||
case ManuallyFailedException::class:
|
||||
Log::error(sprintf('%s:! Address Poll failed for [%s] (%s)',self::LOGKEY,$this->ao->ftn,$exception->getMessage()));
|
||||
break;
|
||||
|
||||
case MaxAttemptsExceededException::class:
|
||||
Log::error(sprintf('%s:! Address Poll was tried too many times for [%s]',self::LOGKEY,$this->ao->ftn));
|
||||
Notification::route('netmail',$this->ao)->notify(new PollingFailed);
|
||||
|
||||
$this->ao->system->autohold = TRUE;
|
||||
$this->ao->system->save();
|
||||
|
||||
exit(0);
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s:! Address Poll to [%s] with an unknown exception [%s]',self::LOGKEY,$this->ao->ftn,$exception->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->ao->id;
|
||||
}
|
||||
}
|
179
app/Jobs/MailSend.php
Normal file
179
app/Jobs/MailSend.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Repat\LaravelJobs\Job;
|
||||
|
||||
use App\Classes\FTN\Message;
|
||||
use App\Models\Address;
|
||||
|
||||
class MailSend implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private const LOGKEY = 'JCM';
|
||||
|
||||
private ?bool $crash;
|
||||
|
||||
/**
|
||||
* @param bool $crash Send crash mail only
|
||||
*/
|
||||
public function __construct(bool $crash=NULL)
|
||||
{
|
||||
$this->crash = $crash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// Netmail waiting by node (only netmail is routed)
|
||||
$netmails = Address::select(['addresses.id','addresses.zone_id','region_id','host_id','node_id','point_id','role','addresses.system_id',DB::raw('count(netmails.id) AS nm')])
|
||||
->join('zones',['zones.id'=>'addresses.zone_id'])
|
||||
->join('domains',['domains.id'=>'zones.domain_id'])
|
||||
->join('netmails',['netmails.tftn_id'=>'addresses.id'])
|
||||
->join('systems',['systems.id'=>'addresses.system_id'])
|
||||
->where('addresses.active',TRUE)
|
||||
->where('zones.active',TRUE)
|
||||
->where('domains.active',TRUE)
|
||||
->where(function($query) {
|
||||
return $query->whereNull('autohold')
|
||||
->orWhere('autohold',FALSE);
|
||||
})
|
||||
->where(function($query) {
|
||||
return $query->whereRaw(sprintf('(flags & %d) > 0',Message::FLAG_INTRANSIT))
|
||||
->orWhereRaw(sprintf('(flags & %d) > 0',Message::FLAG_LOCAL));
|
||||
})
|
||||
->whereRaw(sprintf('(flags & %d) = 0',Message::FLAG_SENT))
|
||||
->when(! is_null($this->crash),function($query) {
|
||||
return $query->when(
|
||||
$this->crash,
|
||||
function($query) {
|
||||
return $query->where('pollmode',$this->crash);
|
||||
},
|
||||
function($query) {
|
||||
return $query->whereNotNull('pollmode');
|
||||
}
|
||||
);
|
||||
})
|
||||
->groupBy('addresses.id')
|
||||
->havingRaw('count(*) > 0')
|
||||
->get();
|
||||
|
||||
// Echomail waiting by node
|
||||
$echomails = Address::select(['addresses.id','addresses.zone_id','region_id','host_id','node_id','point_id','role','addresses.system_id',DB::raw('count(*) AS em')])
|
||||
->distinct()
|
||||
->join('zones',['zones.id'=>'addresses.zone_id'])
|
||||
->join('domains',['domains.id'=>'zones.domain_id'])
|
||||
->join('echomail_seenby',['echomail_seenby.address_id'=>'addresses.id'])
|
||||
->join('systems',['systems.id'=>'addresses.system_id'])
|
||||
->where('addresses.active',TRUE)
|
||||
->where('zones.active',TRUE)
|
||||
->where('domains.active',TRUE)
|
||||
->where(function($query) {
|
||||
return $query->whereNull('autohold')
|
||||
->orWhere('autohold',FALSE);
|
||||
})
|
||||
->whereNotNull('export_at')
|
||||
->whereNull('sent_at')
|
||||
->when(! is_null($this->crash),function($query) {
|
||||
return $query->when(
|
||||
$this->crash,
|
||||
function($query) {
|
||||
return $query->where('pollmode',$this->crash);
|
||||
},
|
||||
function($query) {
|
||||
return $query->whereNotNull('pollmode');
|
||||
}
|
||||
);
|
||||
})
|
||||
->groupBy(['addresses.id'])
|
||||
->havingRaw('count(*) > 0')
|
||||
->FTNOrder()
|
||||
->get();
|
||||
|
||||
// Files waiting by node
|
||||
$files = Address::select(['addresses.id','addresses.zone_id','region_id','host_id','node_id','point_id','role','addresses.system_id',DB::raw('count(*) AS fs')])
|
||||
->distinct()
|
||||
->join('zones',['zones.id'=>'addresses.zone_id'])
|
||||
->join('domains',['domains.id'=>'zones.domain_id'])
|
||||
->join('file_seenby',['file_seenby.address_id'=>'addresses.id'])
|
||||
->join('systems',['systems.id'=>'addresses.system_id'])
|
||||
->where('addresses.active',TRUE)
|
||||
->where('zones.active',TRUE)
|
||||
->where('domains.active',TRUE)
|
||||
->where(function($query) {
|
||||
return $query->whereNull('autohold')
|
||||
->orWhere('autohold',FALSE);
|
||||
})
|
||||
->whereNotNull('export_at')
|
||||
->whereNull('sent_at')
|
||||
->when(! is_null($this->crash),function($query) {
|
||||
return $query->when(
|
||||
$this->crash,
|
||||
function($query) {
|
||||
return $query->where('pollmode',$this->crash);
|
||||
},
|
||||
function($query) {
|
||||
return $query->whereNotNull('pollmode');
|
||||
}
|
||||
);
|
||||
})
|
||||
->groupBy(['addresses.id'])
|
||||
->havingRaw('count(*) > 0')
|
||||
->FTNOrder()
|
||||
->get();
|
||||
|
||||
// Merge our netmails
|
||||
foreach ($echomails as $ao) {
|
||||
if (($x=$netmails->search(function($item) use ($ao) { return $item->id === $ao->id; })) !== FALSE) {
|
||||
$netmails->get($x)->em = $ao->em;
|
||||
} else {
|
||||
$netmails->push($ao);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge our files
|
||||
foreach ($files as $ao) {
|
||||
if (($x=$netmails->search(function($item) use ($ao) { return $item->id === $ao->id; })) !== FALSE) {
|
||||
$netmails->get($x)->fs = $ao->fs;
|
||||
} else {
|
||||
$netmails->push($ao);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove direct links
|
||||
$netmails = $netmails->filter(function($item) { return $item->parent(); });
|
||||
|
||||
foreach ($netmails->groupBy(function($item) { return $item->parent()->ftn; }) as $oo) {
|
||||
$ao = $oo->first();
|
||||
|
||||
Log::info(sprintf('%s:- Polling [%s] - we have mail for [%d] links. (%d Netmail,%d Echomail,%d Files)',
|
||||
self::LOGKEY,
|
||||
$ao->ftn,
|
||||
$oo->count(),
|
||||
$oo->sum('nm'),
|
||||
$oo->sum('em'),
|
||||
$oo->sum('fs'),
|
||||
));
|
||||
|
||||
// @todo Only send crash mail - send normal mail with a schedule or hold mail
|
||||
|
||||
if (Job::where('queue',$this->queue)->get()->pluck('command.address.id')->search($ao->id) !== FALSE) {
|
||||
Log::alert(sprintf('%s:= Not scheduling poll to [%s], there is already one in the queue',self::LOGKEY,$ao->ftn));
|
||||
continue;
|
||||
}
|
||||
|
||||
AddressPoll::dispatch($ao);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@ -283,7 +282,7 @@ class Address extends Model
|
||||
return NULL;
|
||||
|
||||
default:
|
||||
throw new \Exception('Unknown role: '.serialize($this->role));
|
||||
throw new \Exception(sprintf('Unknown role: %s (%d)',serialize($this->role),$this->id));
|
||||
}
|
||||
|
||||
return $parent?->parent();
|
||||
|
69
app/Models/Job.php
Normal file
69
app/Models/Job.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class Job extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $casts = [
|
||||
'payload' => 'array',
|
||||
'reserved_at' => 'datetime',
|
||||
'available_at' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->table = Config::get('queue.connections.' . (Config::get('queue.default', 'database')) . '.table', 'jobs');
|
||||
}
|
||||
|
||||
public function getDisplayNameAttribute()
|
||||
{
|
||||
return $this->payload['displayName'];
|
||||
}
|
||||
|
||||
public function getMaxTriesAttribute()
|
||||
{
|
||||
return $this->payload['maxTries'];
|
||||
}
|
||||
|
||||
public function getDelayAttribute()
|
||||
{
|
||||
return $this->payload['delay'];
|
||||
}
|
||||
|
||||
public function getUUIDAttribute()
|
||||
{
|
||||
return $this->payload['uuid'];
|
||||
}
|
||||
|
||||
public function getTimeoutAttribute()
|
||||
{
|
||||
return $this->payload['timeout'];
|
||||
}
|
||||
|
||||
public function getRetryUntilAttribute()
|
||||
{
|
||||
return !is_null($this->payload['retryUntil']) ? new \Carbon\Carbon($this->payload['retryUntil']) : null;
|
||||
}
|
||||
|
||||
public function getTimeoutAtAttribute()
|
||||
{
|
||||
return !is_null($this->payload['timeout_at']) ? new \Carbon\Carbon($this->payload['timeout_at']) : null;
|
||||
}
|
||||
|
||||
public function getCommandNameAttribute()
|
||||
{
|
||||
return $this->payload['data']['commandName'];
|
||||
}
|
||||
|
||||
public function getCommandAttribute()
|
||||
{
|
||||
return unserialize($this->payload['data']['command']);
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Jobs\AddressPoll;
|
||||
|
||||
class System extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
@ -214,4 +216,13 @@ class System extends Model
|
||||
return ($item->role & $type) && ($myzones->search($item->zone_id) !== FALSE);
|
||||
});
|
||||
}
|
||||
|
||||
public function poll(): ?Job
|
||||
{
|
||||
return Job::where('queue',AddressPoll::QUEUE)
|
||||
->get()
|
||||
->where(function($item) {
|
||||
return $this->akas->pluck('id')->search($item->command->address->id) !== FALSE; })
|
||||
->last();
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ namespace App\Notifications\Channels;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Jobs\AddressPoll as Job;
|
||||
use App\Models\Netmail;
|
||||
use App\Models\Setup;
|
||||
|
||||
@ -43,7 +42,6 @@ class NetmailChannel
|
||||
$so = Setup::findOrFail(config('app.id'))->system;
|
||||
$o = $notification->toNetmail($so,$notifiable);
|
||||
|
||||
Job::dispatch($ao);
|
||||
Log::info(sprintf('%s:= Sent netmail [%s] to [%s]',self::LOGKEY,$o->msgid,$ao->ftn));
|
||||
}
|
||||
}
|
58
app/Notifications/Netmails/PollingFailed.php
Normal file
58
app/Notifications/Netmails/PollingFailed.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Netmails;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\FTN\Message;
|
||||
use App\Notifications\Netmails;
|
||||
use App\Models\{Netmail,System};
|
||||
use App\Traits\{MessagePath,PageTemplate};
|
||||
|
||||
class PollingFailed extends Netmails
|
||||
{
|
||||
use MessagePath,PageTemplate;
|
||||
|
||||
private const LOGKEY = 'NPF';
|
||||
|
||||
private Carbon $attempt;
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param System $so
|
||||
* @param mixed $notifiable
|
||||
* @return Netmail
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function toNetmail(System $so,object $notifiable): Netmail
|
||||
{
|
||||
$o = $this->setupNetmail($so,$notifiable);
|
||||
$ao = $notifiable->routeNotificationFor(static::via);
|
||||
|
||||
Log::info(sprintf('%s:+ Creating auto hold netmail to [%s]',self::LOGKEY,$ao->ftn));
|
||||
|
||||
$o->subject = 'Failed polling - your system is on AUTO HOLD';
|
||||
|
||||
// Message
|
||||
$msg = $this->page(FALSE,'hold');
|
||||
|
||||
$msg->addText("Hi, I've been trying to poll your system without success.\r\r");
|
||||
|
||||
$msg->addText("Your system was automatically placed on hold, which means I no longer attempted to poll you.\r\r");
|
||||
|
||||
$msg->addText(
|
||||
'Since you collected this message, I automatically removed the auto hold status, but if future attempts to poll you fail '.
|
||||
"you'll be automatically placed back on auto hold until you poll me. You'll also get this annoying message each time :(\r\r");
|
||||
|
||||
$msg->addText("To fix this, update your details that I use in the web interface, or change your system to HOLD while you are there.\r\r");
|
||||
|
||||
$o->msg = $msg->render();
|
||||
$o->tagline = 'Painful? We can make that painless :)';
|
||||
|
||||
$o->save();
|
||||
|
||||
return $o;
|
||||
}
|
||||
}
|
36
database/migrations/2023_07_25_223929_system_poll.php
Normal file
36
database/migrations/2023_07_25_223929_system_poll.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('system_logs',function (Blueprint $table) {
|
||||
$table->boolean('originate')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('systems',function (Blueprint $table) {
|
||||
$table->boolean('pollmode')->nullable();
|
||||
$table->boolean('autohold')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('systems',function (Blueprint $table) {
|
||||
$table->dropColumn(['pollmode','autohold']);
|
||||
});
|
||||
Schema::table('system_logs',function (Blueprint $table) {
|
||||
$table->dropColumn(['originate']);
|
||||
});
|
||||
}
|
||||
};
|
@ -272,8 +272,8 @@
|
||||
<table class="table monotable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zone</th>
|
||||
<th>System</th>
|
||||
<th>AKA</th>
|
||||
<th>Uplink</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -286,7 +286,7 @@
|
||||
@if ($x=$oo->parent())
|
||||
{{ $x->ftn4d }}
|
||||
@else
|
||||
No destination for mail.
|
||||
None
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@ -305,8 +305,8 @@
|
||||
<table class="table monotable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zone</th>
|
||||
<th>System</th>
|
||||
<th>AKA</th>
|
||||
<th>Downlink</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
<!-- $o = System::class -->
|
||||
@php
|
||||
use App\Models\Setup;
|
||||
@endphp
|
||||
@ -21,8 +22,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ZeroTier ID -->
|
||||
<div class="col-3">
|
||||
<label for="zt_id" class="form-label">ZeroTier ID</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text"><i class="bi bi-shield-lock-fill"></i></span>
|
||||
<input type="text" class="form-control @error('zt_id') is-invalid @enderror" id="zt_id" placeholder="ZeroTier" name="zt_id" value="{{ old('zt_id',$o->zt_id) }}" @cannot($action,$o)readonly @endcannot>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('zt_id')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active -->
|
||||
<div class="col-2">
|
||||
<div class="offset-2 col-2">
|
||||
@can('update',$o)
|
||||
<label for="active" class="form-label">Active</label>
|
||||
<div class="input-group">
|
||||
@ -36,36 +51,6 @@
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<!-- Hold -->
|
||||
<div class="col-2">
|
||||
@can('update',$o)
|
||||
<label for="hold" class="form-label">Hold Mail</label>
|
||||
<div class="input-group">
|
||||
<div class="btn-group" role="group">
|
||||
<input type="radio" class="btn-check" name="hold" id="hold_yes" value="1" required @if(old('hold',$o->hold))checked @endif>
|
||||
<label class="btn btn-outline-success" for="hold_yes">Yes</label>
|
||||
|
||||
<input type="radio" class="btn-check btn-danger" name="hold" id="hold_no" value="0" required @if(! old('hold',$o->hold))checked @endif>
|
||||
<label class="btn btn-outline-danger" for="hold_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<!-- ZeroTier ID -->
|
||||
<div class="offset-1 col-3">
|
||||
<label for="zt_id" class="form-label">ZeroTier ID</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text"><i class="bi bi-shield-lock-fill"></i></span>
|
||||
<input type="text" class="form-control @error('zt_id') is-invalid @enderror" id="zt_id" placeholder="ZeroTier" name="zt_id" value="{{ old('zt_id',$o->zt_id) }}" @cannot($action,$o)readonly @endcannot>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('zt_id')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@ -86,7 +71,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="col-8">
|
||||
<div class="col-4">
|
||||
<label for="location" class="form-label">Location</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text"><i class="bi bi-globe"></i></span>
|
||||
@ -100,10 +85,41 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hold -->
|
||||
<div class="offset-1 col-2">
|
||||
@can('update',$o)
|
||||
<label for="hold" class="form-label">Hold Mail <i class="bi bi-info-circle" title="Dont give the node any mail regardless of poll mode"></i></label>
|
||||
<div class="input-group">
|
||||
<div class="btn-group" role="group">
|
||||
<input type="radio" class="btn-check" name="hold" id="hold_yes" value="1" required @if(old('hold',$o->hold))checked @endif>
|
||||
<label class="btn btn-outline-warning" for="hold_yes">Yes</label>
|
||||
|
||||
<input type="radio" class="btn-check btn-danger" name="hold" id="hold_no" value="0" required @if(! old('hold',$o->hold))checked @endif>
|
||||
<label class="btn btn-outline-success" for="hold_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<!-- Address -->
|
||||
<div class="col-5">
|
||||
<label for="address" class="form-label">Internet Address</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text"><i class="bi bi-globe"></i></span>
|
||||
<input type="text" class="w-75 form-control @error('address') is-invalid @enderror" id="address" placeholder="FQDN" name="address" value="{{ old('address',$o->address) }}" @cannot($action,$o)readonly @endcannot>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('address')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="col-3">
|
||||
<label for="phone" class="form-label">Phone</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text"><i class="bi bi-telephone-fill"></i></span>
|
||||
@ -116,18 +132,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="col-8">
|
||||
<label for="address" class="form-label">Internet Address</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text"><i class="bi bi-globe"></i></span>
|
||||
<input type="text" class="w-75 form-control @error('address') is-invalid @enderror" id="address" placeholder="FQDN" name="address" value="{{ old('address',$o->address) }}" @cannot($action,$o)readonly @endcannot>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('address')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
<!-- Poll Mode -->
|
||||
<div class="offset-1 col-3">
|
||||
@can('update',$o)
|
||||
<label for="pollmode" class="form-label">Poll Mode <i class="bi bi-info-circle" title="Poll node when mail available, poll on a schedule or hold mail for collection"></i></label>
|
||||
<div class="input-group has-validation">
|
||||
<div class="btn-group @error('pollmode') is-invalid @enderror" role="group">
|
||||
<input type="radio" class="btn-check" name="pollmode" id="poll_crash" value="2" @if((int)old('pollmode',($o->pollmode === TRUE) ? 2 : 0) === 2)checked @endif>
|
||||
<label class="btn btn-outline-success" for="poll_crash">Crash</label>
|
||||
|
||||
<input type="radio" class="btn-check btn-danger" name="pollmode" id="poll_normal" value="1" @if((int)old('pollmode',($o->pollmode === FALSE) ? 1 : 0) === 1)checked @endif>
|
||||
<label class="btn btn-outline-secondary" for="poll_normal">Normal</label>
|
||||
|
||||
<input type="radio" class="btn-check btn-danger" name="pollmode" id="poll_hold" value="0" @if((int)old('pollmode',is_null($o->pollmode) ? 0 : 1) === 0)checked @endif>
|
||||
<label class="btn btn-outline-warning" for="poll_hold">Hold</label>
|
||||
</div>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('pollmode')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -136,6 +162,7 @@
|
||||
<div class="col-12">
|
||||
<h4 class="pt-4 mb-0 pb-2">Mailer Details</h4>
|
||||
|
||||
<!-- Mailer Ports -->
|
||||
<div class="pt-0 row">
|
||||
<div class="col-3">
|
||||
@foreach (\App\Models\Mailer::all() as $mo)
|
||||
@ -160,6 +187,7 @@
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Mail Packet -->
|
||||
<div class="col-2">
|
||||
<label for="pkt_type" class="form-label">Mail Packet</label>
|
||||
<div class="input-group">
|
||||
@ -177,6 +205,55 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (! is_null($o->pollmode))
|
||||
<div class="offset-3 col-4">
|
||||
<table class="table monotable m-0 p-0 small noborder">
|
||||
<tbody style="border-style:dotted;">
|
||||
@if($job = $o->poll())
|
||||
<tr>
|
||||
<td class="cap text-end">@if($job->attempts)Last Attempt @else Scheduled @endif:</td>
|
||||
<td>{{ $job->created_at }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cap text-end">Attempts :</td>
|
||||
<td>{{ $job->attempts ?: 0 }}</td>
|
||||
</tr>
|
||||
@if ($job->attempts)
|
||||
<tr>
|
||||
<td class="cap text-end">Next Attempt :</td>
|
||||
<td>{{ $job->available_at->diffForHumans(now(),$job->available_at->isFuture() ? \Carbon\CarbonInterface::DIFF_ABSOLUTE : \Carbon\CarbonInterface::DIFF_RELATIVE_TO_NOW) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
@else
|
||||
<tr>
|
||||
<td class="cap text-end">Last Poll :</td>
|
||||
<td>{{ ($x=$o->logs->where('originate',TRUE)->last())?->created_at ?: 'Never' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cap text-end">Method :</td>
|
||||
<td>{{ $x?->sessiontype ?: '-' }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
<tr>
|
||||
<td class="cap text-end">Status :</td>
|
||||
<td>
|
||||
@if ($job) Queued
|
||||
@elseif ($o->autohold)Auto Hold
|
||||
@else
|
||||
@switch($o->pollmode)
|
||||
@case(TRUE) Crash @break;
|
||||
@case(FALSE) Normal @break;
|
||||
@default Hold
|
||||
@endswitch
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user