Compare commits

...

2 Commits

Author SHA1 Message Date
c49daadc5f Updates now that we have updated our_address() to differentiate public/mailer advertised addresses with all our addresses
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-15 09:35:35 +10:00
b59317871a Added tool to list node addresses and the address we use,
Fix address edit which reactivated the address,
Fix correct icons when using address merge,
We only advertise validated addresses and use validated addresses for routing,
Show our address on known AKA screen
2025-04-15 09:32:50 +10:00
12 changed files with 159 additions and 29 deletions

View File

@ -0,0 +1,64 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Models\Address;
class AddressCheckNode extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'debug:address:check:nodes'
.' {ftn? : FTN}'
.' {--N|node : Node Order}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check all addresses we use for nodes';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
$ao = NULL;
if ($this->argument('ftn')) {
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao) {
$this->error('FTN not found: ' .$this->argument('ftn'));
return self::FAILURE;
}
$this->info('our address:'.our_address($ao)->ftn);
return self::SUCCESS;
}
$this->table(['System','Node','Ours'],
our_nodes($ao ? $ao->domain : NULL)
->sortBy(fn($item)=>$this->option('node')
? sprintf('%s:%s',$item->system->name,$item->domain->name)
: sprintf('%s',$item->domain->name))
->map(fn($item)=>
[
'System'=>$item->system->name,
'Node'=>$item->ftn.' '.($item->echoareas->count() ? '^' : '').($item->fileareas->count() ? '*' : ''),
'Ours'=>our_address($item)?->ftn,
]));
return self::SUCCESS;
}
}

View File

@ -25,10 +25,11 @@ class ZoneCheck extends Command
$this->warn('Zone: '.$zo->zone_id);
$this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(',')));
$this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'],
$this->table(['id','region_id','ftn','role','parent','children','downlinks','uplink','send from','system','notes'],
$zo->addresses()->FTN()->active()->with(['system','nodes_hub'])->get()->transform(function($item) {
return [
'id'=>$item->id,
'region_id'=>$item->region_id,
'ftn'=>$item->ftn4d,
'role'=>$item->role_name,
'parent'=>$item->parent()?->ftn4d,
@ -36,7 +37,6 @@ class ZoneCheck extends Command
'downlinks'=>$item->downlinks()->count(),
'uplink'=>($x=$item->uplink())?->ftn4d,
'send from'=>$x ? our_address($item->uplink())?->ftn4d : '',
'region_id'=>$item->region_id,
'system'=>$item->system->name,
'notes'=>$item->isRoleOverride() ? 'Role Override' : '',
];

View File

@ -98,7 +98,10 @@ class SystemController extends Controller
$oo = Address::findOrNew($request->validated('submit'));
$oo->zone_id = $request->validated('zone_id');
$oo->security = $request->validated('security');
$oo->active = TRUE;
// Only change to active if it is new
if (! $oo->exists)
$oo->active = TRUE;
switch ($request->validated('action')) {
case 'region':

View File

@ -194,7 +194,7 @@ class AddressIdle implements ShouldQueue
return collect();
$age = Carbon::now()->subDays($days)->endOfDay();
$ours = our_address($do)->pluck('ftn');
$ours = our_address($do,FALSE)->pluck('ftn');
return Address::FTN()
->ActiveFTN()

View File

@ -60,7 +60,7 @@ class MessageProcess implements ShouldQueue
$this->mo = unserialize(utf8_decode($this->mo));
// Load our details
$ftns = our_address();
$ftns = our_address(NULL,FALSE);
// If we are a netmail
if ($this->mo instanceof Netmail) {

View File

@ -115,12 +115,12 @@ class NodelistImport implements ShouldQueue
}
$file_crc = (int)$matches[4];
$do = Domain::where('name',strtolower($matches[1] ?: $this->domain))->single();
$do = Domain::where('name',strtolower($this->domain ?: $matches[1]))->single();
if (! $do) {
Log::error(sprintf('%s:! Domain not found [%s].',static::LOGKEY,strtolower($matches[1] ?: $this->domain)));
throw new \Exception('Nodelist Domain not found: '.$this->file);
throw new \Exception('Nodelist Domain not found: '.($this->domain ?: $matches[1]));
}
$date = Carbon::createFromFormat('D, M d, Y H:i',$matches[2].'0:00');
@ -143,6 +143,8 @@ class NodelistImport implements ShouldQueue
elseif ($no->addresses->count()) {
Log::error(sprintf('%s:! Nodelist [%s] for [%s] has existing records [%d]',self::LOGKEY,$date,$do->name,$no->addresses->count()));
// This can occur when a nodelist doesnt change, but is sent out anyway
// @todo Need to immediately fail this job
return;
}
@ -291,7 +293,7 @@ class NodelistImport implements ShouldQueue
Log::info(sprintf('%s:- Processing existing address [%s] (%d)',self::LOGKEY,$ao->ftn,$ao->id));
// If the address is linked to a user's system, or our system, we'll not process it any further
if (our_address()->contains($ao->id)) {
if (our_address(NULL,FALSE)->contains($ao->id)) {
Log::info(sprintf('%s:! Limiting update to an address belonging to me',self::LOGKEY));
$protect = TRUE;
@ -563,7 +565,7 @@ class NodelistImport implements ShouldQueue
->filter(fn($item)=>(! $item->point_id))
->pluck('id')
->diff($no->addresses->pluck('id'))
->diff(our_address($do)->pluck('id'))
->diff(our_address($do,FALSE)->pluck('id'))
->diff(our_nodes($do)->pluck('id'));
$remove = Address::whereIn('id',$remove)->get();

View File

@ -89,7 +89,7 @@ class PacketProcess implements ShouldQueue
}
// Check the packet is to our address, if not we'll reject it.
if (! our_address($pkt->tftn->zone->domain)->contains($pkt->tftn)) {
if (! our_address($pkt->tftn->zone->domain,FALSE)->contains($pkt->tftn)) {
Log::error(sprintf('%s:! Packet [%s] is not to our address? [%s]',self::LOGKEY,$this->filename,$pkt->tftn->ftn));
// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
@ -123,7 +123,7 @@ class PacketProcess implements ShouldQueue
Log::info(sprintf('%s:- Netmail from [%s] to [%s]',self::LOGKEY,$msg->fftn->ftn,$msg->tftn?->ftn ?: $msg->set_tftn));
// If we dont have a destination, we need to bounce it, if we would be the parent of the address
if ((! $msg->tftn) && our_address()->contains(Address::newFTN($msg->set_tftn)?->parent())) {
if ((! $msg->tftn) && our_address(NULL,FALSE)->contains(Address::newFTN($msg->set_tftn)?->parent())) {
Log::alert(sprintf('%s:! Netmail destination [%s] doesnt exist, bouncing back to [%s]',self::LOGKEY,$msg->set_tftn,$pkt->fftn->ftn));
Notification::route('netmail',$msg->fftn)->notify(new NetmailNoDestination($msg));

View File

@ -764,7 +764,7 @@ class Address extends Model
public function getFTNAttribute(): string
{
if (! $this->relationLoaded('zone'))
$this->load(['zone:id,domain_id,zone_id','zone.domain:domains.id,name']);
$this->load(['zone:id,domain_id,active,zone_id','zone.domain:domains.id,name,active']);
return sprintf('%s@%s',$this->getFTN4DAttribute(),$this->zone->domain->name);
}
@ -1024,7 +1024,7 @@ class Address extends Model
public function downlinks(): Collection
{
// We have no session data for this address, (and its not our address), by definition it has no children
if (! $this->is_hosted && (! our_address()->pluck('id')->contains($this->id)))
if (! $this->is_hosted && (! our_address(NULL,FALSE)->pluck('id')->contains($this->id)))
return new Collection;
// If this system is not marked to default route for this address

View File

@ -184,12 +184,14 @@ final class Echomail extends Model implements Packet
Log::debug(sprintf('%s:^ Message [%d] from point address is [%d]',self::LOGKEY,$model->id,$model->fftn->point_id));
// Make sure our sender is first in the path
// @todo we need to capture the path for mail directly from points so we dont re-export to it.
if (($model->fftn->point_id === 0) && (! $model->isFlagSet(Message::FLAG_LOCAL)) && (! $path->contains($model->fftn_id))) {
Log::alert(sprintf('%s:? Echomail adding sender to start of PATH [%s].',self::LOGKEY,$model->fftn_id));
$path->prepend($model->fftn_id);
}
// Make sure our pktsrc is last in the path
// @todo mail directly from points may have a blank path, so we need to make one up.
if ($model->set->has('set_sender') && (! $path->contains($model->set->get('set_sender')->id)) && ($model->set->get('set_sender')->point_id === 0)) {
Log::alert(sprintf('%s:? Echomail adding pktsrc to end of PATH [%s].',self::LOGKEY,$model->set->get('set_sender')->ftn));
$path->push($model->set->get('set_sender')->id);
@ -257,7 +259,7 @@ final class Echomail extends Model implements Packet
->addresses
->filter(function($item) use ($model) { return $model->echoarea->can_read($item->security); })
->pluck('id')
->diff(our_address($model->fftn->zone->domain)->pluck('id'))
->diff(our_address($model->fftn->zone->domain,FALSE)->pluck('id'))
->diff($seenby);
if ($exportto->count()) {

View File

@ -84,14 +84,27 @@ if (! function_exists('hexstr')) {
/**
* Return our addresses.
*
* We have two address types:
* + a) address that we advertise to receive mail and use to package up mail (Public)
* + b) address that we accept and process mail
*
* a is a subset of b, where b might have addresses we used in the past but no longer (but mail still comes in, and we
* dont want to send out mail with those addresses anymore)
*
* Public addresses are when validated is set to true
* When determining our public addresses for a specific Address (when Address::class is passed), we only return addresses
* when security is not null
*
* If domain provided, limit the list to those within the domain - returning a Collection::class
* If address provided, return our address that would be used for the provided address - return Address::class
*
* @param Domain|Address|null $o - Domain or Address
* @param Domain|Address|null $o Domain or Address
* @param bool $public Return only public addresses
* @return Collection|Address|NULL
* @throws Exception
*/
function our_address(Domain|Address $o=NULL): Collection|Address|NULL
function our_address(Domain|Address $o=NULL,bool $public=TRUE): Collection|Address|NULL
{
if (! Config::has('setup'))
Config::set('setup',Setup::findOrFail(config('app.id')));
@ -99,7 +112,7 @@ function our_address(Domain|Address $o=NULL): Collection|Address|NULL
$so = Config::get('setup');
$so->loadMissing([
'system:id,name,sysop,location',
'system.akas:addresses.id,addresses.zone_id,region_id,host_id,node_id,point_id,addresses.system_id,addresses.active,role',
'system.akas:addresses.id,addresses.zone_id,region_id,host_id,node_id,point_id,addresses.system_id,addresses.active,role,validated,security',
'system.akas.zone:id,domain_id,zone_id',
'system.akas.zone.domain:id,name',
]);
@ -117,20 +130,22 @@ function our_address(Domain|Address $o=NULL): Collection|Address|NULL
// Looking for addresses in the same domain, and if fido.strict, addresses that have a higher role (ie: uplink)
case Address::class:
$filter = $so->system->akas
->filter(fn($item)=>$item->zone->domain_id === $o->zone->domain_id)
->filter(fn($item)=>((! $public) || ($public && $item->validated)) && ($item->zone->domain_id === $o->zone->domain_id))
->sortBy('role_id');
// If we are looking for a specific address, and there is only 1 result, return it, otherwise return what we have
if (config('fido.strict') && ($x=$filter->filter(fn($item)=>$item->role_id <= $o->role_id)->sortBy('role_id'))->count())
$filter = $x;
return $filter->count() ? $filter->last()->unsetRelation('nodes_hub') : NULL;
return $filter->count()
? ($filter->filter(fn($item)=>$item->region_id === $o->region_id)->count()
? $filter->last()
: $filter->first())->unsetRelation('nodes_hub')
: NULL;
// Addresses in this domain
case Domain::class:
return $so->system->akas
->filter(fn($item)=>$item->zone->domain_id === $o->id)
->sortBy('role_id');
->filter(fn($item)=>((! $public) || ($public && $item->validated)) && ($item->zone->domain_id === $o->id));
// We shouldnt get here
default:
@ -140,7 +155,7 @@ function our_address(Domain|Address $o=NULL): Collection|Address|NULL
function our_hostname(Address $o): string
{
$our = our_address($o->domain)->first();
$our = our_address($o->domain,FALSE)->first();
$ourhostname = $our->system->address;
switch ($our->role_id) {

View File

@ -24,7 +24,7 @@ $user->load(['systems.akas.zone.domain.echoareas','systems.akas.echoareas']);
<div class="col-12">
@if(($x=$user
->addresses()
->diff(our_address())
->diff(our_address(NULL,FALSE))
->filter(fn($item)=>($item->point_id === 0) && ($item->zone->domain->isManaged())))->count())
<h2>Hub Details for your nets</h2>

View File

@ -112,21 +112,37 @@
@if($o->addresses->count())
<p>This system has the following addresses assigned to it:</p>
<table class="table monotable">
<table class="table monotable w-100" id="address_table">
<thead>
<tr>
<th>Address</th>
<th>Active</th>
<th>@if($o->setup)Parent @else Uplink @endif</th>
<th>Security</th>
<th colspan="2">Role</th>
<th>Role</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach ($o->addresses as $oo)
<tr>
<td @class(['trashed'=>$oo->trashed(),'inactive'=>(! $oo->trashed()) && (! $oo->active)])>{{ $oo->ftn }}<span class="float-end"><data value="{{ $oo->id }}:{{ $oo->validated ? 1 : 0 }}" class="validated"><i title="@if($oo->validated)Mail flowing @else Mail held @endif" @class(['bi','bi-activity'=>$oo->validated,'bi-radioactive'=>(! $oo->validated)])></i></data></span></td>
<td>{{ $oo->active ? 'YES' : 'NO' }}</td>
<tr @class(['text-secondary'=>$oo->trashed() || (! $oo->active_domain),'inactive'=>(! $oo->trashed()) && (! $oo->active)])>
<td>{{ $oo->ftn }}<span class="float-end"><data value="{{ $oo->id }}:{{ $oo->validated ? 1 : 0 }}" class="validated"><i title="@if($oo->validated)Mail flowing @else Mail held @endif" @class(['bi','bi-activity'=>$oo->validated,'bi-radioactive'=>(! $oo->validated)])></i></data></span></td>
<td>-{{ $oo->active ? 'Active' : 'Not Active' }}-</td>
<td>
@if($o->setup)
{{ ($oo->validated && $x=$oo->parent()) ? $x->ftn : '' }}
@else
@if($x=our_address($oo))
<!-- @todo To implement, if we enable address overriding -->
@if(true)
Auto <small class="text-secondary">[{{ $x->ftn4d }}]</small>
@else
<small>[Override]</small>
@endif
@endif
@endif
</td>
<td class="text-end">{{ $oo->security }}</td>
<td>{{ $oo->role_name }}</td>
<td class="nowrap actions">
@ -614,13 +630,16 @@
@endsection
@section('page-css')
@css('datatables')
<style>
span.btn.btn-danger a {
color: white
}
</style>
@endsection
@section('page-scripts')
@js('datatables')
<script type="text/javascript">
$(document).ready(function () {
if (window.location.hash) {
@ -664,6 +683,31 @@
@endcan
@endif
$('#address_table').DataTable({
paging: true,
pageLength: 25,
searching: true,
ordering: true,
order: [[1,'asc']],
conditionalPaging: {
style: 'fade',
speed: 500 // optional
},
rowGroup: {
dataSrc: [1],
},
columnDefs: [
{
targets: [1],
visible: false,
},
{
targets: [5],
orderable: false
}
],
});
$('data.validated').on('click',function(item) {
that = $(this);
var values = item.delegateTarget.value.split(':');