mirror of
https://github.com/snipe/snipe-it.git
synced 2025-02-02 08:21:09 -08:00
Vue importer (#3235)
* Begin work on vueifying the importer * Beginning work on migrating the importer to use a vue/components for future interactivity Update JS More importer work. Move to a vue based modal, begin handling of processing. Still need to port error messages. More importer work. Move to a vue based modal, begin handling of processing. Still need to port error messages. Update importer. Add error display. Fix modal, update vue-strap to vue2 More progress. Add select2 vue bits. * Move to querying the db to find importer matches. It scales better on large datasets. Fix select2 related issues. We were trying to initialize it twice, which led to the custom data being overwritten. * Better error handling on uploads and deletion of files. Restore progressbar on upload. * Add support for generic exception reporting if app.debug is enabled. * Handle Http 500 errors better. Display errors if debug is enabled. Assorted cleanups. * Fix codacy issues, remove unused methods. * Only bind vue to the importer for now. * Load vue for passport as well.
This commit is contained in:
parent
a9bf34cf61
commit
5ba2ec881c
|
@ -68,15 +68,17 @@ class Handler extends ExceptionHandler
|
|||
switch ($e->getStatusCode()) {
|
||||
case '404':
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode . ' endpoint not found'), 404);
|
||||
break;
|
||||
case '405':
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405);
|
||||
break;
|
||||
default:
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $e->getStatusCode()), 405);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode), 405);
|
||||
|
||||
}
|
||||
}
|
||||
// Try to parse 500 Errors ina bit nicer way when debug is enabled.
|
||||
if (config('app.debug')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, "An Error has occured! " . $e->getMessage()), 500);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,16 +2,34 @@
|
|||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetRequest;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Artisan;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use Input;
|
||||
use Lang;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Paginator;
|
||||
use Response;
|
||||
use Slack;
|
||||
use Str;
|
||||
use TCPDF;
|
||||
use Validator;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* This class controls all actions related to assets for
|
||||
|
@ -325,13 +343,11 @@ class AssetsController extends Controller
|
|||
->update(array('assigned_to' => null));
|
||||
|
||||
$asset->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.delete.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.delete.success')));
|
||||
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
141
app/Http/Controllers/Api/ImportController.php
Normal file
141
app/Http/Controllers/Api/ImportController.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ItemImportRequest;
|
||||
use App\Http\Transformers\ImportsTransformer;
|
||||
use App\Models\Company;
|
||||
use App\Models\Import;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
$imports = Import::latest()->get();
|
||||
return (new ImportsTransformer)->transformImports($imports);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
//
|
||||
if (!Company::isCurrentUserAuthorized()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
|
||||
} elseif (!config('app.lock_passwords')) {
|
||||
$files = Input::file('files');
|
||||
$path = config('app.private_uploads').'/imports';
|
||||
$results = [];
|
||||
$import = new Import;
|
||||
foreach ($files as $file) {
|
||||
|
||||
if (!in_array($file->getMimeType(), array(
|
||||
'application/vnd.ms-excel',
|
||||
'text/csv',
|
||||
'text/plain',
|
||||
'text/comma-separated-values',
|
||||
'text/tsv'))) {
|
||||
$results['error']='File type must be CSV';
|
||||
return $results;
|
||||
}
|
||||
|
||||
$date = date('Y-m-d-his');
|
||||
$fixed_filename = str_replace(' ', '-', $file->getClientOriginalName());
|
||||
try {
|
||||
$file->move($path, $date.'-'.$fixed_filename);
|
||||
} catch (FileException $exception) {
|
||||
$results['error']=trans('admin/hardware/message.upload.error');
|
||||
if (config('app.debug')) {
|
||||
$results['error'].= ' ' . $exception->getMessage();
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500);
|
||||
|
||||
}
|
||||
$file_name = date('Y-m-d-his').'-'.$fixed_filename;
|
||||
$import->file_path = $file_name;
|
||||
$import->filesize = filesize($path.'/'.$file_name);
|
||||
$import->save();
|
||||
$results[] = $import;
|
||||
}
|
||||
$results = (new ImportsTransformer)->transformImports($results);
|
||||
return [
|
||||
'files' => $results
|
||||
];
|
||||
}
|
||||
$results['error']=trans('general.feature_disabled');
|
||||
return $results;
|
||||
}
|
||||
/**
|
||||
* Processes the specified Import.
|
||||
*
|
||||
* @param \App\Import $import
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function process(ItemImportRequest $request, $import_id)
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$errors = $request->import(Import::find($import_id));
|
||||
$redirectTo = "hardware";
|
||||
switch ($request->get('import-type')) {
|
||||
case "asset":
|
||||
$redirectTo = "hardware.index";
|
||||
break;
|
||||
case "accessory":
|
||||
$redirectTo = "accessories.index";
|
||||
break;
|
||||
case "consumable":
|
||||
$redirectTo = "consumables.index";
|
||||
break;
|
||||
case "component":
|
||||
$redirectTo = "components.index";
|
||||
break;
|
||||
case "license":
|
||||
$redirectTo = "licenses.index";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($errors) { //Failure
|
||||
return response()->json(Helper::formatStandardApiResponse('import-errors', null, $errors), 500);
|
||||
}
|
||||
//Flash message before the redirect
|
||||
Session::flash('success', trans('admin/hardware/message.import.success'));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, ['redirect_url' => route($redirectTo)]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Import $import
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($import_id)
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$import = Import::find($import_id);
|
||||
try {
|
||||
unlink(config('app.private_uploads').'/imports/'.$import->file_path);
|
||||
$import->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('message.import.file_delete_success')));
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.import.file_delete_error')), 500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -681,103 +681,9 @@ class AssetsController extends Controller
|
|||
public function getImportUpload()
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$path = config('app.private_uploads').'/imports/assets';
|
||||
$files = array();
|
||||
|
||||
if (!Company::isCurrentUserAuthorized()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
|
||||
}
|
||||
|
||||
// Check if the uploads directory exists. If not, try to create it.
|
||||
if (!file_exists($path)) {
|
||||
mkdir($path, 0755, true);
|
||||
}
|
||||
if ($handle = opendir($path)) {
|
||||
|
||||
/* This is the correct way to loop over the directory. */
|
||||
while (false !== ($entry = readdir($handle))) {
|
||||
clearstatcache();
|
||||
if (substr(strrchr($entry, '.'), 1)=='csv') {
|
||||
$files[] = array(
|
||||
'filename' => $entry,
|
||||
'filesize' => Setting::fileSizeConvert(filesize($path.'/'.$entry)),
|
||||
'modified' => filemtime($path.'/'.$entry)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
closedir($handle);
|
||||
$files = array_reverse($files);
|
||||
}
|
||||
|
||||
return View::make('hardware/import')->with('files', $files);
|
||||
return View::make('hardware/import');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload the import file via AJAX
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @return array
|
||||
*/
|
||||
public function postAPIImportUpload(AssetFileRequest $request)
|
||||
{
|
||||
|
||||
if (!Company::isCurrentUserAuthorized()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
|
||||
} elseif (!config('app.lock_passwords')) {
|
||||
|
||||
$files = Input::file('files');
|
||||
$path = config('app.private_uploads').'/imports/assets';
|
||||
$results = array();
|
||||
|
||||
foreach ($files as $file) {
|
||||
|
||||
if (!in_array($file->getMimeType(), array(
|
||||
'application/vnd.ms-excel',
|
||||
'text/csv',
|
||||
'text/plain',
|
||||
'text/comma-separated-values',
|
||||
'text/tsv'))) {
|
||||
$results['error']='File type must be CSV';
|
||||
return $results;
|
||||
}
|
||||
|
||||
$date = date('Y-m-d-his');
|
||||
$fixed_filename = str_replace(' ', '-', $file->getClientOriginalName());
|
||||
try {
|
||||
$file->move($path, $date.'-'.$fixed_filename);
|
||||
} catch (FileException $exception) {
|
||||
$results['error']=trans('admin/hardware/message.upload.error');
|
||||
if (config('app.debug')) {
|
||||
$results['error'].= ' ' . $exception->getMessage();
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
$name = date('Y-m-d-his').'-'.$fixed_filename;
|
||||
$filesize = Setting::fileSizeConvert(filesize($path.'/'.$name));
|
||||
$results[] = compact('name', 'filesize');
|
||||
}
|
||||
return [
|
||||
'files' => $results
|
||||
];
|
||||
}
|
||||
$results['error']=trans('general.feature_disabled');
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function getDeleteImportFile($filename)
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
if (unlink(config('app.private_uploads').'/imports/assets/'.$filename)) {
|
||||
return redirect()->back()->with('success', trans('admin/hardware/message.import.file_delete_success'));
|
||||
}
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.import.file_delete_error'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the uploaded file
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Import;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
|
@ -29,10 +30,9 @@ class ItemImportRequest extends FormRequest
|
|||
];
|
||||
}
|
||||
|
||||
public function import()
|
||||
public function import(Import $import)
|
||||
{
|
||||
|
||||
$filename = config('app.private_uploads') . '/imports/assets/' . $this->get('filename');
|
||||
$filename = config('app.private_uploads') . '/imports/' . $import->file_path;
|
||||
$class = title_case($this->input('import-type'));
|
||||
$classString = "App\\Importer\\{$class}Importer";
|
||||
$importer = new $classString($filename);
|
||||
|
@ -58,6 +58,7 @@ class ItemImportRequest extends FormRequest
|
|||
public function errorCallback($item, $field, $errorString)
|
||||
{
|
||||
$this->errors[$item->name][$field] = $errorString;
|
||||
// $this->errors[$item->name] = $errorString;
|
||||
}
|
||||
|
||||
private $errors;
|
||||
|
|
39
app/Http/Transformers/ImportsTransformer.php
Normal file
39
app/Http/Transformers/ImportsTransformer.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
use App\Models\Import;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class ImportsTransformer
|
||||
{
|
||||
|
||||
public function transformImports($imports)
|
||||
{
|
||||
$array = array();
|
||||
foreach ($imports as $import) {
|
||||
$array[] = self::transformImport($import);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function transformImport(Import $import)
|
||||
{
|
||||
$array = [
|
||||
'id' => $import->id,
|
||||
'file_path' => $import->file_path,
|
||||
'filesize' => Setting::fileSizeConvert($import->filesize),
|
||||
'name' => $import->name,
|
||||
'import_type' => $import->import_type,
|
||||
'created_at' => $import->created_at->diffForHumans(),
|
||||
|
||||
];
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function transformImportsDatatable($imports)
|
||||
{
|
||||
return (new DatatablesTransformer)->transformDatatables($imports);
|
||||
}
|
||||
}
|
|
@ -6,14 +6,16 @@ use App\Helpers\Helper;
|
|||
use App\Models\Asset;
|
||||
use App\Models\Category;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Statuslabel;
|
||||
|
||||
class AssetImporter extends ItemImporter
|
||||
{
|
||||
protected $assets;
|
||||
protected $defaultStatusLabelId;
|
||||
|
||||
public function __construct($filename)
|
||||
{
|
||||
parent::__construct($filename);
|
||||
$this->assets = Asset::all();
|
||||
$this->defaultStatusLabelId = Statuslabel::first()->id;
|
||||
}
|
||||
|
||||
protected function handle($row)
|
||||
|
@ -41,11 +43,8 @@ class AssetImporter extends ItemImporter
|
|||
public function createAssetIfNotExists(array $row)
|
||||
{
|
||||
$editingAsset = false;
|
||||
$asset = new Asset;
|
||||
$asset_id = $this->assets->search(function ($key) {
|
||||
return strcasecmp($key->asset_tag, $this->item['asset_tag']) == 0;
|
||||
});
|
||||
if ($asset_id !== false) {
|
||||
$asset = Asset::where(['asset_tag'=> $this->item['asset_tag']])->first();
|
||||
if ($asset) {
|
||||
if (!$this->updating) {
|
||||
$this->log('A matching Asset ' . $this->item['asset_tag'] . ' already exists');
|
||||
return;
|
||||
|
@ -53,9 +52,9 @@ class AssetImporter extends ItemImporter
|
|||
|
||||
$this->log("Updating Asset");
|
||||
$editingAsset = true;
|
||||
$asset = $this->assets[$asset_id];
|
||||
} else {
|
||||
$this->log("No Matching Asset, Creating a new one");
|
||||
$asset = new Asset;
|
||||
}
|
||||
$this->item['serial'] = $this->array_smart_fetch($row, "serial number");
|
||||
$this->item['image'] = $this->array_smart_fetch($row, "image");
|
||||
|
@ -65,13 +64,11 @@ class AssetImporter extends ItemImporter
|
|||
}
|
||||
if (isset($this->item["status_label"])) {
|
||||
$this->item['status_id'] = $this->item["status_label"]->id;
|
||||
} elseif (!$editingAsset) {
|
||||
// Assume if we are editing, we already have a status and can ignore.
|
||||
$this->log("No status field found, defaulting to first status.");
|
||||
$this->item['status_id'] = $this->defaultStatusLabelId;
|
||||
}
|
||||
// We should require a status or come up with a better way of doing this..
|
||||
// elseif (!$editingAsset) {
|
||||
// // Assume if we are editing, we already have a status and can ignore.
|
||||
// $this->log("No status field found, defaulting to first status.");
|
||||
// $status_id = $this->status_labels->first()->id;
|
||||
// }
|
||||
|
||||
|
||||
// By default we're set this to location_id in the item.
|
||||
|
@ -90,9 +87,7 @@ class AssetImporter extends ItemImporter
|
|||
$asset->{$custom_field} = $val;
|
||||
}
|
||||
}
|
||||
if (!$editingAsset) {
|
||||
$this->assets->add($asset);
|
||||
}
|
||||
|
||||
if (!$this->testRun) {
|
||||
if ($asset->save()) {
|
||||
$asset->logCreate('Imported using csv importer');
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
<?php
|
||||
namespace App\Importer;
|
||||
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Location;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use ForceUTF8\Encoding;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
@ -60,15 +54,9 @@ abstract class Importer
|
|||
/**
|
||||
* ObjectImporter constructor.
|
||||
* @param string $filename
|
||||
* @param callable $logCallback
|
||||
* @param callable $progressCallback
|
||||
* @param callable $errorCallback
|
||||
* @param bool $testRun
|
||||
* @param int $user_id
|
||||
* @param bool $updating
|
||||
* @param null $usernameFormat
|
||||
*/
|
||||
function __construct(string $filename) {
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
|
||||
$this->filename = $filename;
|
||||
$this->csv = Reader::createFromPath($filename);
|
||||
|
@ -79,22 +67,12 @@ abstract class Importer
|
|||
$this->tempPassword = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
|
||||
}
|
||||
// Cached Values for import lookups
|
||||
protected $locations;
|
||||
protected $categories;
|
||||
protected $manufacturers;
|
||||
protected $asset_models;
|
||||
protected $companies;
|
||||
protected $status_labels;
|
||||
protected $suppliers;
|
||||
protected $assets;
|
||||
protected $accessories;
|
||||
protected $consumables;
|
||||
protected $customFields;
|
||||
|
||||
public function import()
|
||||
{
|
||||
$results = $this->normalizeInputArray($this->csv->fetchAssoc());
|
||||
$this->initializeLookupArrays();
|
||||
$this->customFields = CustomField::All(['name']);
|
||||
DB::transaction(function () use (&$results) {
|
||||
Model::unguard();
|
||||
$resultsCount = sizeof($results);
|
||||
|
@ -123,22 +101,6 @@ abstract class Importer
|
|||
return $newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Cached versions of all used methods.
|
||||
*/
|
||||
public function initializeLookupArrays()
|
||||
{
|
||||
$this->locations = Location::All(['name', 'id']);
|
||||
$this->categories = Category::All(['name', 'category_type', 'id']);
|
||||
$this->manufacturers = Manufacturer::All(['name', 'id']);
|
||||
$this->asset_models = AssetModel::All(['name', 'model_number', 'category_id', 'manufacturer_id', 'id']);
|
||||
$this->companies = Company::All(['name', 'id']);
|
||||
$this->status_labels = Statuslabel::All(['name', 'id']);
|
||||
$this->suppliers = Supplier::All(['name', 'id']);
|
||||
$this->customFields = CustomField::All(['name']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the given key exists in the array, and trim excess white space before returning it
|
||||
*
|
||||
|
@ -156,7 +118,7 @@ abstract class Importer
|
|||
$val = e(Encoding::toUTF8(trim($array[ $key ])));
|
||||
}
|
||||
$key = title_case($key);
|
||||
$this->log("${key}: ${val}");
|
||||
// $this->log("${key}: ${val}");
|
||||
return $val;
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,9 @@ class ItemImporter extends Importer
|
|||
*/
|
||||
private function shouldUpdateField($field)
|
||||
{
|
||||
if (empty($field)) {
|
||||
return false;
|
||||
}
|
||||
return !($this->updating && empty($field));
|
||||
}
|
||||
/**
|
||||
|
@ -156,23 +159,18 @@ class ItemImporter extends Importer
|
|||
$asset_model_name ='Unknown';
|
||||
}
|
||||
$editingModel = $this->updating;
|
||||
$asset_model_id = $this->asset_models->search(function ($key) use ($asset_model_name, $asset_modelNumber) {
|
||||
return strcasecmp($key->name, $asset_model_name) ==0
|
||||
&& $key->model_number == $asset_modelNumber;
|
||||
});
|
||||
// We need strict compare here because the index returned above can be 0.
|
||||
// This casts to false and causes false positives
|
||||
$asset_model = new AssetModel;
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
if ($asset_model_id !== false) {
|
||||
$asset_model = $this->asset_models[$asset_model_id];
|
||||
$asset_model = AssetModel::where(['name' => $asset_model_name, 'model_number' => $asset_modelNumber])->first();
|
||||
|
||||
if ($asset_model) {
|
||||
|
||||
if (!$this->updating) {
|
||||
$this->log("A matching model already exists, returning it.");
|
||||
return $asset_model;
|
||||
}
|
||||
$this->log("Matching Model found, updating it.");
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
$asset_model->update($item);
|
||||
if (!$this->testRun) {
|
||||
$asset_model->save();
|
||||
|
@ -180,9 +178,13 @@ class ItemImporter extends Importer
|
|||
return $asset_model;
|
||||
}
|
||||
$this->log("No Matching Model, Creating a new one");
|
||||
|
||||
$asset_model = new AssetModel();
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
|
||||
$asset_model->fill($item);
|
||||
$this->asset_models->add($asset_model);
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->log('TEST RUN - asset_model ' . $asset_model->name . ' not created');
|
||||
|
@ -214,15 +216,11 @@ class ItemImporter extends Importer
|
|||
if (empty($asset_category)) {
|
||||
$asset_category = 'Unnamed Category';
|
||||
}
|
||||
$category = $this->categories->search(function ($key) use ($asset_category, $item_type) {
|
||||
return (strcasecmp($key->name, $asset_category) == 0)
|
||||
&& $key->category_type === $item_type;
|
||||
});
|
||||
// We need strict compare here because the index returned above can be 0.
|
||||
// This casts to false and causes false positives
|
||||
if ($category !== false) {
|
||||
$category = Category::where(['name' => $asset_category, 'category_type' => $item_type])->first();
|
||||
|
||||
if ($category) {
|
||||
$this->log("A matching category: " . $asset_category . " already exists");
|
||||
return $this->categories[$category];
|
||||
return $category;
|
||||
}
|
||||
|
||||
$category = new Category();
|
||||
|
@ -231,11 +229,9 @@ class ItemImporter extends Importer
|
|||
$category->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->categories->add($category);
|
||||
return $category;
|
||||
}
|
||||
if ($category->save()) {
|
||||
$this->categories->add($category);
|
||||
$this->log('Category ' . $asset_category . ' was created');
|
||||
return $category;
|
||||
}
|
||||
|
@ -253,24 +249,19 @@ class ItemImporter extends Importer
|
|||
*/
|
||||
public function createOrFetchCompany($asset_company_name)
|
||||
{
|
||||
$company = $this->companies->search(function ($key) use ($asset_company_name) {
|
||||
return strcasecmp($key->name, $asset_company_name) == 0;
|
||||
});
|
||||
// We need strict compare here because the index returned above can be 0.
|
||||
// This casts to false and causes false positives
|
||||
if ($company !== false) {
|
||||
$company = Company::where(['name' => $asset_company_name])->first();
|
||||
if ($company) {
|
||||
$this->log('A matching Company ' . $asset_company_name . ' already exists');
|
||||
return $this->companies[$company];
|
||||
return $company;
|
||||
}
|
||||
$company = new Company();
|
||||
$company->name = $asset_company_name;
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->companies->add($company);
|
||||
|
||||
return $company;
|
||||
}
|
||||
if ($company->save()) {
|
||||
$this->companies->add($company);
|
||||
$this->log('Company ' . $asset_company_name . ' was created');
|
||||
return $company;
|
||||
}
|
||||
|
@ -291,14 +282,11 @@ class ItemImporter extends Importer
|
|||
if (empty($asset_statuslabel_name)) {
|
||||
return null;
|
||||
}
|
||||
$status = $this->status_labels->search(function ($key) use ($asset_statuslabel_name) {
|
||||
return strcasecmp($key->name, $asset_statuslabel_name) == 0;
|
||||
});
|
||||
// We need strict compare here because the index returned above can be 0.
|
||||
// This casts to false and causes false positives
|
||||
if ($status !== false) {
|
||||
$status = Statuslabel::where(['name' => $asset_statuslabel_name])->first();
|
||||
|
||||
if ($status) {
|
||||
$this->log('A matching Status ' . $asset_statuslabel_name . ' already exists');
|
||||
return $this->status_labels[$status];
|
||||
return $status;
|
||||
}
|
||||
$this->log("Creating a new status");
|
||||
$status = new Statuslabel();
|
||||
|
@ -309,12 +297,10 @@ class ItemImporter extends Importer
|
|||
$status->archived = 0;
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->status_labels->add($status);
|
||||
return $status;
|
||||
}
|
||||
|
||||
if ($status->save()) {
|
||||
$this->status_labels->add($status);
|
||||
$this->log('Status ' . $asset_statuslabel_name . ' was created');
|
||||
return $status;
|
||||
}
|
||||
|
@ -338,14 +324,11 @@ class ItemImporter extends Importer
|
|||
if (empty($item_manufacturer)) {
|
||||
$item_manufacturer='Unknown';
|
||||
}
|
||||
$manufacturer = $this->manufacturers->search(function ($key) use ($item_manufacturer) {
|
||||
return strcasecmp($key->name, $item_manufacturer) == 0;
|
||||
});
|
||||
// We need strict compare here because the index returned above can be 0.
|
||||
// This casts to false and causes false positives
|
||||
if ($manufacturer !== false) {
|
||||
$manufacturer = Manufacturer::where(['name'=> $item_manufacturer])->first();
|
||||
|
||||
if ($manufacturer) {
|
||||
$this->log('Manufacturer ' . $item_manufacturer . ' already exists') ;
|
||||
return $this->manufacturers[$manufacturer];
|
||||
return $manufacturer;
|
||||
}
|
||||
|
||||
//Otherwise create a manufacturer.
|
||||
|
@ -354,11 +337,9 @@ class ItemImporter extends Importer
|
|||
$manufacturer->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->manufacturers->add($manufacturer);
|
||||
return $manufacturer;
|
||||
}
|
||||
if ($manufacturer->save()) {
|
||||
$this->manufacturers->add($manufacturer);
|
||||
$this->log('Manufacturer ' . $manufacturer->name . ' was created');
|
||||
return $manufacturer;
|
||||
}
|
||||
|
@ -380,14 +361,11 @@ class ItemImporter extends Importer
|
|||
$this->log('No location given, so none created.');
|
||||
return null;
|
||||
}
|
||||
$location = $this->locations->search(function ($key) use ($asset_location) {
|
||||
return strcasecmp($key->name, $asset_location) == 0;
|
||||
});
|
||||
// We need strict compare here because the index returned above can be 0.
|
||||
// This casts to false and causes false positives
|
||||
$location = Location::where(['name' => $asset_location])->first();
|
||||
|
||||
if ($location !== false) {
|
||||
$this->log('Location ' . $asset_location . ' already exists');
|
||||
return $this->locations[$location];
|
||||
return $location;
|
||||
}
|
||||
// No matching locations in the collection, create a new one.
|
||||
$location = new Location();
|
||||
|
@ -399,11 +377,9 @@ class ItemImporter extends Importer
|
|||
$location->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->locations->add($location);
|
||||
return $location;
|
||||
}
|
||||
if ($location->save()) {
|
||||
$this->locations->add($location);
|
||||
$this->log('Location ' . $asset_location . ' was created');
|
||||
return $location;
|
||||
}
|
||||
|
@ -425,14 +401,11 @@ class ItemImporter extends Importer
|
|||
$item_supplier='Unknown';
|
||||
}
|
||||
|
||||
$supplier = $this->suppliers->search(function ($key) use ($item_supplier) {
|
||||
return strcasecmp($key->name, $item_supplier) == 0;
|
||||
});
|
||||
// We need strict compare here because the index returned above can be 0.
|
||||
// This casts to false and causes false positives
|
||||
if ($supplier !== false) {
|
||||
$supplier = Supplier::where(['name' => $item_supplier ])->first();
|
||||
|
||||
if ($supplier) {
|
||||
$this->log('Supplier ' . $item_supplier . ' already exists');
|
||||
return $this->suppliers[$supplier];
|
||||
return $supplier;
|
||||
}
|
||||
|
||||
$supplier = new Supplier();
|
||||
|
@ -440,11 +413,9 @@ class ItemImporter extends Importer
|
|||
$supplier->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->suppliers->add($supplier);
|
||||
return $supplier;
|
||||
}
|
||||
if ($supplier->save()) {
|
||||
$this->suppliers->add($supplier);
|
||||
$this->log('Supplier ' . $item_supplier . ' was created');
|
||||
return $supplier;
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ class Category extends SnipeModel
|
|||
public $rules = array(
|
||||
'user_id' => 'numeric|nullable',
|
||||
'name' => 'required|min:1|max:255|unique_undeleted',
|
||||
'require_acceptance' => 'required|boolean',
|
||||
'use_default_eula' => 'required|boolean',
|
||||
'require_acceptance' => 'boolean',
|
||||
'use_default_eula' => 'boolean',
|
||||
'category_type' => 'required|in:asset,accessory,consumable,component',
|
||||
);
|
||||
|
||||
|
|
9
app/Models/Import.php
Normal file
9
app/Models/Import.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Import extends Model
|
||||
{
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateImportsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('imports', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('name')->nullable();
|
||||
$table->string('file_path');
|
||||
$table->integer('filesize');
|
||||
$table->string('import_type')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('imports');
|
||||
}
|
||||
}
|
|
@ -15,8 +15,11 @@
|
|||
"laravel-elixir-vue-2": "^0.2.0",
|
||||
"laravel-elixir-webpack-official": "^1.0.2",
|
||||
"lodash": "^4.16.2",
|
||||
"vue": "^2.0.1",
|
||||
"vue-resource": "^1.0.3"
|
||||
"vue": "=2.1.6",
|
||||
"vue-loader": "^10.0.2",
|
||||
"vue-resource": "^1.0.3",
|
||||
"vue-strap": "github:wffranco/vue-strap.git",
|
||||
"vue-template-compiler": "=2.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.1",
|
||||
|
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
14854
public/build/assets/css/app-8f77387cf2.css
Normal file
14854
public/build/assets/css/app-8f77387cf2.css
Normal file
File diff suppressed because it is too large
Load diff
35056
public/build/assets/js/all-5e5715ba54.js
Normal file
35056
public/build/assets/js/all-5e5715ba54.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"assets/css/app.css": "assets/css/app-fc4d2fdcc4.css",
|
||||
"assets/js/all.js": "assets/js/all-93b23559b9.js"
|
||||
"assets/js/all.js": "assets/js/all-5e5715ba54.js"
|
||||
}
|
36
resources/assets/js/components/alert.vue
Normal file
36
resources/assets/js/components/alert.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="col-md-12" :class="alertType">
|
||||
<div class="alert" :class="alertClassName">
|
||||
<button type="button" class="close" @click="hideEvent">×</button>
|
||||
<i class="fa fa-check faa-pulse animated" v-show="alertType == 'success'"></i>
|
||||
<strong>{{ title }} </strong>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
props: ['alertType', 'title'],
|
||||
|
||||
computed: {
|
||||
alertClassName() {
|
||||
return 'alert-' + this.alertType;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
hideEvent() {
|
||||
this.$emit('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
42
resources/assets/js/components/importer/importer-errors.vue
Normal file
42
resources/assets/js/components/importer/importer-errors.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="box" v-if="errors">
|
||||
<div class="box-body">
|
||||
<div class="alert alert-warning">
|
||||
<strong>Warning</strong> Some Errors occured while importing
|
||||
</div>
|
||||
|
||||
<div class="errors-table">
|
||||
<table class="table table-striped table-bordered" id="errors-table">
|
||||
<thead>
|
||||
<th>Item</th>
|
||||
<th>Errors</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(error, item) in errors">
|
||||
<td>{{ item }}</td>
|
||||
<td v-for="(value, field) in error">
|
||||
<b>{{ field }}:</b>
|
||||
<span v-for="errorString in value">{{errorString[0]}}</span>
|
||||
<br />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
props: ['errors'],
|
||||
}
|
||||
|
||||
</script>
|
227
resources/assets/js/components/importer/importer.vue
Normal file
227
resources/assets/js/components/importer/importer.vue
Normal file
|
@ -0,0 +1,227 @@
|
|||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<alert v-show="alert.visible" :alertType="alert.type" v-on:hide="alert.visible = false">{{ alert.message }}</alert>
|
||||
<errors :errors="importErrors"></errors>
|
||||
<modal v-model="displayImportModal" effect="fade">
|
||||
<div slot="modal-header" class="modal-title">Import File:</div>
|
||||
<div slot="modal-body" class="modal-body">
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-4 col-xs-12">
|
||||
<label for="import-type">Import Type:</label>
|
||||
</div>
|
||||
<div class="col-md-8 col-xs-12">
|
||||
<select2 :options="modal.importTypes" v-model="modal.importType">
|
||||
<option disabled value="0"></option>
|
||||
</select2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-4 col-xs-12">
|
||||
<label for="import-update">Update Existing Values?:</label>
|
||||
</div>
|
||||
<div class="col-md-8 col-xs-12">
|
||||
<input type="checkbox" name="import-update" v-model="modal.update">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="modal-footer" class="modal-footer">
|
||||
<div class="row">
|
||||
<div class="alert alert-success col-md-5 col-md-offset-1" style="text-align:left" v-if="modal.statusText">{{ this.modal.statusText }}</div>
|
||||
<button type="button" class="btn btn-default" @click="displayImportModal = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" @click="postSave">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="col-md-3">
|
||||
<!-- The fileinput-button span is used to style the file input field as button -->
|
||||
<span class="btn btn-info fileinput-button">
|
||||
<i class="fa fa-plus icon-white"></i>
|
||||
<span>Select Import File...</span>
|
||||
<!-- The file input field used as target for the file upload widget -->
|
||||
<input id="fileupload" type="file" name="files[]" data-url="/api/v1/imports" accept="text/csv">
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-9" v-show="progress.visible" style="padding-bottom:20px">
|
||||
<div class="col-md-11">
|
||||
<div class="progress progress-striped-active" style="margin-top: 8px">
|
||||
<div class="progress-bar" :class="progress.currentClass" role="progressbar" :style="progressWidth">
|
||||
<span>{{ progress.statusText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped" id="upload-table">
|
||||
<thead>
|
||||
<th>File</th>
|
||||
<th>Created</th>
|
||||
<th>Size</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="file in files">
|
||||
<td>{{ file.file_path }}</td>
|
||||
<td>{{ file.created_at }} </td>
|
||||
<td>{{ file.filesize }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" @click="showModal(file)"><i class="fa fa-spinner process"></i>Process</button>
|
||||
<button class="btn btn-danger btn-sm" @click="deleteFile(file)"><i class="fa fa-trash icon-white"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
require('blueimp-file-upload');
|
||||
var modal = require('vue-strap').modal
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
files: [],
|
||||
displayImportModal: false,
|
||||
activeFile: null,
|
||||
alert: {
|
||||
type: null,
|
||||
message: null,
|
||||
visible: false,
|
||||
},
|
||||
modal: {
|
||||
importType: 'asset',
|
||||
update: false,
|
||||
importTypes: [
|
||||
{ id: 'asset', text: 'Assets' },
|
||||
{ id: 'accessory', text: 'Accessories' },
|
||||
{ id: 'consumable', text: 'Consumable' },
|
||||
{ id: 'component', text: 'Components' },
|
||||
{ id: 'license', text: 'Licenses' }
|
||||
],
|
||||
statusText: null,
|
||||
},
|
||||
importErrors: null,
|
||||
progress: {
|
||||
currentClass: "progress-bar-warning",
|
||||
currentPercent: "0",
|
||||
statusText: '',
|
||||
visible: false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the component (Vue 2.x).
|
||||
*/
|
||||
mounted() {
|
||||
this.fetchFiles();
|
||||
let vm = this;
|
||||
$('#fileupload').fileupload({
|
||||
dataType: 'json',
|
||||
done(e, data) {
|
||||
vm.progress.currentClass="progress-bar-success";
|
||||
vm.progress.statusText = "Success!";
|
||||
vm.files = data.result.files.concat(vm.files);
|
||||
},
|
||||
add(e, data) {
|
||||
data.headers = {
|
||||
"X-Requested-With": 'XMLHttpRequest',
|
||||
"X-CSRF-TOKEN": Laravel.csrfToken
|
||||
};
|
||||
data.process().done( () => {data.submit();});
|
||||
vm.progress.visible=true;
|
||||
},
|
||||
progress(e, data) {
|
||||
var progress = parseInt((data.loaded / data.total * 100, 10));
|
||||
vm.progress.currentPercent = progress;
|
||||
vm.progress.statusText = progress+'% Complete';
|
||||
},
|
||||
fail(e, data) {
|
||||
vm.progress.currentClass = "progress-bar-danger";
|
||||
vm.progress.statusText = data.errorThrown;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchFiles() {
|
||||
this.$http.get('/api/v1/imports')
|
||||
.then( ({data}) => this.files = data, // Success
|
||||
//Fail
|
||||
(response) => {
|
||||
this.alert.type="danger";
|
||||
this.alert.visible=true;
|
||||
this.alert.message="Something went wrong fetching files...";
|
||||
});
|
||||
},
|
||||
deleteFile(file, key) {
|
||||
this.$http.delete("/api/v1/imports/"+file.id)
|
||||
.then((response) => this.files.splice(key, 1), // Success
|
||||
(response) => {// Fail
|
||||
this.alert.type="danger";
|
||||
this.alert.visible=true;
|
||||
this.alert.message=response.body.messages;
|
||||
}
|
||||
);
|
||||
},
|
||||
showModal(file) {
|
||||
this.activeFile = file;
|
||||
this.displayImportModal = true;
|
||||
},
|
||||
|
||||
postSave() {
|
||||
this.$http.post('/api/v1/imports/process/'+this.activeFile.id, {
|
||||
'import-update': this.modal.update,
|
||||
'import-type': this.modal.importType
|
||||
}).then( (response) => {
|
||||
// Success
|
||||
this.modal.statusText = "Success... Redirecting.";
|
||||
window.location.href = response.body.messages.redirect_url;
|
||||
}, (response) => {
|
||||
// Failure
|
||||
if(response.body.status == 'import-errors') {
|
||||
this.importErrors = response.body.messages;
|
||||
} else {
|
||||
this.alert.message= response.body.messages;
|
||||
this.alert.type="danger";
|
||||
this.alert.visible=true;
|
||||
}
|
||||
this.displayImportModal=false;
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
progressWidth() {
|
||||
return "width: "+this.progress.currentPercent*10+'%';
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
modal,
|
||||
errors: require('./importer-errors.vue'),
|
||||
alert: require('../alert.vue'),
|
||||
select2: require('../select2.vue')
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
40
resources/assets/js/components/select2.vue
Normal file
40
resources/assets/js/components/select2.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<select style="width:100%">
|
||||
<slot></slot>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
require('select2');
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
props: ['options', 'value'],
|
||||
|
||||
mounted() {
|
||||
var vm = this;
|
||||
$(this.$el)
|
||||
.select2({
|
||||
data: this.options
|
||||
})
|
||||
.on('change', function() { vm.$emit('input', this.value) } );
|
||||
},
|
||||
watch: {
|
||||
value: function (value) {
|
||||
$(this.$el).val(value)
|
||||
},
|
||||
options: function (options) {
|
||||
$(this.$el).select2({data: options})
|
||||
},
|
||||
destroyed: function() {
|
||||
$(this.$el).off().select2('destroy')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -138,7 +138,13 @@ $(document).ready(function () {
|
|||
var iOS = /iPhone|iPad|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
if(!iOS)
|
||||
{
|
||||
$(".select2").select2();
|
||||
// Vue collision: Avoid overriding a vue select2 instance
|
||||
// by checking to see if the item has already been select2'd.
|
||||
$('select2:not([class^="select2-container"])').each((i,obj) => {
|
||||
{
|
||||
$(obj).select2();
|
||||
}
|
||||
});
|
||||
}
|
||||
$('.datepicker').datepicker();
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -6,13 +6,11 @@
|
|||
*/
|
||||
|
||||
require('./bootstrap');
|
||||
|
||||
/**
|
||||
* Next, we will create a fresh Vue application instance and attach it to
|
||||
* the page. Then, you may begin adding components to this application
|
||||
* or customize the JavaScript scaffolding to fit your unique needs.
|
||||
*/
|
||||
|
||||
Vue.component(
|
||||
'passport-clients',
|
||||
require('./components/passport/Clients.vue')
|
||||
|
@ -28,7 +26,12 @@ Vue.component(
|
|||
require('./components/passport/PersonalAccessTokens.vue')
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'importer',
|
||||
require('./components/importer/importer.vue')
|
||||
);
|
||||
|
||||
const app = new Vue({
|
||||
el: '#app'
|
||||
});
|
||||
// Commented out currently to avoid trying to load vue everywhere.
|
||||
// const app = new Vue({
|
||||
// el: '#app'
|
||||
// });
|
||||
|
|
|
@ -13,5 +13,12 @@
|
|||
@else
|
||||
<p class="help-block">{{ trans('general.feature_disabled') }}</p>
|
||||
@endif
|
||||
|
||||
@stop
|
||||
|
||||
@section('moar_scripts')
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,5 +1,5 @@
|
|||
@extends('layouts/default')
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/lib/jquery.fileupload.css') }}">
|
||||
{{-- Page title --}}
|
||||
@section('title')
|
||||
{{ trans('general.import') }}
|
||||
|
@ -8,209 +8,15 @@
|
|||
|
||||
{{-- Page content --}}
|
||||
@section('content')
|
||||
|
||||
|
||||
|
||||
@if (session()->has('import_errors'))
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="alert alert-warning">
|
||||
<strong>Warning</strong> {{trans('admin/hardware/message.import.errorDetail')}}
|
||||
</div>
|
||||
|
||||
<div class="errors-table">
|
||||
<table class="table table-striped table-bordered" id="errors-table">
|
||||
<thead>
|
||||
<th>Asset</th>
|
||||
<th>Errors</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (session('import_errors') as $asset => $itemErrors)
|
||||
<tr>
|
||||
<td> {{ $asset }}</td>
|
||||
<td>
|
||||
@foreach ($itemErrors as $field => $values )
|
||||
<b>{{ $field }}:</b>
|
||||
@foreach( $values as $errorString)
|
||||
<span>{{$errorString[0]}} </span>
|
||||
@endforeach
|
||||
<br />
|
||||
@endforeach
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Modal import dialog --}}
|
||||
<div class="modal fade" id="importModal">
|
||||
<form id="import-modal-form" class="form-horizontal" method="post" action="{{ route('assets/import/process-file') }}" autocomplete="off" role="form">
|
||||
{{ csrf_field()}}
|
||||
<input type="hidden" id="modal-filename" name="filename" value="">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Import File:</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-4 col-xs-12">
|
||||
<label for="import-type">Import Type:</label>
|
||||
</div>
|
||||
<div class="col-md-8 col-xs-12">
|
||||
{{ Form::select('import-type', array('asset' => 'Assets', 'accessory' => "Accessories", 'consumable' => "Consumables", 'component' => "Components") , 'asset', array('class'=>'select2 parent', 'style'=>'width:100%','id' =>'import-type')) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-4 col-xs-12">
|
||||
<label for="import-update">Update Existing Values?:</label>
|
||||
</div>
|
||||
<div class="col-md-8 col-xs-12">
|
||||
{{ Form::checkbox('import-update') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('button.cancel') }}</button>
|
||||
<!-- <button type="button" class="btn btn-primary" id="modal-save">{{ trans('general.save') }}</button> -->
|
||||
{{Form::submit(trans('general.save'), ['class' => 'btn btn-primary'])}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="col-md-3">
|
||||
<!-- The fileinput-button span is used to style the file input field as button -->
|
||||
<span class="btn btn-info fileinput-button">
|
||||
<i class="fa fa-plus icon-white"></i>
|
||||
<span>Select Import File...</span>
|
||||
<!-- The file input field used as target for the file upload widget -->
|
||||
<input id="fileupload" type="file" name="files[]" data-url="{{ route('api.hardware.importFile') }}" accept="text/csv">
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-9" id="progress-container" style="visibility: hidden; padding-bottom: 20px;">
|
||||
<!-- The global progress bar -->
|
||||
<div class="col-md-11">
|
||||
<div id="progress" class="progress progress-striped active" style="margin-top: 8px;">
|
||||
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 45%">
|
||||
<span id="progress-bar-text">0% Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<div class="pull-right progress-checkmark" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped" id="upload-table">
|
||||
<thead>
|
||||
<th>File</th>
|
||||
<th>Created</th>
|
||||
<th>Size</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($files as $file)
|
||||
<tr>
|
||||
<td>{{ $file['filename'] }}</td>
|
||||
<td>{{ date("M d, Y g:i A", $file['modified']) }} </td>
|
||||
<td>{{ $file['filesize'] }}</td>
|
||||
<td>
|
||||
<a href="#" data-toggle="modal" data-target="#importModal" data-filename={{$file['filename']}} class="btn btn-sm btn-info"><i class="fa fa-spinner process"></i> Process</a>
|
||||
<a class="btn btn-danger btn-sm" href="import/delete/{{ $file['filename'] }}"><i class="fa fa-trash icon-white"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app">
|
||||
<importer>
|
||||
</div>
|
||||
@stop
|
||||
|
||||
@section('moar_scripts')
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/lib/jquery.fileupload.css') }}">
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/lib/jquery.fileupload-ui.css') }}">
|
||||
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
//binds to onchange event of your input field
|
||||
var uploadedFileSize = 0;
|
||||
$('#fileupload').bind('change', function() {
|
||||
uploadedFileSize = this.files[0].size;
|
||||
$('#progress-container').css('visibility', 'visible');
|
||||
});
|
||||
|
||||
$('.process').bind('click', function() {
|
||||
$('.process').addClass('fa-spin');
|
||||
});
|
||||
|
||||
$('#fileupload').fileupload({
|
||||
//maxChunkSize: 100000,
|
||||
dataType: 'json',
|
||||
formData: {_token: '{{ csrf_token() }}'},
|
||||
progress: function (e, data) {
|
||||
//var overallProgress = $('#fileupload').fileupload('progress');
|
||||
//var activeUploads = $('#fileupload').fileupload('active');
|
||||
var progress = parseInt((data.loaded / uploadedFileSize) * 100, 10);
|
||||
$('.progress-bar').addClass('progress-bar-warning').css('width',progress + '%');
|
||||
$('#progress-bar-text').html(progress + '%');
|
||||
//console.dir(overallProgress);
|
||||
},
|
||||
|
||||
done: function (e, data) {
|
||||
console.dir(data);
|
||||
|
||||
// We use this instead of the fail option, since our API
|
||||
// returns a 200 OK status which always shows as "success"
|
||||
|
||||
if (data && data.jqXHR.responseJSON && data.jqXHR.responseJSON.error) {
|
||||
$('#progress-bar-text').html(data.jqXHR.responseJSON.error);
|
||||
$('.progress-bar').removeClass('progress-bar-warning').addClass('progress-bar-danger').css('width','100%');
|
||||
$('.progress-checkmark').fadeIn('fast').html('<i class="fa fa-times fa-3x icon-white" style="color: #d9534f"></i>');
|
||||
//console.log(data.jqXHR.responseJSON.error);
|
||||
} else {
|
||||
$('.progress-bar').removeClass('progress-bar-warning').removeClass('progress-bar-danger').addClass('progress-bar-success').css('width','100%');
|
||||
$('.progress-checkmark').fadeIn('fast');
|
||||
$('#progress-container').delay(950).css('visibility', 'visible');
|
||||
$('.progress-bar-text').html('Finished!');
|
||||
$('.progress-checkmark').fadeIn('fast').html('<i class="fa fa-check fa-3x icon-white" style="color: green"></i>');
|
||||
$.each(data.result.files, function (index, file) {
|
||||
$('<tr><td>' + file.name + '</td><td>Just now</td><td>' + file.filesize + '</td><td><a class="btn btn-info btn-sm" href="#" data-toggle="modal" data-target="#importModal" data-filename='+ file.name + '><i class="fa fa-spinner process"></i> Process</a> <a class="btn btn-danger btn-sm" href="import/delete/' +file.name + '"><i class="fa fa-trash icon-white"></i></a></td></tr>').prependTo("#upload-table > tbody");
|
||||
});
|
||||
}
|
||||
$('#progress').removeClass('active');
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Modal Import options handling
|
||||
$('#importModal').on("show.bs.modal", function(event) {
|
||||
var link = $(event.relatedTarget);
|
||||
var filename = link.data('filename');
|
||||
$(this).find('.modal-title').text("Import File: " + filename );
|
||||
$("#modal-filename").val(filename);
|
||||
});
|
||||
</script>
|
||||
@stop
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app'
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -16,3 +16,11 @@
|
|||
@endif
|
||||
|
||||
@stop
|
||||
|
||||
@section('moar_scripts')
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -18,6 +18,7 @@ use App\Models\Statuslabel;
|
|||
|
||||
|
||||
Route::group(['prefix' => 'v1','namespace' => 'Api'], function () {
|
||||
/*---Hardware API---*/
|
||||
|
||||
Route::resource('users', 'UsersController',
|
||||
['names' =>
|
||||
|
@ -33,7 +34,6 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () {
|
|||
]
|
||||
);
|
||||
|
||||
|
||||
Route::resource('licenses', 'LicensesController',
|
||||
['names' =>
|
||||
[
|
||||
|
@ -47,6 +47,20 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () {
|
|||
'parameters' => ['license' => 'license_id']
|
||||
]
|
||||
);
|
||||
Route::post('imports/process/{import_id}', [ 'as' => 'api.imports.importFile', 'uses'=> 'ImportController@process']);
|
||||
|
||||
Route::resource('imports', 'ImportController',
|
||||
['names' =>
|
||||
[
|
||||
'index' => 'api.imports.index',
|
||||
'show' => 'api.imports.show',
|
||||
'update' => 'api.imports.update',
|
||||
'store' => 'api.imports.store',
|
||||
'destroy' => 'api.imports.destroy'
|
||||
],
|
||||
'except' => ['edit']
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
Route::resource('models', 'AssetModelsController',
|
||||
|
@ -246,7 +260,6 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () {
|
|||
|
||||
|
||||
/*---Hardware API---*/
|
||||
Route::post('hardware/import', [ 'as' => 'api.assets.importFile', 'uses'=> 'AssetsController@postAPIImportUpload']);
|
||||
|
||||
Route::match(['DELETE'], 'hardware/{id}', ['uses' => 'AssetsController@destroy','as' => 'api.assets.destroy']);
|
||||
|
||||
|
|
|
@ -72,10 +72,7 @@ Route::group(
|
|||
'uses' => 'AssetsController@postUpload'
|
||||
]);
|
||||
|
||||
Route::get('{assetId}/deletefile/{fileId}', [
|
||||
'as' => 'delete/assetfile',
|
||||
'uses' => 'AssetsController@getDeleteFile'
|
||||
]);
|
||||
|
||||
|
||||
Route::get('{assetId}/showfile/{fileId}', [
|
||||
'as' => 'show/assetfile',
|
||||
|
|
Loading…
Reference in a new issue