mirror of
https://github.com/snipe/snipe-it.git
synced 2025-03-05 20:52:15 -08:00
Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net> # Conflicts: # public/js/build/app.js # public/js/dist/all.js # public/mix-manifest.json # resources/views/livewire/slack-settings-form.blade.php
This commit is contained in:
commit
58ea1592bb
|
@ -1092,6 +1092,15 @@ class Helper
|
|||
return $file_name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Universal helper to show file size in human-readable formats
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since 5.0
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function formatFilesizeUnits($bytes)
|
||||
{
|
||||
if ($bytes >= 1073741824)
|
||||
|
@ -1121,30 +1130,56 @@ class Helper
|
|||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is weird but used by the side nav to determine which URL to point the user to
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since 5.0
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function SettingUrls(){
|
||||
$settings=['#','fields.index', 'statuslabels.index', 'models.index', 'categories.index', 'manufacturers.index', 'suppliers.index', 'departments.index', 'locations.index', 'companies.index', 'depreciations.index'];
|
||||
|
||||
return $settings;
|
||||
}
|
||||
public static function AgeFormat($date) {
|
||||
$year = Carbon::parse($date)
|
||||
->diff(now())->y;
|
||||
$month = Carbon::parse($date)
|
||||
->diff(now())->m;
|
||||
$days = Carbon::parse($date)
|
||||
->diff(now())->d;
|
||||
$age='';
|
||||
if ($year) {
|
||||
$age .= $year.'y ';
|
||||
}
|
||||
if ($month) {
|
||||
$age .= $month.'m ';
|
||||
}
|
||||
if ($days) {
|
||||
$age .= $days.'d';
|
||||
}
|
||||
|
||||
return $age;
|
||||
|
||||
/**
|
||||
* Generic helper (largely used by livewire right now) that returns the font-awesome icon
|
||||
* for the object type.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function iconTypeByItem($item) {
|
||||
|
||||
switch ($item) {
|
||||
case 'asset':
|
||||
return 'fas fa-barcode';
|
||||
break;
|
||||
case 'accessory':
|
||||
return 'fas fa-keyboard';
|
||||
break;
|
||||
case 'component':
|
||||
return 'fas fa-hdd';
|
||||
break;
|
||||
case 'consumable':
|
||||
return 'fas fa-tint';
|
||||
break;
|
||||
case 'license':
|
||||
return 'far fa-save';
|
||||
break;
|
||||
case 'location':
|
||||
return 'fas fa-map-marker-alt';
|
||||
break;
|
||||
case 'user':
|
||||
return 'fas fa-user';
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -941,18 +941,21 @@ class AssetsController extends Controller
|
|||
* @since [v6.0]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function checkinByTag(Request $request)
|
||||
public function checkinByTag(Request $request, $tag = null)
|
||||
{
|
||||
$this->authorize('checkin', Asset::class);
|
||||
$asset = Asset::where('asset_tag', $request->input('asset_tag'))->first();
|
||||
if(null == $tag && null !== ($request->input('asset_tag'))) {
|
||||
$tag = $request->input('asset_tag');
|
||||
}
|
||||
$asset = Asset::where('asset_tag', $tag)->first();
|
||||
|
||||
if ($asset) {
|
||||
return $this->checkin($request, $asset->id);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset'=> e($request->input('asset_tag'))
|
||||
], 'Asset with tag '.e($request->input('asset_tag')).' not found'));
|
||||
'asset'=> e($tag)
|
||||
], 'Asset with tag '.e($tag).' not found'));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -630,7 +630,7 @@ class AssetsController extends Controller
|
|||
try {
|
||||
$results = $csv->getRecords();
|
||||
} catch (\Exception $e) {
|
||||
return back()->with('error', 'There was an error reading the CSV file: '.$e->getMessage());
|
||||
return back()->with('error', trans('general.error_in_import_file', ['error' => $e->getMessage()]));
|
||||
}
|
||||
$item = [];
|
||||
$status = [];
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Transformers\ImportsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Import;
|
||||
|
||||
class ImportsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('import');
|
||||
$imports = (new ImportsTransformer)->transformImports(Import::latest()->get());
|
||||
|
||||
return view('importer/import')->with('imports', $imports);
|
||||
}
|
||||
}
|
|
@ -123,6 +123,11 @@ class BulkUsersController extends Controller
|
|||
'warning' => trans('admin/users/message.bulk_manager_warn'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($request->input('null_location_id')=='1') {
|
||||
$this->update_array['location_id'] = null;
|
||||
}
|
||||
|
||||
if (! $manager_conflict) {
|
||||
$this->conditionallyAddItem('manager_id');
|
||||
}
|
||||
|
|
322
app/Http/Livewire/Importer.php
Normal file
322
app/Http/Livewire/Importer.php
Normal file
|
@ -0,0 +1,322 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use Livewire\Component;
|
||||
|
||||
use App\Models\Import;
|
||||
use Storage;
|
||||
|
||||
use Log;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
|
||||
|
||||
class Importer extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public $files;
|
||||
|
||||
public $progress; //upload progress - '-1' means don't show
|
||||
public $progress_message;
|
||||
public $progress_bar_class;
|
||||
|
||||
public $message; //status/error message?
|
||||
public $message_type; //success/error?
|
||||
|
||||
//originally from ImporterFile
|
||||
public $import_errors; //
|
||||
public ?Import $activeFile = null;
|
||||
public $importTypes;
|
||||
public $columnOptions;
|
||||
public $statusType;
|
||||
public $statusText;
|
||||
public $update;
|
||||
public $send_welcome;
|
||||
public $run_backup;
|
||||
public $field_map; // we need a separate variable for the field-mapping, because the keys in the normal array are too complicated for Livewire to understand
|
||||
public $file_id; // TODO: I can't figure out *why* we need this, but it really seems like we do. I can't seem to pull the id from the activeFile for some reason?
|
||||
|
||||
protected $rules = [
|
||||
'files.*.file_path' => 'required|string',
|
||||
'files.*.created_at' => 'required|string',
|
||||
'files.*.filesize' => 'required|integer',
|
||||
'activeFile' => 'Import',
|
||||
'activeFile.import_type' => 'string',
|
||||
'activeFile.field_map' => 'array',
|
||||
'activeFile.header_row' => 'array',
|
||||
'field_map' => 'array'
|
||||
];
|
||||
|
||||
public function generate_field_map()
|
||||
{
|
||||
\Log::debug("header row is: ".print_r($this->activeFile->header_row,true));
|
||||
\Log::debug("Field map is: ".print_r($this->field_map,true));
|
||||
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
|
||||
return json_encode(array_filter($tmp));
|
||||
}
|
||||
|
||||
// all of these 'statics', alas, may have to change to something else to handle translations?
|
||||
// I'm not sure. Maybe I use them to 'populate' the translations? TBH, I don't know yet.
|
||||
static $general = [
|
||||
'category' => 'Category',
|
||||
'company' => 'Company',
|
||||
'email' => 'Email',
|
||||
'item_name' => 'Item Name',
|
||||
'location' => 'Location',
|
||||
'maintained' => 'Maintained',
|
||||
'manufacturer' => 'Manufacturer',
|
||||
'notes' => 'Notes',
|
||||
'order_number' => 'Order Number',
|
||||
'purchase_cost' => 'Purchase Cost',
|
||||
'purchase_date' => 'Purchase Date',
|
||||
'quantity' => 'Quantity',
|
||||
'requestable' => 'Requestable',
|
||||
'serial' => 'Serial Number',
|
||||
'supplier' => 'Supplier',
|
||||
'username' => 'Username',
|
||||
'department' => 'Department',
|
||||
];
|
||||
|
||||
static $accessories = [
|
||||
'model_number' => 'Model Number',
|
||||
];
|
||||
|
||||
static $assets = [
|
||||
'asset_tag' => 'Asset Tag',
|
||||
'asset_model' => 'Model Name',
|
||||
'byod' => 'BYOD',
|
||||
'checkout_class' => 'Checkout Type',
|
||||
'checkout_location' => 'Checkout Location',
|
||||
'image' => 'Image Filename',
|
||||
'model_number' => 'Model Number',
|
||||
'full_name' => 'Full Name',
|
||||
'status' => 'Status',
|
||||
'warranty_months' => 'Warranty Months',
|
||||
];
|
||||
|
||||
static $consumables = [
|
||||
'item_no' => "Item Number",
|
||||
'model_number' => "Model Number",
|
||||
'min_amt' => "Minimum Quantity",
|
||||
];
|
||||
|
||||
static $licenses = [
|
||||
'asset_tag' => 'Assigned To Asset',
|
||||
'expiration_date' => 'Expiration Date',
|
||||
'full_name' => 'Full Name',
|
||||
'license_email' => 'Licensed To Email',
|
||||
'license_name' => 'Licensed To Name',
|
||||
'purchase_order' => 'Purchase Order',
|
||||
'reassignable' => 'Reassignable',
|
||||
'seats' => 'Seats',
|
||||
];
|
||||
|
||||
static $users = [
|
||||
'employee_num' => 'Employee Number',
|
||||
'first_name' => 'First Name',
|
||||
'jobtitle' => 'Job Title',
|
||||
'last_name' => 'Last Name',
|
||||
'phone_number' => 'Phone Number',
|
||||
'manager_first_name' => 'Manager First Name',
|
||||
'manager_last_name' => 'Manager Last Name',
|
||||
'activated' => 'Activated',
|
||||
'address' => 'Address',
|
||||
'city' => 'City',
|
||||
'state' => 'State',
|
||||
'country' => 'Country',
|
||||
'vip' => 'VIP'
|
||||
];
|
||||
|
||||
//array of "real fieldnames" to a list of aliases for that field
|
||||
static $aliases = [
|
||||
'model_number' =>
|
||||
[
|
||||
'model',
|
||||
'model no',
|
||||
'model no.',
|
||||
'model number',
|
||||
'model num',
|
||||
'model num.'
|
||||
],
|
||||
'warranty_months' =>
|
||||
[
|
||||
'Warranty',
|
||||
'Warranty Months'
|
||||
],
|
||||
'qty' =>
|
||||
[
|
||||
'QTY',
|
||||
'Quantity'
|
||||
],
|
||||
'min_amt' =>
|
||||
[
|
||||
'Min Amount',
|
||||
'Min QTY'
|
||||
],
|
||||
'next_audit_date' =>
|
||||
[
|
||||
'Next Audit',
|
||||
],
|
||||
|
||||
|
||||
];
|
||||
|
||||
private function getColumns($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'asset':
|
||||
$results = self::$general + self::$assets;
|
||||
break;
|
||||
case 'accessory':
|
||||
$results = self::$general + self::$accessories;
|
||||
break;
|
||||
case 'consumable':
|
||||
$results = self::$general + self::$consumables;
|
||||
break;
|
||||
case 'license':
|
||||
$results = self::$general + self::$licenses;
|
||||
break;
|
||||
case 'user':
|
||||
$results = self::$general + self::$users;
|
||||
break;
|
||||
default:
|
||||
$results = self::$general;
|
||||
}
|
||||
asort($results, SORT_FLAG_CASE | SORT_STRING);
|
||||
if ($type == "asset") {
|
||||
// add Custom Fields after a horizontal line
|
||||
$results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’";
|
||||
foreach (CustomField::orderBy('name')->get() as $field) {
|
||||
$results[$field->db_column_name()] = $field->name;
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function updating($name, $new_import_type)
|
||||
{
|
||||
if ($name == "activeFile.import_type") {
|
||||
\Log::info("WE ARE CHANGING THE import_type!!!!! TO: " . $new_import_type);
|
||||
\Log::info("so, what's \$this->>field_map at?: " . print_r($this->field_map, true));
|
||||
// go through each header, find a matching field to try and map it to.
|
||||
foreach ($this->activeFile->header_row as $i => $header) {
|
||||
// do we have something mapped already?
|
||||
if (array_key_exists($i, $this->field_map)) {
|
||||
// yes, we do. Is it valid for this type of import?
|
||||
// (e.g. the import type might have been changed...?)
|
||||
if (array_key_exists($this->field_map[$i], $this->columnOptions[$new_import_type])) {
|
||||
//yes, this key *is* valid. Continue on to the next field.
|
||||
continue;
|
||||
} else {
|
||||
//no, this key is *INVALID* for this import type. Better set it to null
|
||||
// and we'll hope that the aliases or something else picks it up.
|
||||
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
|
||||
} // TODO - strictly speaking, this isn't necessary here I don't think.
|
||||
}
|
||||
// first, check for exact matches
|
||||
foreach ($this->columnOptions[$new_import_type] as $value => $text) {
|
||||
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
|
||||
$this->field_map[$i] = $value;
|
||||
continue 2; //don't bother with the alias check, go to the next header
|
||||
}
|
||||
}
|
||||
// if you got here, we didn't find a match. Try the aliases
|
||||
foreach (self::$aliases as $key => $alias_values) {
|
||||
foreach ($alias_values as $alias_value) {
|
||||
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
|
||||
// Make *absolutely* sure that this key actually _exists_ in this import type -
|
||||
// you can trigger this by importing accessories with a 'Warranty' column (which don't exist
|
||||
// in "Accessories"!)
|
||||
if (array_key_exists($key, $this->columnOptions[$new_import_type])) {
|
||||
$this->field_map[$i] = $key;
|
||||
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// and if you got here, we got nothing. Let's recommend 'null'
|
||||
$this->field_map[$i] = null; // Booooo :(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function boot() { // FIXME - delete or undelete.
|
||||
///////$this->activeFile = null; // I do *not* understand why I have to do this, but, well, whatever.
|
||||
}
|
||||
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->authorize('import');
|
||||
$this->progress = -1; // '-1' means 'don't show the progressbar'
|
||||
$this->progress_bar_class = 'progress-bar-warning';
|
||||
\Log::info("Hey, we are calling MOUNT (in the importer-file) !!!!!!!!"); //fcuk
|
||||
$this->importTypes = [
|
||||
'asset' => trans('general.assets'),
|
||||
'accessory' => trans('general.accessories'),
|
||||
'consumable' => trans('general.consumables'),
|
||||
'component' => trans('general.components'),
|
||||
'license' => trans('general.licenses'),
|
||||
'user' => trans('general.users'),
|
||||
];
|
||||
|
||||
$this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean
|
||||
foreach($this->importTypes AS $type => $name) {
|
||||
$this->columnOptions[$type] = $this->getColumns($type);
|
||||
}
|
||||
if ($this->activeFile) {
|
||||
$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : [];
|
||||
}
|
||||
}
|
||||
|
||||
public function selectFile($id)
|
||||
{
|
||||
\Log::info("TOGGLE EVENT FIRED!");
|
||||
\Log::error("The ID we are trying to find is AS FOLLOWS: ".$id);
|
||||
$this->activeFile = Import::find($id);
|
||||
$this->field_map = null;
|
||||
foreach($this->activeFile->header_row as $element) {
|
||||
if(isset($this->activeFile->field_map[$element])) {
|
||||
$this->field_map[] = $this->activeFile->field_map[$element];
|
||||
} else {
|
||||
$this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings
|
||||
}
|
||||
}
|
||||
//$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : []; // this is wrong
|
||||
$this->file_id = $id;
|
||||
$this->import_errors = null;
|
||||
$this->statusText = null;
|
||||
\Log::error("The import type we are about to try and load up is gonna be this: ".$this->activeFile->import_type);
|
||||
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
// TODO: why don't we just do File::find($id)? This seems dumb.
|
||||
foreach($this->files as $file) {
|
||||
\Log::debug("File id is: ".$file->id);
|
||||
if($id == $file->id) {
|
||||
if(Storage::delete('private_uploads/imports/'.$file->file_path)) {
|
||||
$file->delete();
|
||||
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_success');
|
||||
$this->message_type = 'success';
|
||||
return;
|
||||
} else {
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_error');
|
||||
$this->message_type = 'danger';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
|
||||
return view('livewire.importer')
|
||||
->extends('layouts.default')
|
||||
->section('content');
|
||||
}
|
||||
}
|
|
@ -84,7 +84,7 @@ class AssetsTransformer
|
|||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'),
|
||||
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
|
||||
'age' => $asset->purchase_date ? Helper::AgeFormat($asset->purchase_date) : '',
|
||||
'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '',
|
||||
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
|
||||
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
|
||||
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
|
||||
|
|
|
@ -238,7 +238,7 @@ class CustomField extends Model
|
|||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
* @return string
|
||||
*/
|
||||
public function db_column_name()
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||
{
|
||||
Route::group([
|
||||
'middleware' => 'web',
|
||||
'namespace' => $this->namespace,
|
||||
// 'namespace' => $this->namespace, //okay, I don't know what this means, but somehow this might be a problem for us?
|
||||
], function ($router) {
|
||||
require base_path('routes/web/hardware.php');
|
||||
require base_path('routes/web/models.php');
|
||||
|
@ -65,7 +65,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||
{
|
||||
Route::group([
|
||||
'middleware' => 'auth:api',
|
||||
'namespace' => $this->namespace,
|
||||
// 'namespace' => $this->namespace, // this might also be a problem? I don't really know :/
|
||||
'prefix' => 'api',
|
||||
], function ($router) {
|
||||
require base_path('routes/api.php');
|
||||
|
|
Binary file not shown.
BIN
public/js/dist/all.js
vendored
BIN
public/js/dist/all.js
vendored
Binary file not shown.
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"/js/build/app.js": "/js/build/app.js?id=8dad04ad2d75bbdf1d44f823292edc0e",
|
||||
"/js/build/app.js": "/js/build/app.js?id=4300e10fce87e56d57d190b9487a5788",
|
||||
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=392cc93cfc0be0349bab9697669dd091",
|
||||
"/css/build/overrides.css": "/css/build/overrides.css?id=65f62931cea7258a2b655268f7085cbd",
|
||||
"/css/build/app.css": "/css/build/app.css?id=eeb524c41b0c1f1713410f3a264b3ed1",
|
||||
|
@ -34,7 +34,7 @@
|
|||
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=ee4896df8b8f008ce73a9a0c2549aefd",
|
||||
"/js/build/vendor.js": "/js/build/vendor.js?id=47ecbb4bb3b0e02315f391caadbdf971",
|
||||
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=14d9a2affec7b066d20fcba2e6e67ad2",
|
||||
"/js/dist/all.js": "/js/dist/all.js?id=b7b40d82fc946ac9bce5e775087e9cc9",
|
||||
"/js/dist/all.js": "/js/dist/all.js?id=7dc8edf9b594414cb1141bbc39d15383",
|
||||
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
|
||||
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=8f449de09e00c15481b144b44b1a7185",
|
||||
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
|
||||
<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="fas fa-check faa-pulse animated" aria-hidden="true" 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>
|
|
@ -1,42 +0,0 @@
|
|||
|
||||
<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>
|
|
@ -1,327 +0,0 @@
|
|||
<template>
|
||||
<tr v-show="processDetail">
|
||||
<td colspan="5">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="row">
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-5 col-xs-12">
|
||||
<label for="import-type">Import Type:</label>
|
||||
</div>
|
||||
|
||||
<div class="col-md-7 col-xs-12">
|
||||
<select2 :options="options.importTypes" v-model="options.importType" required>
|
||||
<option disabled value="0"></option>
|
||||
</select2>
|
||||
</div>
|
||||
|
||||
</div><!-- /dynamic-form-row -->
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-5 col-xs-12">
|
||||
<label for="import-update">Update Existing Values?:</label>
|
||||
</div>
|
||||
<div class="col-md-7 col-xs-12">
|
||||
<input type="checkbox" class="icheckbox_minimal" name="import-update" v-model="options.update">
|
||||
</div>
|
||||
</div><!-- /dynamic-form-row -->
|
||||
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-5 col-xs-12">
|
||||
<label for="send-welcome">Send Welcome Email for new Users?</label>
|
||||
</div>
|
||||
<div class="col-md-7 col-xs-12">
|
||||
<input type="checkbox" class="icheckbox_minimal" name="send-welcome" v-model="options.send_welcome">
|
||||
</div>
|
||||
</div><!-- /dynamic-form-row -->
|
||||
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-5 col-xs-12">
|
||||
<label for="run-backup">Backup before importing?</label>
|
||||
</div>
|
||||
<div class="col-md-7 col-xs-12">
|
||||
<input type="checkbox" class="icheckbox_minimal" name="run-backup" v-model="options.run_backup">
|
||||
</div>
|
||||
</div><!-- /dynamic-form-row -->
|
||||
|
||||
<div class="alert col-md-8 col-md-offset-2" style="text-align:left"
|
||||
:class="alertClass"
|
||||
v-if="statusText">
|
||||
{{ this.statusText }}
|
||||
</div><!-- /alert -->
|
||||
</div> <!-- /div row -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="padding-top: 30px;">
|
||||
<div class="col-md-4 text-right"><h4>Header Field</h4></div>
|
||||
<div class="col-md-4"><h4>Import Field</h4></div>
|
||||
<div class="col-md-4"><h4>Sample Value</h4></div>
|
||||
</div>
|
||||
</div><!-- /div row -->
|
||||
|
||||
<template v-for="(header, index) in file.header_row">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-4 text-right">
|
||||
<label :for="header" class="control-label">{{ header }}</label>
|
||||
</div>
|
||||
<div class="col-md-4 form-group">
|
||||
<div required>
|
||||
<select2 :options="columns" v-model="columnMappings[header]">
|
||||
<option value="0">Do Not Import</option>
|
||||
</select2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<p class="form-control-static">{{ activeFile.first_row[index] }}</p>
|
||||
</div>
|
||||
</div><!-- /div col-md-8 -->
|
||||
</div><!-- /div row -->
|
||||
</template>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-2 text-right" style="padding-top: 20px;">
|
||||
<button type="button" class="btn btn-sm btn-default" @click="processDetail = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary" @click="postSave">Import</button>
|
||||
<br><br>
|
||||
</div>
|
||||
</div><!-- /div row -->
|
||||
<div class="row">
|
||||
<div class="alert col-md-8 col-md-offset-2" style="padding-top: 20px;"
|
||||
:class="alertClass"
|
||||
v-if="statusText">
|
||||
{{ this.statusText }}
|
||||
</div>
|
||||
</div><!-- /div row -->
|
||||
|
||||
</div><!-- /div v-show -->
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var baseUrl = $('meta[name="baseUrl"]').attr('content');
|
||||
export default {
|
||||
props: ['file', 'customFields'],
|
||||
data() {
|
||||
return {
|
||||
activeFile: this.file,
|
||||
processDetail: false,
|
||||
statusText: null,
|
||||
statusType: null,
|
||||
options: {
|
||||
importType: this.file.import_type,
|
||||
update: false,
|
||||
send_welcome: false,
|
||||
run_backup: false,
|
||||
importTypes: [
|
||||
{ id: 'asset', text: 'Assets' },
|
||||
{ id: 'accessory', text: 'Accessories' },
|
||||
{ id: 'consumable', text: 'Consumables' },
|
||||
{ id: 'component', text: 'Components' },
|
||||
{ id: 'license', text: 'Licenses' },
|
||||
{ id: 'user', text: 'Users' }
|
||||
],
|
||||
statusText: null,
|
||||
},
|
||||
columnOptions: {
|
||||
general: [
|
||||
{id: 'category', text: 'Category' },
|
||||
{id: 'company', text: 'Company' },
|
||||
{id: 'email', text: 'Email' },
|
||||
{id: 'item_name', text: 'Item Name' },
|
||||
{id: 'location', text: 'Location' },
|
||||
{id: 'maintained', text: 'Maintained' },
|
||||
{id: 'manufacturer', text: 'Manufacturer' },
|
||||
{id: 'order_number', text: 'Order Number' },
|
||||
{id: 'purchase_cost', text: 'Purchase Cost' },
|
||||
{id: 'purchase_date', text: 'Purchase Date' },
|
||||
{id: 'quantity', text: 'Quantity' },
|
||||
{id: 'requestable', text: 'Requestable' },
|
||||
{id: 'serial', text: 'Serial Number' },
|
||||
{id: 'supplier', text: 'Supplier' },
|
||||
{id: 'username', text: 'Username' },
|
||||
{id: 'department', text: 'Department' },
|
||||
],
|
||||
accessories:[
|
||||
{id: 'model_number', text: 'Model Number'},
|
||||
{id: 'notes', text: 'Notes' },
|
||||
],
|
||||
assets: [
|
||||
{id: 'asset_tag', text: 'Asset Tag' },
|
||||
{id: 'asset_model', text: 'Model Name' },
|
||||
{id: 'checkout_class', text: 'Checkout Type' },
|
||||
{id: 'checkout_location', text: 'Checkout Location' },
|
||||
{id: 'image', text: 'Image Filename' },
|
||||
{id: 'model_number', text: 'Model Number' },
|
||||
{id: 'asset_notes', text: 'Asset Notes' },
|
||||
{id: 'model_notes', text: 'Model Notes' },
|
||||
{id: 'full_name', text: 'Full Name' },
|
||||
{id: 'status', text: 'Status' },
|
||||
{id: 'warranty_months', text: 'Warranty Months' },
|
||||
{id: 'last_audit_date', text: 'Last Audit Date' },
|
||||
{id: 'next_audit_date', text: 'Audit Date' },
|
||||
{id: 'byod', text: 'BYOD' },
|
||||
],
|
||||
consumables: [
|
||||
{id: 'item_no', text: "Item Number"},
|
||||
{id: 'model_number', text: "Model Number"},
|
||||
{id: 'min_amt', text: "Minimum Quantity"},
|
||||
{id: 'notes', text: 'Notes' },
|
||||
],
|
||||
licenses: [
|
||||
{id: 'asset_tag', text: 'Assigned To Asset'},
|
||||
{id: 'expiration_date', text: 'Expiration Date' },
|
||||
{id: 'full_name', text: 'Full Name' },
|
||||
{id: 'license_email', text: 'Licensed To Email' },
|
||||
{id: 'license_name', text: 'Licensed To Name' },
|
||||
{id: 'notes', text: 'Notes' },
|
||||
{id: 'purchase_order', text: 'Purchase Order' },
|
||||
{id: 'reassignable', text: 'Reassignable' },
|
||||
{id: 'seats', text: 'Seats' },
|
||||
],
|
||||
users: [
|
||||
{id: 'employee_num', text: 'Employee Number' },
|
||||
{id: 'first_name', text: 'First Name' },
|
||||
{id: 'jobtitle', text: 'Job Title' },
|
||||
{id: 'last_name', text: 'Last Name' },
|
||||
{id: 'phone_number', text: 'Phone Number' },
|
||||
{id: 'manager_first_name', text: 'Manager First Name' },
|
||||
{id: 'notes', text: 'Notes' },
|
||||
{id: 'manager_last_name', text: 'Manager Last Name' },
|
||||
{id: 'activated', text: 'Activated' },
|
||||
{id: 'address', text: 'Address' },
|
||||
{id: 'city', text: 'City' },
|
||||
{id: 'state', text: 'State' },
|
||||
{id: 'country', text: 'Country' },
|
||||
{id: 'zip', text: 'ZIP' },
|
||||
{id: 'remote', text: 'Remote'},
|
||||
{id: 'vip', text: 'VIP'},
|
||||
|
||||
],
|
||||
customFields: this.customFields,
|
||||
},
|
||||
columnMappings: this.file.field_map || {},
|
||||
activeColumn: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.eventHub.$on('showDetails', this.toggleExtendedDisplay)
|
||||
this.populateSelect2ActiveItems();
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
// function to sort objects by their display text.
|
||||
function sorter(a,b) {
|
||||
if (a.text < b.text)
|
||||
return -1;
|
||||
if (a.text > b.text)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
switch(this.options.importType) {
|
||||
case 'asset':
|
||||
return this.columnOptions.general
|
||||
.concat(this.columnOptions.assets)
|
||||
.concat(this.columnOptions.customFields)
|
||||
.sort(sorter);
|
||||
case 'accessory':
|
||||
return this.columnOptions.general
|
||||
.concat(this.columnOptions.accessories)
|
||||
.sort(sorter);
|
||||
case 'consumable':
|
||||
console.log('Returned consumable');
|
||||
return this.columnOptions.general
|
||||
.concat(this.columnOptions.consumables)
|
||||
.sort(sorter);
|
||||
case 'license':
|
||||
return this.columnOptions.general.concat(this.columnOptions.licenses).sort(sorter);
|
||||
case 'user':
|
||||
return this.columnOptions.general.concat(this.columnOptions.users).sort(sorter);
|
||||
}
|
||||
return this.columnOptions.general;
|
||||
},
|
||||
alertClass() {
|
||||
if(this.statusType=='success') {
|
||||
return 'alert-success';
|
||||
}
|
||||
if(this.statusType=='error') {
|
||||
return 'alert-danger';
|
||||
}
|
||||
return 'alert-info';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
columns() {
|
||||
this.populateSelect2ActiveItems();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
postSave() {
|
||||
console.log('saving');
|
||||
console.log(this.options.importType);
|
||||
if(!this.options.importType) {
|
||||
this.statusType='error';
|
||||
this.statusText= "An import type is required... ";
|
||||
return;
|
||||
}
|
||||
this.statusType='pending';
|
||||
this.statusText = "Processing...";
|
||||
this.$http.post(baseUrl + 'api/v1/imports/process/' + this.file.id, {
|
||||
'import-update': this.options.update,
|
||||
'send-welcome': this.options.send_welcome,
|
||||
'import-type': this.options.importType,
|
||||
'run-backup': this.options.run_backup,
|
||||
'column-mappings': this.columnMappings
|
||||
}).then( ({body}) => {
|
||||
// Success
|
||||
this.statusType="success";
|
||||
this.statusText = "Success... Redirecting.";
|
||||
window.location.href = body.messages.redirect_url;
|
||||
}, ({body}) => {
|
||||
// Failure
|
||||
if(body.status == 'import-errors') {
|
||||
window.eventHub.$emit('importErrors', body.messages);
|
||||
this.statusType='error';
|
||||
this.statusText = "Error";
|
||||
} else {
|
||||
this.$emit('alert', {
|
||||
message: body.messages,
|
||||
type: "danger",
|
||||
visible: true,
|
||||
})
|
||||
}
|
||||
this.displayImportModal=false;
|
||||
});
|
||||
},
|
||||
populateSelect2ActiveItems() {
|
||||
if(this.file.field_map == null) {
|
||||
// Begin by populating the active selection in dropdowns with blank values.
|
||||
for (var i=0; i < this.file.header_row.length; i++) {
|
||||
this.$set(this.columnMappings, this.file.header_row[i], null);
|
||||
}
|
||||
// Then, for any values that have a likely match, we make that active.
|
||||
for(var j=0; j < this.columns.length; j++) {
|
||||
let column = this.columns[j];
|
||||
let lower = this.file.header_row.map((value) => value.toLowerCase());
|
||||
let index = lower.indexOf(column.text.toLowerCase())
|
||||
if(index != -1) {
|
||||
this.$set(this.columnMappings, this.file.header_row[index], column.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleExtendedDisplay(fileId) {
|
||||
if(fileId == this.file.id) {
|
||||
this.processDetail = !this.processDetail
|
||||
}
|
||||
},
|
||||
updateModel(header, value) {
|
||||
this.columnMappings[header] = value;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
select2: require('../select2.vue').default
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,130 +0,0 @@
|
|||
|
||||
|
||||
<script>
|
||||
require('blueimp-file-upload');
|
||||
var baseUrl = $('meta[name="baseUrl"]').attr('content');
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
files: [],
|
||||
displayImportModal: false,
|
||||
activeFile: null,
|
||||
alert: {
|
||||
type: null,
|
||||
message: null,
|
||||
visible: false,
|
||||
},
|
||||
importErrors: null,
|
||||
progress: {
|
||||
currentClass: "progress-bar-warning",
|
||||
currentPercent: "0",
|
||||
statusText: '',
|
||||
visible: false
|
||||
},
|
||||
customFields: [],
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.eventHub.$on('importErrors', this.updateImportErrors);
|
||||
this.fetchFiles();
|
||||
this.fetchCustomFields();
|
||||
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);
|
||||
console.log(data.result.header_row);
|
||||
},
|
||||
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";
|
||||
// Display any errors returned from the $.ajax()
|
||||
vm.progress.statusText = data.jqXHR.responseJSON.messages;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchFiles() {
|
||||
this.$http.get(baseUrl + '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...";
|
||||
});
|
||||
},
|
||||
fetchCustomFields() {
|
||||
this.$http.get(baseUrl + 'api/v1/fields')
|
||||
.then( ({data}) => {
|
||||
data = data.rows;
|
||||
data.forEach((item) => {
|
||||
this.customFields.push({
|
||||
'id': item.db_column_name,
|
||||
'text': item.name,
|
||||
})
|
||||
})
|
||||
});
|
||||
},
|
||||
deleteFile(file, key) {
|
||||
this.$http.delete(baseUrl + 'api/v1/imports/' + file.id)
|
||||
.then(
|
||||
// Success, remove file from array.
|
||||
(response) => {
|
||||
this.files.splice(key, 1);
|
||||
this.alert.type = response.body.status; // A failed delete can still cause a 200 status code.
|
||||
this.alert.visible = true;
|
||||
this.alert.message = response.body.messages;
|
||||
},
|
||||
(response) => {// Fail
|
||||
// this.files.splice(key, 1);
|
||||
this.alert.type="error";
|
||||
this.alert.visible=true;
|
||||
this.alert.message=response.body.messages;
|
||||
}
|
||||
);
|
||||
},
|
||||
toggleEvent(fileId) {
|
||||
window.eventHub.$emit('showDetails', fileId)
|
||||
},
|
||||
updateAlert(alert) {
|
||||
this.alert = alert;
|
||||
},
|
||||
updateImportErrors(errors) {
|
||||
this.importErrors = errors;
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
progressWidth() {
|
||||
return "width: "+this.progress.currentPercent*10+'%';
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
alert: require('../alert.vue').default,
|
||||
errors: require('./importer-errors.vue').default,
|
||||
importFile: require('./importer-file.vue').default,
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -605,3 +605,54 @@ function htmlEntities(str) {
|
|||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
/**
|
||||
* Universal Livewire Select2 and iCheck integration
|
||||
*
|
||||
* How to use:
|
||||
*
|
||||
* 1. Set the class of your select2 elements to 'livewire-select2' and your icheck elements to 'livewire-icheck' (as appropriate).
|
||||
* (For iCheck, you may still need to apply the other iCheck classes like 'minimal' or 'iCheck')
|
||||
* 2. Name your element to match a property in your Livewire component
|
||||
* 3. Add an attribute called 'data-livewire-component' that points to $_instance->id (via `{{ }}` if you're in a blade,
|
||||
* or just $_instance->id if not).
|
||||
* 4. For iCheck, you need to wrap the 'checkbox' element with wire:ignore - perhaps in the <label> if it wraps the
|
||||
* <input> element, or just put a <span wire:ignore></span> around just the input element.
|
||||
* 5. If you have dynamically shown/hidden checkboxes, you might need to initialize iCheck on them on component page-load.
|
||||
* Just use $('.livewire-icheck').iCheck(), or for the minimal-style, use:
|
||||
*
|
||||
* $('input[type="checkbox"].minimal.livewire-icheck, input[type="radio"].minimal.livewire-icheck').iCheck({
|
||||
* checkboxClass: 'icheckbox_minimal-blue',
|
||||
* radioClass: 'iradio_minimal-blue'
|
||||
* });
|
||||
*
|
||||
* (which is stolen from above here in this JS file)
|
||||
*/
|
||||
$(function () {
|
||||
$('.livewire-select2').select2()
|
||||
|
||||
$(document).on('select2:select', '.livewire-select2', function (event) {
|
||||
var target = $(event.target)
|
||||
if(!event.target.name || !target.data('livewire-component')) {
|
||||
console.error("You need to set both name (which should match a Livewire property) and data-livewire-component on your Livewire-ed select2 elements!")
|
||||
console.error("For data-livewire-component, you probably want to use $_instance->id or {{ $_instance->id }}, as appropriate")
|
||||
return false
|
||||
}
|
||||
window.livewire.find(target.data('livewire-component')).set(event.target.name, this.options[this.selectedIndex].value)
|
||||
})
|
||||
|
||||
window.livewire.hook('message.processed', function (el,component) {
|
||||
$('.livewire-select2').select2();
|
||||
//$('.livewire-icheck').iCheck(); //this seems to blow up pretty badly.
|
||||
});
|
||||
|
||||
$(document).on('ifToggled', '.livewire-icheck', function (event) {
|
||||
if(!event.target.name || !$(event.target).data('livewire-component')) {
|
||||
console.error("You need to set both name (which should match a Livewire property) and data-livewire-component on your iCheck elements!")
|
||||
console.error("For data-livewire-component, you probably want to use $_instance->id or {{ $_instance->id }}, as appropriate")
|
||||
return false
|
||||
}
|
||||
window.livewire.find($(event.target).data('livewire-component')).set(event.target.name, event.target.checked)
|
||||
})
|
||||
|
||||
})
|
|
@ -26,10 +26,11 @@ Vue.component(
|
|||
require('./components/passport/PersonalAccessTokens.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'importer',
|
||||
require('./components/importer/importer.vue').default
|
||||
);
|
||||
// This component has been removed and replaced with a Livewire implementation
|
||||
// Vue.component(
|
||||
// 'importer',
|
||||
// require('./components/importer/importer.vue').default
|
||||
// );
|
||||
|
||||
// This component has been removed and replaced with a Livewire implementation
|
||||
// Vue.component(
|
||||
|
|
|
@ -35,7 +35,6 @@ return [
|
|||
'superadmin_permission_warning' => 'Only superadmins may grant a user superadmin access.',
|
||||
'admin_permission_warning' => 'Only users with admins rights or greater may grant a user admin access.',
|
||||
'remove_group_memberships' => 'Remove Group Memberships',
|
||||
'warning_deletion' => 'WARNING:',
|
||||
'warning_deletion_information' => 'You are about to checkin ALL items from the :count user(s) listed below. Super admin names are highlighted in red.',
|
||||
'update_user_assets_status' => 'Update all assets for these users to this status',
|
||||
'checkin_user_properties' => 'Check in all properties associated with these users',
|
||||
|
|
|
@ -158,6 +158,8 @@ return [
|
|||
'asset_maintenances' => 'Asset Maintenances',
|
||||
'item' => 'Item',
|
||||
'item_name' => 'Item Name',
|
||||
'import_file' => 'import CSV file',
|
||||
'import_type' => 'CSV import type',
|
||||
'insufficient_permissions' => 'Insufficient permissions!',
|
||||
'kits' => 'Predefined Kits',
|
||||
'language' => 'Language',
|
||||
|
@ -228,6 +230,7 @@ return [
|
|||
'requested_assets_menu' => 'Requested Assets',
|
||||
'request_canceled' => 'Request Canceled',
|
||||
'save' => 'Save',
|
||||
'select_var' => 'Select :thing... ', // this will eventually replace all of our other selects
|
||||
'select' => 'Select',
|
||||
'select_all' => 'Select All',
|
||||
'search' => 'Search',
|
||||
|
@ -263,7 +266,6 @@ return [
|
|||
'sure_to_delete' => 'Are you sure you wish to delete',
|
||||
'submit' => 'Submit',
|
||||
'target' => 'Target',
|
||||
'toggle_navigation' => 'Toogle Navigation',
|
||||
'time_and_date_display' => 'Time and Date Display',
|
||||
'total_assets' => 'total assets',
|
||||
'total_licenses' => 'total licenses',
|
||||
|
@ -385,7 +387,8 @@ return [
|
|||
'bulk_soft_delete' =>'Also soft-delete these users. Their asset history will remain intact unless/until you purge deleted records in the Admin Settings.',
|
||||
'bulk_checkin_delete_success' => 'Your selected users have been deleted and their items have been checked in.',
|
||||
'bulk_checkin_success' => 'The items for the selected users have been checked in.',
|
||||
'set_to_null' => 'Delete values for this asset|Delete values for all :asset_count assets ',
|
||||
'set_to_null' => 'Delete values for this asset|Delete values for all :asset_count assets ',
|
||||
'set_users_field_to_null' => 'Delete :field values for this user|Delete :field values for all :user_count users ',
|
||||
'na_no_purchase_date' => 'N/A - No purchase date provided',
|
||||
'assets_by_status' => 'Assets by Status',
|
||||
'assets_by_status_type' => 'Assets by Status Type',
|
||||
|
@ -418,5 +421,16 @@ return [
|
|||
'merged_log_this_user_into' => 'Merged this user (ID :to_id - :to_username) into user ID :from_id (:from_username) ',
|
||||
'merged_log_this_user_from' => 'Merged user ID :from_id (:from_username) into this user (ID :to_id - :to_username)',
|
||||
'clear_and_save' => 'Clear & Save',
|
||||
|
||||
'update_existing_values' => 'Update Existing Values?',
|
||||
'send_welcome_email_to_users' => ' Send Welcome Email for new Users?',
|
||||
'back_before_importing' => 'Backup before importing?',
|
||||
'csv_header_field' => 'CSV Header Field',
|
||||
'import_field' => 'Import Field',
|
||||
'sample_value' => 'Sample Value',
|
||||
'no_headers' => 'No Columns Found',
|
||||
'error_in_import_file' => 'There was an error reading the CSV file: :error',
|
||||
'percent_complete' => ':percent % Complete',
|
||||
'errors_importing' => 'Some Errors occurred while importing: ',
|
||||
'warning' => 'WARNING: :warning',
|
||||
'success_redirecting' => '"Success... Redirecting.',
|
||||
];
|
|
@ -145,7 +145,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Next audit Date -->
|
||||
<div class="form-group {{ $errors->has('expected_checkin') ? ' has-error' : '' }}">
|
||||
<div class="form-group {{ $errors->has('next_audit_date') ? ' has-error' : '' }}">
|
||||
<label for="next_audit_date" class="col-md-3 control-label">{{ trans('general.next_audit_date') }}</label>
|
||||
<div class="col-md-3">
|
||||
<div class="input-group date" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-autoclose="true">
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
@extends('layouts/default')
|
||||
|
||||
{{-- Page title --}}
|
||||
@section('title')
|
||||
{{ trans('general.import') }}
|
||||
@parent
|
||||
@stop
|
||||
|
||||
{{-- Page content --}}
|
||||
@section('content')
|
||||
{{-- Hide importer until vue has rendered it, if we continue using vue for other things we should move this higher in the style --}}
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display:none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="app">
|
||||
<importer inline-template v-cloak>
|
||||
<div class="row">
|
||||
<alert v-show="alert.visible" :alert-type="alert.type" v-on:hide="alert.visible = false">@{{ alert.message }}</alert>
|
||||
<errors :errors="importErrors"></errors>
|
||||
|
||||
<div class="col-md-9">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="col-md-9" v-show="progress.visible" style="padding-bottom:20px">
|
||||
<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 class="col-md-3 text-right pull-right">
|
||||
|
||||
<!-- The fileinput-button span is used to style the file input field as button -->
|
||||
@if (!config('app.lock_passwords'))
|
||||
<span class="btn btn-primary fileinput-button">
|
||||
<span>{{ trans('button.select_file') }}</span>
|
||||
<!-- The file input field used as target for the file upload widget -->
|
||||
<label for="files[]"><span class="sr-only">{{ trans('button.select_file') }}</span></label>
|
||||
<input id="fileupload" type="file" name="files[]" data-url="{{ route('api.imports.index') }}" accept="text/csv" aria-label="files[]">
|
||||
</span>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 table-responsive" style="padding-top: 30px;">
|
||||
|
||||
<table data-pagination="true"
|
||||
data-id-table="upload-table"
|
||||
data-search="true"
|
||||
data-side-pagination="client"
|
||||
id="upload-table"
|
||||
class="col-md-12 table table-striped snipe-table">
|
||||
|
||||
<tr>
|
||||
<th class="col-md-6">{{ trans('general.file_name') }}</th>
|
||||
<th class="col-md-3">{{ trans('general.created_at') }}</th>
|
||||
<th class="col-md-1">{{ trans('general.filesize') }}</th>
|
||||
<th class="col-md-1 text-right"><span class="sr-only">{{ trans('general.actions') }}</span></th>
|
||||
</tr>
|
||||
|
||||
<template v-for="(currentFile, index) in files">
|
||||
<tr>
|
||||
<td class="col-md-6">@{{ currentFile.file_path }}</td>
|
||||
<td class="col-md-3">@{{ currentFile.created_at }} </td>
|
||||
<td class="col-md-1">@{{ currentFile.filesize }}</td>
|
||||
<td class="col-md-1 text-right">
|
||||
<button class="btn btn-sm btn-info" @click="toggleEvent(currentFile.id)">
|
||||
<i class="fas fa-retweet fa-fw" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ trans('general.import') }}</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm btn-danger" @click="deleteFile(currentFile, index)">
|
||||
<i class="fas fa-trash icon-white" aria-hidden="true"></i><span class="sr-only"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
<import-file
|
||||
:key="currentFile.id"
|
||||
:file="currentFile"
|
||||
:custom-fields="customFields"
|
||||
@alert="updateAlert(alert)">
|
||||
</import-file>
|
||||
</template>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>{{ trans('general.importing') }}</h2>
|
||||
<p>{!! trans('general.importing_help') !!}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</importer>
|
||||
</div>
|
||||
@stop
|
||||
|
||||
@section('moar_scripts')
|
||||
<script nonce="{{ csrf_token() }}">
|
||||
new Vue({
|
||||
el: '#app'
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,15 +1,13 @@
|
|||
<span> {{-- This <span> doesn't seem to fix it, neither does a div? --}}
|
||||
<div class="form-group{{ $errors->has('custom_fieldset') ? ' has-error' : '' }}">
|
||||
<label for="custom_fieldset" class="col-md-3 control-label">{{ trans('admin/models/general.fieldset') }}</label>
|
||||
<span wire:ignore> {{-- wire:ignore is because Select 2 mangles the dom in many awful ways, and so does iCheckbox --}}
|
||||
<div class="col-md-9">
|
||||
{{ Form::select('custom_fieldset', Helper::customFieldsetList(), old('custom_fieldset', $fieldset_id), array('class'=>'select2 js-fieldset-field', 'style'=>'width:350px', 'aria-label'=>'custom_fieldset', 'wire:model' => 'fieldset_id', 'id' => 'glooobits')) }} {{-- when we have this wrapped in 'ignore', the wire:model won't work --}}
|
||||
{{ Form::select('fieldset_id', Helper::customFieldsetList(), old('fieldset_id', $fieldset_id), array('class'=>'select2 js-fieldset-field livewire-select2', 'style'=>'width:350px', 'aria-label'=>'custom_fieldset', 'data-livewire-component' => $_instance->id)) }}
|
||||
{!! $errors->first('custom_fieldset', '<span class="alert-msg" aria-hidden="true"><br><i class="fas fa-times"></i> :message</span>') !!}
|
||||
<label class="m-l-xs">
|
||||
{{ Form::checkbox('add_default_values', 1, Request::old('add_default_values', $add_default_values), ['class' => 'minimal', 'wire:model' => "add_default_values", 'id' => 'add_default_values']) }}
|
||||
<label class="m-l-xs" wire:ignore>
|
||||
{{ Form::checkbox('add_default_values', 1, Request::old('add_default_values', $add_default_values), ['class' => 'minimal livewire-icheck', 'data-livewire-component' => $_instance->id, 'id' => 'add_default_values']) }}
|
||||
{{ trans('admin/models/general.add_default_values') }}
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@if ($this->add_default_values ) {{-- 'if the checkbox is enabled *AND* there are more than 0 fields in the fieldsset' --}}
|
||||
|
@ -63,34 +61,4 @@
|
|||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<script>
|
||||
// *still* haven't figured out why this doesn't seem to work at all...
|
||||
// And even if it did, I hate having $(function () {}) as my DOM-ready checker in some places, and
|
||||
// DOMContentLoaded in another...
|
||||
/* document.addEventListener("DOMContentLoaded", function () {
|
||||
Livewire.hook('component.initialized', function (component) {
|
||||
$('#glooobits').on('select2:select',function (event) { //'change' seems to be the jquery-compatible version but I think the select2 versions might be nicer.
|
||||
console.log("Select2 has changed!!!!!")
|
||||
console.dir(event)
|
||||
@this.set('fieldset_id',event.params.data.id)
|
||||
// Livewire.first().set('fieldset_id',event.params.data.id) // I still don't know why @this does'nt work here?
|
||||
})
|
||||
|
||||
})
|
||||
}) */
|
||||
|
||||
</script>
|
||||
@push('js')
|
||||
<script>
|
||||
$(function () {
|
||||
$('#glooobits').on('select2:select',function (event) { //'change' seems to be the jquery-compatible version but I think the select2 versions might be nicer.
|
||||
{{-- @this.set('fieldset_id',event.params.data.id) --}}
|
||||
Livewire.first().set('fieldset_id',event.params.data.id) // I still don't know why @this does'nt work here?
|
||||
})
|
||||
$('#add_default_values').on('ifToggled',function (event) {
|
||||
Livewire.first().set('add_default_values',event.target.checked)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@endpush
|
||||
</span>
|
||||
|
|
382
resources/views/livewire/importer.blade.php
Normal file
382
resources/views/livewire/importer.blade.php
Normal file
|
@ -0,0 +1,382 @@
|
|||
@section('title')
|
||||
{{ trans('general.import') }}
|
||||
@parent
|
||||
@stop
|
||||
<div>
|
||||
{{-- Livewire requires a 'master' <div>, above --}}
|
||||
<div class="row">
|
||||
|
||||
{{-- alert --}}
|
||||
@if($message != '')
|
||||
<div class="col-md-12" class="{{ $message_type }}">
|
||||
<div class="alert alert-{{ $this->message_type }} ">
|
||||
<button type="button" class="close" wire:click="$set('message','')">×</button>
|
||||
@if($message_type == 'success')
|
||||
<i class="fas fa-check faa-pulse animated" aria-hidden="true"></i>
|
||||
@endif
|
||||
<strong>{{-- title --}} </strong>
|
||||
{{ $message }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($import_errors)
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="alert alert-warning">
|
||||
<strong><i class="fa fa-warning info" aria-hidden="true"></i> {{ trans('general.warning', ['warning'=> trans('general.errors_importing')]) }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="errors-table">
|
||||
<table class="table table-striped table-bordered" id="errors-table">
|
||||
<thead>
|
||||
<th>{{ trans('general.item') }}</th>
|
||||
<th>{{ trans('general.error') }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php \Log::error("import errors are: ".print_r($import_errors,true)); @endphp
|
||||
@foreach($import_errors AS $key => $actual_import_errors)
|
||||
@php \Log::error("Key is: $key"); @endphp
|
||||
@foreach($actual_import_errors AS $table => $error_bag)
|
||||
@php \Log::error("Table is: $table"); @endphp
|
||||
@foreach($error_bag as $field => $error_list)
|
||||
@php \Log::error("Field is: $field"); @endphp
|
||||
<tr>
|
||||
<td>{{ $activeFile->file_path ?? "Unknown File" }}</td>
|
||||
<td>
|
||||
<b>{{ $field }}:</b>
|
||||
<span>{{ implode(", ",$error_list) }}</span>
|
||||
<br />
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endforeach
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="col-md-9">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
@if($progress != -1)
|
||||
<div class="col-md-9" style="padding-bottom:20px" id='progress-container'>
|
||||
<div class="progress progress-striped-active" style="margin-top: 8px"> {{-- so someof these values are in importer.vue! --}}
|
||||
<div id='progress-bar' class="progress-bar {{ $progress_bar_class }}" role="progressbar" style="width: {{ $progress }}%">
|
||||
<span id='progress-text'>{{ $progress_message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="col-md-3 text-right pull-right">
|
||||
|
||||
<!-- The fileinput-button span is used to style the file input field as button -->
|
||||
@if (!config('app.lock_passwords'))
|
||||
<span class="btn btn-primary fileinput-button">
|
||||
<span>{{ trans('button.select_file') }}</span>
|
||||
<!-- The file input field used as target for the file upload widget -->
|
||||
<label for="files[]"><span class="sr-only">{{ trans('admin/importer/general.select_file') }}</span></label>
|
||||
<input id="fileupload" type="file" name="files[]" data-url="{{ route('api.imports.index') }}" accept="text/csv" aria-label="files[]">
|
||||
</span>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 table-responsive" style="padding-top: 30px;">
|
||||
<table data-pagination="true"
|
||||
data-id-table="upload-table"
|
||||
data-search="true"
|
||||
data-side-pagination="client"
|
||||
id="upload-table"
|
||||
class="col-md-12 table table-striped snipe-table">
|
||||
|
||||
<tr>
|
||||
<th class="col-md-6">
|
||||
{{ trans('general.file_name') }}
|
||||
</th>
|
||||
<th class="col-md-3">
|
||||
{{ trans('general.created_at') }}
|
||||
</th>
|
||||
<th class="col-md-1">
|
||||
{{ trans('general.filesize') }}
|
||||
</th>
|
||||
<th class="col-md-1 text-right">
|
||||
<span class="sr-only">{{ trans('general.actions') }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@foreach($files as $currentFile)
|
||||
|
||||
<tr style="{{ ($activeFile && ($currentFile->id == $activeFile->id)) ? 'font-weight: bold' : '' }}" class="{{ ($activeFile && ($currentFile->id == $activeFile->id)) ? 'warning' : '' }}">
|
||||
<td class="col-md-6">{{ $currentFile->file_path }}</td>
|
||||
<td class="col-md-3">{{ Helper::getFormattedDateObject($currentFile->created_at, 'datetime', false) }}</td>
|
||||
<td class="col-md-1">{{ Helper::formatFilesizeUnits($currentFile->filesize) }}</td>
|
||||
<td class="col-md-1 text-right">
|
||||
<button class="btn btn-sm btn-info" wire:click="selectFile({{ $currentFile->id }})">
|
||||
<i class="fas fa-retweet fa-fw" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ trans('general.import') }}</span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" wire:click="destroy({{ $currentFile->id }})">
|
||||
<i class="fas fa-trash icon-white" aria-hidden="true"></i><span class="sr-only"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@if( $currentFile && $activeFile && ($currentFile->id == $activeFile->id))
|
||||
<tr class="warning">
|
||||
<td colspan="4">
|
||||
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
|
||||
<label for="activeFile.import_type" class="col-md-3 col-xs-12 text-right">
|
||||
{{ trans('general.import_type') }}
|
||||
</label>
|
||||
|
||||
<div class="col-md-9 col-xs-12">
|
||||
{{ Form::select('activeFile.import_type', $importTypes, $activeFile->import_type, [
|
||||
'id' => 'import_type',
|
||||
'class' => 'livewire-select2',
|
||||
'style' => 'min-width: 350px',
|
||||
'data-placeholder' => trans('general.select_var', ['thing' => trans('general.import_type')]), /* TODO: translate me */
|
||||
'placeholder' => '', //needed so that the form-helper will put an empty option first
|
||||
'data-minimum-results-for-search' => '-1', // Remove this if the list gets long enough that we need to search
|
||||
'data-livewire-component' => $_instance->id
|
||||
]) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<label for="update" class="col-md-9 col-md-offset-3 col-xs-12" wire:ignore>
|
||||
<input type="checkbox" class="minimal livewire-icheck" name="update" data-livewire-component="{{ $_instance->id }}">
|
||||
{{ trans('general.update_existing_values') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<label for="send_welcome" class="col-md-9 col-md-offset-3 col-xs-12" wire:ignore>
|
||||
<input type="checkbox" class="minimal livewire-icheck" name="send_welcome" data-livewire-component="{{ $_instance->id }}">
|
||||
{{ trans('general.send_welcome_email_to_users') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<label for="run_backup" class="col-md-9 col-md-offset-3 col-xs-12" wire:ignore>
|
||||
<input type="checkbox" class="minimal livewire-icheck" name="run_backup" data-livewire-component="{{ $_instance->id }}">
|
||||
{{ trans('general.back_before_importing') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
@if ($statusText)
|
||||
<div class="form-group">
|
||||
<div class="alert col-md-8 col-md-offset-2 {{ $statusType == 'success' ? 'alert-success' : ($statusType == 'error' ? 'alert-danger' : 'alert-info') }}" style="text-align:left">
|
||||
{{ $statusText }}
|
||||
</div><!-- /alert -->
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
@if ($activeFile->import_type)
|
||||
<div class="form-group col-md-12">
|
||||
<hr style="border-top: 1px solid lightgray">
|
||||
<h3><i class="{{ Helper::iconTypeByItem($activeFile->import_type) }}"></i> Map {{ ucwords($activeFile->import_type) }} Import Fields</h3>
|
||||
<hr style="border-top: 1px solid lightgray">
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<div class="col-md-3 text-right">
|
||||
<strong>{{ trans('general.csv_header_field') }}</strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>{{ trans('general.import_field') }}</strong>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<strong>{{ trans('general.sample_value') }}</strong>
|
||||
</div>
|
||||
</div><!-- /div row -->
|
||||
|
||||
@if($activeFile->header_row)
|
||||
|
||||
@foreach($activeFile->header_row as $index => $header)
|
||||
|
||||
<div class="form-group col-md-12" wire:key="header-row-{{ $index }}">
|
||||
|
||||
<label for="field_map.{{ $index }}" class="col-md-3 control-label text-right">{{ $header }}</label>
|
||||
<div class="col-md-4">
|
||||
|
||||
{{ Form::select('field_map.'.$index, $columnOptions[$activeFile->import_type], @$field_map[$index],
|
||||
[
|
||||
'class' => 'mappings livewire-select2',
|
||||
'placeholder' => 'Do Not Import',
|
||||
'style' => 'min-width: 100%',
|
||||
'data-livewire-component' => $_instance->id
|
||||
],[
|
||||
'-' => ['disabled' => true] // this makes the "-----" line unclickable
|
||||
])
|
||||
}}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<p class="form-control-static">{{ str_limit($activeFile->first_row[$index], 50, '...') }}</p>
|
||||
</div>
|
||||
</div><!-- /div row -->
|
||||
@endforeach
|
||||
@else
|
||||
{{ trans('general.no_headers') }}
|
||||
@endif
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<div class="col-md-3 text-left">
|
||||
<a href="#" wire:click="$set('activeFile',null)">{{ trans('general.cancel') }}</a>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<button type="submit" class="btn btn-primary col-md-5" id="import">Import</button>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($statusText)
|
||||
<div class="alert col-md-12 col-md-offset-2 {{ $statusType == 'success' ? 'alert-success' : ($statusType == 'error' ? 'alert-danger' : 'alert-info') }}" style="padding-top: 20px;">
|
||||
{{ $statusText }}
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="form-group col-md-12">
|
||||
<div class="col-md-3 text-left">
|
||||
<a href="#" wire:click="$set('activeFile',null)"><?php echo e(trans('general.cancel')); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
@endif {{-- end of if ... activeFile->import_type --}}
|
||||
|
||||
</div><!-- /div v-show --> </td>
|
||||
</tr>
|
||||
@endif
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>{{ trans('general.importing') }}</h2>
|
||||
<p>{!! trans('general.importing_help') !!}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@push('js')
|
||||
<script>
|
||||
|
||||
{{-- TODO: Maybe change this to the file upload thing that's baked-in to Livewire? --}}
|
||||
$('#fileupload').fileupload({
|
||||
dataType: 'json',
|
||||
done: function(e, data) {
|
||||
@this.progress_bar_class = 'progress-bar-success';
|
||||
@this.progress_message = '{{ trans('general.notification_success') }}'; // TODO - we're already round-tripping to the server here - I'd love it if we could get internationalized text here
|
||||
@this.progress = 100;
|
||||
},
|
||||
add: function(e, data) {
|
||||
data.headers = {
|
||||
"X-Requested-With": 'XMLHttpRequest',
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
|
||||
};
|
||||
data.process().done( function () {data.submit();});
|
||||
@this.progress = 0;
|
||||
},
|
||||
progress: function(e, data) {
|
||||
@this.progress = parseInt((data.loaded / data.total * 100, 10));
|
||||
@this.progress_message = @this.progress+'% Complete'; // TODO - make this use general.percent_complete as a translation, passing :percent as a variable
|
||||
},
|
||||
fail: function(e, data) {
|
||||
@this.progress_bar_class = "progress-bar-danger";
|
||||
@this.progress = 100;
|
||||
|
||||
var error_message = ''
|
||||
for(var i in data.jqXHR.responseJSON.messages) {
|
||||
error_message += i+": "+data.jqXHR.responseJSON.messages[i].join(", ")
|
||||
}
|
||||
@this.progress_message = error_message;
|
||||
}
|
||||
})
|
||||
|
||||
// For the importFile part:
|
||||
$(function () {
|
||||
// initialize iCheck for use with livewire
|
||||
$('.minimal.livewire-icheck').iCheck({
|
||||
checkboxClass: 'icheckbox_minimal-blue',
|
||||
})
|
||||
|
||||
// we have to hook up to the `<tr id='importer-file'>` at the root of this display,
|
||||
// because the #import button isn't visible until you click an import_type
|
||||
$('#upload-table').on('click', '#import', function () {
|
||||
if(!@this.activeFile.import_type) {
|
||||
@this.statusType='error';
|
||||
@this.statusText= "An import type is required... "; //TODO: translate?
|
||||
return;
|
||||
}
|
||||
@this.statusType ='pending';
|
||||
@this.statusText = "{{ trans('admin/hardware/form.processing_spinner') }}";
|
||||
@this.generate_field_map().then(function (mappings_raw) {
|
||||
var mappings = JSON.parse(mappings_raw)
|
||||
// console.warn("Here is the mappings:")
|
||||
// console.dir(mappings)
|
||||
// console.warn("Uh, active file id is, I guess: "+@this.activeFile.id)
|
||||
var this_file = @this.file_id; // okay, I actually don't know what I'm doing here.
|
||||
$.post({
|
||||
{{-- I want to do something like: route('api.imports.importFile', $activeFile->id) }} --}}
|
||||
url: "api/v1/imports/process/"+this_file, // maybe? Good a guess as any..FIXME. HARDCODED DUMB FILE
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
'import-update': !!@this.update,
|
||||
'send-welcome': !!@this.send_welcome,
|
||||
'import-type': @this.activeFile.import_type,
|
||||
'run-backup': !!@this.run_backup,
|
||||
'column-mappings': mappings
|
||||
}),
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
|
||||
}
|
||||
}).done( function (body) {
|
||||
// Success
|
||||
@this.statusType="success";
|
||||
@this.statusText = {{ trans('general.success_redirecting') }};
|
||||
// console.dir(body)
|
||||
window.location.href = body.messages.redirect_url;
|
||||
}).fail( function (jqXHR, textStatus, error) {
|
||||
// Failure
|
||||
var body = jqXHR.responseJSON
|
||||
if(body.status == 'import-errors') {
|
||||
@this.emit('importError', body.messages);
|
||||
@this.import_errors = body.messages
|
||||
|
||||
@this.statusType='error';
|
||||
@this.statusText = "Error";
|
||||
} else {
|
||||
console.warn("Not import-errors, just regular errors")
|
||||
console.dir(body)
|
||||
{{-- @this.emit('alert', body.error)--}}
|
||||
@this.message_type="danger"
|
||||
@this.message = body.error
|
||||
}
|
||||
@this.activeFile = null; //@this.set('hideDetails')
|
||||
});
|
||||
})
|
||||
return false;
|
||||
});})
|
||||
|
||||
</script>
|
||||
@endpush
|
|
@ -42,6 +42,15 @@
|
|||
<!-- Location -->
|
||||
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
|
||||
|
||||
<div class="form-group">
|
||||
<div class=" col-md-9 col-md-offset-3">
|
||||
<label>
|
||||
{{ Form::checkbox('null_location_id', '1', false, ['class' => 'minimal']) }}
|
||||
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.location'), 'user_count' => count($users)]) }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Company -->
|
||||
@if (\App\Models\Company::canManageUsersCompanies())
|
||||
|
|
|
@ -699,7 +699,7 @@ $(document).ready(function() {
|
|||
data: {},
|
||||
headers: {
|
||||
"X-Requested-With": 'XMLHttpRequest',
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') // TODO` - we should do this in ajaxSetup
|
||||
},
|
||||
dataType: 'json',
|
||||
|
||||
|
|
|
@ -482,6 +482,20 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
|
|||
]
|
||||
)->name('api.assets.checkout.bytag');
|
||||
|
||||
Route::post('bytag/{any}/checkin',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
'checkinbytag'
|
||||
]
|
||||
)->name('api.asset.checkinbytagPath');
|
||||
|
||||
Route::post('checkinbytag',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
'checkinbytag'
|
||||
]
|
||||
)->name('api.asset.checkinbytag');
|
||||
|
||||
Route::get('byserial/{any}',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
|
@ -511,13 +525,6 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
|
|||
]
|
||||
)->name('api.asset.checkin');
|
||||
|
||||
Route::post('checkinbytag',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
'checkinbytag'
|
||||
]
|
||||
)->name('api.asset.checkinbytag');
|
||||
|
||||
Route::post('{id}/checkout',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
|
|
|
@ -236,11 +236,10 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser
|
|||
|
|
||||
|
|
||||
*/
|
||||
Route::group(['prefix' => 'import', 'middleware' => ['auth']], function () {
|
||||
Route::get('/',
|
||||
[ImportsController::class, 'index']
|
||||
)->name('imports.index');
|
||||
});
|
||||
|
||||
Route::get('/import',
|
||||
\App\Http\Livewire\Importer::class
|
||||
)->middleware('auth')->name('imports.index');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue