Internal enhancements to system registration and editing

This commit is contained in:
Deon George 2022-01-02 01:52:21 +11:00
parent fa2ac9a656
commit afaa7d8bc7
12 changed files with 235 additions and 87 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ npm-debug.log
yarn-error.log
.env
.phpunit.result.cache
/config/ssl/

View File

@ -7,8 +7,10 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ViewErrorBag;
use App\Http\Requests\SystemRegister;
use App\Models\{Address,Echoarea,System,SystemZone,Zone};
use App\Rules\{FidoInteger,TwoByteInteger};
@ -253,26 +255,9 @@ class SystemController extends Controller
/**
* Add or edit a node
*/
public function add_edit(Request $request,System $o)
public function add_edit(SystemRegister $request,System $o)
{
if ($request->post()) {
$this->authorize('admin',$o);
$request->validate([
'name' => 'required|min:3',
'location' => 'required|min:3',
'sysop' => 'required|min:3',
'phone' => 'nullable|regex:/^([0-9-]+)$/',
'address' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i',
'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',
'zt_id' => 'nullable|size:10|regex:/^([A-Fa-f0-9]){10}$/|unique:systems,zt_id,'.($o->exists ? $o->id : 0),
]);
foreach (['name','location','sysop','phone','address','port','active','method','notes','mailer_type','mailer_address','mailer_port','zt_id'] as $key)
$o->{$key} = $request->post($key);
@ -283,8 +268,11 @@ class SystemController extends Controller
$o->load(['addresses.zone.domain']);
return view('system.addedit')
->with('o',$o);
return Gate::check('update',$o)
? view('system.addedit')
->with('action',$o->exists ? 'update' : 'create')
->with('o',$o)
: redirect()->to('user/system/register');
}
/**
@ -448,7 +436,7 @@ class SystemController extends Controller
/**
* register system
*/
public function system_register(Request $request)
public function system_register(SystemRegister $request)
{
$o = System::findOrNew($request->system_id);
@ -465,12 +453,17 @@ class SystemController extends Controller
if ($request->post('submit')) {
Auth::user()->systems()->save($o);
// @todo if the system already exists and part of one of our nextworks, we'll need to send the registration email to confirm the address.
// @todo if the system already exists and part of one of our networks, we'll need to send the registration email to confirm the address.
// @todo mark the system (or addresses) as "pending" at this stage until it is confirmed
return redirect()->to(url('ftn/system/addedit',$o->id));
}
// Re-flash our previously input data
if ($request->old)
session()->flashInput($request->old);
return view('system.widget.form-system')
->with('action',$request->action)
->with('o',$o)
->with('errors',new ViewErrorBag);
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use App\Models\System;
class SystemRegister extends FormRequest
{
private System $so;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(Request $request)
{
$this->so = System::findOrNew($request->system_id);
return Gate::allows($this->so->exists ? 'update' : 'create',$this->so);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(Request $request)
{
if (! $request->isMethod('post'))
return [];
if ((! $this->so->exists) && ($request->action == 'create')) {
return [
'name' => 'required|min:3',
];
}
return array_filter(array_merge(
[
'name' => 'required|min:3',
],
($this->so->exists || ($request->action != 'create')) ? [
'location' => 'required|min:3',
'sysop' => 'required|min:3',
'phone' => 'nullable|regex:/^([0-9-]+)$/',
'address' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i',
'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',
'zt_id' => 'nullable|size:10|regex:/^([A-Fa-f0-9]){10}$/|unique:systems,zt_id,'.($this->so->exists ? $this->so->id : 0),
] : [],
$this->so->exists ? ['active' => 'required|boolean'] : [],
));
}
}

View File

@ -4,17 +4,34 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\DomainController;
use App\Traits\ScopeActive;
use Illuminate\Support\Collection;
class System extends Model
{
use HasFactory,ScopeActive;
use HasFactory;
protected $dates = ['last_session'];
/* SCOPES */
/**
* Only query active records
*/
public function scopeActive($query)
{
$uo = Auth::user();
return $query
->when(! $uo->isAdmin(),function($query) use ($uo) {
return $query->whereIn('id',$uo->systems->pluck('id'))
->orWhere($this->getTable().'.active',TRUE);
})
->orderBy('name');
}
/* RELATIONS */
public function addresses()

View File

@ -10,6 +10,22 @@ class SystemPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can create the model.
*
* A user can create a system if it doesnt exist.
*
* @param User $user
* @param System $system
* @return bool
*/
public function create(User $user, System $system): bool
{
// Site Admins can always create
// If it doesnt exist, then a user can create it.
return ($user->isAdmin() || (! $system->exists));
}
/**
* Determine whether the user can update the model.
*
@ -17,9 +33,9 @@ class SystemPolicy
* If it has addresses, at least one of the addresses must have been validated.
* (The assumption is, if a system has multiple addresses, they would be valid, or an admin can remove them.)
*
* @param \App\Models\User $user
* @param \App\Models\System $system
* @return \Illuminate\Auth\Access\Response|bool
* @param User $user
* @param System $system
* @return bool
*/
public function update(User $user, System $system): bool
{

View File

@ -30,7 +30,7 @@ use App\Models\Setup;
<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)
@foreach (\App\Models\System::active()->cursor() as $oo)
<option value="{{ $oo->id }}" @if(old('system_id',$o->system_id)==$oo->id)selected @endif>{{ $oo->name }}</option>
@endforeach
</select>

View File

@ -1,9 +1,6 @@
@php
use App\Models\Setup;
@endphp
<form class="row g-0 needs-validation" method="post" novalidate>
@csrf
<input type="hidden" name="system_id" value="{{ $o->id }}">
<div class="row">
<div class="col-12">

View File

@ -13,10 +13,10 @@
<div class="row">
<div class="col-12">
<p>This system is aware of the following systems @can('admin',(new \App\Models\System))(you can <a href="{{ url('ftn/system/addedit') }}">add</a> more)@endcan:</p>
<p>This system is aware of the following systems @can('create',(new \App\Models\System))(you can <a href="{{ url('ftn/system/addedit') }}">add</a> more)@endcan:</p>
@if (\App\Models\System::count() == 0)
@can('admin',(new \App\Models\System))
@if (\App\Models\System::active()->count() == 0)
@can('create',(new \App\Models\System))
<p>There are no systems setup, to <a href="{{ url('ftn/system/addedit') }}">set up your first</a>.</p>
@else
<p class="pad">There are no systems - you need to ask an admin to create one for you.</p>
@ -37,10 +37,10 @@
</thead>
<tbody>
@foreach (\App\Models\System::active()->orderBy('name')->with(['addresses.zone.domain'])->get() as $oo)
@foreach (\App\Models\System::active()->with(['addresses.zone.domain'])->get() as $oo)
<tr>
<td><a href="{{ url('ftn/system/addedit',[$oo->id]) }}">{{ $oo->id }}</a></td>
<td>{{ $oo->name }}</td>
<td>{{ $oo->name }} @if(! $oo->active)<span class="float-end"><small>[i]</small></span>@endif</td>
<td>{{ $oo->sysop }}</td>
<td>{{ $oo->location }}</td>
<td>

View File

@ -29,7 +29,7 @@ Move Address
<span class="input-group-text"><i class="bi bi-display-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()->where('id','<>',$o->system_id)->orderBy('name')->cursor() as $oo)
@foreach (\App\Models\System::active()->where('id','<>',$o->system_id)->cursor() as $oo)
<option value="{{ $oo->id }}" @if(old('system_id')==$oo->id)selected @endif>{{ $oo->name }}</option>
@endforeach
</select>

View File

@ -5,10 +5,10 @@
<div class="row">
<!-- Name -->
<div class="col-4">
<label for="name" class="form-label">Name</label>
<label for="name" class="form-label">BBS Name</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-tag-fill"></i></span>
<input type="text" class="form-control @error('name') is-invalid @enderror" id="name" placeholder="Name" name="name" value="{{ old('name',$o->name) }}" required @cannot('update',$o)disabled @endcannot autofocus>
<span class="input-group-text"><i class="bi bi-pc"></i></span>
<input type="text" class="form-control @error('name') is-invalid @enderror" id="name" placeholder="Name" name="name" value="{{ old('name',$o->name) }}" required @cannot('update',$o)readonly @endcannot autofocus>
<span class="invalid-feedback" role="alert">
@error('name')
{{ $message }}
@ -22,7 +22,7 @@
<!-- Active -->
<div class="col-2">
@if($o->exists)
@can('admin',$o)
@can('update',$o)
<label for="active" class="form-label">Active</label>
<div class="input-group">
<div class="btn-group" role="group">
@ -42,7 +42,7 @@
<label for="zt_id" class="form-label">ZeroTier ID</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-shield-lock-fill"></i></span>
<input type="text" class="form-control @error('zt_id') is-invalid @enderror" id="zt_id" placeholder="ZeroTier" name="zt_id" value="{{ old('zt_id',$o->zt_id) }}" @cannot('update',$o)disabled @endcannot>
<input type="text" class="form-control @error('zt_id') is-invalid @enderror" id="zt_id" placeholder="ZeroTier" name="zt_id" value="{{ old('zt_id',$o->zt_id) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('zt_id')
{{ $message }}
@ -58,7 +58,7 @@
<label for="sysop" class="form-label">Sysop</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-globe"></i></span>
<input type="text" class="form-control @error('sysop') is-invalid @enderror" id="sysop" placeholder="Sysop" name="sysop" value="{{ old('sysop',$o->sysop) }}" required @cannot('admin',$o)disabled @endcannot autocomplete="name">
<input type="text" class="form-control @error('sysop') is-invalid @enderror" id="sysop" placeholder="Sysop" name="sysop" value="{{ old('sysop',$o->sysop) }}" required @cannot('admin',$o)readonly @endcannot autocomplete="name">
<span class="invalid-feedback" role="alert">
@error('sysop')
{{ $message }}
@ -74,7 +74,7 @@
<label for="location" class="form-label">Location</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-globe"></i></span>
<input type="text" class="form-control @error('location') is-invalid @enderror" id="location" placeholder="Location" name="location" value="{{ old('location',$o->location) }}" required @cannot('update',$o)disabled @endcannot>
<input type="text" class="form-control @error('location') is-invalid @enderror" id="location" placeholder="Location" name="location" value="{{ old('location',$o->location) }}" required @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('location')
{{ $message }}
@ -96,7 +96,7 @@
<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('update',$o)disabled @endcannot>
<select class="form-select @error('method') is-invalid @enderror" id="mailer_type" name="mailer_type" @cannot($action,$o)readonly @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>
@ -108,8 +108,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('update',$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('update',$o)disabled @endcannot>
<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($action,$o)readonly @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($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('mailer_address')
{{ $message }}
@ -127,7 +127,7 @@
<label for="phone" class="form-label">Phone</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-telephone-fill"></i></span>
<input type="text" class="form-control @error('phone') is-invalid @enderror" id="phone" placeholder="Phone" name="phone" value="{{ old('phone',$o->phone) }}" @cannot('update',$o)disabled @endcannot>
<input type="text" class="form-control @error('phone') is-invalid @enderror" id="phone" placeholder="Phone" name="phone" value="{{ old('phone',$o->phone) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('phone')
{{ $message }}
@ -149,7 +149,7 @@
<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="method" name="method" @cannot('update',$o)disabled @endcannot>
<select class="form-select @error('method') is-invalid @enderror" id="method" name="method" @cannot($action,$o)readonly @endcannot>
<option></option>
<option value="23" @if(old('method',$o->method) == 23)selected @endif>Telnet</option>
<option value="22" @if(old('method',$o->method) == 22)selected @endif>SSH</option>
@ -162,8 +162,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('address') is-invalid @enderror" id="address" placeholder="FQDN" name="address" value="{{ old('address',$o->address) }}" @cannot('update',$o)disabled @endcannot>
<input type="text" class="form-control @error('port') is-invalid @enderror" id="port" placeholder="Port" name="port" value="{{ old('port',$o->port) }}" @cannot('update',$o)disabled @endcannot>
<input type="text" class="w-75 form-control @error('address') is-invalid @enderror" id="address" placeholder="FQDN" name="address" value="{{ old('address',$o->address) }}" @cannot($action,$o)readonly @endcannot>
<input type="text" class="form-control @error('port') is-invalid @enderror" id="port" placeholder="Port" name="port" value="{{ old('port',$o->port) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('address')
{{ $message }}
@ -191,7 +191,7 @@
<div class="row">
<div class="col-12">
@if($o->exists)
@can('update',$o)
@can($action,$o)
<a href="{{ url('ftn/system') }}" class="btn btn-danger">Cancel</a>
<button type="submit" name="submit" class="btn btn-success float-end">@if ($o->exists)Save @else Add @endif</button>
@else
@ -200,7 +200,7 @@
<button type="submit" class="btn btn-success float-end" name="submit" value="register">Register</button>
@endcan
@else
<button type="submit" class="btn btn-success float-end" name="submit" value="register">Register</button>
<button type="submit" class="btn btn-success float-end" name="submit" value="create">Register</button>
@endif
</div>
</div>

View File

@ -13,17 +13,17 @@
<div class="greyframe titledbox shadow0xb0">
<h2 class="cap">Register System</h2>
<div id="register">
<div id="create">
<div class="row">
<div class="col-4">
<label for="system" class="form-label">BBS Name</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-pc"></i></span>
<input type="text" style="z-index: 0" class="form-control col-11 @error('zone_id') is-invalid @enderror" id="system" placeholder="BBS Name" name="system" value="{{ old('system') }}" required autofocus>
<input type="text" style="z-index: 0" class="form-control col-11 @error('name') is-invalid @enderror" id="name" placeholder="BBS Name" name="name" value="{{ old('name') }}" required autofocus>
<span id="search-icon" style="width: 0;"><i style="border-radius: 50%;" class="spinner-border spinner-border-sm text-dark d-none"></i></span>
<div id="system_search_results"></div>
<span class="invalid-feedback" role="alert">
@error('zone_id')
@error('name')
{{ $message }}
@else
BBS Name is required.
@ -33,21 +33,40 @@
</div>
</div>
<div class="row">
<div class="col-12 pb-2">
<button type="button" name="submit" class="btn btn-success">Next</button><span id="next" class="m-2"><i class="spinner-border spinner-border-sm text-light d-none"></i></span>
@if (old('submit') != 'create')
<div class="row">
<div class="col-12 pb-2">
<button type="button" name="submit" class="btn btn-success">Next</button><span id="next" class="m-2"><i class="spinner-border spinner-border-sm text-light d-none"></i></span>
</div>
</div>
</div>
@endif
</div>
</div>
</div>
</div>
</form>
<div class="modal fade" id="no-auth" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger">
<h5 class="modal-title">ERROR: No authorisation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>It appears that you are not allowed to create this entry.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@endsection
@section('page-css')
<style>
input#system + span {
input#name + span {
left: -1.5em;
top: 0.5em;
position:relative
@ -87,14 +106,76 @@
@section('page-scripts')
<script>
var system_id;
var noauth = new bootstrap.Modal(document.getElementById('no-auth'), {});
function validation(item,message) {
var attr = $('input[id='+item+']');
attr.addClass('is-invalid')
attr.parent().find('.invalid-feedback').empty().append(message);
}
function getform(icon) {
$.ajax({
url : '{{ url('user/system/register') }}',
type : 'POST',
data : { system_id: system_id,name: $('#name').val(),action: 'create',old: {!! json_encode(old()) !!} },
dataType : 'json',
async : true,
cache : false,
beforeSend : function() {
if (icon)
icon.toggleClass('d-none');
},
complete : function(data) {
switch (data.status) {
case 200:
// if json is null, means no match, won't do again.
if(data.responseText==null || (data.responseText.length===0)) return;
$('#create').empty().append(data.responseText);
@if($errors->count())
@foreach($errors->keys() as $key)
validation('{{ $key }}','{{ $errors->first($key) }}');
@endforeach
@endif
break;
case 403:
if (icon)
icon.toggleClass('d-none');
noauth.show();
break;
case 419:
location.reload();
break;
case 422:
validation('name',data.responseJSON.errors.name[0]);
if (icon)
icon.toggleClass('d-none');
break;
default:
return false;
}
}
})
}
if ({{ old('submit') == 'create' ? 'true' : 'false' }}) {
getform();
}
$(document).ready(function() {
$('input[id=system]').typeahead({
$('input[id=name]').typeahead({
autoSelect: false,
scrollHeight: 10,
theme: 'bootstrap5',
delay: 500,
minLength: 2,
minLength: 3,
items: {{ $search_limit ?? 5 }},
fitToElement: false,
selectOnBlur: false,
@ -126,29 +207,10 @@
$('button[name=submit]').on('click',function() {
icon = $(this).parent().find('i');
if (! $('#system').val())
if (! $('#name').val())
return;
$.ajax({
url : '{{ url('user/system/register') }}',
type : 'POST',
data : { system_id: system_id,system_name: $('#system').val() },
dataType : 'html',
async : true,
cache : false,
beforeSend : function() {
icon.removeClass('d-none');
},
success : function(data) {
// if json is null, means no match, won't do again.
if(data==null || (data.length===0)) return;
$('#register').empty().append(data);
},
complete : function() {
//icon.addClass('d-none');
}
})
getform(icon);
})
});

View File

@ -89,7 +89,7 @@
<span class="input-group-text"><i class="bi bi-laptop-fill"></i></span>
<select style="width: 80%;" class="form-select @error('system_id') is-invalid @enderror" id="system" name="system_id" required @cannot('admin',$o)disabled @endcannot>
<option value="">&nbsp;</option>
@foreach (\App\Models\System::active()->orderBy('name')->cursor() as $oo)
@foreach (\App\Models\System::active()->cursor() as $oo)
<option value="{{ $oo->id }}" @if(old('system_id',$o->system_id)==$oo->id)selected @endif>{{ $oo->name }}</option>
@endforeach
</select>