Initial reseller domain report, enable editing domain service details

This commit is contained in:
Deon George 2021-07-13 12:31:56 +10:00
parent bc26f7b881
commit b515a1edeb
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
26 changed files with 652 additions and 56 deletions

View File

@ -9,5 +9,5 @@ RUN export COMPOSER_HOME=/var/www/.composer \
&& mv .env.example .env \ && mv .env.example .env \
&& FORCE_PERMS=1 NGINX_START=FALSE /sbin/init \ && FORCE_PERMS=1 NGINX_START=FALSE /sbin/init \
&& chmod +x /var/www/html/artisan \ && chmod +x /var/www/html/artisan \
&& touch .migrate \ && /var/www/html/artisan storage:link \
&& rm -rf /var/www/.composer && rm -rf /var/www/.composer

View File

@ -72,6 +72,15 @@ class SearchController extends Controller
$result->push(['name'=>sprintf('%s (%s)',$o->name,$o->service->sid),'value'=>'/u/service/'.$o->id,'category'=>'Broadband']); $result->push(['name'=>sprintf('%s (%s)',$o->name,$o->service->sid),'value'=>'/u/service/'.$o->id,'category'=>'Broadband']);
} }
# Look for Domain Name
foreach (Service\Domain::Search($request->input('term'))
->whereIN('account_id',$accounts)
->orderBy('domain_name')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s (%s)',$o->service_name,$o->service->sid),'value'=>'/u/service/'.$o->id,'category'=>'Domains']);
}
return $result; return $result;
} }
} }

View File

@ -3,18 +3,64 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View; use Illuminate\View\View;
use App\Models\Service; use App\Models\Service;
class ServiceController extends Controller class ServiceController extends Controller
{ {
/**
* Edit a domain service details
*
* @param Request $request
* @param Service $o
* @return \Illuminate\Http\RedirectResponse
*/
public function domain_edit(Request $request,Service $o)
{
session()->flash('service_update',TRUE);
$validation = $request->validate([
'service.domain_name' => sprintf('required|unique:%s,domain_name,%d',$o->type->getTable(),$o->type->id),
'service.domain_expire' => 'required|date',
'service.domain_tld_id' => 'required|exists:ab_domain_tld,id',
'service.domain_registrar_id' => 'required|exists:ab_domain_registrar,id',
'service.registrar_account' => 'required',
'service.registrar_username' => 'required|string|min:5',
'service.registrar_ns' => 'required|string|min:5',
]);
$o->type->forceFill($validation['service'])->save();
return redirect()->back()->with('success','Record updated.');
}
/**
* List all the domains managed by the user
*
* @return View
*/
public function domain_list(): View
{
$o = Service\Domain::serviceActive()
->serviceUserAuthorised(Auth::user())
->select('service_domains.*')
->join('ab_service',['ab_service.id'=>'service_domains.service_id'])
->with(['service.account','registrar'])
->get();
return view('r.service.domain.list')
->with('o',$o);
}
/** /**
* Update a service * Update a service
* *
* @note: Route Middleware protects this path * @note: Route Middleware protects this path
* @param Request $request
* @param Service $o * @param Service $o
* @return View|void * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/ */
public function update(Request $request,Service $o) public function update(Request $request,Service $o)
{ {

View File

@ -2,6 +2,8 @@
namespace App\Interfaces; namespace App\Interfaces;
use Carbon\Carbon;
interface ServiceItem interface ServiceItem
{ {
/** /**
@ -11,6 +13,11 @@ interface ServiceItem
*/ */
public function getServiceDescriptionAttribute(): string; public function getServiceDescriptionAttribute(): string;
/**
* Date the service expires
*/
public function getServiceExpireAttribute(): Carbon;
/** /**
* Return the Service Name. * Return the Service Name.
* *

View File

@ -3,8 +3,11 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Leenooks\Traits\ScopeActive;
class DomainRegistrar extends Model class DomainRegistrar extends Model
{ {
use ScopeActive;
protected $table = 'ab_domain_registrar'; protected $table = 'ab_domain_registrar';
} }

View File

@ -3,13 +3,25 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Leenooks\Traits\ScopeActive;
class DomainTld extends Model class DomainTld extends Model
{ {
use ScopeActive;
protected $table = 'ab_domain_tld'; protected $table = 'ab_domain_tld';
/* RELATIONS */
public function services() public function services()
{ {
return $this->hasMany(Service::class); return $this->hasMany(Service::class);
} }
/* ATTRIBUTES */
public function getNameAttribute($value): string
{
return strtoupper($value);
}
} }

View File

@ -24,6 +24,7 @@ use App\Traits\NextKey;
* Services that belong to an account * Services that belong to an account
* *
* Attributes for services: * Attributes for services:
* + billing_period : The period that this service is billed for by default
* + name_short : Service Product short name, eg: phone number, domain name, certificate CN * + name_short : Service Product short name, eg: phone number, domain name, certificate CN
* + sid : System ID for service * + sid : System ID for service
* *
@ -89,12 +90,19 @@ class Service extends Model implements IDs
'type', 'type',
]; ];
// @todo Change to self::INACTIVE_STATUS
private $inactive_status = [ private $inactive_status = [
'CANCELLED', 'CANCELLED',
'ORDER-REJECTED', 'ORDER-REJECTED',
'ORDER-CANCELLED', 'ORDER-CANCELLED',
]; ];
public const INACTIVE_STATUS = [
'CANCELLED',
'ORDER-REJECTED',
'ORDER-CANCELLED',
];
/** /**
* Valid status shows the applicable next status for an action on a service * Valid status shows the applicable next status for an action on a service
* Each status can be * Each status can be
@ -362,6 +370,15 @@ class Service extends Model implements IDs
}); });
} }
/**
* Only query records that the user is authorised to see
*/
public function scopeAuthorised($query,User $uo)
{
return $query
->whereIN($this->getTable().'.account_id',$uo->all_accounts()->pluck('id')->unique()->toArray());
}
/** /**
* Find inactive services. * Find inactive services.
* *
@ -1192,10 +1209,21 @@ class Service extends Model implements IDs
return $this->active OR ($this->order_status AND ! in_array($this->order_status,$this->inactive_status)); return $this->active OR ($this->order_status AND ! in_array($this->order_status,$this->inactive_status));
} }
/**
* Do we bill for this service
*
* @return bool
*/
public function isBilled(): bool
{
return ! ($this->external_billing && $this->suspend_billing);
}
/** /**
* Should this service be invoiced soon * Should this service be invoiced soon
* *
* @todo get the number of days from account setup * @todo get the number of days from account setup
* @todo Use self::isBilled();
* @return bool * @return bool
*/ */
public function isInvoiceDueSoon($days=30): bool public function isInvoiceDueSoon($days=30): bool
@ -1221,6 +1249,7 @@ class Service extends Model implements IDs
* @param bool $future Next item to be billed (not in the next x days) * @param bool $future Next item to be billed (not in the next x days)
* @return Collection * @return Collection
* @throws Exception * @throws Exception
* @todo Use self::isBilled();
*/ */
public function next_invoice_items(bool $future,Carbon $billdate=NULL): Collection public function next_invoice_items(bool $future,Carbon $billdate=NULL): Collection
{ {

View File

@ -92,6 +92,11 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
return strtoupper($this->service_address) ?: 'NO Service Address'; return strtoupper($this->service_address) ?: 'NO Service Address';
} }
public function getServiceExpireAttribute(): \Carbon\Carbon
{
// TODO: Implement getServiceExpireAttribute() method.
}
/** /**
* Return the service number * Return the service number
* *

View File

@ -2,23 +2,41 @@
namespace App\Models\Service; namespace App\Models\Service;
use App\Models\Base\ServiceType; use Carbon\Carbon;
use App\Models\DomainRegistrar;
use App\Models\DomainTld;
use App\Interfaces\ServiceItem;
use App\Traits\NextKey;
use App\Models\Base\ServiceType;
use App\Models\{Account,DomainRegistrar,DomainTld,Service};
use App\Interfaces\ServiceItem;
use App\Traits\{NextKey,ScopeServiceActive,ScopeServiceUserAuthorised};
/**
* Class Domain (Service)
* Services that domain names
*
* Attributes for services:
* + service_description : Description as shown in a Service Context
* + service_expire : The date the service expires
* + service_name : Name as shown in a Service Context
*
* @package App\Models\Service
*/
class Domain extends ServiceType implements ServiceItem class Domain extends ServiceType implements ServiceItem
{ {
use NextKey; use ScopeServiceActive,ScopeServiceUserAuthorised;
const RECORD_ID = 'service__domain';
protected $dates = [ protected $dates = [
'domain_expire', 'domain_expire',
]; ];
protected $table = 'ab_service__domain'; protected $table = 'service_domains';
protected $with = ['tld']; protected $with = ['tld'];
/* RELATIONS */
public function account()
{
return $this->hasOneThrough(Account::class,Service::class);
}
public function registrar() public function registrar()
{ {
return $this->belongsTo(DomainRegistrar::class,'domain_registrar_id'); return $this->belongsTo(DomainRegistrar::class,'domain_registrar_id');
@ -29,15 +47,46 @@ class Domain extends ServiceType implements ServiceItem
return $this->belongsTo(DomainTld::class,'domain_tld_id'); return $this->belongsTo(DomainTld::class,'domain_tld_id');
} }
/* SCOPES */
/**
* Search for a record
*
* @param $query
* @param string $term
* @return mixed
*/
public function scopeSearch($query,string $term)
{
// If we have a period in the name, we'll ignore everything after it.
$term = strstr($term,'.',TRUE) ?: $term;
// Build our where clause
return parent::scopeSearch($query,$term)
->orwhere('domain_name','like','%'.$term.'%');
}
/* ATTRIBUTES */
public function getServiceDescriptionAttribute(): string public function getServiceDescriptionAttribute(): string
{ {
// N/A // N/A
return 'Domain Name'; return 'Domain Name';
} }
public function getServiceExpireAttribute(): Carbon
{
return $this->domain_expire;
}
/**
* The name of the domain with its TLD
*
* @return string
*/
public function getServiceNameAttribute(): string public function getServiceNameAttribute(): string
{ {
return sprintf('%s.%s',strtoupper($this->domain_name),strtoupper($this->tld->name)); return strtoupper(sprintf('%s.%s',$this->domain_name,$this->tld->name));
} }
public function inContract(): bool public function inContract(): bool

View File

@ -7,6 +7,7 @@ use App\Models\Base\ServiceType;
use App\Models\DomainTld; use App\Models\DomainTld;
use App\Models\HostServer; use App\Models\HostServer;
use App\Traits\NextKey; use App\Traits\NextKey;
use Carbon\Carbon;
class Host extends ServiceType implements ServiceItem class Host extends ServiceType implements ServiceItem
{ {
@ -34,6 +35,11 @@ class Host extends ServiceType implements ServiceItem
return 'Hosting'; return 'Hosting';
} }
public function getServiceExpireAttribute(): Carbon
{
// TODO: Implement getServiceExpireAttribute() method.
}
public function getServiceNameAttribute(): string public function getServiceNameAttribute(): string
{ {
return sprintf('%s.%s',strtoupper($this->domain_name),strtoupper($this->tld->name)); return sprintf('%s.%s',strtoupper($this->domain_name),strtoupper($this->tld->name));

View File

@ -58,6 +58,11 @@ class SSL extends ServiceType implements ServiceItem
} }
} }
public function getServiceExpireAttribute(): Carbon
{
// TODO: Implement getServiceExpireAttribute() method.
}
public function getServiceNameAttribute(): string public function getServiceNameAttribute(): string
{ {
return $this->cert return $this->cert

View File

@ -5,6 +5,7 @@ namespace App\Models\Service;
use App\Interfaces\ServiceItem; use App\Interfaces\ServiceItem;
use App\Models\Base\ServiceType; use App\Models\Base\ServiceType;
use App\Traits\NextKey; use App\Traits\NextKey;
use Carbon\Carbon;
class Voip extends ServiceType implements ServiceItem class Voip extends ServiceType implements ServiceItem
{ {
@ -27,6 +28,11 @@ class Voip extends ServiceType implements ServiceItem
return $this->service_address ?: 'VOIP'; return $this->service_address ?: 'VOIP';
} }
public function getServiceExpireAttribute(): Carbon
{
// TODO: Implement getServiceExpireAttribute() method.
}
/** /**
* Return the service number * Return the service number
* *

View File

@ -74,6 +74,8 @@ class User extends Authenticatable
'customer', 'customer',
]; ];
/* RELATIONS */
/** /**
* The accounts that this user manages * The accounts that this user manages
* *
@ -175,7 +177,7 @@ class User extends Authenticatable
return $this->hasMany(static::class,'parent_id','id'); return $this->hasMany(static::class,'parent_id','id');
} }
/** ATTRIBUTES **/ /* ATTRIBUTES */
public function getActiveDisplayAttribute($value) public function getActiveDisplayAttribute($value)
{ {
@ -268,6 +270,8 @@ class User extends Authenticatable
return sprintf('<a href="/u/account/view/%s">%s</a>',$this->id,$this->user_id); return sprintf('<a href="/u/account/view/%s">%s</a>',$this->id,$this->user_id);
} }
/* METHODS */
/** /**
* Users password reset email notification * Users password reset email notification
* *
@ -278,7 +282,7 @@ class User extends Authenticatable
$this->notify((new ResetPasswordNotification($token))->onQueue('high')); $this->notify((new ResetPasswordNotification($token))->onQueue('high'));
} }
/** SCOPES */ /* SCOPES */
// @todo use trait // @todo use trait
public function scopeActive() public function scopeActive()
@ -323,6 +327,8 @@ class User extends Authenticatable
return $query; return $query;
} }
/* GENERAL METHODS */
/** /**
* Determine if the user is an admin of the account with $id * Determine if the user is an admin of the account with $id
* *
@ -334,8 +340,6 @@ class User extends Authenticatable
return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray()); return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray());
} }
/** FUNCTIONS */
/** /**
* Get a list of accounts for the clients of this user * Get a list of accounts for the clients of this user
* *
@ -437,8 +441,8 @@ class User extends Authenticatable
public function client_service_movements(): DatabaseCollection public function client_service_movements(): DatabaseCollection
{ {
return Service::active() return Service::active()
->authorised($this)
->where('order_status','!=','ACTIVE') ->where('order_status','!=','ACTIVE')
->whereIN('account_id',$this->all_accounts()->pluck('id')->unique()->toArray())
->with(['account','product']) ->with(['account','product'])
->get(); ->get();
} }
@ -630,6 +634,14 @@ class User extends Authenticatable
->from($payment,'summary'); ->from($payment,'summary');
} }
/**
* Determine what the logged in user's role is
* + Wholesaler - aka Super User
* + Reseller - services accounts on behalf of their customers
* + Customer - end user customer
*
* @return string
*/
public function role() public function role()
{ {
// If I have agents and no parent, I am the wholesaler // If I have agents and no parent, I am the wholesaler

View File

@ -31,5 +31,9 @@ class AuthServiceProvider extends ServiceProvider
Gate::define('wholesaler', function ($user) { Gate::define('wholesaler', function ($user) {
return $user->isWholesaler(); return $user->isWholesaler();
}); });
Gate::define('reseller', function ($user) {
return $user->isReseller();
});
} }
} }

View File

@ -0,0 +1,25 @@
<?php
/**
* Add a ScopeActive to an Eloquent Model to only show active services (including those soon to be active)
*/
namespace App\Traits;
use App\Models\Service;
trait ScopeServiceActive
{
/**
* Only query active service records
*/
public function scopeServiceActive($query)
{
return $query->where(function($q) {
return $q->where('ab_service.active',TRUE)
->orWhere(function($q) {
return $q->whereNotNull('order_status')
->whereNotIn('ab_service.order_status',Service::INACTIVE_STATUS);
});
});
}
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Add a ScopeAuthorised to an Eloquent Model
* This will help limit the scope of accounts that a user can see.
*/
namespace App\Traits;
use App\Models\User;
trait ScopeServiceUserAuthorised
{
/**
* Only query records that the user is authorised to see
*/
public function scopeServiceUserAuthorised($query,User $uo)
{
return $query
->whereIN('ab_service.account_id',$uo->all_accounts()->pluck('id')->unique()->toArray());
}
}

View File

@ -28,6 +28,6 @@ class ReworkAccount extends Migration
*/ */
public function down() public function down()
{ {
//abort(500,'cant go back'); abort(500,'cant go back');
} }
} }

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ReworkDomains extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::statement('ALTER TABLE ab_service__domain MODIFY COLUMN id INT auto_increment');
DB::statement('ALTER TABLE ab_service__domain RENAME TO service_domains');
Schema::table('service_domains', function (Blueprint $table) {
$table->renameColumn('registrar_type','registrar_account');
$table->unique(['domain_name','domain_tld_id']);
$table->dropColumn(['registrar_password','registrar_pending_transfer']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
abort(500,'cant go back');
}
}

10
public/css/custom.css vendored
View File

@ -3,6 +3,14 @@ hr.d-print-block {
padding-bottom: 20px; padding-bottom: 20px;
} }
.table:not(.table-borderless) tbody tr:last-child th,
.table:not(.table-borderless) tbody tr:last-child td { .table:not(.table-borderless) tbody tr:last-child td {
border-bottom: 2px solid #dee2e6; border-bottom: 1px solid #dee2e6;
}
@media print {
.table:not(.table-borderless) tbody tr:last-child th,
.table:not(.table-borderless) tbody tr:last-child td {
border-bottom: 2px solid #dee2e6;
}
} }

View File

@ -0,0 +1,172 @@
<div class="row">
<div class="col-6">
<div class="form-group has-validation">
<label for="domain_name">Domain Name</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-globe-asia"></i></span>
</div>
<input type="text" class="form-control col-9 text-right @error('service.domain_name') is-invalid @enderror" id="domain_name" name="service[domain_name]" placeholder="Domain Name..." value="{{ old('service.domain_name',$o->domain_name) }}" required>
<div class="input-group-append">
<span class="input-group-text">.</span>
</div>
<select class="form-control col-3" name="service[domain_tld_id]">
@foreach(\App\Models\DomainTld::active()->orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == $o->domain_tld_id)selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('service.domain_name')
{{ $message }}
@else
Domain Name is required.
@enderror
</span>
</div>
<span class="input-helper">Licensed Domain Name.</span>
</div>
</div>
<div class="col-3">
<div class="form-group has-validation">
<label for="domain_expire">Expiry</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control @error('service.domain_expire') is-invalid @enderror" id="domain_expire" name="service[domain_expire]" value="{{ old('service.domain_expire',$o->service_expire->format('Y-m-d')) }}" required>
<span class="invalid-feedback" role="alert">
@error('service.domain_expire')
{{ $message }}
@else
Domain Expiry is required.
@enderror
</span>
</div>
<span class="input-helper">Date Domain Expires.</span>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group has-validation">
<label for="domain_registrar_id">Registrar</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-handshake"></i></span>
</div>
<select class="form-control @error('service.domain_registrar_id') is-invalid @enderror" id="domain_registrar_id" name="service[domain_registrar_id]">
<option></option>
@foreach(\App\Models\DomainRegistrar::active()->orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == $o->domain_registrar_id)selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('service.domain_registrar_id')
{{ $message }}
@else
Domain Registrar is required.
@enderror
</span>
</div>
<span class="input-helper">Domain Name Registrar.</span>
</div>
</div>
<div class="col-3">
<div class="form-group has-validation">
<label for="registrar_account">Registrar Account</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control @error('service.registrar_account') is-invalid @enderror" id="registrar_account" name="service[registrar_account]" value="{{ old('service.registrar_account',$o->registrar_account) }}">
<span class="invalid-feedback" role="alert">
@error('service.registrar_account')
{{ $message }}
@else
Registrar Account ID is required.
@enderror
</span>
</div>
<span class="input-helper">Registrar Account ID.</span>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group has-validation">
<label for="registrar_ns">DNS Location</label>
<div class="input-group flex-nowrap">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-project-diagram"></i></span>
</div>
<select class="form-control @error('service.registrar_ns') is-invalid @enderror" id="registrar_ns" name="service[registrar_ns]">
@foreach(\App\Models\Service\Domain::select('registrar_ns')->distinct()->get() as $oo)
<option value="{{ $oo->registrar_ns }}" @if($oo->registrar_ns == $o->registrar_ns)selected @endif>{{ $oo->registrar_ns }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('service.registrar_ns')
{{ $message }}
@else
DNS Details is required.
@enderror
</span>
</div>
<span class="input-helper">Domain DNS details.</span>
</div>
</div>
<div class="col-3">
<div class="form-group has-validation">
<label for="registrar_username">Registrar Username</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control @error('service.registrar_username') is-invalid @enderror" id="registrar_username" name="service[registrar_username]" value="{{ old('service.registrar_username',$o->registrar_username) }}">
<span class="invalid-feedback" role="alert">
@error('service.registrar_username')
{{ $message }}
@else
Registrar Username is required.
@enderror
</span>
</div>
<span class="input-helper">Registrar Username ID.</span>
</div>
</div>
</div>
@section('page-scripts')
@css('//cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css','s2-css')
@js('//cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js','s2-js','jquery')
<style>
.select2-selection.select2-selection--single {
height: calc(2.25rem + 2px) !important;
}
.select2.select2-container.select2-container--default {
display: flex;
flex: 1 1 auto;
}
.select2.select2-container.select2-container--default .selection {
width: 100%;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
$('#registrar_ns').select2({
dropdownAutoWidth: true,
width: 'style',
tags: true,
});
});
</script>
@append

View File

@ -0,0 +1,19 @@
<div class="row">
<div class="col-12">
<h4>Update Service details</h4>
<form class="g-0 needs-validation" method="POST" action="{{ url('a/service/edit',[ $o->id]) }}">
@csrf
@includeIf('a.service.widgets.'.$o->stype.'.update',['o'=>$o->type])
<div class="row">
<div class="col-12">
@can('wholesaler')
<button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($site->exists)Save @else Add @endif</button>
@endcan
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,91 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Domain Names
@endsection
@section('page_title')
Domains
@endsection
@section('contentheader_title')
Domain Names
@endsection
@section('contentheader_description')
Domain Names currently managed.
@endsection
@section('main-content')
<div class="row">
<div class="col-12">
<div class="card card-dark">
<div class="card-body">
<table class="table table-hover" id="service_domain">
<thead>
<tr>
<th>Service ID</tH>
<th>Account</th>
<th>Domain</th>
<th>Expires</th>
<th>Registrar</th>
<th>DNS Host</th>
<th>Next Billed</th>
<th>Price</th>
<th>Term</th>
</tr>
</thead>
<tbody>
@foreach ($o as $oo)
<tr @if ($oo->service_expire->isPast()) class="table-danger" @endif>
<td><a href="{{ url('u/service',[$oo->service_id]) }}">{{ $oo->service->sid }}</td>
<td>{{ $oo->service->account->name }}</td>
<td>{{ $oo->service_name }}</td>
<td>{{ $oo->service_expire->format('Y-m-d') }}</td>
<td>{{ $oo->registrar->name }}</td>
<td>{{ $oo->registrar_ns }}</td>
<td>@if ($oo->service->isBilled()) <span class="@if($oo->service->suspend_billing)strike @endif">{{ $oo->service->invoice_next->format('Y-m-d') }}</span> @else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_period }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
@css('//cdn.datatables.net/1.10.25/css/dataTables.bootstrap4.min.css','jq-dt-css')
@js('//cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js','jq-dt-js')
@js('//cdn.datatables.net/1.10.25/js/dataTables.bootstrap4.min.js','jq-dt-bs5-js','jq-dt-js')
@js('//cdn.datatables.net/rowgroup/1.1.3/js/dataTables.rowGroup.min.js','jq-dt-rowgroup-js','jq-dt-js')
@css('//cdn.datatables.net/rowgroup/1.1.3/css/rowGroup.bootstrap4.min.css','jq-dt-rowgroup-css','jq-dt-jss')
<style>
.strike {
text-decoration: line-through;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
$('#service_domain').DataTable({
order: [[4,'asc'],[1,'asc'],[2,'desc']],
rowGroup: {
dataSrc: 4,
},
columnDefs: [
{
targets: [4],
visible: false,
}
],
});
$('#invoices_due tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -25,16 +25,16 @@
<div class="col-7"> <div class="col-7">
<div class="card"> <div class="card">
<div class="card-header bg-dark d-flex p-0"> <div class="card-header bg-dark d-flex p-0">
<ul class="nav nav-pills p-2 w-100"> <ul class="nav nav-pills w-100 p-2">
{{-- {{--
<li class="nav-item"><a class="nav-link active" href="#product" data-toggle="tab">Product</a></li> <li class="nav-item"><a class="nav-link active" href="#product" data-toggle="tab">Product</a></li>
<li class="nav-item"><a class="nav-link" href="#traffic" data-toggle="tab">Traffic</a></li> <li class="nav-item"><a class="nav-link" href="#traffic" data-toggle="tab">Traffic</a></li>
--}} --}}
@if (! $o->suspend_billing AND ! $o->external_billing) @if (! $o->suspend_billing AND ! $o->external_billing)
<li class="nav-item active"><a class="nav-link" href="#pending_items" data-toggle="tab">Pending Items</a></li> <li class="nav-item"><a class="nav-link {{ (! session()->has('service_update')) ? 'active' : '' }}" href="#pending_items" data-toggle="tab">Pending Items</a></li>
@endif @endif
@if ($o->hasUsage()) @if ($o->hasUsage())
<li class="nav-item active"><a class="nav-link" href="#traffic" data-toggle="tab">Traffic</a></li> <li class="nav-item"><a class="nav-link {{ (! $o->isBilled && (! session()->has('service_update'))) ? 'active' : '' }}" href="#traffic" data-toggle="tab">Traffic</a></li>
@endif @endif
{{-- {{--
@ -43,11 +43,13 @@
--}} --}}
@can('wholesaler') @can('wholesaler')
<li class="nav-item ml-auto"><a class="nav-link" href="#internal" data-toggle="tab">Internal</a></li> <li class="nav-item ml-auto"><a class="nav-link" href="#internal" data-toggle="tab">Internal</a></li>
<li class="nav-item"><a class="nav-link {{ session()->has('service_update') ? 'active' : '' }}" href="#update" data-toggle="tab">Update</a></li>
@endcan @endcan
</ul> </ul>
@can('update',$o) @can('update',$o)
<ul class="nav nav-pills ml-auto p-2"> <ul class="nav nav-pills ml-auto p-2">
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#"> <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">
ACTION <span class="caret"></span> ACTION <span class="caret"></span>
@ -69,12 +71,12 @@
Product. Product.
</div> </div>
@if (! $o->suspend_billing AND ! $o->external_billing) @if (! $o->suspend_billing AND ! $o->external_billing)
<div class="tab-pane fade show active" id="pending_items" role="tabpanel"> <div class="tab-pane fade {{ (! session()->has('service_update')) ? 'active show' : '' }}" id="pending_items" role="tabpanel">
@include('common.service.widget.invoice') @include('common.service.widget.invoice')
</div> </div>
@endif @endif
@if ($o->hasUsage()) @if ($o->hasUsage())
<div class="tab-pane fade show" id="traffic" role="tabpanel"> <div class="tab-pane fade {{ (! $o->isBilled && (! session()->has('service_update'))) ? 'active show' : '' }}" id="traffic" role="tabpanel">
@if ($o->type->usage(30)->count()) @if ($o->type->usage(30)->count())
@include('u.service.widgets.'.$o->stype.'.usagegraph',['o'=>$o->type]) @include('u.service.widgets.'.$o->stype.'.usagegraph',['o'=>$o->type])
@endif @endif
@ -90,6 +92,10 @@
<div class="tab-pane fade" id="internal" role="tabpanel"> <div class="tab-pane fade" id="internal" role="tabpanel">
@include('a.service.widgets.internal') @include('a.service.widgets.internal')
</div> </div>
<div class="tab-pane fade {{ session()->has('service_update') ? 'active show' : '' }}" id="update" role="tabpanel">
@include('a.service.widgets.update')
</div>
@endcan @endcan
</div> </div>
</div> </div>

View File

@ -63,3 +63,21 @@
</ul> </ul>
</li> </li>
@endcan @endcan
@can('reseller')
<li class="nav-header">RESELLER</li>
<li class="nav-item has-treeview @if(preg_match('#^a/report/(domain)#',request()->path()))menu-open @else menu-closed @endif">
<a href="#" class="nav-link @if(preg_match('#^a/report/(domain)#',request()->path())) active @endif">
<i class="nav-icon fas fa-list"></i> <p>REPORT<i class="fas fa-angle-left right"></i></p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ url('r/report/domain') }}" class="nav-link @if(preg_match('#^r/report/domain#',request()->path()))active @endif">
<i class="nav-icon fas fa-globe-asia"></i> <p>Domain Names</p>
</a>
</li>
</ul>
</li>
@endcan

View File

@ -16,10 +16,10 @@ use App\Http\Controllers\{AdminController,
*/ */
Route::group(['middleware'=>['auth:api','role:reseller']], function() { Route::group(['middleware'=>['auth:api','role:reseller']], function() {
Route::get('/r/agents','ResellerServicesController@agents'); // Route::get('/r/agents','ResellerServicesController@agents');
Route::get('/r/accounts',[ResellerServicesController::class,'accounts']); Route::get('/r/accounts',[ResellerServicesController::class,'accounts']);
Route::get('/r/clients','ResellerServicesController@clients'); // Route::get('/r/clients','ResellerServicesController@clients');
Route::get('/r/service_inactive','ResellerServicesController@service_inactive'); // Route::get('/r/service_inactive','ResellerServicesController@service_inactive');
Route::post('r/invoices/{o}',[AdminController::class,'pay_invoices']) Route::post('r/invoices/{o}',[AdminController::class,'pay_invoices'])
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware(['theme:adminlte-be','role:wholesaler']); ->middleware(['theme:adminlte-be','role:wholesaler']);

View File

@ -1,5 +1,6 @@
<?php <?php
use Leenooks\Controllers\AdminController as LeenooksAdminController;
use App\Http\Controllers\{AdminController, use App\Http\Controllers\{AdminController,
Auth\LoginController, Auth\LoginController,
CheckoutController, CheckoutController,
@ -8,6 +9,7 @@ use App\Http\Controllers\{AdminController,
OrderController, OrderController,
PaypalController, PaypalController,
SearchController, SearchController,
ServiceController,
WelcomeController}; WelcomeController};
/* /*
@ -25,10 +27,10 @@ Auth::routes();
Route::get('/logout',[LoginController::class,'logout']); Route::get('/logout',[LoginController::class,'logout']);
Route::group(['middleware'=>['theme:adminlte-be']],function() { Route::group(['middleware'=>['theme:adminlte-be']],function() {
Route::get('auth/{socialProvider}','Auth\SocialLoginController@redirectToProvider'); // Route::get('auth/{socialProvider}','Auth\SocialLoginController@redirectToProvider');
Route::get('auth/{socialProvider}/callback','Auth\SocialLoginController@handleProviderCallback'); // Route::get('auth/{socialProvider}/callback','Auth\SocialLoginController@handleProviderCallback');
Route::get('auth/{socialProvider}/link','Auth\SocialLoginController@link'); // Route::get('auth/{socialProvider}/link','Auth\SocialLoginController@link');
Route::post('auth/{socialProvider}/linkcomplete','Auth\SocialLoginController@linkcomplete'); // Route::post('auth/{socialProvider}/linkcomplete','Auth\SocialLoginController@linkcomplete');
}); });
// Generic Image Renderer - Render images that we dont have with a generic image // Generic Image Renderer - Render images that we dont have with a generic image
@ -37,25 +39,31 @@ Route::get('image/generic/{width}/{height}/{color}/{name?}',[MediaController::cl
// Our Admin Routes // Our Admin Routes
Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'prefix'=>'a'],function() { Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'prefix'=>'a'],function() {
Route::match(['get','post'],'setup',[AdminController::class,'setup']); Route::match(['get','post'],'setup',[AdminController::class,'setup']);
Route::get('service/{o}','AdminHomeController@service'); // Route::get('service/{o}','AdminHomeController@service');
Route::post('service/{o}','AdminHomeController@service_update'); // Route::post('service/{o}','AdminHomeController@service_update');
Route::get('report/products','Wholesale\ReportController@products'); // Route::get('report/products','Wholesale\ReportController@products');
Route::match(['get','post'],'payment/add',[AdminController::class,'pay_add']); Route::match(['get','post'],'payment/add',[AdminController::class,'pay_add']);
Route::post('service/edit/{o}',[ServiceController::class,'domain_edit'])
->where('o','[0-9]+')
->middleware('can:update,o');
//Route::get('accounting/connect','AccountingController@connect'); //Route::get('accounting/connect','AccountingController@connect');
}); });
Route::get('admin/switch/stop','\Leenooks\Controllers\AdminController@user_switch_stop')->name('switch.user.start')->middleware('auth'); Route::get('admin/switch/stop',[LeenooksAdminController::class,'user_switch_stop'])->name('switch.user.start')->middleware('auth');
// Our Reseller Routes // Our Reseller Routes
Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix'=>'r'],function() { Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix'=>'r'],function() {
Route::get('supplier/index','SuppliersController@index'); // Route::get('supplier/index','SuppliersController@index');
Route::get('supplier/create','SuppliersController@create'); // Route::get('supplier/create','SuppliersController@create');
Route::post('supplier/store','SuppliersController@store'); // Route::post('supplier/store','SuppliersController@store');
Route::get('switch/start/{id}','\Leenooks\Controllers\AdminController@user_switch_start')->name('switch.user.stop'); Route::get('switch/start/{id}',[LeenooksAdminController::class,'user_switch_start'])->name('switch.user.stop');
Route::match(['get','post'],'service/update/{o}','ServiceController@update') // Route::match(['get','post'],'service/update/{o}','ServiceController@update')
->where('o','[0-9]+') // ->where('o','[0-9]+')
->middleware('can:update,o'); // ->middleware('can:update,o');
Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix'=>'report'],function() {
Route::get('domain',[ServiceController::class,'domain_list']);
});
}); });
// Our User Routes // Our User Routes
@ -64,9 +72,9 @@ Route::group(['middleware'=>['theme:adminlte-be','auth'],'prefix'=>'u'],function
Route::get('home/{o}',[HomeController::class,'home']) Route::get('home/{o}',[HomeController::class,'home'])
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware('can:view,o'); ->middleware('can:view,o');
Route::get('account/{o}/invoice','User\AccountController@view_invoice_next') // Route::get('account/{o}/invoice','User\AccountController@view_invoice_next')
->where('o','[0-9]+') // ->where('o','[0-9]+')
->middleware('can:view,o'); // ->middleware('can:view,o');
Route::post('checkout/pay',[CheckoutController::class,'pay']); Route::post('checkout/pay',[CheckoutController::class,'pay']);
Route::get('invoice/{o}',[HomeController::class,'invoice']) Route::get('invoice/{o}',[HomeController::class,'invoice'])
->where('o','[0-9]+') ->where('o','[0-9]+')
@ -81,19 +89,19 @@ Route::group(['middleware'=>['theme:adminlte-be','auth'],'prefix'=>'u'],function
Route::get('service/{o}',[HomeController::class,'service']) Route::get('service/{o}',[HomeController::class,'service'])
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware('can:view,o'); ->middleware('can:view,o');
Route::get('service/cancel/{o}','ServiceController@update') // Route::get('service/cancel/{o}','ServiceController@update')
->where('o','[0-9]+') // ->where('o','[0-9]+')
->middleware('can:update,o'); // ->middleware('can:update,o');
Route::get('service/progress/{o}/{status}','UserHomeController@service_progress') // Route::get('service/progress/{o}/{status}','UserHomeController@service_progress')
->where('o','[0-9]+') // ->where('o','[0-9]+')
->middleware('can:progress,o,status'); // ->middleware('can:progress,o,status');
}); });
// Doorman Code Routes // Doorman Code Routes
Route::group(['middleware'=>['theme:adminlte-be'],'prefix'=>'u'],function() { Route::group(['middleware'=>['theme:adminlte-be'],'prefix'=>'u'],function() {
Route::get('invoice/{o}/email/{code}','UserHomeController@invoice_pdf_email') // Route::get('invoice/{o}/email/{code}','UserHomeController@invoice_pdf_email')
->where('o','[0-9]+') // ->where('o','[0-9]+')
->where('code','[0-9A-Z]{6}'); // ->where('code','[0-9A-Z]{6}');
}); });
// Frontend Routes (Non-Authed Users) // Frontend Routes (Non-Authed Users)