Merge branch 'features/fixes_4792_ajax_tables_on_license_view' into develop

This commit is contained in:
snipe 2018-01-10 22:57:44 -08:00
commit 90db97b980
19 changed files with 246 additions and 169 deletions

View file

@ -5,7 +5,9 @@ namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\LicenseSeatsTransformer;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Company;
class LicensesController extends Controller
@ -162,4 +164,38 @@ class LicensesController extends Controller
{
//
}
/**
* Get license seat listing
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Contracts\View\View
*/
public function seats(Request $request, $licenseId)
{
if ($license = License::find($licenseId)) {
$seats = LicenseSeat::where('license_id', $licenseId)->with('license', 'user', 'asset');
$offset = request('offset', 0);
$limit = request('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$total = $seats->count();
$seats = $seats->skip($offset)->take($limit)->get();
if ($seats) {
return (new LicenseSeatsTransformer)->transformLicenseSeats($seats, $total);
}
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.does_not_exist')), 200);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace App\Http\Transformers;
use App\Models\LicenseSeat;
use Gate;
use Illuminate\Database\Eloquent\Collection;
use App\Helpers\Helper;
class LicenseSeatsTransformer
{
public function transformLicenseSeats (Collection $seats, $total)
{
$array = array();
$seat_count = 0;
foreach ($seats as $seat) {
$seat_count++;
$array[] = self::transformLicenseSeat($seat, $seat_count);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformLicenseSeat (LicenseSeat $seat, $seat_count)
{
$array = [
'id' => (int) $seat->id,
'license_id' => (int) $seat->license->id,
'name' => 'Seat '.$seat_count,
'assigned_user' => ($seat->user) ? [
'id' => (int) $seat->user->id,
'name'=> e($seat->user->present()->fullName)
] : null,
'assigned_asset' => ($seat->asset) ? [
'id' => (int) $seat->asset->id,
'name'=> e($seat->asset->present()->fullName)
] : null,
'location' => ($seat->location()) ? [
'id' => (int) $seat->location()->id,
'name'=> e($seat->location()->name)
] : null,
'reassignable' => (bool) $seat->license->reassignable,
'user_can_checkout' => (($seat->assigned_to=='') && ($seat->asset_id=='')) ? true : false,
];
$permissions_array['available_actions'] = [
'checkout' => Gate::allows('checkout', License::class) ? true : false,
'checkin' => Gate::allows('checkin', License::class) ? true : false,
'clone' => Gate::allows('create', License::class) ? true : false,
'update' => Gate::allows('update', License::class) ? true : false,
'delete' => Gate::allows('delete', License::class) ? true : false,
];
$array += $permissions_array;
return $array;
}
}

View file

@ -34,4 +34,17 @@ class LicenseSeat extends Model implements ICompanyableChild
{
return $this->belongsTo('\App\Models\Asset', 'asset_id')->withTrashed();
}
public function location()
{
if (($this->user) && ($this->user->location)) {
return $this->user->location;
} elseif (($this->asset) && ($this->asset->location)) {
return $this->asset->location;
}
return false;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/js/jspdf.min.js vendored

Binary file not shown.

Binary file not shown.

BIN
public/js/xlsx.core.min.js vendored Normal file

Binary file not shown.

View file

@ -40,9 +40,9 @@
@section('moar_scripts')
@include ('partials.bootstrap-table', [
'exportFile' => 'accessories-export',
'search' => true,
'showFooter' => true,
'columns' => \App\Presenters\AccessoryPresenter::dataTableLayout()
'columns' => \App\Presenters\AccessoryPresenter::dataTableLayout(),
'exportFile' => 'accessories-export',
])
@stop

View file

@ -38,90 +38,37 @@
<div class="tab-content">
<div class="tab-pane active" id="tab_1">
<div class="row">
<div class="col-md-7">
<table class="table table-striped">
<div class="col-md-8">
<div class="table-responsive">
<table
name="license-seats"
class="table table-striped snipe-table"
id="licenseSeats"
data-id-table="licenseSeats"
data-search="false"
data-url="{{ route('api.license.seats',['licence_id' => $license->id]) }}"
data-export="true"
data-export-options="{'fileName': 'license-seats'}"
data-cookie="true"
data-cookie-id-table="licenseSeats-Table">
<thead>
<tr>
<th class="col-md-2">{{ trans('admin/licenses/general.seat') }}</th>
<th class="col-md-2">{{ trans('admin/licenses/general.user') }}</th>
<th class="col-md-4">{{ trans('admin/licenses/form.asset') }}</th>
<th class="col-md-2"></th>
</tr>
<tr>
<th class="col-md-1" data-field="name">{{ trans('admin/licenses/general.seat') }}</th>
<th class="col-md-3" data-formatter="usersLinkObjFormatter" data-field="assigned_user">{{ trans('admin/licenses/general.user') }}</th>
<th class="col-md-3" data-formatter="hardwareLinkObjFormatter" data-field="assigned_asset">{{ trans('admin/licenses/form.asset') }}</th>
<th class="col-md-3" data-formatter="locationsLinkObjFormatter" data-field="location">{{ trans('general.location') }}</th>
<th class="col-md-1" data-searchable="false" data-sortable="false" data-field="checkincheckout" data-formatter="licenseSeatInOutFormatter">Checkin/Checkout</th>
</tr>
</thead>
<tbody>
<?php $count=1; ?>
@if ($license->licenseseats)
@foreach ($license->licenseseats as $licensedto)
<tr>
<td>Seat {{ $count }} </td>
<td>
@if (($licensedto->user) && ($licensedto->deleted_at == NULL))
@can('users.view')
<a href="{{ route('users.show', $licensedto->assigned_to) }}">
<i class="fa fa-user"></i>
{{ $licensedto->user->present()->fullName() }}
</a>
@else
<i class="fa fa-user"></i>
{{ $licensedto->user->present()->fullName() }}
@endcan
@elseif (($licensedto->user) && ($licensedto->deleted_at != NULL))
<del>{{ $licensedto->user->present()->fullName() }}</del>
@endif
</td>
<td>
@if ($licensedto->asset)
@can('view', $licensedto->asset)
<a href="{{ route('hardware.show', $licensedto->asset_id) }}">
<i class="fa fa-barcode"></i>
{{ $licensedto->asset->name }} {{ $licensedto->asset->asset_tag }}
</a>
@else
<i class="fa fa-barcode"></i>
{{ $licensedto->asset->name }} {{ $licensedto->asset->asset_tag }}
@endcan
@if ($licensedto->asset->location)
@can('locations.view')
({!! $licensedto->asset->location->present()->nameUrl() !!})
@else
({{ $licensedto->asset->location->present()->name() }})
@endcan
@endif
@endif
</td>
<td>
@can('checkout', $license)
@if (($licensedto->assigned_to) || ($licensedto->asset_id))
@if ($license->reassignable)
<a href="{{ route('licenses.checkin', $licensedto->id) }}" class="btn btn-sm bg-purple">
{{ trans('general.checkin') }}
</a>
@else
<span>Assigned</span>
@endif
@else
<a href="{{ route('licenses.checkout', $license->id) }}" class="btn btn-sm bg-maroon">
{{ trans('general.checkout') }}
</a>
@endif
@endcan
</td>
</tr>
<?php $count++; ?>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
<div class="col-md-5">
<div class="col-md-4">
<div class="table">
<table class="table">
<tbody>
@ -134,7 +81,7 @@
@if ($license->manufacturer)
<tr>
<td>{{ trans('admin/hardware/form.manufacturer') }}</td>
<td>{{ trans('admin/hardware/form.manufacturer') }}:</td>
<td><p style="line-height: 23px;">
@can('view', \App\Models\Manufacturer::class)
<a href="{{ route('manufacturers.show', $license->manufacturer->id) }}">
@ -169,7 +116,7 @@
@if (!is_null($license->serial))
<tr>
<td>{{ trans('admin/licenses/form.license_key') }}</td>
<td>{{ trans('admin/licenses/form.license_key') }}: </td>
<td style="word-wrap: break-word;overflow-wrap: break-word;word-break: break-word;">
@can('viewKeys', $license)
{!! nl2br(e($license->serial)) !!}
@ -182,16 +129,16 @@
@endif
@if (!is_null($license->license_name))
@if ($license->license_name!='')
<tr>
<td>{{ trans('admin/licenses/form.to_name') }}</td>
<td>{{ trans('admin/licenses/form.to_name') }}: </td>
<td>{{ $license->license_name }}</td>
</tr>
@endif
@if (!is_null($license->license_email))
@if ($license->license_email!='')
<tr>
<td>{{ trans('admin/licenses/form.to_email') }}</td>
<td>{{ trans('admin/licenses/form.to_email') }}:</td>
<td>{{ $license->license_email }}</td>
</tr>
@endif
@ -318,7 +265,7 @@
<thead>
<tr>
<th class="col-md-5">{{ trans('general.notes') }}</th>
<th class="col-md-5"><span class="line"></span>{{ trans('general.file_name') }}</th>
<th class="col-md-5">{{ trans('general.file_name') }}</th>
<th class="col-md-2"></th>
<th class="col-md-2"></th>
</tr>
@ -423,7 +370,7 @@
@section('moar_scripts')
@include ('partials.bootstrap-table', ['simple_view' => true])
@include ('partials.bootstrap-table')
@stop

View file

@ -1,15 +1,16 @@
{{-- This Will load our default bootstrap-table settings on any table with a class of "snipe-table" and export it to the passed 'exportFile' name --}}
<script src="{{ asset('js/bootstrap-table.js') }}"></script>
<script src="{{ asset('js/bootstrap-table.min.js') }}"></script>
<script src="{{ asset('js/extensions/mobile/bootstrap-table-mobile.js') }}"></script>
@if (!isset($simple_view))
<script src="{{ asset('js/extensions/cookie/bootstrap-table-cookie.min.js?v=1') }}"></script>
<script src="{{ asset('js/extensions/export/bootstrap-table-export.js?v=1') }}"></script>
<script src="{{ asset('js/extensions/cookie/bootstrap-table-cookie.js?v=1') }}"></script>
<script src="{{ asset('js/extensions/export/tableExport.js') }}"></script>
<script src="{{ asset('js/extensions/export/jquery.base64.js') }}"></script>
<script src="{{ asset('js/FileSaver.min.js') }}"></script>
<script src="{{ asset('js/xlsx.core.min.js') }}"></script>
<script src="{{ asset('js/jspdf.min.js') }}"></script>
<script src="{{ asset('js/jspdf.plugin.autotable.js') }}"></script>
<script src="{{ asset('js/extensions/export/jquery.base64.js') }}"></script>
<script src="{{ asset('js/extensions/export/tableExport.min.js') }}"></script>
@if (!isset($simple_view))
<script src="{{ asset('js/extensions/toolbar/bootstrap-table-toolbar.js') }}"></script>
<script src="{{ asset('js/extensions/sticky-header/bootstrap-table-sticky-header.js') }}"></script>
@endif
@ -17,6 +18,9 @@
<script nonce="{{ csrf_token() }}">
var $table = $('.snipe-table');
$(function () {
buildTable($table, 20, 50);
});
@ -33,49 +37,57 @@
$('.snipe-table').bootstrapTable('destroy').bootstrapTable({
classes: 'table table-responsive table-no-bordered',
undefinedText: '',
iconsPrefix: 'fa',
classes: 'table table-responsive table-no-bordered',
@if (isset($search))
search: true,
@endif
paginationVAlign: 'both',
sidePagination: '{{ (isset($clientSearch)) ? 'client' : 'server' }}',
sortable: true,
@if (!isset($simple_view))
showRefresh: true,
pagination: true,
pageSize: 20,
cookie: true,
cookieExpire: '2y',
showExport: true,
stickyHeader: true,
stickyHeaderOffsetY: stickyHeaderOffsetY + 'px',
@if (isset($showFooter))
showFooter: true,
@endif
showColumns: true,
trimOnSearch: false,
@if (isset($multiSort))
showMultiSort: true,
@endif
@if (isset($exportFile))
ajaxOptions: {
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
},
undefinedText: '',
iconsPrefix: 'fa',
search: {{ (isset($search)) ? 'true' : 'false' }},
paginationVAlign: 'both',
sidePagination: '{{ (isset($clientSearch)) ? 'client' : 'server' }}',
sortable: true,
pageSize: 20,
pagination: true,
cookie: true,
cookieExpire: '2y',
cookieIdTable: '{{ Route::currentRouteName() }}',
@if (isset($columns))
columns: {!! $columns !!},
@endif
mobileResponsive: true,
maintainSelected: true,
paginationFirstText: "{{ trans('general.first') }}",
paginationLastText: "{{ trans('general.last') }}",
paginationPreText: "{{ trans('general.previous') }}",
paginationNextText: "{{ trans('general.next') }}",
formatLoadingMessage: function () {
return '<h4><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> Loading... please wait.... </h4>';
},
icons: {
advancedSearchIcon: 'fa fa-search-plus',
paginationSwitchDown: 'fa-caret-square-o-down',
paginationSwitchUp: 'fa-caret-square-o-up',
columns: 'fa-columns',
@if( isset($multiSort))
sort: 'fa fa-sort-amount-desc',
plus: 'fa fa-plus',
minus: 'fa fa-minus',
@endif
refresh: 'fa-refresh'
},
showExport: true,
exportDataType: 'all',
exportTypes: ['csv', 'excel', 'doc', 'txt','json', 'xml', 'pdf'],
exportOptions: {
fileName: '{{ $exportFile . "-" }}' + (new Date()).toISOString().slice(0,10),
ignoreColumn: ['actions','change','checkbox','checkincheckout','icon'],
fileName: '{{ ((isset($exportFile)) ? $exportFile : 'table') }}-' + (new Date()).toISOString().slice(0,10),
ignoreColumn: ['actions','image','change','checkbox','checkincheckout','icon'],
worksheetName: "Snipe-IT Export",
escape: false,
jspdf: {
orientation: 'l',
autotable: {
@ -89,38 +101,30 @@
}
}
},
@endif
@endif
@if (!isset($simple_view))
@if (isset($columns))
columns: {!! $columns !!},
@endif
showRefresh: true,
stickyHeader: true,
stickyHeaderOffsetY: stickyHeaderOffsetY + 'px',
mobileResponsive: true,
maintainSelected: true,
paginationFirstText: "{{ trans('general.first') }}",
paginationLastText: "{{ trans('general.last') }}",
paginationPreText: "{{ trans('general.previous') }}",
paginationNextText: "{{ trans('general.next') }}",
formatLoadingMessage: function () {
return '<h4><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> Loading... please wait.... </h4>';
},
pageList: ['20', '30','50','100','150','200'],
icons: {
advancedSearchIcon: 'fa fa-search-plus',
paginationSwitchDown: 'fa-caret-square-o-down',
paginationSwitchUp: 'fa-caret-square-o-up',
columns: 'fa-columns',
@if( isset($multiSort))
sort: 'fa fa-sort-amount-desc',
plus: 'fa fa-plus',
minus: 'fa fa-minus',
@endif
refresh: 'fa-refresh'
}
@if (isset($showFooter))
showFooter: true,
@endif
});
showColumns: true,
trimOnSearch: false,
@if (isset($multiSort))
showMultiSort: true,
@endif
@endif
pageList: ['20', '30','50','100','150','200']
});
}
@ -285,18 +289,25 @@
}
// We need a special formatter for license seats, since they don't work exactly the same
// Checkouts need the license ID, checkins need the specific seat ID
function licenseSeatInOutFormatter(value, row) {
// The user is allowed to check the license seat out and it's available
if ((row.available_actions.checkout == true) && (row.user_can_checkout == true) && ((!row.asset_id) && (!row.assigned_to))) {
return '<a href="{{ url('/') }}/licenses/' + row.license_id + '/checkout" class="btn btn-sm bg-maroon" data-tooltip="true" title="Check this item out">{{ trans('general.checkout') }}</a>';
} else {
return '<a href="{{ url('/') }}/licenses/' + row.id + '/checkin" class="btn btn-sm bg-purple" data-tooltip="true" title="Check in this license seat.">{{ trans('general.checkin') }}</a>';
}
}
function genericCheckinCheckoutFormatter(destination) {
return function (value,row) {
// The user is allowed to check items out, AND the item is deployable
if ((row.available_actions.checkout == true) && (row.user_can_checkout == true) && (!row.assigned_to)) {
// case for licenses
if (row.next_seat) {
return '<a href="{{ url('/') }}/' + destination + '/' + row.next_seat + '/checkout" class="btn btn-sm bg-maroon" data-tooltip="true" title="Check this item out to a user">{{ trans('general.checkout') }}</a>';
} else {
return '<a href="{{ url('/') }}/' + destination + '/' + row.id + '/checkout" class="btn btn-sm bg-maroon" data-tooltip="true" title="Check this item out to a user">{{ trans('general.checkout') }}</a>';
}
if ((row.available_actions.checkout == true) && (row.user_can_checkout == true) && ((!row.asset_id) && (!row.assigned_to))) {
return '<a href="{{ url('/') }}/' + destination + '/' + row.id + '/checkout" class="btn btn-sm bg-maroon" data-tooltip="true" title="Check this item out">{{ trans('general.checkout') }}</a>';
// The user is allowed to check items out, but the item is not deployable
} else if (((row.user_can_checkout == false)) && (row.available_actions.checkout == true) && (!row.assigned_to)) {
@ -305,9 +316,9 @@
// The user is allowed to check items in
} else if (row.available_actions.checkin == true) {
if (row.assigned_to) {
return '<nobr><a href="{{ url('/') }}/' + destination + '/' + row.id + '/checkin" class="btn btn-sm bg-purple" data-tooltip="true" title="Check this item in so it is available for re-imaging, re-issue, etc.">{{ trans('general.checkin') }}</a>';
return '<a href="{{ url('/') }}/' + destination + '/' + row.id + '/checkin" class="btn btn-sm bg-purple" data-tooltip="true" title="Check this item in so it is available for re-imaging, re-issue, etc.">{{ trans('general.checkin') }}</a>';
} else if (row.assigned_pivot_id) {
return '<nobr><a href="{{ url('/') }}/' + destination + '/' + row.assigned_pivot_id + '/checkin" class="btn btn-sm bg-purple" data-tooltip="true" title="Check this item in so it is available for re-imaging, re-issue, etc.">{{ trans('general.checkin') }}</a>';
return '<a href="{{ url('/') }}/' + destination + '/' + row.assigned_pivot_id + '/checkin" class="btn btn-sm bg-purple" data-tooltip="true" title="Check this item in so it is available for re-imaging, re-issue, etc.">{{ trans('general.checkin') }}</a>';
}
}

View file

@ -351,6 +351,13 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () {
/*--- Licenses API ---*/
Route::group(['prefix' => 'licenses'], function () {
Route::get('{licenseId}/seats', [
'as' => 'api.license.seats',
'uses' => 'LicensesController@seats'
]);
}); // Licenses group
Route::resource('licenses', 'LicensesController',
[
'names' =>
@ -367,6 +374,7 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () {
); // Licenses resource
/*--- Locations API ---*/
Route::group(['prefix' => 'locations'], function () {