Mail routing parent/children, domain name validation, nodelist import changes and other fixes

This commit is contained in:
Deon George 2021-07-26 21:21:58 +10:00
parent 49bc946024
commit b179b1b3e9
15 changed files with 537 additions and 171 deletions

View File

@ -317,7 +317,7 @@ class Packet extends FTNBase
private function newHeader(Address $o): void private function newHeader(Address $o): void
{ {
$date = Carbon::now(); $date = Carbon::now();
$ao = Setup::findOrFail(config('app.id'))->system->match($o); $ao = Setup::findOrFail(config('app.id'))->system->match($o->zone);
// Create Header // Create Header
$this->header = [ $this->header = [

View File

@ -14,9 +14,10 @@ class DomainController extends Controller
public const NODE_RC = 1<<1; // Region public const NODE_RC = 1<<1; // Region
public const NODE_NC = 1<<2; // Host public const NODE_NC = 1<<2; // Host
public const NODE_HC = 1<<3; // Hub public const NODE_HC = 1<<3; // Hub
public const NODE_PVT = 1<<4; // Pvt public const NODE_POINT = 1<<4; // Point
public const NODE_HOLD = 1<<5; // Hold public const NODE_PVT = 1<<5; // Pvt
public const NODE_DOWN = 1<<6; // Down public const NODE_HOLD = 1<<6; // Hold
public const NODE_DOWN = 1<<7; // Down
// http://ftsc.org/docs/frl-1002.001 // http://ftsc.org/docs/frl-1002.001
public const NUMBER_MAX = 0x7fff; public const NUMBER_MAX = 0x7fff;
@ -27,7 +28,7 @@ class DomainController extends Controller
} }
/** /**
* Add or edit a node * Add or edit a domain
*/ */
public function add_edit(Request $request,Domain $o) public function add_edit(Request $request,Domain $o)
{ {
@ -35,7 +36,8 @@ class DomainController extends Controller
$this->authorize('admin',$o); $this->authorize('admin',$o);
$request->validate([ $request->validate([
'name' => 'required|max:8|unique:domains,name,'.($o->exists ? $o->id : 0), // http://ftsc.org/docs/old/fsp-1028.002
'name' => 'required|max:8|regex:/^[a-z-_~]{1,8}$/|unique:domains,name,'.($o->exists ? $o->id : 0),
'dnsdomain' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i|unique:domains,dnsdomain,'.($o->exists ? $o->id : NULL), 'dnsdomain' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i|unique:domains,dnsdomain,'.($o->exists ? $o->id : NULL),
'active' => 'required|boolean', 'active' => 'required|boolean',
'public' => 'required|boolean', 'public' => 'required|boolean',

View File

@ -121,6 +121,32 @@ class SystemController extends Controller
] ]
]); ]);
// Find the Hub address
// Find the zones <HOST>/0 address, and assign it to this host.
$oo = Address::where('zone_id',$request->zone_id)
->where('region_id',$request->region_id)
->where('host_id',$request->host_id_new)
->where('node_id',0)
->where('point_id',0)
->single();
// Its not defined, so we'll create it.
if (! $oo) {
$oo = new Address;
$oo->forceFill([
'zone_id'=>$request->zone_id,
'region_id'=>$request->region_id,
'host_id'=>$request->host_id_new,
'node_id'=>0,
'point_id'=>0,
'role'=>DomainController::NODE_NC,
]);
}
$oo->system_id = $request->system_id;
$oo->active = TRUE;
$o->addresses()->save($oo);
$oo = new Address; $oo = new Address;
$oo->zone_id = $request->post('zone_id'); $oo->zone_id = $request->post('zone_id');
$oo->region_id = $request->post('region_id'); $oo->region_id = $request->post('region_id');
@ -200,8 +226,8 @@ class SystemController extends Controller
$validate = $request->validate([ $validate = $request->validate([
'zone_id' => 'required|exists:zones,id', 'zone_id' => 'required|exists:zones,id',
'sespass' => 'required|string|min:4', 'sespass' => 'required|string|min:4',
'pktpass' => 'required|string|min:4|max:8', 'pktpass' => 'nullable|string|min:4|max:8',
'ticpass' => 'required|string|min:4', 'ticpass' => 'nullable|string|min:4',
'fixpass' => 'required|string|min:4', 'fixpass' => 'required|string|min:4',
]); ]);
@ -242,6 +268,8 @@ class SystemController extends Controller
return redirect()->action([self::class,'home']); return redirect()->action([self::class,'home']);
} }
$o->load(['addresses.zone.domain']);
return view('system.addedit') return view('system.addedit')
->with('o',$o); ->with('o',$o);
} }

View File

@ -5,7 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use App\Models\Zone; use App\Models\{Address,Zone};
class ZoneController extends Controller class ZoneController extends Controller
{ {
@ -64,6 +64,30 @@ class ZoneController extends Controller
$o->save(); $o->save();
// Find the zones 0/0 address, and assign it to this host.
$ao = Address::where('zone_id',$request->zone_id)
->where('region_id',0)
->where('host_id',0)
->where('node_id',0)
->where('point_id',0)
->single();
// Its not defined, so we'll create it.
if (! $ao) {
$ao = new Address;
$ao->forceFill([
'region_id'=>0,
'host_id'=>0,
'node_id'=>0,
'point_id'=>0,
'role'=>DomainController::NODE_ZC,
]);
}
$ao->system_id = $request->system_id;
$ao->active = TRUE;
$o->addresses()->save($ao);
return redirect()->action([self::class,'home']); return redirect()->action([self::class,'home']);
} }

View File

@ -89,7 +89,6 @@ class NodelistImport implements ShouldQueue
switch ($fields[0]) { switch ($fields[0]) {
case 'Zone': $zone = $fields[1]; case 'Zone': $zone = $fields[1];
// We ignore the Zone entries, since they are dynamically created.
Zone::unguard(); Zone::unguard();
$zo = Zone::firstOrNew([ $zo = Zone::firstOrNew([
'zone_id'=>$zone, 'zone_id'=>$zone,
@ -99,7 +98,7 @@ class NodelistImport implements ShouldQueue
Zone::reguard(); Zone::reguard();
$region = 0; $region = 0;
$host = $fields[1]; $host = 0;
$hub_id = NULL; $hub_id = NULL;
$role = DomainController::NODE_ZC; $role = DomainController::NODE_ZC;
@ -118,8 +117,7 @@ class NodelistImport implements ShouldQueue
$hub_id = NULL; $hub_id = NULL;
$role = DomainController::NODE_NC; $role = DomainController::NODE_NC;
// We ignore the Host entries, since they are dynamically created. break;
continue 2;
case 'Hub': case 'Hub':
$node = $fields[1]; $node = $fields[1];
@ -243,10 +241,10 @@ class NodelistImport implements ShouldQueue
try { try {
$so->addresses()->save($ao); $so->addresses()->save($ao);
if ($role == DomainController::NODE_HC) if ($ao->role == DomainController::NODE_HC)
$hub_id = $ao->id; $hub_id = $ao->id;
$this->no->addresses()->attach($ao,['role'=>$role]); $this->no->addresses()->attach($ao,['role'=>$ao->role]);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error(sprintf('%s:Error with line [%s] (%s)',self::LOGKEY,$line,$e->getMessage()),['fields'=>$fields]); Log::error(sprintf('%s:Error with line [%s] (%s)',self::LOGKEY,$line,$e->getMessage()),['fields'=>$fields]);

View File

@ -30,37 +30,150 @@ class Address extends Model
/* RELATIONS */ /* RELATIONS */
/** /**
* Find children dependant on this record * Find children dependent on this record
*/ */
public function children() public function children()
{ {
switch (strtolower($this->role)) { // We have no session data for this address, by definition it has no children
case 'region': if (! $this->session('sespass'))
return $this->hasMany(self::class,'region_id','region_id') return $this->hasMany(self::class,'id','void');
->where('zone_id',$this->zone_id)
->where(function($q) {
return $q->where('host_id',0)
->orWhere('role',DomainController::NODE_NC);
})
->where('id','<>',$this->id);
case 'host': switch ($this->role) {
return $this->hasMany(self::class,'host_id','host_id') case DomainController::NODE_ZC:
$children = self::select('addresses.*')
->where('zone_id',$this->zone_id);
break;
case DomainController::NODE_RC:
$children = self::select('addresses.*')
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id);
break;
case DomainController::NODE_NC:
$children = self::select('addresses.*')
->where('zone_id',$this->zone_id) ->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id) ->where('region_id',$this->region_id)
->whereNull('hub_id') ->where('host_id',$this->host_id);
->where('id','<>',$this->id);
case 'hub': break;
return $this->hasMany(self::class,'hub_id','id');
case 'node': case DomainController::NODE_HC:
// Identify our children.
$children = self::select('addresses.*')
->where('hub_id',$this->id);
break;
case DomainController::NODE_ACTIVE:
case DomainController::NODE_POINT:
// Nodes dont have children, but must return a relationship instance // Nodes dont have children, but must return a relationship instance
return $this->hasOne(self::class,NULL,'void'); return $this->hasOne(self::class,NULL,'void');
default: default:
throw new Exception('Unknown role: '.$this->role); throw new Exception('Unknown role: '.serialize($this->role));
} }
// Remove any children that we have session details for (SAME AS HC)
$sessions = self::select('hubnodes.*')
->join('system_zone',['system_zone.system_id'=>'addresses.system_id','system_zone.zone_id'=>'addresses.zone_id'])
->join('addresses as hubnodes',['hubnodes.zone_id'=>'addresses.zone_id','hubnodes.id'=>'addresses.id'])
->where('addresses.zone_id',$this->zone_id)
->where('addresses.system_id','<>',$this->system_id)
->whereIN('addresses.system_id',$children->get()->pluck('system_id'));
// For each of the session, identify their children
$session_kids = collect();
foreach ($sessions->get() as $so)
$session_kids = $session_kids->merge(($x=$so->children) ? $x->pluck('id') : []);
// ZC's receive all mail, except for defined nodes, and defined hubs/hosts/rcs
return $this->hasMany(self::class,'zone_id','zone_id')
->whereIn('id',$children->get()->pluck('id')->toArray())
->whereNotIn('id',$sessions->get()->pluck('id')->toArray())
->whereNotIn('id',$session_kids->toArray())
->where('system_id','<>',$this->system_id)
->select('addresses.*')
->orderBy('region_id')
->orderBy('host_id')
->orderBy('node_id')
->orderBy('point_id')
->with(['zone.domain']);
}
/**
* Who we send this systems mail to.
*
* @return Address|null
* @throws Exception
*/
public function parent(): ?Address
{
// If we are have session password, then we dont have a parent
if ($this->session('sespass'))
return $this;
switch ($this->role) {
// ZCs dont have parents.
case DomainController::NODE_ZC:
return NULL;
// RC
case DomainController::NODE_RC:
$parent = self::where('zone_id',$this->zone_id)
->where('region_id',0)
->where('host_id',0)
->where('node_id',0)
->single();
break;
// Hosts
case DomainController::NODE_NC:
// See if we have a RC
$parent = self::where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',0)
->where('node_id',0)
->single();
if (! $parent) {
// See if we have a RC
$parent = self::where('zone_id',$this->zone_id)
->where('region_id',0)
->where('host_id',0)
->where('node_id',0)
->single();
}
break;
// Hubs
case DomainController::NODE_HC:
// Normal Nodes
case DomainController::NODE_ACTIVE:
// If we are a child of a hub, then check our hub
$parent = (($this->hub_id)
? self::where('id',$this->hub_id)
: self::where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->host_id)
->where('node_id',0))
->single();
break;
case DomainController::NODE_POINT:
// @todo Points - if the boss is defined, we should return it.
return NULL;
default:
throw new Exception('Unknown role: '.serialize($this->role));
}
return $parent?->parent();
} }
public function system() public function system()
@ -95,23 +208,21 @@ class Address extends Model
return sprintf('%s.%d',$this->getFTN3DAttribute(),$this->point_id); return sprintf('%s.%d',$this->getFTN3DAttribute(),$this->point_id);
} }
public function getRoleAttribute($value) public function getRoleNameAttribute(): string
{ {
switch ($value) { switch ($this->role) {
case DomainController::NODE_ZC; case DomainController::NODE_ACTIVE:
return 'Zone'; return 'NODE';
case DomainController::NODE_RC; case DomainController::NODE_ZC:
return 'Region'; return 'ZC';
case DomainController::NODE_NC; case DomainController::NODE_RC:
return 'Host'; return 'RC';
case DomainController::NODE_HC; case DomainController::NODE_NC:
return 'Hub'; return 'NC';
case DomainController::NODE_PVT; case DomainController::NODE_HC:
return 'PVT'; return 'HUB';
case DomainController::NODE_DOWN; case DomainController::NODE_POINT:
return 'DOWN'; return 'POINT';
case NULL:
return 'Node';
default: default:
return '?'; return '?';
} }
@ -130,17 +241,48 @@ class Address extends Model
{ {
$ftn = self::parseFTN($ftn); $ftn = self::parseFTN($ftn);
// Are we looking for a region address
if (($ftn['f'] === 0) && $ftn['p'] === 0) {
$o = (new self)->active()
->select('addresses.*')
->where('zones.zone_id',$ftn['z'])
->where(function($q) use ($ftn) {
return $q
->where(function($q) use ($ftn) {
return $q->where('region_id',$ftn['n'])
->where('host_id',0);
});
})
->where('node_id',$ftn['f'])
->where('point_id',$ftn['p'])
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->where('zones.active',TRUE)
->where('domains.active',TRUE)
->where('addresses.active',TRUE)
->when($ftn['d'],function($query,$domain) {
$query->where('domains.name',$domain);
})
->when((! $ftn['d']),function($query) {
$query->where('domains.default',TRUE);
})
->single();
if ($o && $o->system->active)
return $o;
}
$o = (new self)->active() $o = (new self)->active()
->select('addresses.*') ->select('addresses.*')
->where('zones.zone_id',$ftn['z']) ->where('zones.zone_id',$ftn['z'])
->where('host_id',$ftn['n']) ->where('host_id',$ftn['n'])
->where('node_id',$ftn['f'])
->where('point_id',$ftn['p'])
->join('zones',['zones.id'=>'addresses.zone_id']) ->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id']) ->join('domains',['domains.id'=>'zones.domain_id'])
->where('zones.active',TRUE) ->where('zones.active',TRUE)
->where('domains.active',TRUE) ->where('domains.active',TRUE)
->where('addresses.active',TRUE) ->where('addresses.active',TRUE)
->where('node_id',$ftn['f'])
->where('point_id',$ftn['p'])
->when($ftn['d'],function($query,$domain) { ->when($ftn['d'],function($query,$domain) {
$query->where('domains.name',$domain); $query->where('domains.name',$domain);
}) })

View File

@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model;
use App\Http\Controllers\DomainController; use App\Http\Controllers\DomainController;
use App\Traits\ScopeActive; use App\Traits\ScopeActive;
use Illuminate\Support\Collection;
class System extends Model class System extends Model
{ {
@ -61,12 +62,12 @@ class System extends Model
/** /**
* Return the system's address in the same zone * Return the system's address in the same zone
* *
* @param Address $o * @param Zone $o
* @return Address * @return Collection
*/ */
public function match(Address $o): Address public function match(Zone $o): Collection
{ {
return $this->addresses->where('zone_id',$o->zone_id)->first(); return $this->addresses->where('zone_id',$o->id);
} }
/** /**
@ -85,6 +86,8 @@ class System extends Model
return sprintf('RC-%s-%05d',$o->zone->domain->name,$o->region_id); return sprintf('RC-%s-%05d',$o->zone->domain->name,$o->region_id);
case DomainController::NODE_NC; case DomainController::NODE_NC;
return sprintf('NC-%s-%05d',$o->zone->domain->name,$o->host_id);
case DomainController::NODE_HC; case DomainController::NODE_HC;
case NULL: case NULL:
default: default:

View File

@ -25,7 +25,10 @@ class Zone extends Model
public function addresses() public function addresses()
{ {
return $this->hasMany(Address::class); return $this->hasMany(Address::class)
->active()
->FTNorder()
->with(['system.sessions','system.setup','zone.domain']);
} }
public function domain() public function domain()

View File

@ -2,10 +2,11 @@
namespace Database\Seeders; namespace Database\Seeders;
use Carbon\Carbon;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use App\Models\Software; use App\Models\{Domain,Software,System,Zone};
class InitialSetupSeeder extends Seeder class InitialSetupSeeder extends Seeder
{ {
@ -32,65 +33,49 @@ class InitialSetupSeeder extends Seeder
'type'=>Software::SOFTWARE_MAILER, 'type'=>Software::SOFTWARE_MAILER,
]); ]);
DB::table('domains')->insert([ $so = new System;
$so->forceFill([
'name'=>'Clearing Houz - Dev',
'sysop'=>'System Sysop',
'location'=>'Melbourne, AU',
'active'=>TRUE,
]);
$so->save();
$do = new Domain;
$do->forceFill([
'name'=>'private', 'name'=>'private',
'default'=>TRUE, 'default'=>TRUE,
'active'=>TRUE, 'active'=>TRUE,
'public'=>TRUE, 'public'=>TRUE,
'notes'=>'PrivateNet: Internal Testing Network' 'notes'=>'PrivateNet: Internal Testing Network'
]); ]);
$do->save();
DB::table('zones')->insert([ $zo = new Zone;
$zo->forceFill([
'zone_id'=>'10', 'zone_id'=>'10',
'domain_id'=>1,
'active'=>TRUE, 'active'=>TRUE,
'system_id'=>$so->id,
]); ]);
$do->zones()->save($zo);
DB::table('nodes')->insert([
'zone_id'=>'1',
'host_id'=>'999',
'node_id'=>'2',
'is_host'=>TRUE,
'active'=>TRUE,
'system'=>'FTN Clearing House Dev',
'sysop'=>'Deon George',
'location'=>'Parkdale, AUS',
'email'=>'deon@leenooks.net',
'address'=>'10.1.3.165',
'port'=>24554,
'protocol_id'=>1,
'software_id'=>1,
]);
DB::table('nodes')->insert([
'zone_id'=>'1',
'host_id'=>'999',
'node_id'=>'1',
'is_host'=>TRUE,
'active'=>TRUE,
'system'=>'Alterant MailHUB DEV',
'sysop'=>'Deon George',
'location'=>'Parkdale, AUS',
'email'=>'deon@leenooks.net',
'address'=>'d-1-4.ipv4.leenooks.vpn',
'port'=>14554,
'sespass'=>'PASSWORD',
'protocol_id'=>1,
'software_id'=>1,
]);
DB::table('setups')->insert([ DB::table('setups')->insert([
'opt_md'=>'1', 'system_id'=>$so->id,
]); 'zmodem'=>0,
'emsi_protocols'=>0,
DB::table('node_setup')->insert([ 'binkp'=>0,
'node_id'=>'1', 'protocols'=>0,
'setup_id'=>'1', 'permissions'=>0,
'options'=>0,
]); ]);
DB::table('users')->insert([ DB::table('users')->insert([
'name'=>'Deon George', 'name'=>'Deon George',
'email'=>'deon@leenooks.net', 'email'=>'deon@leenooks.net',
'email_verified_at'=>Carbon::now(),
'admin'=>TRUE,
'active'=>TRUE,
'password'=>'$2y$10$bJQDLfxnKrh6o5Sa02MZOukXcLTNQiByXSTJ7fTr.kHMpV2wxbG6.', 'password'=>'$2y$10$bJQDLfxnKrh6o5Sa02MZOukXcLTNQiByXSTJ7fTr.kHMpV2wxbG6.',
]); ]);
} }

View File

@ -20,7 +20,7 @@ class NodeHierarchy extends Seeder
{ {
DB::table('domains') DB::table('domains')
->insert([ ->insert([
'name'=>'Domain A', 'name'=>'domain-a',
'active'=>TRUE, 'active'=>TRUE,
'public'=>TRUE, 'public'=>TRUE,
'default'=>FALSE, 'default'=>FALSE,
@ -30,7 +30,7 @@ class NodeHierarchy extends Seeder
DB::table('domains') DB::table('domains')
->insert([ ->insert([
'name'=>'Domain B', 'name'=>'domain-b',
'active'=>TRUE, 'active'=>TRUE,
'public'=>TRUE, 'public'=>TRUE,
'default'=>FALSE, 'default'=>FALSE,
@ -38,7 +38,7 @@ class NodeHierarchy extends Seeder
'updated_at'=>Carbon::now(), 'updated_at'=>Carbon::now(),
]); ]);
foreach (['Domain A','Domain B'] as $domain) { foreach (['domain-a','domain-b'] as $domain) {
$domain = Domain::where('name',$domain)->singleOrFail(); $domain = Domain::where('name',$domain)->singleOrFail();
$this->hierarchy($domain,100); $this->hierarchy($domain,100);
$this->hierarchy($domain,101); $this->hierarchy($domain,101);
@ -66,6 +66,19 @@ class NodeHierarchy extends Seeder
]); ]);
$zo = Zone::where('zone_id',$zoneid)->where('domain_id',$domain->id)->singleOrFail(); $zo = Zone::where('zone_id',$zoneid)->where('domain_id',$domain->id)->singleOrFail();
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'region_id'=>0,
'host_id'=>0,
'node_id'=>0,
'point_id'=>0,
'system_id'=>$so->id,
'role'=>DomainController::NODE_ZC,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
// Nodes // Nodes
foreach ($nodes as $nid) { foreach ($nodes as $nid) {
@ -120,7 +133,7 @@ class NodeHierarchy extends Seeder
// Hosts // Hosts
foreach ($hosts as $hid) { foreach ($hosts as $hid) {
$hostid = $rid*100+$hid; $hostid = $rid*10+$hid;
$so = $this->system(sprintf('Host %03d:%03d/0.0@%s (Region %03d)',$zoneid,$hostid,$domain->name,$rid)); $so = $this->system(sprintf('Host %03d:%03d/0.0@%s (Region %03d)',$zoneid,$hostid,$domain->name,$rid));
DB::table('addresses') DB::table('addresses')
->insert([ ->insert([

View File

@ -39,6 +39,7 @@ If you have more than 1 BBS, then the Clearing House can receive all your mail f
<ul> <ul>
<li>Supports BINKP network transfers</li> <li>Supports BINKP network transfers</li>
<li>Supports EMSI network transfers</li> <li>Supports EMSI network transfers</li>
<li>Automatic route configuration</li>
<li>Manages ECHO areas and FILE areas <sup>To be implemented</sup></li> <li>Manages ECHO areas and FILE areas <sup>To be implemented</sup></li>
<li>Supports PING and TRACE responses <sup>To be implemented</sup></li> <li>Supports PING and TRACE responses <sup>To be implemented</sup></li>
<li>Nodelist Management <sup>To be implemented</sup></li> <li>Nodelist Management <sup>To be implemented</sup></li>

View File

@ -68,26 +68,7 @@
<tbody> <tbody>
@foreach ($o->zones->sortBy('zone_id') as $oz) @foreach ($o->zones->sortBy('zone_id') as $oz)
<!-- First System Zone --> @foreach ($oz->addresses as $ao)
<tr>
<td>{{ sprintf('ZC-%s-%05d',$oz->domain->name,$oz->zone_id) }}</td>
<td>{{ $oz->system->sysop }}</td>
<td>{{ $oz->system->location }}</td>
<td>{{ $oz->zone_id }}:0/0</td>
<td>{{ $oz->system->last_session ? $oz->system->last_session->format('Y-m-d H:i') : '-' }}</td>
</tr>
@foreach ($oz->addresses()->active()->FTNorder()->where('point_id',0)->whereNull('hub_id')->with(['system.sessions','system.setup','zone.domain'])->get() as $ao)
@if ($ao->role == 'Host')
<tr>
<td>{{ sprintf('NC-%s-%05d',$oz->domain->name,$ao->host_id) }} @auth<span class="float-end"><small>[{{ $ao->system_id }}]</small></span>@endauth</td>
<td>{{ $ao->system->sysop }}</td>
<td>{{ $ao->system->location }}</td>
<td>{{ $oz->zone_id }}:{{ $ao->host_id }}/0</td>
<td>{{ $ao->system->last_session ? $ao->system->last_session->format('Y-m-d H:i') : '-' }}</td>
</tr>
@endif
<tr> <tr>
<td><a href="{{ url('ftn/system/addedit',[$ao->system_id]) }}">{{ $ao->system->full_name($ao) }}</a> @auth<span class="float-end"><small>@if($ao->session('sespass'))<sup>*</sup>@elseif($ao->system->setup)<sup class="success">+</sup>@endif[{{ $ao->system_id }}]</small></span>@endauth</td> <td><a href="{{ url('ftn/system/addedit',[$ao->system_id]) }}">{{ $ao->system->full_name($ao) }}</a> @auth<span class="float-end"><small>@if($ao->session('sespass'))<sup>*</sup>@elseif($ao->system->setup)<sup class="success">+</sup>@endif[{{ $ao->system_id }}]</small></span>@endauth</td>
<td>{{ $ao->system->sysop }}</td> <td>{{ $ao->system->sysop }}</td>
@ -95,19 +76,6 @@
<td>{{ $ao->ftn_3d }}</td> <td>{{ $ao->ftn_3d }}</td>
<td>{{ $ao->system->last_session ? $ao->system->last_session->format('Y-m-d H:i') : '-' }}</td> <td>{{ $ao->system->last_session ? $ao->system->last_session->format('Y-m-d H:i') : '-' }}</td>
</tr> </tr>
<!-- If this node is a hub -->
@if ($ao->role == 'Hub')
@foreach ($oz->addresses()->active()->FTNorder()->where('point_id',0)->where('hub_id',$ao->id)->with(['system.sessions','zone.domain'])->get() as $aoo)
<tr>
<td><a href="{{ url('ftn/system/addedit',[$ao->system_id]) }}">{{ $aoo->system->full_name($aoo) }}</a> @auth<span class="float-end"><small>[{{ $aoo->system_id }}]</small></span>@endauth</td>
<td>{{ $aoo->system->sysop }}</td>
<td>{{ $aoo->system->location }}</td>
<td>{{ $aoo->ftn_3d }}</td>
<td>{{ $aoo->system->last_session ? $aoo->system->last_session->format('Y-m-d H:i') : '-' }}</td>
</tr>
@endforeach
@endif
@endforeach @endforeach
@endforeach @endforeach
</tbody> </tbody>

View File

@ -157,7 +157,7 @@
<tr> <tr>
<td>{{ $oo->ftn }}</td> <td>{{ $oo->ftn }}</td>
<td>{{ $oo->active ? 'YES' : 'NO' }}</td> <td>{{ $oo->active ? 'YES' : 'NO' }}</td>
<td>{{ $oo->role }}</td> <td>{{ $oo->role_name }}</td>
<td style="width: 70px;"> <td style="width: 70px;">
@if (! $oo->children) @if (! $oo->children)
@can('admin',$oo) @can('admin',$oo)
@ -185,6 +185,83 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Routing -->
<div class="accordion-item">
<h3 class="accordion-header" id="routing" data-bs-toggle="collapse" data-bs-target="#collapse_routing" aria-expanded="false" aria-controls="collapse_routing">Mail Routing</h3>
<div id="collapse_routing" class="accordion-collapse collapse {{ (session()->has('add_address')) ? 'show' : '' }}" aria-labelledby="addresses" data-bs-parent="#accordion_homepage">
<div class="accordion-body">
<div class="row">
<!-- Zone mail sent to -->
<div class="col-6">
@if(! $o->setup && ($x=\App\Models\Zone::active()
->whereIn('id',$o->zones->pluck('id'))
->whereNotIn('id',$o->sessions->pluck('id'))
->get())->count())
<h4>This host's mail is sent to:</h4>
<table class="table monotable">
<thead>
<tr>
<th>Zone</th>
<th>System</th>
</tr>
</thead>
<tbody>
@foreach($x as $zo)
<!-- @todo Not showing when multiple addresses in the same domain -->
@foreach ($o->match($zo) as $oo)
<tr>
<td>{{ $oo->ftn }}</td>
<td>
@if ($x=$oo->parent())
{{ $x->ftn4d }}
@else
No destination for mail.
@endif
</td>
</tr>
@endforeach
@endforeach
</tbody>
</table>
@endif
</div>
<!-- Systems this host collects for -->
<div class="col-6">
<h4>This host collects mail for the following systems:</h4>
@if($o->sessions->count())
<table class="table monotable">
<thead>
<tr>
<th>Zone</th>
<th>System</th>
</tr>
</thead>
<tbody>
@foreach ($o->sessions->sortBy('zone_id') as $zo)
@foreach ($o->match($zo) as $oo)
<tr>
<td>{{ $oo->ftn }}</td>
<td>{!! (($x=$oo->children) && $x->count()) ? $x->pluck('ftn4d')->join('<br>') : 'None' !!}</td>
</tr>
@endforeach
@endforeach
</tbody>
</table>
@else
<p>This host doesnt collect mail.</p>
@endif
</div>
</div>
</div>
</div>
</div>
@else @else
@include('system.form-system') @include('system.form-system')
@endif @endif

View File

@ -1,7 +1,6 @@
@if(($x=\App\Models\Zone::active() @if(($x=\App\Models\Zone::active()
->whereIn('id',$o->zones->pluck('id')) ->whereIn('id',$o->zones->pluck('id'))
->whereNotIn('id',$o->sessions->pluck('id')) ->whereNotIn('id',$o->sessions->pluck('id'))
->with(['domain'])
->get())->count()) ->get())->count())
<hr> <hr>

View File

@ -2,16 +2,22 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Http\Controllers\DomainController;
use App\Models\Address;
use Database\Seeders\NodeHierarchy;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase; use Tests\TestCase;
use App\Models\Domain; use App\Models\Domain;
class RoutingTest extends TestCase class RoutingTest extends TestCase
{ {
use DatabaseTransactions;
private function zone(): Collection private function zone(): Collection
{ {
$do = Domain::where('name','Domain A')->singleOrFail(); $do = Domain::where('name','domain-a')->singleOrFail();
$zo = $do->zones->where('zone_id',100)->pop(); $zo = $do->zones->where('zone_id',100)->pop();
return $zo->addresses; return $zo->addresses;
} }
@ -23,36 +29,118 @@ class RoutingTest extends TestCase
*/ */
public function test_zc() public function test_zc()
{ {
$this->seed(NodeHierarchy::class);
$nodes = $this->zone(); $nodes = $this->zone();
$this->assertEquals(51,$nodes->count()); $this->assertEquals(52,$nodes->count());
/* // Pick ZC without any session info - we have 0 children
* ZCs addresses are not in the address table, so we cannot workout children $ao = Address::findFTN('100:0/0@domain-a');
$zc = $nodes->where('role',DomainController::NODE_ZC); $this->assertEquals($ao->role,DomainController::NODE_ZC);
$this->assertEquals(1,$zc->count()); $this->assertCount(0,$ao->children);
$zc = $zc->pop(); $this->assertNull($ao->parent());
// ZC has 2 Region and 3 nodes as children // Add session info, and we have 51 children
$this->assertEquals(5,$zc->children()); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
*/ $ao = Address::findFTN('100:0/0@domain-a');
$this->assertCount(51,$ao->children);
// A node's parent should be the ZC
$ao = Address::findFTN('100:10/0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_NC);
$this->assertEquals('100:0/0.0@domain-a',$ao->parent()->ftn);
$ao = Address::findFTN('100:21/2001.0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_ACTIVE);
$this->assertEquals('100:0/0.0@domain-a',$ao->parent()->ftn);
// Pick a NC and we have 10 less children
$ao = Address::findFTN('100:10/0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_NC);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:10/0@domain-a');
$this->assertCount(9,$ao->children);
$ao = Address::findFTN('100:0/0@domain-a');
$this->assertCount(41,$ao->children);
// A node's parent should be the ZC
$ao = Address::findFTN('100:20/0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_NC);
$this->assertEquals('100:0/0.0@domain-a',$ao->parent()->ftn);
$ao = Address::findFTN('100:10/2001.0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_ACTIVE);
$this->assertEquals('100:10/0.0@domain-a',$ao->parent()->ftn);
// Pick a Node and we have 1 less child
$ao = Address::findFTN('100:21/2001.0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_ACTIVE);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:21/2001.0@domain-a');
$this->assertNULL($ao->children);
$ao = Address::findFTN('100:0/0@domain-a');
$this->assertCount(40,$ao->children);
// Define address on a HC and we have 3 less children
$ao = Address::findFTN('100:21/1000.0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_HC);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:21/1000.0@domain-a');
$this->assertCount(2,$ao->children);
$ao = Address::findFTN('100:0/0@domain-a');
$this->assertCount(37,$ao->children);
// Define address on a NC and we have 10 less children
$ao = Address::findFTN('100:20/0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_NC);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:0/0@domain-a');
$this->assertCount(27,$ao->children);
} }
/** /**
* Test the RC address. * Test the ZC address.
* *
* @return void * @return void
*/ */
public function test_rc() public function test_rc()
{ {
$nodes = $this->zone(); $this->seed(NodeHierarchy::class);
$rc = $nodes->where('role','Region');
$this->assertEquals(2,$rc->count());
// First RC // Pick ZC without any session info - we have 0 children
$rc = $rc->pop(); $ao = Address::findFTN('100:1/0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_RC);
$this->assertCount(0,$ao->children);
// RC has 3 nodes and 2 hosts as children // Add session info, and we have 51 children
$this->assertEquals(5,$rc->children->count()); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:1/0@domain-a');
$this->assertCount(23,$ao->children);
// Pick a NC and we have 10 less children
$ao = Address::findFTN('100:10/0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_NC);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:10/0@domain-a');
$this->assertCount(9,$ao->children);
$ao = Address::findFTN('100:1/0@domain-a');
$this->assertCount(13,$ao->children);
// Pick a Node and we have 1 less child
$ao = Address::findFTN('100:11/2001.0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_ACTIVE);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:11/2001.0@domain-a');
$this->assertNULL($ao->children);
$ao = Address::findFTN('100:1/0@domain-a');
$this->assertCount(12,$ao->children);
// Define address on a HC and we have 3 less children
$ao = Address::findFTN('100:11/1000.0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_HC);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:11/1000.0@domain-a');
$this->assertCount(2,$ao->children);
$ao = Address::findFTN('100:1/0@domain-a');
$this->assertCount(9,$ao->children);
} }
/** /**
@ -62,15 +150,31 @@ class RoutingTest extends TestCase
*/ */
public function test_nc() public function test_nc()
{ {
$nodes = $this->zone(); $this->seed(NodeHierarchy::class);
$nc = $nodes->where('role','Host');
$this->assertEquals(4,$nc->count());
// First NC // Pick a HC without any session info - we have 0 children
$nc = $nc->pop(); $ao = Address::findFTN('100:20/0@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_NC);
$this->assertCount(0,$ao->children);
// NC has 3 nodes and 2 hubs as children // Add session info, we have 10 children
$this->assertEquals(5,$nc->children->count()); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:20/0@domain-a');
$this->assertCount(9,$ao->children);
// Add session info to a hub, we have 3 less children
$ao = Address::findFTN('100:20/2000@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_HC);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:20/0@domain-a');
$this->assertCount(6,$ao->children);
// Add session info to a node, we have 1 less child
$ao = Address::findFTN('100:20/1@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_ACTIVE);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:20/0@domain-a');
$this->assertCount(5,$ao->children);
} }
/** /**
@ -80,15 +184,34 @@ class RoutingTest extends TestCase
*/ */
public function test_hc() public function test_hc()
{ {
$nodes = $this->zone(); $this->seed(NodeHierarchy::class);
$hc = $nodes->where('role','Hub');
//dd($hc->pluck('ftn'));
$this->assertEquals(8,$hc->count());
// First HC // Pick a HC without any session info - we have 0 children
$hc = $hc->pop(); $ao = Address::findFTN('100:20/2000@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_HC);
$this->assertCount(0,$ao->children);
// HC has 2 children // Add session info, we have 2 children
$this->assertEquals(2,$hc->children->count()); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:20/2000@domain-a');
$this->assertCount(2,$ao->children);
// Add session info to 1 child, we have 1 children
$ao = Address::findFTN('100:20/2001@domain-a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:20/2000@domain-a');
$this->assertCount(1,$ao->children);
}
public function test_node()
{
$this->seed(NodeHierarchy::class);
// Node with session details still doesnt have any children
$ao = Address::findFTN('100:20/2001@domain-a');
$this->assertEquals($ao->role,DomainController::NODE_ACTIVE);
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:20/2001@domain-a');
$this->assertNULL($ao->children);
} }
} }