mirror of
https://github.com/snipe/snipe-it.git
synced 2024-11-09 23:24:06 -08:00
Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net> # Conflicts: # public/css/build/app.css # public/css/build/overrides.css # public/css/dist/all.css # public/mix-manifest.json
This commit is contained in:
commit
1b310f3a24
|
@ -3190,6 +3190,15 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Scarzy",
|
||||
"name": "Scarzy",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1197791?v=4",
|
||||
"profile": "https://github.com/Scarzy",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
|
|
@ -79,12 +79,12 @@ USER root
|
|||
|
||||
VOLUME ["/var/lib/snipeit"]
|
||||
|
||||
# Entrypoints
|
||||
COPY docker/entrypoint_alpine.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
# Startup script
|
||||
COPY docker/startup_alpine.sh /startup.sh
|
||||
RUN chmod +x /startup.sh
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
CMD ["/startup.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
|
|
|
@ -97,7 +97,7 @@ RUN set -eux; \
|
|||
VOLUME [ "/var/lib/snipeit" ]
|
||||
|
||||
COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
|
||||
COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
|
||||
COPY --chmod=655 docker/startup_alpine_fpm.sh /startup.sh
|
||||
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
|
||||
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
|
||||
ENTRYPOINT [ "/startup.sh" ]
|
||||
CMD [ "/startup.sh", "php-fpm" ]
|
||||
|
|
200
app/Http/Controllers/Api/AssetModelFilesController.php
Normal file
200
app/Http/Controllers/Api/AssetModelFilesController.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
|
||||
/**
|
||||
* This class controls file related actions related
|
||||
* to assets for the Snipe-IT Asset Management application.
|
||||
*
|
||||
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
|
||||
*
|
||||
* @version v1.0
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
class AssetModelFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Accepts a POST to upload a file to the server.
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param int $assetModelId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// Make sure we are allowed to update this asset
|
||||
$this->authorize('update', $assetModel);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
// If the file storage directory doesn't exist; create it
|
||||
if (! Storage::exists('private_uploads/assetmodels')) {
|
||||
Storage::makeDirectory('private_uploads/assetmodels', 775);
|
||||
}
|
||||
|
||||
// Loop over the attached files and add them to the asset
|
||||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
|
||||
|
||||
$assetModel->logUpload($file_name, e($request->get('notes')));
|
||||
}
|
||||
|
||||
// All done - report success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
|
||||
}
|
||||
|
||||
// We only reach here if no files were included in the POST, so tell the user this
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the files for an asset.
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function list($assetModelId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('view', $assetModel);
|
||||
|
||||
// Check that there are some uploads on this asset that can be listed
|
||||
if ($assetModel->uploads->count() > 0) {
|
||||
$files = array();
|
||||
foreach ($assetModel->uploads as $upload) {
|
||||
array_push($files, $upload);
|
||||
}
|
||||
// Give the list of files back to the user
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/models/message.upload.success')));
|
||||
}
|
||||
|
||||
// There are no files.
|
||||
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/models/message.upload.success')));
|
||||
}
|
||||
|
||||
// Send back an error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error')), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('view', $assetModel);
|
||||
|
||||
// Check that the file being requested exists for the asset
|
||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
// Form the full filename with path
|
||||
$file = 'private_uploads/assetmodels/'.$log->filename;
|
||||
Log::debug('Checking for '.$file);
|
||||
|
||||
if ($log->action_type == 'audit') {
|
||||
$file = 'private_uploads/audits/'.$log->filename;
|
||||
}
|
||||
|
||||
// Check the file actually exists on the filesystem
|
||||
if (! Storage::exists($file)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
}
|
||||
|
||||
// Send back an error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the associated file
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @param int $fileId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
$rel_path = 'private_uploads/assetmodels';
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('update', $assetModel);
|
||||
|
||||
// Check for the file
|
||||
$log = Actionlog::find($fileId);
|
||||
if ($log) {
|
||||
// Check the file actually exists, and delete it
|
||||
if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
Storage::delete($rel_path.'/'.$log->filename);
|
||||
}
|
||||
// Delete the record of the file
|
||||
$log->delete();
|
||||
|
||||
// All deleting done - notify the user of success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
|
||||
}
|
||||
|
||||
// The file doesn't seem to really exist, so report an error
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
|
||||
}
|
||||
}
|
|
@ -851,6 +851,24 @@ th.css-component > .th-inner::before
|
|||
margin-top:50px
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 992px){
|
||||
.info-stack-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.col-md-3.col-xs-12.col-sm-push-9.info-stack{
|
||||
left:auto;
|
||||
order:1;
|
||||
}
|
||||
.col-md-9.col-xs-12.col-sm-pull-3.info-stack{
|
||||
right:auto;
|
||||
order:2;
|
||||
}
|
||||
.info-stack-container > .col-md-9.col-xs-12.col-sm-pull-3.info-stack > .row-new-striped > .row > .col-sm-2{
|
||||
width:auto;
|
||||
float:none;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 1318px) and (min-width: 1200px){
|
||||
.admin.box{
|
||||
height:170px;
|
||||
|
|
|
@ -43,5 +43,11 @@ return array(
|
|||
'success' => 'Model deleted!|:success_count models deleted!',
|
||||
'success_partial' => ':success_count model(s) were deleted, however :fail_count were unable to be deleted because they still have assets associated with them.'
|
||||
),
|
||||
'download' => [
|
||||
'error' => 'File(s) not downloaded. Please try again.',
|
||||
'success' => 'File(s) successfully downloaded.',
|
||||
'does_not_exist' => 'No file exists',
|
||||
'no_match' => 'No matching record for that asset/file',
|
||||
],
|
||||
|
||||
);
|
||||
|
|
|
@ -561,5 +561,6 @@ return [
|
|||
'remaining_var' => ':count Remaining',
|
||||
'label' => 'Label',
|
||||
'import_asset_tag_exists' => 'An asset with the asset tag :asset_tag already exists and an update was not requested. No change was made.',
|
||||
'countries_manually_entered_help' => 'Values with an asterisk (*) were manually entered and do not match existing ISO 3166 dropdown values',
|
||||
|
||||
];
|
||||
|
|
|
@ -135,6 +135,7 @@ return [
|
|||
'EC'=>'Ecuador',
|
||||
'EE'=>'Estonia',
|
||||
'EG'=>'Egypt',
|
||||
'GB-ENG'=>'England',
|
||||
'ER'=>'Eritrea',
|
||||
'ES'=>'Spain',
|
||||
'ET'=>'Ethiopia',
|
||||
|
@ -233,6 +234,7 @@ return [
|
|||
'NG'=>'Nigeria',
|
||||
'NI'=>'Nicaragua',
|
||||
'NL'=>'Netherlands',
|
||||
'GB-NIR' => 'Northern Ireland',
|
||||
'NO'=>'Norway',
|
||||
'NP'=>'Nepal',
|
||||
'NR'=>'Nauru',
|
||||
|
@ -260,7 +262,7 @@ return [
|
|||
'RU'=>'Russian Federation',
|
||||
'RW'=>'Rwanda',
|
||||
'SA'=>'Saudi Arabia',
|
||||
'UK'=>'Scotland',
|
||||
'GB-SCT'=>'Scotland',
|
||||
'SB'=>'Solomon Islands',
|
||||
'SC'=>'Seychelles',
|
||||
'SS'=>'South Sudan',
|
||||
|
@ -312,6 +314,7 @@ return [
|
|||
'VI'=>'Virgin Islands (U.S.)',
|
||||
'VN'=>'Viet Nam',
|
||||
'VU'=>'Vanuatu',
|
||||
'GB-WLS' =>'Wales',
|
||||
'WF'=>'Wallis And Futuna Islands',
|
||||
'WS'=>'Samoa',
|
||||
'YE'=>'Yemen',
|
||||
|
|
|
@ -32,18 +32,27 @@ Form::macro('countries', function ($name = 'country', $selected = null, $class =
|
|||
|
||||
$idclause = (!is_null($id)) ? $id : '';
|
||||
|
||||
$select = '<select name="'.$name.'" class="'.$class.'" style="width:100%" '.$idclause.' aria-label="'.$name.'" data-placeholder="'.trans('localizations.select_country').'">';
|
||||
// Pull the autoglossonym array from the localizations translation file
|
||||
$countries_array = trans('localizations.countries');
|
||||
|
||||
$select = '<select name="'.$name.'" class="'.$class.'" style="width:100%" '.$idclause.' aria-label="'.$name.'" data-placeholder="'.trans('localizations.select_country').'" data-allow-clear="true" data-tags="true">';
|
||||
$select .= '<option value="" role="option">'.trans('localizations.select_country').'</option>';
|
||||
|
||||
// Pull the autoglossonym array from the localizations translation file
|
||||
foreach (trans('localizations.countries') as $abbr => $country) {
|
||||
foreach ($countries_array as $abbr => $country) {
|
||||
|
||||
// We have to handle it this way to handle deprecication warnings since you can't strtoupper on null
|
||||
if ($abbr!='') {
|
||||
$abbr = strtoupper($abbr);
|
||||
}
|
||||
|
||||
$select .= '<option value="'.$abbr.'"'.(($selected == $abbr) ? ' selected="selected" role="option" aria-selected="true"' : ' aria-selected="false"').'>'.$country.'</option> ';
|
||||
// Loop through the countries configured in the localization file
|
||||
$select .= '<option value="'.$abbr.'" selected="selected" role="option" '.(($selected == $abbr) ? ' selected="selected" role="option" aria-selected="true"' : ' aria-selected="false"').'>'.$country.'</option> ';
|
||||
|
||||
}
|
||||
|
||||
// If the country value doesn't exist in the array, add it as a new option and select it so we don't drop that data
|
||||
if (!in_array($selected, $countries_array)) {
|
||||
$select .= '<option value="' . $selected . '" selected="selected" role="option" aria-selected="true">' . $selected .' *</option> ';
|
||||
}
|
||||
|
||||
$select .= '</select>';
|
||||
|
|
|
@ -160,9 +160,9 @@
|
|||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="info-stack-container">
|
||||
<!-- Start button column -->
|
||||
<div class="col-md-3 col-xs-12 col-sm-push-9">
|
||||
<div class="col-md-3 col-xs-12 col-sm-push-9 info-stack">
|
||||
|
||||
<div class="col-md-12 text-center">
|
||||
@if (($asset->image) || (($asset->model) && ($asset->model->image!='')))
|
||||
|
@ -334,7 +334,7 @@
|
|||
|
||||
<!-- End button column -->
|
||||
|
||||
<div class="col-md-9 col-xs-12 col-sm-pull-3">
|
||||
<div class="col-md-9 col-xs-12 col-sm-pull-3 info-stack">
|
||||
|
||||
<div class="row-new-striped">
|
||||
|
||||
|
@ -1078,6 +1078,7 @@
|
|||
|
||||
</div> <!--/end striped container-->
|
||||
</div> <!-- end col-md-9 -->
|
||||
</div><!-- end info-stack-container -->
|
||||
</div> <!--/.row-->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
|
@ -1120,7 +1121,7 @@
|
|||
</table>
|
||||
@else
|
||||
|
||||
<div class="alert alert-info alert-block">
|
||||
<div class="alert alert-info alert-block hidden-print">
|
||||
<x-icon type="info-circle" />
|
||||
{{ trans('general.no_results') }}
|
||||
</div>
|
||||
|
@ -1157,7 +1158,7 @@
|
|||
<td>{{ Helper::formatCurrencyOutput($component->purchase_cost) }} each</td>
|
||||
<td>{{ $component->serial }}</td>
|
||||
<td>
|
||||
<a href="{{ route('components.checkin.show', $component->pivot->id) }}" class="btn btn-sm bg-purple" data-tooltip="true">{{ trans('general.checkin') }}</a>
|
||||
<a href="{{ route('components.checkin.show', $component->pivot->id) }}" class="btn btn-sm bg-purple hidden-print" data-tooltip="true">{{ trans('general.checkin') }}</a>
|
||||
</td>
|
||||
|
||||
<?php $totalCost = $totalCost + ($component->purchase_cost *$component->pivot->assigned_qty) ?>
|
||||
|
@ -1175,7 +1176,7 @@
|
|||
</tfoot>
|
||||
</table>
|
||||
@else
|
||||
<div class="alert alert-info alert-block">
|
||||
<div class="alert alert-info alert-block hidden-print">
|
||||
<x-icon type="info-circle" />
|
||||
{{ trans('general.no_results') }}
|
||||
</div>
|
||||
|
@ -1239,7 +1240,7 @@
|
|||
|
||||
@else
|
||||
|
||||
<div class="alert alert-info alert-block">
|
||||
<div class="alert alert-info alert-block hidden-print">
|
||||
<x-icon type="info-circle" />
|
||||
{{ trans('general.no_results') }}
|
||||
</div>
|
||||
|
@ -1399,11 +1400,11 @@
|
|||
</td>
|
||||
<td>
|
||||
@if (($file->filename) && (Storage::exists('private_uploads/assets/'.$file->filename)))
|
||||
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'download'=>'true']) }}" class="btn btn-sm btn-default">
|
||||
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'download'=>'true']) }}" class="btn btn-sm btn-default hidden-print">
|
||||
<x-icon type="download" />
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default hidden-print" target="_blank">
|
||||
<x-icon type="external-link" />
|
||||
</a>
|
||||
@endif
|
||||
|
@ -1415,7 +1416,7 @@
|
|||
</td>
|
||||
<td>
|
||||
@can('update', \App\Models\Asset::class)
|
||||
<a class="btn delete-asset btn-sm btn-danger btn-sm" href="{{ route('delete/assetfile', [$asset->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
|
||||
<a class="btn delete-asset btn-sm btn-danger btn-sm hidden-print" href="{{ route('delete/assetfile', [$asset->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
|
||||
<x-icon type="delete" />
|
||||
</a>
|
||||
@endcan
|
||||
|
@ -1427,7 +1428,7 @@
|
|||
|
||||
@else
|
||||
|
||||
<div class="alert alert-info alert-block">
|
||||
<div class="alert alert-info alert-block hidden-print">
|
||||
<x-icon type="info-circle" />
|
||||
{{ trans('general.no_results') }}
|
||||
</div>
|
||||
|
@ -1503,12 +1504,12 @@
|
|||
</td>
|
||||
<td>
|
||||
@if (($file->filename) && (Storage::exists('private_uploads/assetmodels/'.$file->filename)))
|
||||
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<x-icon type="download" />
|
||||
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id]) }}" class="btn btn-sm btn-default hidden-print">
|
||||
<x-icon type="download" class="hidden-print" />
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<x-icon type="external-link" />
|
||||
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default hidden-print" target="_blank">
|
||||
<x-icon type="external-link" class="hidden-print" />
|
||||
</a>
|
||||
|
||||
@endif
|
||||
|
@ -1520,8 +1521,8 @@
|
|||
</td>
|
||||
<td>
|
||||
@can('update', \App\Models\AssetModel::class)
|
||||
<a class="btn delete-asset btn-sm btn-danger btn-sm" href="{{ route('delete/modelfile', [$asset->model->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
|
||||
<x-icon type="delete" /></i>
|
||||
<a class="btn delete-asset btn-sm btn-danger btn-sm hidden-print" href="{{ route('delete/modelfile', [$asset->model->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
|
||||
<x-icon type="delete" class="hidden-print"/></i>
|
||||
</a>
|
||||
@endcan
|
||||
</td>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
{{ Form::label('country', trans('general.country'), array('class' => 'col-md-3 control-label')) }}
|
||||
<div class="col-md-7">
|
||||
{!! Form::countries('country', old('country', $item->country), 'select2') !!}
|
||||
<p class="help-block">{{ trans('general.countries_manually_entered_help') }}</p>
|
||||
{!! $errors->first('country', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -451,6 +451,8 @@
|
|||
<label class="col-md-3 control-label" for="country">{{ trans('general.country') }}</label>
|
||||
<div class="col-md-6">
|
||||
{!! Form::countries('country', old('country', $user->country), 'col-md-12 select2') !!}
|
||||
|
||||
<p class="help-block">{{ trans('general.countries_manually_entered_help') }}</p>
|
||||
{!! $errors->first('country', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -159,9 +159,9 @@
|
|||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="info-stack-container">
|
||||
<!-- Start button column -->
|
||||
<div class="col-md-3 col-xs-12 col-sm-push-9">
|
||||
<div class="col-md-3 col-xs-12 col-sm-push-9 info-stack">
|
||||
|
||||
|
||||
|
||||
|
@ -197,7 +197,9 @@
|
|||
{{ trans('admin/users/general.print_assigned') }}
|
||||
</a>
|
||||
@else
|
||||
<button style="width: 100%;" class="btn btn-sm btn-primary hidden-print" rel="noopener" disabled title="{{ trans('admin/users/message.user_has_no_assets_assigned') }}">{{ trans('admin/users/general.print_assigned') }}</button>
|
||||
<button style="width: 100%;" class="btn btn-sm btn-primary btn-social hidden-print" rel="noopener" disabled title="{{ trans('admin/users/message.user_has_no_assets_assigned') }}">
|
||||
<x-icon type="print" />
|
||||
{{ trans('admin/users/general.print_assigned') }}</button>
|
||||
@endif
|
||||
</div>
|
||||
@endcan
|
||||
|
@ -306,7 +308,7 @@
|
|||
|
||||
<!-- End button column -->
|
||||
|
||||
<div class="col-md-9 col-xs-12 col-sm-pull-3">
|
||||
<div class="col-md-9 col-xs-12 col-sm-pull-3 info-stack">
|
||||
|
||||
<div class="row-new-striped">
|
||||
|
||||
|
@ -765,6 +767,7 @@
|
|||
@endif
|
||||
</div> <!--/end striped container-->
|
||||
</div> <!-- end col-md-9 -->
|
||||
</div><!-- end info-stack-container-->
|
||||
</div> <!--/.row-->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
|
|
|
@ -798,6 +798,33 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
|
|||
]
|
||||
)->name('api.models.restore');
|
||||
|
||||
Route::post('{model_id}/files',
|
||||
[
|
||||
Api\AssetModelFilesController::class,
|
||||
'store'
|
||||
]
|
||||
)->name('api.models.files.store');
|
||||
|
||||
Route::get('{model_id}/files',
|
||||
[
|
||||
Api\AssetModelFilesController::class,
|
||||
'list'
|
||||
]
|
||||
)->name('api.models.files.index');
|
||||
|
||||
Route::get('{model_id}/file/{file_id}',
|
||||
[
|
||||
Api\AssetModelFilesController::class,
|
||||
'show'
|
||||
]
|
||||
)->name('api.models.files.show');
|
||||
|
||||
Route::delete('{model_id}/file/{file_id}',
|
||||
[
|
||||
Api\AssetModelFilesController::class,
|
||||
'destroy'
|
||||
]
|
||||
)->name('api.models.files.destroy');
|
||||
});
|
||||
|
||||
Route::resource('models',
|
||||
|
|
120
tests/Feature/AssetModels/Api/AssetModelFilesTest.php
Normal file
120
tests/Feature/AssetModels/Api/AssetModelFilesTest.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\AssetModels\Api;
|
||||
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AssetModelFilesTest extends TestCase
|
||||
{
|
||||
public function testAssetModelApiAcceptsFileUpload()
|
||||
{
|
||||
// Upload a file to a model
|
||||
|
||||
// Create a model to work with
|
||||
$model = AssetModel::factory()->count(1)->create();
|
||||
|
||||
// Create a superuser to run this as
|
||||
$user = User::factory()->superuser()->create();
|
||||
|
||||
//Upload a file
|
||||
$this->actingAsForApi($user)
|
||||
->post(
|
||||
route('api.models.files.store', ['model_id' => $model[0]["id"]]), [
|
||||
'file' => [UploadedFile::fake()->create("test.jpg", 100)]
|
||||
])
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
public function testAssetModelApiListsFiles()
|
||||
{
|
||||
// List all files on a model
|
||||
|
||||
// Create an model to work with
|
||||
$model = AssetModel::factory()->count(1)->create();
|
||||
|
||||
// Create a superuser to run this as
|
||||
$user = User::factory()->superuser()->create();
|
||||
|
||||
// List the files
|
||||
$this->actingAsForApi($user)
|
||||
->getJson(
|
||||
route('api.models.files.index', ['model_id' => $model[0]["id"]]))
|
||||
->assertOk()
|
||||
->assertJsonStructure([
|
||||
'status',
|
||||
'messages',
|
||||
'payload',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAssetModelApiDownloadsFile()
|
||||
{
|
||||
// Download a file from a model
|
||||
|
||||
// Create a model to work with
|
||||
$model = AssetModel::factory()->count(1)->create();
|
||||
|
||||
// Create a superuser to run this as
|
||||
$user = User::factory()->superuser()->create();
|
||||
|
||||
//Upload a file
|
||||
$this->actingAsForApi($user)
|
||||
->post(
|
||||
route('api.models.files.store', ['model_id' => $model[0]["id"]]), [
|
||||
'file' => [UploadedFile::fake()->create("test.jpg", 100)]
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
// List the files to get the file ID
|
||||
$result = $this->actingAsForApi($user)
|
||||
->getJson(
|
||||
route('api.models.files.index', ['model_id' => $model[0]["id"]]))
|
||||
->assertOk();
|
||||
|
||||
// Get the file
|
||||
$this->actingAsForApi($user)
|
||||
->get(
|
||||
route('api.models.files.show', [
|
||||
'model_id' => $model[0]["id"],
|
||||
'file_id' => $result->decodeResponseJson()->json()["payload"][0]["id"],
|
||||
]))
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
public function testAssetModelApiDeletesFile()
|
||||
{
|
||||
// Delete a file from a model
|
||||
|
||||
// Create a model to work with
|
||||
$model = AssetModel::factory()->count(1)->create();
|
||||
|
||||
// Create a superuser to run this as
|
||||
$user = User::factory()->superuser()->create();
|
||||
|
||||
//Upload a file
|
||||
$this->actingAsForApi($user)
|
||||
->post(
|
||||
route('api.models.files.store', ['model_id' => $model[0]["id"]]), [
|
||||
'file' => [UploadedFile::fake()->create("test.jpg", 100)]
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
// List the files to get the file ID
|
||||
$result = $this->actingAsForApi($user)
|
||||
->getJson(
|
||||
route('api.models.files.index', ['model_id' => $model[0]["id"]]))
|
||||
->assertOk();
|
||||
|
||||
// Delete the file
|
||||
$this->actingAsForApi($user)
|
||||
->delete(
|
||||
route('api.models.files.destroy', [
|
||||
'model_id' => $model[0]["id"],
|
||||
'file_id' => $result->decodeResponseJson()->json()["payload"][0]["id"],
|
||||
]))
|
||||
->assertOk();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue