Changed to using new Address Model, Implemented Setup, Some minor CSS changes

This commit is contained in:
Deon George 2021-06-24 20:16:37 +10:00
parent ec6594b701
commit d1ca78d372
33 changed files with 766 additions and 172 deletions

View File

@ -3,12 +3,11 @@
namespace App\Classes; namespace App\Classes;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Models\Node as NodeModel; use App\Models\Address;
/** /**
* Object representing the node we are communicating with * Object representing the node we are communicating with
@ -64,7 +63,7 @@ class Node
// The nodes password // The nodes password
case 'password': case 'password':
return ($this->ftns_authed->count() && $x=$this->ftns_authed->first()->sespass) ? $x : '-'; return ($this->ftns_authed->count() && ($x=$this->ftns_authed->first()->session('sespass'))) ? $x : '-';
// Return how long our session has been connected // Return how long our session has been connected
case 'session_time': case 'session_time':
@ -102,8 +101,8 @@ class Node
{ {
switch ($key) { switch ($key) {
case 'ftn': case 'ftn':
if (! is_object($value) OR ! $value instanceof NodeModel) if (! is_object($value) OR ! $value instanceof Address)
throw new Exception('Not a node object: '.(is_object($value) ? get_class($value) : serialize($value))); throw new Exception('Not an Address object: '.(is_object($value) ? get_class($value) : serialize($value)));
// Ignore any duplicate FTNs that we get // Ignore any duplicate FTNs that we get
if ($this->ftns->search(function($item) use ($value) { return $item->id === $value->id; }) !== FALSE) { if ($this->ftns->search(function($item) use ($value) { return $item->id === $value->id; }) !== FALSE) {
@ -154,11 +153,11 @@ class Node
throw new Exception('Already authed'); throw new Exception('Already authed');
foreach ($this->ftns as $o) { foreach ($this->ftns as $o) {
if (! $o->sespass) if (! $o->session('sespass'))
continue; continue;
// If we have challenge, then we are doing MD5 // If we have challenge, then we are doing MD5
$exp_pwd = $challenge ? $this->md5_challenge($o->sespass,$challenge) : $o->sespass; $exp_pwd = $challenge ? $this->md5_challenge($o->session('sespass'),$challenge) : $o->session('sespass');
if ($exp_pwd === $password) if ($exp_pwd === $password)
$this->ftns_authed->push($o); $this->ftns_authed->push($o);
@ -208,11 +207,10 @@ class Node
* When we originate a call to a node, we need to store the node we are connecting with in the ftns_authed, so * When we originate a call to a node, we need to store the node we are connecting with in the ftns_authed, so
* authentication proceeds when we send our M_pwd * authentication proceeds when we send our M_pwd
* *
* @param NodeModel $o * @param Address $o
*/ */
public function originate(NodeModel $o): void public function originate(Address $o): void
{ {
$this->ftns->push($o);
$this->ftns_authed->push($o); $this->ftns_authed->push($o);
} }

View File

@ -8,8 +8,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\File\{Receive,Send}; use App\Classes\File\{Receive,Send};
use App\Classes\Sock\SocketClient; use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Models\Node as NodeModel; use App\Models\{Address,Setup};
use App\Models\Setup;
abstract class Protocol abstract class Protocol
{ {
@ -79,7 +78,7 @@ abstract class Protocol
protected const MO_CHAT = 4; protected const MO_CHAT = 4;
protected SocketClient $client; /* Our socket details */ protected SocketClient $client; /* Our socket details */
protected Setup $setup; /* Our setup */ protected ?Setup $setup; /* Our setup */
protected Node $node; /* The node we are communicating with */ protected Node $node; /* The node we are communicating with */
protected Send $send; /* The list of files we are sending */ protected Send $send; /* The list of files we are sending */
protected Receive $recv; /* The list of files we are receiving */ protected Receive $recv; /* The list of files we are receiving */
@ -92,6 +91,14 @@ abstract class Protocol
abstract protected function protocol_init(): int; abstract protected function protocol_init(): int;
abstract protected function protocol_session(): int; abstract protected function protocol_session(): int;
public function __construct(Setup $o=NULL)
{
if ($o && ! $o->system->addresses->count())
throw new Exception('We dont have any FTN addresses assigned');
$this->setup = $o;
}
/** /**
* @throws Exception * @throws Exception
*/ */
@ -181,11 +188,11 @@ abstract class Protocol
* *
* @param int $type * @param int $type
* @param SocketClient $client * @param SocketClient $client
* @param NodeModel|null $o * @param Address|null $o
* @return int * @return int
* @throws Exception * @throws Exception
*/ */
public function session(int $type,SocketClient $client,NodeModel $o=NULL): int public function session(int $type,SocketClient $client,Address $o=NULL): int
{ {
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$type)); Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$type));
@ -198,9 +205,6 @@ abstract class Protocol
$this->recv = new Receive; $this->recv = new Receive;
if ($o) { if ($o) {
// Our configuration and initialise values
$this->setup = Setup::findOrFail(self::setup);
// The node we are communicating with // The node we are communicating with
$this->node = new Node; $this->node = new Node;

View File

@ -12,7 +12,7 @@ use League\Flysystem\UnreadableFileException;
use App\Classes\Protocol as BaseProtocol; use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient; use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Models\Node; use App\Models\Address;
final class Binkd extends BaseProtocol final class Binkd extends BaseProtocol
{ {
@ -100,7 +100,7 @@ final class Binkd extends BaseProtocol
{ {
// If our parent returns a PID, we've forked // If our parent returns a PID, we've forked
if (! parent::onConnect($client)) { if (! parent::onConnect($client)) {
$this->session(self::SESSION_BINKP,$client,(new Node)); $this->session(self::SESSION_BINKP,$client,(new Address));
$this->client->close(); $this->client->close();
Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->getAddress())); Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->getAddress()));
} }
@ -143,7 +143,7 @@ final class Binkd extends BaseProtocol
// If we are originating, we'll show the remote our address in the same network // If we are originating, we'll show the remote our address in the same network
// @todo Implement hiding our AKAs not in this network. // @todo Implement hiding our AKAs not in this network.
if ($this->originate) if ($this->originate)
$this->msgs(self::BPM_ADR,join(' ',$this->setup->nodes->pluck('ftn')->toArray())); $this->msgs(self::BPM_ADR,join(' ',$this->setup->system->addresses->pluck('ftn')->toArray()));
} }
/** /**
@ -291,6 +291,7 @@ final class Binkd extends BaseProtocol
// @todo We maybe should count these and abort if there are too many? // @todo We maybe should count these and abort if there are too many?
if ($this->DEBUG) if ($this->DEBUG)
Log::debug(sprintf('%s: - Socket EAGAIN',__METHOD__)); Log::debug(sprintf('%s: - Socket EAGAIN',__METHOD__));
return 1; return 1;
} }
@ -304,6 +305,7 @@ final class Binkd extends BaseProtocol
// @todo Check that this is correct. // @todo Check that this is correct.
Log::debug(sprintf('%s: - Was the socket closed by the remote?',__METHOD__)); Log::debug(sprintf('%s: - Was the socket closed by the remote?',__METHOD__));
$this->error = -2; $this->error = -2;
return 0; return 0;
} }
@ -592,7 +594,7 @@ final class Binkd extends BaseProtocol
Log::debug(sprintf('%s: - Parsing AKA [%s]',__METHOD__,$rem_aka)); Log::debug(sprintf('%s: - Parsing AKA [%s]',__METHOD__,$rem_aka));
try { try {
if (! ($o = Node::findFTN($rem_aka))) { if (! ($o = Address::findFTN($rem_aka))) {
Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',__METHOD__,$rem_aka)); Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',__METHOD__,$rem_aka));
continue; continue;
@ -608,10 +610,10 @@ final class Binkd extends BaseProtocol
} }
// Check if the remote has our AKA // Check if the remote has our AKA
if ($this->setup->nodes->pluck('ftn')->search($o->ftn) !== FALSE) { if ($this->setup->system->addresses->pluck('ftn')->search($rem_aka) !== FALSE) {
Log::error(sprintf('%s: ! AKA is OURS [%s]',__METHOD__,$o->ftn)); Log::error(sprintf('%s: ! AKA is OURS [%s]',__METHOD__,$rem_aka));
$this->msgs(self::BPM_ERR,sprintf('Sorry that is my AKA [%s]',$o->ftn)); $this->msgs(self::BPM_ERR,sprintf('Sorry that is my AKA [%s]',$rem_aka));
$this->rc = self::S_FAILURE; $this->rc = self::S_FAILURE;
return 0; return 0;
@ -671,7 +673,7 @@ final class Binkd extends BaseProtocol
// If we are not the originator, we'll show our addresses in common. // If we are not the originator, we'll show our addresses in common.
// @todo make this an option to hideAKAs or not // @todo make this an option to hideAKAs or not
if (! $this->originate) if (! $this->originate)
$this->msgs(self::BPM_ADR,join(' ',$this->setup->nodes->pluck('ftn')->toArray())); $this->msgs(self::BPM_ADR,join(' ',$this->setup->system->addresses->pluck('ftn')->toArray()));
return 1; return 1;
} }

View File

@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\Protocol as BaseProtocol; use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient; use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Models\Node; use App\Models\Address;
use App\Interfaces\CRC as CRCInterface; use App\Interfaces\CRC as CRCInterface;
use App\Interfaces\Zmodem as ZmodemInterface; use App\Interfaces\Zmodem as ZmodemInterface;
use App\Traits\CRC as CRCTrait; use App\Traits\CRC as CRCTrait;
@ -80,7 +80,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
{ {
// If our parent returns a PID, we've forked // If our parent returns a PID, we've forked
if (! parent::onConnect($client)) { if (! parent::onConnect($client)) {
$this->session(self::SESSION_AUTO,$client,(new Node)); $this->session(self::SESSION_AUTO,$client,(new Address));
$this->client->close(); $this->client->close();
Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->getAddress())); Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->getAddress()));
} }
@ -183,7 +183,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Site address, password and compatibility // Site address, password and compatibility
// @todo Only show the AKAs that is relevant to the node we are connecting to // @todo Only show the AKAs that is relevant to the node we are connecting to
$makedata .= sprintf('{EMSI}{%s}{%s}{%s}{%s}', $makedata .= sprintf('{EMSI}{%s}{%s}{%s}{%s}',
join(' ',$this->setup->nodes->pluck('ftn')->toArray()), join(' ',$this->setup->system->addresses->pluck('ftn')->toArray()),
$this->node->password == '-' ? '' : $this->node->password, $this->node->password == '-' ? '' : $this->node->password,
join(',',$link_codes), join(',',$link_codes),
join(',',$compat_codes), join(',',$compat_codes),
@ -295,7 +295,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
Log::debug(sprintf('%s: - Parsing AKA [%s]',__METHOD__,$rem_aka)); Log::debug(sprintf('%s: - Parsing AKA [%s]',__METHOD__,$rem_aka));
try { try {
if (! ($o = Node::findFTN($rem_aka))) { if (! ($o = Address::findFTN($rem_aka))) {
Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',__METHOD__,$rem_aka)); Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',__METHOD__,$rem_aka));
continue; continue;
} }
@ -307,7 +307,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
} }
// Check if the remote has our AKA // Check if the remote has our AKA
if ($this->setup->nodes->pluck('ftn')->search($o->ftn) !== FALSE) { if ($this->setup->system->addresses->pluck('ftn')->search($o->ftn) !== FALSE) {
Log::error(sprintf('%s: ! AKA is OURS [%s]',__METHOD__,$o->ftn)); Log::error(sprintf('%s: ! AKA is OURS [%s]',__METHOD__,$o->ftn));
continue; continue;
@ -338,9 +338,9 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
} }
if (! $c) { if (! $c) {
Log::info(sprintf('%s: - Remote has password [%s] on us',__METHOD__,$p)); Log::info(sprintf('%s: - Remote has password [%s] on us, and we expect [%s]',__METHOD__,$p,$this->node->password));
if ($p) if ($p || $this->node->password)
$this->node->optionSet(self::O_BAD); $this->node->optionSet(self::O_BAD);
} else { } else {
@ -861,7 +861,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if ($gotreq++) if ($gotreq++)
return self::OK; return self::OK;
$this->client->buffer_add(self::EMSI_INQ); $this->client->buffer_add(self::EMSI_INQ.self::CR);
$this->client->buffer_flush(5); $this->client->buffer_flush(5);
} elseif ($p && strstr($p,self::EMSI_BEG) && strstr($p,self::EMSI_ARGUS1)) { } elseif ($p && strstr($p,self::EMSI_BEG) && strstr($p,self::EMSI_ARGUS1)) {

View File

@ -5,10 +5,10 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\Binkd as BinkdClass;
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer; use App\Classes\Sock\SocketServer;
use App\Models\Setup;
use App\Classes\Protocol\Binkd as BinkdClass;
class BinkpReceive extends Command class BinkpReceive extends Command
{ {
@ -35,8 +35,8 @@ class BinkpReceive extends Command
{ {
Log::info('Listening for BINKP connections...'); Log::info('Listening for BINKP connections...');
$server = new SocketServer(24554,'0.0.0.0'); $server = new SocketServer(Setup::BINKP_PORT,Setup::BINKP_BIND);
$server->setConnectionHandler([new BinkdClass,'onConnect']); $server->setConnectionHandler([new BinkdClass(Setup::findOrFail(config('app.id'))),'onConnect']);
try { try {
$server->listen(); $server->listen();

View File

@ -3,12 +3,12 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\Binkd as BinkdClass; use App\Classes\Protocol\Binkd as BinkdClass;
use App\Classes\Sock\SocketClient; use App\Classes\Sock\SocketClient;
use App\Models\Node; use App\Models\{Address,Setup};
class BinkpSend extends Command class BinkpSend extends Command
{ {
@ -36,11 +36,19 @@ class BinkpSend extends Command
{ {
Log::info('Call BINKP send'); Log::info('Call BINKP send');
$no = Node::findFTN($this->argument('ftn')); $no = Address::findFTN($this->argument('ftn'));
if (! $no)
throw new ModelNotFoundException('Unknown node: '.$this->argument('ftn'));
$client = SocketClient::create($no->address,$no->port); if ($no->system->mailer_type != Setup::O_BINKP)
throw new \Exception(sprintf('Node [%s] doesnt support BINKD',$this->argument('ftn')));
$o = new BinkdClass; if ((! $no->system->mailer_address) || (! $no->system->mailer_port))
throw new \Exception(sprintf('Unable to poll [%s] missing mailer details',$this->argument('ftn')));
$client = SocketClient::create($no->system->mailer_address,$no->system->mailer_port);
$o = new BinkdClass(Setup::findOrFail(config('app.id')));
$o->session(BinkdClass::SESSION_BINKP,$client,$no); $o->session(BinkdClass::SESSION_BINKP,$client,$no);
Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]); Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]);

View File

@ -5,10 +5,10 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\EMSI as EMSIClass;
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer; use App\Classes\Sock\SocketServer;
use App\Models\Setup;
use App\Classes\Protocol\EMSI as EMSIClass;
class EMSIReceive extends Command class EMSIReceive extends Command
{ {
@ -35,8 +35,8 @@ class EMSIReceive extends Command
{ {
Log::info('Listening for EMSI connections...'); Log::info('Listening for EMSI connections...');
$server = new SocketServer(60179,'0.0.0.0'); $server = new SocketServer(Setup::EMSI_PORT,Setup::EMSI_BIND);
$server->setConnectionHandler([new EMSIClass,'onConnect']); $server->setConnectionHandler([new EMSIClass(Setup::findOrFail(config('app.id'))),'onConnect']);
try { try {
$server->listen(); $server->listen();

View File

@ -2,13 +2,13 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Node;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\Sock\SocketClient;
use App\Classes\Protocol\EMSI as EMSIClass; use App\Classes\Protocol\EMSI as EMSIClass;
use App\Classes\Sock\SocketClient;
use App\Models\{Address,Setup};
class EMSISend extends Command class EMSISend extends Command
{ {
@ -36,11 +36,19 @@ class EMSISend extends Command
{ {
Log::info('Call EMSI send'); Log::info('Call EMSI send');
$no = Node::findFTN($this->argument('ftn')); $no = Address::findFTN($this->argument('ftn'));
if (! $no)
throw new ModelNotFoundException('Unknown node: '.$this->argument('ftn'));
$client = SocketClient::create($no->address,$no->port,38400); if ($no->system->mailer_type != Setup::O_EMSI)
throw new \Exception(sprintf('Node [%s] doesnt support EMSI',$this->argument('ftn')));
$o = new EMSIClass; if ((! $no->system->mailer_address) || (! $no->system->mailer_port))
throw new \Exception(sprintf('Unable to poll [%s] missing mailer details',$this->argument('ftn')));
$client = SocketClient::create($no->system->mailer_address,$no->system->mailer_port,38400);
$o = new EMSIClass(Setup::findOrFail(config('app.id')));
$o->session(EMSIClass::SESSION_AUTO,$client,$no); $o->session(EMSIClass::SESSION_AUTO,$client,$no);
Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]); Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]);

View File

@ -2,15 +2,13 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Classes\Protocol\Binkd;
use App\Classes\Protocol\EMSI;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\{Binkd,EMSI};
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer; use App\Classes\Sock\SocketServer;
use App\Models\Setup;
use App\Classes\Protocol\EMSI as EMSIClass;
class StartServer extends Command class StartServer extends Command
{ {
@ -35,18 +33,20 @@ class StartServer extends Command
*/ */
public function handle() public function handle()
{ {
$o = Setup::findOrFail(config('app.id'));
// @todo This should be a config item. // @todo This should be a config item.
$start = [ $start = [
'emsi' => [ 'emsi' => [
'address'=>'0.0.0.0', 'address'=>Setup::EMSI_BIND,
'port'=>60179, 'port'=>Setup::EMSI_PORT,
'class'=>new EMSI, 'class'=>new EMSI($o),
], ],
'binkp' => [ 'binkp' => [
'address'=>'0.0.0.0', 'address'=>Setup::BINKP_BIND,
'port'=>24554, 'port'=>Setup::BINKP_PORT,
'class'=>new Binkd, 'class'=>new Binkd($o),
], ],
]; ];

View File

@ -2,9 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Models\Domain; use App\Models\{Domain,Setup};
class HomeController extends Controller class HomeController extends Controller
{ {
@ -28,8 +29,34 @@ class HomeController extends Controller
* *
* @note: Protected by Route * @note: Protected by Route
*/ */
public function setup() public function setup(Request $request)
{ {
return view('setup'); $o = Setup::findOrNew(config('app.id'));
if ($request->post()) {
$request->validate([
'system_id' => 'required|exists:systems,id',
'binkp' => 'nullable|array',
'binkp.*' => 'nullable|numeric',
'options' => 'nullable|array',
'options.*' => 'nullable|numeric',
]);
if (! $o->exists) {
$o->id = config('app.id');
$o->zmodem = 0;
$o->emsi_protocols = 0;
$o->protocols = 0;
$o->permissions = 0;
}
$o->binkp = collect($request->post('binkp'))->sum();
$o->options = collect($request->post('options'))->sum();
$o->system_id = $request->post('system_id');
$o->save();
}
return view('setup')
->with('o',$o);
} }
} }

View File

@ -193,10 +193,14 @@ class SystemController extends Controller
'sysop' => 'required|min:3', 'sysop' => 'required|min:3',
'address' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i', 'address' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i',
'port' => 'nullable|digits_between:2,5', 'port' => 'nullable|digits_between:2,5',
'method' => 'nullable|numeric',
'mailer_type' => 'nullable|numeric',
'mailer_address' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i',
'mailer_port' => 'nullable|digits_between:2,5',
'active' => 'required|boolean', 'active' => 'required|boolean',
]); ]);
foreach (['name','location','sysop','address','port','active','method','notes'] as $key) foreach (['name','location','sysop','address','port','active','method','notes','mailer_type','mailer_address','mailer_port'] as $key)
$o->{$key} = $request->post($key); $o->{$key} = $request->post($key);
$o->save(); $o->save();

View File

@ -2,12 +2,16 @@
namespace App\Models; namespace App\Models;
use Exception;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use App\Http\Controllers\DomainController; use App\Http\Controllers\DomainController;
use App\Traits\ScopeActive;
class Address extends Model class Address extends Model
{ {
use ScopeActive;
/* RELATIONS */ /* RELATIONS */
public function system() public function system()
@ -49,4 +53,65 @@ class Address extends Model
return '?'; return '?';
} }
} }
/* GENERAL METHODS */
/**
* Find a record in the DB for a node string, eg: 10:1/1.0
*
* @param string $ftn
* @return Node|null
* @throws Exception
*/
public static function findFTN(string $ftn): ?self
{
$matches = [];
// http://ftsc.org/docs/frl-1028.002
if (! preg_match('#^([0-9]+):([0-9]+)/([0-9]+)(.([0-9]+))?(@([a-z0-9\-_~]{0,8}))?$#',strtolower($ftn),$matches))
throw new Exception('Invalid FTN: '.$ftn);
// Check our numbers are correct.
foreach ([1,2,3] as $i) {
if (! $matches[$i] || ($matches[$i] > DomainController::NUMBER_MAX))
throw new Exception('Invalid FTN: '.$ftn);
}
if (isset($matches[5]) AND $matches[5] > DomainController::NUMBER_MAX)
throw new Exception('Invalid FTN: '.$ftn);
$o = (new self)->active()
->select('addresses.*')
->where('zones.zone_id',$matches[1])
->where('host_id',$matches[2])
->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)
->where('node_id',$matches[3])
->where('point_id',(isset($matches[5]) AND $matches[5]) ? $matches[5] : 0)
->when(isset($matches[7]),function($query) use ($matches) {
$query->where('domains.name',$matches[7]);
})
->when((! isset($matches[7]) OR ! $matches[7]),function($query) {
$query->where('domains.default',TRUE);
})
->single();
return ($o && $o->system->active) ? $o : NULL;
}
public function session(string $type): ?string
{
static $session = NULL;
if (is_null($session)) {
$session = (new AddressZone)
->where('zone_id',$this->zone_id)
->where('system_id',$this->system_id)
->single();
}
return $session ? $session->{$type} : NULL;
}
} }

View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AddressZone extends Model
{
protected $table = 'address_zone';
}

View File

@ -16,22 +16,60 @@ use Illuminate\Support\Facades\File;
*/ */
class Setup extends Model class Setup extends Model
{ {
public const S_DOMAIN = 1<<1; // Users can create Domains
public const S_SYSTEM = 1<<2; // Users can create Systems
public const BINKP_OPT_CHT = 1<<1; /* CHAT mode - not implemented */
public const BINKP_OPT_CR = 1<<2; /* Crypt mode - not implemented */
public const BINKP_OPT_MB = 1<<3; /* Multi-Batch mode */
public const BINKP_OPT_MD = 1<<4; /* CRAM-MD5 mode */
public const BINKP_OPT_ND = 1<<5; /* http://ftsc.org/docs/fsp-1027.001: No-dupes mode */
public const BINKP_OPT_NDA = 1<<6; /* http://ftsc.org/docs/fsp-1027.001: Asymmetric ND mode */
public const BINKP_OPT_NR = 1<<7; /* http://ftsc.org/docs/fsp-1027.001: Non-Reliable mode */
public const BINKP_OPT_MPWD = 1<<8; /* Multi-Password mode - not implemented */
public const BINKP_PORT = 24554;
public const BINKP_BIND = '0.0.0.0';
public const EMSI_PORT = 60179;
public const EMSI_BIND = self::BINKP_BIND;
public const O_BINKP = 1<<1; /* Listen for BINKD connections */
public const O_EMSI = 1<<2; /* Listen for EMSI connections */
public const O_HIDEAKA = 1<<3; /* Hide AKAs to different Zones */
// Our non model attributes and values // Our non model attributes and values
private array $internal = []; private array $internal = [];
/* RELATIONS */
public function system()
{
return $this->belongsTo(System::class);
}
/* ATTRIBUTES */
public function getLocationAttribute()
{
return $this->system->location;
}
public function getSysopAttribute()
{
return $this->system->sysop;
}
public function getSystemNameAttribute()
{
return $this->system->name;
}
/* METHODS */
public function __construct(array $attributes = []) public function __construct(array $attributes = [])
{ {
parent::__construct($attributes); parent::__construct($attributes);
// @todo This should go in a config file in the config dir
$this->opt_cht = 0; /* CHAT mode - not implemented*/
$this->opt_cr = 0; /* Crypt mode - not implemented*/
$this->opt_mb = 1; /* Multi-Batch mode */
$this->opt_md = 0; /* CRAM-MD5 mode */
$this->opt_nd = 0; /* http://ftsc.org/docs/fsp-1027.001: No-dupes mode */
$this->opt_nda = 1; /* http://ftsc.org/docs/fsp-1027.001: Asymmetric ND mode */
$this->opt_mpwd = 0; /* Multi-Password mode - not implemented */
$this->opt_nr = 1; /* http://ftsc.org/docs/fsp-1027.001: Non-Reliable mode */
$this->binkp_options = ['m','d','r','b']; $this->binkp_options = ['m','d','r','b'];
/* EMSI SETTINGS */ /* EMSI SETTINGS */
@ -43,32 +81,6 @@ class Setup extends Model
$this->inbound = '/tmp'; $this->inbound = '/tmp';
} }
/* RELATIONS */
public function nodes()
{
return $this->belongsToMany(Node::class);
}
/* ATTRIBUTES */
public function getLocationAttribute()
{
return $this->nodes->first()->location;
}
public function getSysopAttribute()
{
return $this->nodes->first()->sysop;
}
public function getSystemNameAttribute()
{
return $this->nodes->first()->system;
}
/* METHODS */
/** /**
* @throws Exception * @throws Exception
*/ */
@ -78,15 +90,15 @@ class Setup extends Model
case 'binkp_options': case 'binkp_options':
case 'ignore_nrq': case 'ignore_nrq':
case 'inbound': case 'inbound':
case 'opt_nr': case 'opt_nr': // @todo - this keys are now in #binkp as bits
case 'opt_nd': case 'opt_nd':
case 'opt_nda': case 'opt_nda':
case 'opt_md': case 'opt_md':
case 'opt_cr': case 'opt_cr':
case 'opt_mb': case 'opt_mb':
case 'opt_cht': case 'opt_cht':
case 'opt_mpwd':
case 'do_prevent': case 'do_prevent':
case 'options':
return $this->internal[$key] ?? FALSE; return $this->internal[$key] ?? FALSE;
case 'version': case 'version':
@ -113,13 +125,43 @@ class Setup extends Model
case 'opt_cr': case 'opt_cr':
case 'opt_mb': case 'opt_mb':
case 'opt_cht': case 'opt_cht':
case 'opt_mpwd':
case 'do_prevent': case 'do_prevent':
case 'options':
$this->internal[$key] = $value; $this->internal[$key] = $value;
break; break;
default: default:
parent::__get($key); parent::__set($key,$value);
} }
} }
public function binkpOptionClear(int $key): void
{
$this->binkp &= ~$key;
}
public function binkpOptionGet(int $key): int
{
return ($this->binkp & $key);
}
public function binkpOptionSet(int $key): void
{
$this->binkp |= $key;
}
public function optionClear(int $key): void
{
$this->options &= ~$key;
}
public function optionGet(int $key): int
{
return ($this->options & $key);
}
public function optionSet(int $key): void
{
$this->options |= $key;
}
} }

View File

@ -11,8 +11,6 @@ class System extends Model
{ {
use HasFactory,ScopeActive; use HasFactory,ScopeActive;
/* SCOPES */
/* RELATIONS */ /* RELATIONS */
public function addresses() public function addresses()
@ -23,6 +21,4 @@ class System extends Model
->orderBy('node_id') ->orderBy('node_id')
->orderBy('point_id'); ->orderBy('point_id');
} }
/* CASTS */
} }

View File

@ -14,6 +14,7 @@ return [
*/ */
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'Laravel'),
'id' => env('APP_SETUP_ID', 1),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddSystemToSetups extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('setups', function (Blueprint $table) {
$table->integer('options');
$table->integer('system_id');
$table->foreign('system_id')->references('id')->on('systems');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('setups', function (Blueprint $table) {
$table->dropForeign(['system_id']);
$table->dropColumn(['system_id','options']);
});
}
}

View File

@ -0,0 +1,86 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddMailerToSystem extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('systems', function (Blueprint $table) {
$table->string('mailer_address')->nullable();
$table->integer('mailer_port')->nullable();
$table->integer('mailer_type')->nullable();
$table->string('zt_id',10)->nullable()->unique();
$table->unique(['mailer_type','mailer_address','mailer_port']);
});
Schema::table('zones', function (Blueprint $table) {
$table->dropColumn(['ztid']);
});
Schema::table('zones', function (Blueprint $table) {
$table->string('zt_id',16)->unique()->nullable();
$table->ipAddress('zt_ipv4')->nullable();
$table->integer('zt_ipv4_mask')->nullable();
$table->unique(['zt_ipv4','zt_ipv4_mask']);
$table->ipAddress('zt_ipv6')->nullable();
$table->integer('zt_ipv6_mask')->nullable();
$table->unique(['zt_ipv6','zt_ipv6_mask']);
});
Schema::create('address_zone', function (Blueprint $table) {
$table->string('sespass')->nullable();
$table->string('pktpass',8)->nullable();
$table->string('ticpass')->nullable();
$table->string('fixpass')->nullable();
$table->ipAddress('zt_ipv4')->nullable();
$table->ipAddress('zt_ipv6')->nullable();
$table->integer('system_id');
$table->foreign('system_id')->references('id')->on('systems');
$table->integer('zone_id');
$table->foreign('zone_id')->references('id')->on('zones');
$table->unique(['system_id','zone_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('address_zone');
Schema::table('zones', function (Blueprint $table) {
$table->dropUnique(['zt_id']);
$table->dropColumn(['zt_id']);
$table->dropUnique(['zt_ipv4','zt_ipv4_mask']);
$table->dropUnique(['zt_ipv6','zt_ipv6_mask']);
$table->dropColumn(['zt_ipv4','zt_ipv4_mask']);
$table->dropColumn(['zt_ipv6','zt_ipv6_mask']);
});
Schema::table('zones', function (Blueprint $table) {
$table->string('ztid')->nullable();
});
Schema::table('systems', function (Blueprint $table) {
$table->dropUnique(['zt_id']);
$table->dropUnique(['mailer_type','mailer_address','mailer_port']);
$table->dropColumn(['mailer_address','mailer_port','mailer_type','zt_id']);
});
}
}

19
public/css/fixes.css vendored Normal file
View File

@ -0,0 +1,19 @@
/* Fixes for select 2 and our theme */
/*
.select2 .select2-container .select2-container--classic { width: 80% !important;}
*/
.select2-container .select2-selection--single { border-radius: 0 4px 4px 0; height: inherit;}
.select2-container .select2-selection--single .select2-selection__rendered { line-height: 36px; }
.select2-container--classic .select2-selection--single .select2-selection__arrow { line-height: 36px; }
.select2-results { color: #000; }
/*
.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable,
*/
.select2-container--default .select2-results__option--selected { background-color: #024cc4; color: #eeeeee;}
/* Bootstrap 5 fixes */
/* select import, round the right side */
.input-group .form-select {
border-top-right-radius: 4px !important;
border-bottom-right-radius: 4px !important;
}

View File

@ -390,6 +390,11 @@ form div.row {
--bs-gutter-x: 0; --bs-gutter-x: 0;
} }
.form-check.form-switch .form-check-input {
top: -.15em;
position: relative;
}
.greyframe { .greyframe {
position:relative; position:relative;
background-color: #192124; background-color: #192124;
@ -439,6 +444,11 @@ label.form-label {
margin-bottom: 1px; margin-bottom: 1px;
} }
label.list-group-item {
background-color: inherit;
color: inherit;
}
p { p {
margin:0 0 1em; margin:0 0 1em;
padding:0; padding:0;

View File

@ -40,7 +40,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>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>
<li>Network Applications <sup>To be implemented</sup></li> <li>Network Applications <sup>To be implemented</sup></li>

View File

@ -4,5 +4,5 @@
@if(file_exists('js/custom-auth.js')) @if(file_exists('js/custom-auth.js'))
<!-- Any Custom JS --> <!-- Any Custom JS -->
<script src="{{ asset('js/custom-auth.js') }}"></script> <script type="text/javascript" src="{{ asset('js/custom-auth.js') }}"></script>
@endif @endif

View File

@ -111,10 +111,10 @@
@section('page-scripts') @section('page-scripts')
@can('admin',$o) @can('admin',$o)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"> <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
<script> <script type="text/javascript">
var simplemde = new SimpleMDE({ element: $("#homepage")[0] }); var simplemde = new SimpleMDE({ element: $("#homepage")[0] });
</script> </script>
@endcan @endcan

View File

@ -4,7 +4,9 @@
@endsection @endsection
@section('content') @section('content')
<h1>{{ $o->name }} <small class="push-right">Last Update: {{ $o->updated_at }}</small></h1> <h1>{{ $o->name }} <small class="float-end pt-4">Last Update: {{ $o->updated_at->format('Y-m-d H:i') }}</small></h1>
<p class="float-end"><small>Expand each heading for information about this FTN network</small></p>
<div class="accordion" id="accordion_homepage"> <div class="accordion" id="accordion_homepage">
<!-- About --> <!-- About -->

View File

@ -9,10 +9,17 @@
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<!-- CSS only --> <!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous"> <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"> <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
<link href="{{ asset('oldschool/css/main.css') }}" rel="stylesheet" media="screen" type="text/css"> <link type="text/css" rel="stylesheet" href="{{ asset('oldschool/css/main.css') }}" media="screen">
<link rel="icon" type="image/png" href="{{ asset('favicon.ico') }}"> @yield('page-css')
@if(file_exists('css/fixes.css'))
<!-- CSS Fixes -->
<link rel="stylesheet" href="{{ asset('css/fixes.css') }}">
@endif
<link type="image/png" rel="icon" href="{{ asset('favicon.ico') }}">
</head> </head>

View File

@ -3,9 +3,9 @@
<script type="text/javascript" src="{{ asset('//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js') }}" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script> <script type="text/javascript" src="{{ asset('//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js') }}" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
<!-- JavaScript Bundle with Popper --> <!-- JavaScript Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
<script> <script type="text/javascript">
// Example starter JavaScript for disabling form submissions if there are invalid fields // Example starter JavaScript for disabling form submissions if there are invalid fields
(function () { (function () {
'use strict' 'use strict'

View File

@ -18,7 +18,7 @@
@endauth @endauth
<dl> <dl>
<dt>Expore Networks</dt> <dt>Expore Networks</dt>
@foreach (\App\Models\Domain::active()->public()->get() as $o) @foreach (\App\Models\Domain::active()->public()->orderBy('name')->get() as $o)
<dd><a href="{{ url('network',['id'=>$o->id]) }}" title="{{ $o->description }}">{{ $o->name }}</a></dd> <dd><a href="{{ url('network',['id'=>$o->id]) }}" title="{{ $o->description }}">{{ $o->name }}</a></dd>
@endforeach @endforeach
</dl> </dl>

View File

@ -1,8 +1,193 @@
@php
use App\Models\Setup;
@endphp
@extends('layouts.app') @extends('layouts.app')
@section('htmlheader_title') @section('htmlheader_title')
Setup Setup
@endsection @endsection
@section('content') @section('content')
<div class="row">
<div class="col-12">
<h2>Site Setup</h2> <h2>Site Setup</h2>
</div>
</div>
<div class="row pt-0">
<div class="col-12">
<div class="greyframe titledbox shadow0xb0">
<h2 class="cap">@if($o->exists) Update @else Initial @endif Setup</h2>
<form class="row g-0 needs-validation" method="post" novalidate>
@csrf
<div class="row">
<div class="col-4">
<label for="system_id" class="form-label">System</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-tag-fill"></i></span>
<select style="width: 80%;" class="form-select @error('system_id') is-invalid @enderror" id="system_id" name="system_id" required @cannot('admin',$o)disabled @endcannot>
<option value="">&nbsp;</option>
@foreach (\App\Models\System::active()->orderBy('name')->cursor() as $oo)
<option value="{{ $oo->id }}" @if(old('system_id',$o->system_id)==$oo->id)selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('system_id')
{{ $message }}
@else
A system is required.
@enderror
</span>
<span class="input-helper">Add a <a href="{{ url('ftn/system/addedit') }}">NEW System</a></span>
</div>
</div>
<div class="col-4 ms-auto">
@if ($o->exists)
<table class="table monotable">
<thead>
<tr><th colspan="2">System Addresses</th></tr>
</thead>
<tbody>
@foreach ($o->system->addresses->groupBy('zone_id') as $zones)
<tr>
<th>{{ $zones->first()->zone->domain->name }}</th>
<th class="text-end">{!! join('<br>',$zones->pluck('ftn')->toArray()) !!}</th>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
</div>
<div class="row">
<div class="col-6">
<h3>Site Permissions</h3>
<!-- @todo
* Inbound Working Dir
-->
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="hideaka" name="options[hideaka]" value="{{ Setup::O_HIDEAKA }}" @if(old('options.hideaka',$o->binkpOptionGet(Setup::O_HIDEAKA))) checked @endif disabled>
<label class="form-check-label" for="hideaka">Hide AKA to different Domains <sup>not implemented</sup></label>
</div>
</div>
<div class="col-6">
<h3>ZeroTier API</h3>
<!-- @todo
* Host/Port/Key
-->
</div>
</div>
<div class="row">
<div class="col-12">
<h4>Protocol Configuration</h4>
</div>
<div class="row">
<div class="col-6">
<h3>BINKP Settings</h3>
<p>Bink has been configured to listen on <strong>{{ Setup::BINKP_BIND }}</strong>:<strong>{{ Setup::BINKP_PORT }}</strong></p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="startbinkd" name="options[binkd]" value="{{ Setup::O_BINKP }}" @if(old('options.binkd',$o->optionGet(Setup::O_BINKP))) checked @endif>
<label class="form-check-label" for="startbinkd">Listen for BINKP connections</label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_cht" name="binkp[cht]" value="{{ Setup::BINKP_OPT_CHT }}" @if(old('binkp.cht',$o->binkpOptionGet(Setup::BINKP_OPT_CHT))) checked @endif disabled>
<label class="form-check-label" for="opt_cht">Chat Mode <sup>not implemented</sup></label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_md" name="binkp[md]" value="{{ Setup::BINKP_OPT_MD }}" @if(old('binkp.md',$o->binkpOptionGet(Setup::BINKP_OPT_MD))) checked @endif>
<label class="form-check-label" for="opt_md">CRAM-MD5 Mode</label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_cr" name="binkp[cr]" value="{{ Setup::BINKP_OPT_CR }}" @if(old('binkp.cr',$o->binkpOptionGet(Setup::BINKP_OPT_CR))) checked @endif disabled>
<label class="form-check-label" for="opt_cr">Crypt mode <sup>not implemented</sup></label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_mb" name="binkp[mb]" value="{{ Setup::BINKP_OPT_MB }}" @if(old('binkp.mb',$o->binkpOptionGet(Setup::BINKP_OPT_MB))) checked @endif>
<label class="form-check-label" for="opt_mb">Multi-Batch mode<sup>*</sup></label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_mpwd" name="binkp[mpwd]" value="{{ Setup::BINKP_OPT_MPWD }}" @if(old('binkp.mpwd',$o->binkpOptionGet(Setup::BINKP_OPT_MPWD))) checked @endif disabled>
<label class="form-check-label" for="opt_mpwd">Multi-Password Mode <sup>not implemented</sup></label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_nd" name="binkp[nd]" value="{{ Setup::BINKP_OPT_ND }}" @if(old('binkp.nd',$o->binkpOptionGet(Setup::BINKP_OPT_ND))) checked @endif>
<label class="form-check-label" for="opt_nd">No Dupes Mode</label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_nda" name="binkp[nda]" value="{{ Setup::BINKP_OPT_NDA }}" @if(old('binkp.nda',$o->binkpOptionGet(Setup::BINKP_OPT_NDA))) checked @endif>
<label class="form-check-label" for="opt_nda">No Dupes Mode - Asymmetric<sup>*</sup></label>
</div>
<div class="mt-1 form-check form-switch">
<input class="form-check-input" type="checkbox" id="opt_nr" name="binkp[nr]" value="{{ Setup::BINKP_OPT_NR }}" @if(old('binkp.nr',$o->binkpOptionGet(Setup::BINKP_OPT_NR))) checked @endif>
<label class="form-check-label" for="opt_nr">Non-Reliable Mode<sup>*</sup></label>
</div>
<p class="pt-3"><sup>*</sup> Recommended Defaults</p>
<table class="table monotable">
{{--
$this->binkp_options = ['m','d','r','b'];
--}}
</table>
</div>
<div class="col-6">
<h3>EMSI Settings</h3>
<p>Bink has been configured to listen on <strong>{{ Setup::EMSI_BIND }}</strong>:<strong>{{ Setup::EMSI_PORT }}</strong></p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="startemsi" name="options[emsi]" value="{{ Setup::O_EMSI }}" @if(old('options.emsi',$o->optionGet(Setup::O_EMSI))) checked @endif>
<label class="form-check-label" for="startemsi">Listen for EMSI connections</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<a href="{{ url('ftn/domain') }}" class="btn btn-danger">Cancel</a>
@if($errors->count())
<span class="pl-5 btn btn-sm btn-danger" role="alert">
There were errors with the submission.
@dump($errors)
</span>
@endif
@can('admin',$o)
<button type="submit" name="submit" class="btn btn-success mr-0 float-end">@if ($o->exists)Save @else Add @endif</button>
@endcan
</div>
</div>
</form>
</div>
</div>
</div>
@endsection @endsection
@section('page-css')
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">
@append
@section('page-scripts')
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#system_id').select2();
});
</script>
@append

View File

@ -1,3 +1,7 @@
@php
use App\Models\Setup;
@endphp
@extends('layouts.app') @extends('layouts.app')
@section('htmlheader_title') @section('htmlheader_title')
@ -39,7 +43,7 @@
</div> </div>
</div> </div>
<div class="col-4"> <div class="col-2">
<label for="active" class="form-label">Active</label> <label for="active" class="form-label">Active</label>
<div class="input-group"> <div class="input-group">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
@ -51,6 +55,10 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-2">
ZeroTier ID
</div>
</div> </div>
<div class="row"> <div class="row">
@ -86,6 +94,47 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12">
<h4>Mailer Details</h4>
<div class="pt-0 row">
<div class="col-4">
<label for="method" class="form-label">Connection Method</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-wifi"></i></span>
<select class="form-select @error('method') is-invalid @enderror" id="mailer_type" name="mailer_type" @cannot('admin',$o)disabled @endcannot>
<option></option>
<option value="{{ Setup::O_BINKP }}" @if(old('mailer_type',$o->mailer_type) == Setup::O_BINKP)selected @endif)}}>BINKP</option>
<option value="{{ Setup::O_EMSI }}" @if(old('mailer_type',$o->mailer_type) == Setup::O_EMSI)selected @endif)}}>EMSI</option>
</select>
</div>
</div>
<div class="col-8">
<label for="address" class="form-label">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('mailer_address') is-invalid @enderror" id="mailer_address" placeholder="FQDN" name="mailer_address" value="{{ old('mailer_address',$o->mailer_address) }}" @cannot('admin',$o)disabled @endcannot>
<input type="text" class="form-control @error('mailer_port') is-invalid @enderror" id="mailer_port" placeholder="Port" name="mailer_port" value="{{ old('mailer_port',$o->mailer_port) }}" @cannot('admin',$o)disabled @endcannot>
<span class="invalid-feedback" role="alert">
@error('mailer_address')
{{ $message }}
@enderror
@error('mailer_port')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<h4>BBS Details</h4>
<div class="pt-0 row">
<div class="col-4"> <div class="col-4">
<label for="method" class="form-label">Connection Method</label> <label for="method" class="form-label">Connection Method</label>
<div class="input-group"> <div class="input-group">
@ -116,6 +165,8 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
@ -408,7 +459,6 @@
@if($errors->count()) @if($errors->count())
<span class="pl-5 btn btn-sm btn-danger" role="alert"> <span class="pl-5 btn btn-sm btn-danger" role="alert">
There were errors with the submission. There were errors with the submission.
@dump($errors)
</span> </span>
@endif @endif
</span> </span>
@ -422,7 +472,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</form> </form>
@else @else
This system does not currently belong to any Fido networks. You'll need to ask an admin to assign addresses. This system does not currently belong to any Fido networks. You'll need to ask an admin to assign addresses.
@ -489,6 +538,26 @@
$('#region_id').on('change',function() { $('#region_id').on('change',function() {
switch(this.value) { switch(this.value) {
case '':
if (! $('#region-address').hasClass('d-none'))
$('#region-address').addClass('d-none');
if (! $('#host-select').hasClass('d-none'))
$('#host-select').addClass('d-none');
if (! $('#host-address').hasClass('d-none'))
$('#host-address').addClass('d-none');
if (! $('#hub-select').hasClass('d-none'))
$('#hub-select').addClass('d-none');
if (! $('#hub-checkbox').hasClass('d-none'))
$('#hub-checkbox').addClass('d-none');
if (! $('#node-address').hasClass('d-none'))
$('#node-address').addClass('d-none');
break;
case 'new': case 'new':
if (! $('#host-select').hasClass('d-none')) if (! $('#host-select').hasClass('d-none'))
$('#host-select').addClass('d-none'); $('#host-select').addClass('d-none');
@ -500,7 +569,7 @@
$('#region-address').removeClass('d-none'); $('#region-address').removeClass('d-none');
if (! $('#hub-select').hasClass('d-none')) if (! $('#hub-select').hasClass('d-none'))
$('#hub-select').addClass('d-none') $('#hub-select').addClass('d-none');
if (! $('#hub-checkbox').hasClass('d-none')) if (! $('#hub-checkbox').hasClass('d-none'))
$('#hub-checkbox').addClass('d-none'); $('#hub-checkbox').addClass('d-none');
@ -521,7 +590,7 @@
$('#region-address').addClass('d-none'); $('#region-address').addClass('d-none');
if (! $('#hub-select').hasClass('d-none')) if (! $('#hub-select').hasClass('d-none'))
$('#hub-select').addClass('d-none') $('#hub-select').addClass('d-none');
if (! $('#hub-checkbox').hasClass('d-none')) if (! $('#hub-checkbox').hasClass('d-none'))
$('#hub-checkbox').addClass('d-none'); $('#hub-checkbox').addClass('d-none');
@ -558,12 +627,26 @@
$('#host_id').on('change',function() { $('#host_id').on('change',function() {
switch(this.value) { switch(this.value) {
case '':
if (! $('#host-address').hasClass('d-none'))
$('#host-address').addClass('d-none');
if (! $('#hub-select').hasClass('d-none'))
$('#hub-select').addClass('d-none');
if (! $('#hub-checkbox').hasClass('d-none'))
$('#hub-checkbox').addClass('d-none');
if (! $('#node-address').hasClass('d-none'))
$('#node-address').addClass('d-none');
break;
case 'new': case 'new':
if ($('#host-address').hasClass('d-none')) if ($('#host-address').hasClass('d-none'))
$('#host-address').removeClass('d-none'); $('#host-address').removeClass('d-none');
if (! $('#hub-select').hasClass('d-none')) if (! $('#hub-select').hasClass('d-none'))
$('#hub-select').addClass('d-none') $('#hub-select').addClass('d-none');
if (! $('#hub-checkbox').hasClass('d-none')) if (! $('#hub-checkbox').hasClass('d-none'))
$('#hub-checkbox').addClass('d-none'); $('#hub-checkbox').addClass('d-none');

View File

@ -12,7 +12,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-9"> <div class="col-10">
<p>This system is aware of the following systems:</p> <p>This system is aware of the following systems:</p>
@if (\App\Models\System::count() == 0) @if (\App\Models\System::count() == 0)
@ -30,6 +30,7 @@
<th>Sysop</th> <th>Sysop</th>
<th>Location</th> <th>Location</th>
<th>Active</th> <th>Active</th>
<th>ZeroTier ID</th>
<th>Connect</th> <th>Connect</th>
</tr> </tr>
</thead> </thead>
@ -37,7 +38,7 @@
<tbody> <tbody>
@can('admin',(new \App\Models\System)) @can('admin',(new \App\Models\System))
<tr> <tr>
<td colspan="6"><a href="{{ url('ftn/system/addedit') }}">Add New System</a></td> <td colspan="7"><a href="{{ url('ftn/system/addedit') }}">Add New System</a></td>
</tr> </tr>
@endcan @endcan
@foreach (\App\Models\System::orderBy('name')->cursor() as $oo) @foreach (\App\Models\System::orderBy('name')->cursor() as $oo)
@ -47,6 +48,7 @@
<td>{{ $oo->sysop }}</td> <td>{{ $oo->sysop }}</td>
<td>{{ $oo->location }}</td> <td>{{ $oo->location }}</td>
<td>{{ $oo->active ? 'YES' : 'NO' }}</td> <td>{{ $oo->active ? 'YES' : 'NO' }}</td>
<td>-</td>
<td> <td>
@switch($oo->method) @switch($oo->method)
@case(23)<a href="telnet://{{ $oo->address }}:{{ $oo->port }}">Telnet</a>@break @case(23)<a href="telnet://{{ $oo->address }}:{{ $oo->port }}">Telnet</a>@break

View File

@ -87,7 +87,7 @@
<div class="row"> <div class="row">
<div class="col-2"> <div class="col-2">
<label for="ztid" class="form-label">ZeroTier ID</label> <label for="ztid" class="form-label">ZeroTier Network ID</label>
<div class="input-group has-validation"> <div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-shield-lock-fill"></i></span> <span class="input-group-text"><i class="bi bi-shield-lock-fill"></i></span>
<input type="text" class="form-control @error('ztid') is-invalid @enderror" id="ztid" placeholder="ZeroTier" name="ztid" value="{{ old('ztid',$o->ztid) }}" @cannot('admin',$o)disabled @endcannot> <input type="text" class="form-control @error('ztid') is-invalid @enderror" id="ztid" placeholder="ZeroTier" name="ztid" value="{{ old('ztid',$o->ztid) }}" @cannot('admin',$o)disabled @endcannot>

View File

@ -55,15 +55,15 @@
@section('page-scripts') @section('page-scripts')
{{-- {{--
<link href="https://cdn.datatables.net/1.10.25/css/jquery.dataTables.min.css" rel="stylesheet" media="screen" type="text/css"> <link type="text/css" rel="stylesheet" href="https://cdn.datatables.net/1.10.25/css/jquery.dataTables.min.css" media="screen">
<link href="https://cdn.datatables.net/rowgroup/1.1.2/css/rowGroup.dataTables.min.css" rel="stylesheet" media="screen" type="text/css"> <link type="text/css" rel="stylesheet" href="https://cdn.datatables.net/rowgroup/1.1.2/css/rowGroup.dataTables.min.css" media="screen">
--}} --}}
<link href="https://cdn.datatables.net/1.10.25/css/dataTables.bootstrap5.min.css" rel="stylesheet" media="screen" type="text/css"> <link type="text/css" rel="stylesheet" href="https://cdn.datatables.net/1.10.25/css/dataTables.bootstrap5.min.css" media="screen" >
<link href="{{ asset('plugin/dataTables/dataTables.bootstrap5.css') }}" rel="stylesheet" media="screen" type="text/css"> <link type="text/css" rel="stylesheet" href="{{ asset('plugin/dataTables/dataTables.bootstrap5.css') }}" media="screen">
<script src="https://cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/rowgroup/1.1.2/js/dataTables.rowGroup.min.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/rowgroup/1.1.2/js/dataTables.rowGroup.min.js"></script>
<script src="https://cdn.datatables.net/1.10.25/js/dataTables.bootstrap5.min.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/1.10.25/js/dataTables.bootstrap5.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {

View File

@ -54,7 +54,7 @@ Route::get('network/{o}',[HomeController::class,'network']);
Route::get('permissions',[HomeController::class,'permissions']); Route::get('permissions',[HomeController::class,'permissions']);
Route::middleware(['auth','can:admin'])->group(function () { Route::middleware(['auth','can:admin'])->group(function () {
Route::get('setup',[HomeController::class,'setup']); Route::match(['get','post'],'setup',[HomeController::class,'setup']);
Route::get('user/list',[UserController::class,'home']); Route::get('user/list',[UserController::class,'home']);
Route::match(['get','post'],'user/addedit/{o?}',[UserController::class,'add_edit']) Route::match(['get','post'],'user/addedit/{o?}',[UserController::class,'add_edit'])