Merge branch 'develop' into user_setup_translation

This commit is contained in:
akemidx 2023-02-02 14:34:09 -05:00 committed by GitHub
commit 29851c626f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 491 additions and 357 deletions

View file

@ -20,13 +20,13 @@ PUBLIC_FILESYSTEM_DISK=local_public
# REQUIRED: DATABASE SETTINGS # REQUIRED: DATABASE SETTINGS
# -------------------------------------------- # --------------------------------------------
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=localhost DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=snipeit-local DB_DATABASE=null
DB_USERNAME=snipeit-local DB_USERNAME=null
DB_PASSWORD=snipeit-local DB_PASSWORD=null
DB_PREFIX=null DB_PREFIX=null
DB_DUMP_PATH='/Applications/MAMP/Library/bin' #DB_DUMP_PATH=
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS # OPTIONAL: SSL DATABASE SETTINGS

2
.gitignore vendored
View file

@ -1,6 +1,8 @@
.couscous .couscous
.DS_Store .DS_Store
.env .env
.env.dusk.*
!.env.dusk.example
.idea .idea
/bin/ /bin/
/bootstrap/compiled.php /bootstrap/compiled.php

View file

@ -43,23 +43,33 @@ you want to run.
## Browser Tests ## Browser Tests
The browser tests use [Dusk](https://laravel.com/docs/8.x/dusk) to run them. Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
When troubleshooting any problems, make sure that your `.env` file is configured
correctly to run the existing application. Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
`cp .env.dusk.example .env.dusk.local`
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
### Test Setup ### Test Setup
Your application needs to be configued and up and running in order for the browser Your application needs to be configured and up and running in order for the browser
tests to actually run. When running the tests locally, you can start the application tests to actually run. When running the tests locally, you can start the application
using the following command: using the following command:
`php artisan serve` `php artisan serve`
Now you are ready to run the test suite. Use the following command from another terminal tab or window:
To run the test suite use the following command from another terminal tab or window:
`php artisan dusk` `php artisan dusk`
To run individual test files, you can pass the path to the test that you want to run. To run individual test files, you can pass the path to the test that you want to run:
`php artisan dusk tests/Browser/LoginTest.php` `php artisan dusk tests/Browser/LoginTest.php`
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
`php artisan dusk:chrome-driver --detect`
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.

View file

@ -44,12 +44,18 @@ class LdapSync extends Command
*/ */
public function handle() public function handle()
{ {
// If LDAP enabled isn't set to 1 (ldap_enabled!=1) then we should cut this short immediately without going any further
if (Setting::getSettings()->ldap_enabled!='1') {
$this->error('LDAP is not enabled. Aborting. See Settings > LDAP to enable it.');
exit();
}
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M')); ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
$ldap_result_username = Setting::getSettings()->ldap_username_field; $ldap_result_username = Setting::getSettings()->ldap_username_field;
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field; $ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field; $ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag; $ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num; $ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
$ldap_result_email = Setting::getSettings()->ldap_email; $ldap_result_email = Setting::getSettings()->ldap_email;
@ -303,17 +309,18 @@ class LdapSync extends Command
$user->activated = 0; $user->activated = 0;
} */ } */
$enabled_accounts = [ $enabled_accounts = [
'512', // 0x200 NORMAL_ACCOUNT '512', // 0x200 NORMAL_ACCOUNT
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD '544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD '66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD '66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED '262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED '262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD '328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD '328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH '4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH '4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED '1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED,
]; ];
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0; $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;

View file

@ -149,7 +149,7 @@ class RestoreFromBackup extends Command
$boring_files[] = $raw_path; $boring_files[] = $raw_path;
continue; continue;
} }
if (@pathinfo($raw_path)['extension'] == 'sql') { if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
\Log::debug("Found a sql file!"); \Log::debug("Found a sql file!");
$sqlfiles[] = $raw_path; $sqlfiles[] = $raw_path;
$sqlfile_indices[] = $i; $sqlfile_indices[] = $i;

View file

@ -63,6 +63,7 @@ class AccessoriesController extends Controller
public function store(ImageUploadRequest $request) public function store(ImageUploadRequest $request)
{ {
$this->authorize(Accessory::class); $this->authorize(Accessory::class);
// create a new model instance // create a new model instance
$accessory = new Accessory(); $accessory = new Accessory();
@ -82,7 +83,6 @@ class AccessoriesController extends Controller
$accessory->supplier_id = request('supplier_id'); $accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes'); $accessory->notes = request('notes');
$accessory = $request->handleImages($accessory); $accessory = $request->handleImages($accessory);
// Was the accessory created? // Was the accessory created?
@ -127,45 +127,47 @@ class AccessoriesController extends Controller
*/ */
public function update(ImageUploadRequest $request, $accessoryId = null) public function update(ImageUploadRequest $request, $accessoryId = null)
{ {
if (is_null($accessory = Accessory::find($accessoryId))) { if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$accessory->users_count"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
} else {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
} }
$min = $accessory->numCheckedOut();
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$min"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$this->authorize($accessory);
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors()); return redirect()->back()->withInput()->withErrors($accessory->getErrors());
} }
@ -217,7 +219,7 @@ class AccessoriesController extends Controller
*/ */
public function show($accessoryID = null) public function show($accessoryID = null)
{ {
$accessory = Accessory::find($accessoryID); $accessory = Accessory::withCount('users as users_count')->find($accessoryID);
$this->authorize('view', $accessory); $this->authorize('view', $accessory);
if (isset($accessory->id)) { if (isset($accessory->id)) {
return view('accessories/view', compact('accessory')); return view('accessories/view', compact('accessory'));

View file

@ -222,8 +222,8 @@ class AcceptanceController extends Controller
'item_model' => $display_model, 'item_model' => $display_model,
'item_serial' => $item->serial, 'item_serial' => $item->serial,
'eula' => $item->getEula(), 'eula' => $item->getEula(),
'check_out_date' => Carbon::parse($acceptance->created_at)->format($branding_settings->date_display_format), 'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format), 'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
'assigned_to' => $assigned_to, 'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name, 'company_name' => $branding_settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
@ -273,7 +273,7 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag, 'item_tag' => $item->asset_tag,
'item_model' => $display_model, 'item_model' => $display_model,
'item_serial' => $item->serial, 'item_serial' => $item->serial,
'declined_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format), 'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'assigned_to' => $assigned_to, 'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name, 'company_name' => $branding_settings->site_name,
'date_settings' => $branding_settings->date_display_format, 'date_settings' => $branding_settings->date_display_format,

View file

@ -41,10 +41,13 @@ class AccessoriesController extends Controller
'min_amt', 'min_amt',
'company_id', 'company_id',
'notes', 'notes',
'users_count',
'qty',
]; ];
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier'); $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
->withCount('users as users_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search')); $accessories = $accessories->TextSearch($request->input('search'));

View file

@ -33,7 +33,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function index() public function index()
{ {
$this->authorize('index', CustomFieldset::class); $this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get(); $fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count()); return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
@ -49,7 +49,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function show($id) public function show($id)
{ {
$this->authorize('view', CustomFieldset::class); $this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) { if ($fieldset = CustomFieldset::find($id)) {
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset); return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
} }
@ -68,7 +68,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
$fieldset->fill($request->all()); $fieldset->fill($request->all());
@ -89,7 +89,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', CustomFieldset::class); $this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset; $fieldset = new CustomFieldset;
$fieldset->fill($request->all()); $fieldset->fill($request->all());
@ -109,7 +109,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function destroy($id) public function destroy($id)
{ {
$this->authorize('delete', CustomFieldset::class); $this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
$modelsCount = $fieldset->models->count(); $modelsCount = $fieldset->models->count();
@ -136,7 +136,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function fields($id) public function fields($id)
{ {
$this->authorize('view', CustomFieldset::class); $this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id); $set = CustomFieldset::findOrFail($id);
$fields = $set->fields; $fields = $set->fields;
@ -153,7 +153,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function fieldsWithDefaultValues($fieldsetId, $modelId) public function fieldsWithDefaultValues($fieldsetId, $modelId)
{ {
$this->authorize('view', CustomFieldset::class); $this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($fieldsetId); $set = CustomFieldset::findOrFail($fieldsetId);

View file

@ -543,8 +543,9 @@ class UsersController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
} }
return response()->Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')); $user->notify((new CurrentInventory($user)));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
} }
/** /**

View file

@ -75,9 +75,9 @@ class CustomFieldsetsController extends Controller
*/ */
public function create() public function create()
{ {
$this->authorize('create', CustomFieldset::class); $this->authorize('create', CustomField::class);
return view('custom_fields.fieldsets.edit'); return view('custom_fields.fieldsets.edit')->with('item', new CustomFieldset());
} }
/** /**
@ -91,7 +91,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', CustomFieldset::class); $this->authorize('create', CustomField::class);
$cfset = new CustomFieldset([ $cfset = new CustomFieldset([
'name' => e($request->get('name')), 'name' => e($request->get('name')),
@ -110,31 +110,52 @@ class CustomFieldsetsController extends Controller
} }
/** /**
* What the actual fuck, Brady? * Presents edit form for fieldset
* *
* @todo Uhh, build this? * @author [A. Gianotto] [<snipe@snipe.net>]
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id * @param int $id
* @since [v1.8] * @since [v6.0.14]
* @return Fuckall * @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($id) public function edit($id)
{ {
// $this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
} }
/** /**
* GET IN THE SEA BRADY. * Saves updated fieldset data
* *
* @todo Uhh, build this too? * @author [A. Gianotto] [<snipe@snipe.net>]
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id * @param int $id
* @since [v1.8] * @since [v6.0.14]
* @return Fuckall * @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update($id) public function update(Request $request, $id)
{ {
// $this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
$fieldset->name = $request->input('name');
if ($fieldset->save()) {
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
}
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
} }
/** /**
@ -148,7 +169,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function destroy($id) public function destroy($id)
{ {
$fieldset = CustomFieldset::find($id); $fieldset = CustomField::find($id);
$this->authorize('delete', $fieldset); $this->authorize('delete', $fieldset);
@ -175,7 +196,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function associate(Request $request, $id) public function associate(Request $request, $id)
{ {
$set = CustomFieldset::find($id); $set = CustomField::find($id);
$this->authorize('update', $set); $this->authorize('update', $set);
@ -202,7 +223,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function makeFieldRequired($fieldset_id, $field_id) public function makeFieldRequired($fieldset_id, $field_id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id); $fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 1]; $fields[$field->id] = ['required' => 1];
@ -220,7 +241,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function makeFieldOptional($fieldset_id, $field_id) public function makeFieldOptional($fieldset_id, $field_id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id); $fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 0]; $fields[$field->id] = ['required' => 0];

View file

@ -38,7 +38,8 @@ class AccessoriesTransformer
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost), 'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null, 'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, 'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
'remaining_qty' => $accessory->numRemaining(), 'remaining_qty' => (int) $accessory->numRemaining(),
'users_count' => $accessory->users_count,
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),

View file

@ -327,20 +327,6 @@ class Accessory extends SnipeModel
return null; return null;
} }
/**
* Check how many items within an accessory are checked out
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0]
* @return int
*/
public function numCheckedOut()
{
$checkedout = 0;
$checkedout = $this->users->count();
return $checkedout;
}
/** /**
* Check how many items of an accessory remain * Check how many items of an accessory remain
@ -351,11 +337,11 @@ class Accessory extends SnipeModel
*/ */
public function numRemaining() public function numRemaining()
{ {
$checkedout = $this->users->count(); $checkedout = $this->users_count;
$total = $this->qty; $total = $this->qty;
$remaining = $total - $checkedout; $remaining = $total - $checkedout;
return $remaining; return (int) $remaining;
} }
/** /**

View file

@ -12,92 +12,7 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
{ {
public function getUserConfig() public function getUserConfig()
{ {
$config = parent::getUserConfig();
// Much of this is copied verbatim from the library, then adjusted for our needs // Much of this is copied verbatim from the library, then adjusted for our needs
$config['class'] = SCIMUser::class;
unset($config['mapping']['example:name:space']);
$config['map_unmapped'] = false; // anything we don't explicitly map will _not_ show up.
$core_namespace = 'urn:ietf:params:scim:schemas:core:2.0:User';
$core = $core_namespace.':';
$mappings =& $config['mapping'][$core_namespace]; //grab this entire key, we don't want to be repeating ourselves
//username - *REQUIRED*
$config['validations'][$core.'userName'] = 'required';
$mappings['userName'] = AttributeMapping::eloquent('username');
//human name - *FIRST NAME REQUIRED*
$config['validations'][$core.'name.givenName'] = 'required';
$config['validations'][$core.'name.familyName'] = 'string'; //not required
$mappings['name']['familyName'] = AttributeMapping::eloquent("last_name");
$mappings['name']['givenName'] = AttributeMapping::eloquent("first_name");
$mappings['name']['formatted'] = (new AttributeMapping())->ignoreWrite()->setRead(
function (&$object) {
return $object->getFullNameAttribute();
}
);
// externalId support
$config['validations'][$core.'externalId'] = 'string|nullable'; // not required, but supported mostly just for Okta
// note that the mapping is *not* namespaced like the other $mappings
$config['mapping']['externalId'] = AttributeMapping::eloquent('scim_externalid');
$config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT...
$config['validations'][$core.'emails.*.value'] = 'email'; // ...(had to remove the recommended 'required' here)
$mappings['emails'] = [[
"value" => AttributeMapping::eloquent("email"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]];
//active
$config['validations'][$core.'active'] = 'boolean';
$mappings['active'] = AttributeMapping::eloquent('activated');
//phone
$config['validations'][$core.'phoneNumbers'] = 'nullable|array';
$config['validations'][$core.'phoneNumbers.*.value'] = 'string'; // another one where want to say 'we don't _need_ a phone number, but if you have one it better have a value.
$mappings['phoneNumbers'] = [[
"value" => AttributeMapping::eloquent("phone"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]];
//address
$config['validations'][$core.'addresses'] = 'nullable|array';
$config['validations'][$core.'addresses.*.streetAddress'] = 'string';
$config['validations'][$core.'addresses.*.locality'] = 'string';
$config['validations'][$core.'addresses.*.region'] = 'nullable|string';
$config['validations'][$core.'addresses.*.postalCode'] = 'nullable|string';
$config['validations'][$core.'addresses.*.country'] = 'string';
$mappings['addresses'] = [[
'type' => AttributeMapping::constant("work")->ignoreWrite(),
'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right.
'streetAddress' => AttributeMapping::eloquent("address"),
'locality' => AttributeMapping::eloquent("city"),
'region' => AttributeMapping::eloquent("state"),
'postalCode' => AttributeMapping::eloquent("zip"),
'country' => AttributeMapping::eloquent("country"),
'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example?
]];
//title
$config['validations'][$core.'title'] = 'string';
$mappings['title'] = AttributeMapping::eloquent('jobtitle');
//Preferred Language
$config['validations'][$core.'preferredLanguage'] = 'string';
$mappings['preferredLanguage'] = AttributeMapping::eloquent('locale');
/* /*
more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?): more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?):
@ -108,66 +23,213 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
- company_id to "organization?" - company_id to "organization?"
*/ */
$enterprise_namespace = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User';
$ent = $enterprise_namespace.':';
// we remove the 'example' namespace and add the Enterprise one $user_prefix = 'urn:ietf:params:scim:schemas:core:2.0:User:';
$config['mapping']['schemas'] = AttributeMapping::constant( [$core_namespace, $enterprise_namespace] )->ignoreWrite(); $enterprise_prefix = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:';
$config['validations'][$ent.'employeeNumber'] = 'string'; return [
$config['validations'][$ent.'department'] = 'string';
$config['validations'][$ent.'manager'] = 'nullable';
$config['validations'][$ent.'manager.value'] = 'string';
$config['mapping'][$enterprise_namespace] = [ // Set to 'null' to make use of auth.providers.users.model (App\User::class)
'employeeNumber' => AttributeMapping::eloquent('employee_num'), 'class' => SCIMUser::class,
'department' =>(new AttributeMapping())->setAdd( // FIXME parent?
function ($value, &$object) { 'validations' => [
$department = Department::where("name", $value)->first(); $user_prefix . 'userName' => 'required',
if ($department) { $user_prefix . 'name.givenName' => 'required',
$object->department_id = $department->id; $user_prefix . 'name.familyName' => 'nullable|string',
} $user_prefix . 'externalId' => 'nullable|string',
} $user_prefix . 'emails' => 'nullable|array',
)->setReplace( $user_prefix . 'emails.*.value' => 'nullable|email',
function ($value, &$object) { $user_prefix . 'active' => 'boolean',
$department = Department::where("name", $value)->first(); $user_prefix . 'phoneNumbers' => 'nullable|array',
if ($department) { $user_prefix . 'phoneNumbers.*.value' => 'nullable|string',
$object->department_id = $department->id; $user_prefix . 'addresses' => 'nullable|array',
$user_prefix . 'addresses.*.streetAddress' => 'nullable|string',
$user_prefix . 'addresses.*.locality' => 'nullable|string',
$user_prefix . 'addresses.*.region' => 'nullable|string',
$user_prefix . 'addresses.*.postalCode' => 'nullable|string',
$user_prefix . 'addresses.*.country' => 'nullable|string',
$user_prefix . 'title' => 'nullable|string',
$user_prefix . 'preferredLanguage' => 'nullable|string',
// Enterprise validations:
$enterprise_prefix . 'employeeNumber' => 'nullable|string',
$enterprise_prefix . 'department' => 'nullable|string',
$enterprise_prefix . 'manager' => 'nullable',
$enterprise_prefix . 'manager.value' => 'nullable|string'
],
'singular' => 'User',
'schema' => [Schema::SCHEMA_USER],
//eager loading
'withRelations' => [],
'map_unmapped' => false,
// 'unmapped_namespace' => 'urn:ietf:params:scim:schemas:laravel:unmapped',
'description' => 'User Account',
// Map a SCIM attribute to an attribute of the object.
'mapping' => [
'id' => AttributeMapping::eloquent("id")->disableWrite(),
'externalId' => AttributeMapping::eloquent('scim_externalid'), // FIXME - I have a PR that changes a lot of this.
'meta' => [
'created' => AttributeMapping::eloquent("created_at")->disableWrite(),
'lastModified' => AttributeMapping::eloquent("updated_at")->disableWrite(),
'location' => (new AttributeMapping())->setRead(
function ($object) {
return route(
'scim.resource',
[
'resourceType' => 'Users',
'resourceObject' => $object->id
]
);
} }
} )->disableWrite(),
)->setRead(
function (&$object) { 'resourceType' => AttributeMapping::constant("User")
return $object->department ? $object->department->name : null; ],
}
), 'schemas' => AttributeMapping::constant(
'manager' => [ [
// FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool. 'urn:ietf:params:scim:schemas:core:2.0:User',
// '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
// 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), ]
// NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing )->ignoreWrite(),
'value' => (new AttributeMapping())->setAdd(
function ($value, &$object) { 'urn:ietf:params:scim:schemas:core:2.0:User' => [
$manager = User::find($value);
if ($manager) { 'userName' => AttributeMapping::eloquent("username"),
$object->manager_id = $manager->id;
'name' => [
'formatted' => (new AttributeMapping())->ignoreWrite()->setRead(
function (&$object) {
return $object->getFullNameAttribute();
}
),
'familyName' => AttributeMapping::eloquent("last_name"),
'givenName' => AttributeMapping::eloquent("first_name"),
'middleName' => null,
'honorificPrefix' => null,
'honorificSuffix' => null
],
'displayName' => null,
'nickName' => null,
'profileUrl' => null,
'title' => AttributeMapping::eloquent('jobtitle'),
'userType' => null,
'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231]
'locale' => null, // see RFC5646
'timezone' => null, // see RFC6557
'active' => AttributeMapping::eloquent('activated'),
'password' => AttributeMapping::eloquent('password')->disableRead(),
// Multi-Valued Attributes
'emails' => [[
"value" => AttributeMapping::eloquent("email"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]],
'phoneNumbers' => [[
"value" => AttributeMapping::eloquent("phone"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]],
'ims' => [[
"value" => null,
"display" => null,
"type" => null,
"primary" => null
]], // Instant messaging addresses for the User
'photos' => [[
"value" => null,
"display" => null,
"type" => null,
"primary" => null
]],
'addresses' => [[
'type' => AttributeMapping::constant("work")->ignoreWrite(),
'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right.
'streetAddress' => AttributeMapping::eloquent("address"),
'locality' => AttributeMapping::eloquent("city"),
'region' => AttributeMapping::eloquent("state"),
'postalCode' => AttributeMapping::eloquent("zip"),
'country' => AttributeMapping::eloquent("country"),
'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example?
]],
'groups' => [[
'value' => null,
'$ref' => null,
'display' => null,
'type' => null,
'type' => null
]],
'entitlements' => null,
'roles' => null,
'x509Certificates' => null
],
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => [
'employeeNumber' => AttributeMapping::eloquent('employee_num'),
'department' => (new AttributeMapping())->setAdd( // FIXME parent?
function ($value, &$object) {
$department = Department::where("name", $value)->first();
if ($department) {
$object->department_id = $department->id;
}
} }
}
)->setReplace( )->setReplace(
function ($value, &$object) { function ($value, &$object) {
$manager = User::find($value); $department = Department::where("name", $value)->first();
if ($manager) { if ($department) {
$object->manager_id = $manager->id; $object->department_id = $department->id;
} }
} }
)->setRead( )->setRead(
function (&$object) { function (&$object) {
return $object->manager_id; return $object->department ? $object->department->name : null;
} }
), ),
'manager' => [
// FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool.
// '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
// 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
// NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing
'value' => (new AttributeMapping())->setAdd(
function ($value, &$object) {
$manager = User::find($value);
if ($manager) {
$object->manager_id = $manager->id;
}
}
)->setReplace(
function ($value, &$object) {
$manager = User::find($value);
if ($manager) {
$object->manager_id = $manager->id;
}
}
)->setRead(
function (&$object) {
return $object->manager_id;
}
),
]
]
] ]
]; ];
return $config;
} }
} }

0
app/Models/User.php Executable file → Normal file
View file

View file

@ -80,19 +80,25 @@ class AccessoryPresenter extends Presenter
], [ ], [
'field' => 'qty', 'field' => 'qty',
'searchable' => false, 'searchable' => false,
'sortable' => false,
'title' => trans('admin/accessories/general.total'),
], [
'field' => 'min_qty',
'searchable' => false,
'sortable' => true, 'sortable' => true,
'title' => trans('general.min_amt'), 'title' => trans('admin/accessories/general.total'),
], [ ], [
'field' => 'remaining_qty', 'field' => 'remaining_qty',
'searchable' => false, 'searchable' => false,
'sortable' => false, 'sortable' => false,
'visible' => false, 'visible' => false,
'title' => trans('admin/accessories/general.remaining'), 'title' => trans('admin/accessories/general.remaining'),
],[
'field' => 'users_count',
'searchable' => false,
'sortable' => true,
'visible' => true,
'title' => trans('general.checked_out'),
], [
'field' => 'min_qty',
'searchable' => false,
'sortable' => true,
'title' => trans('general.min_amt'),
], [ ], [
'field' => 'purchase_date', 'field' => 'purchase_date',
'searchable' => true, 'searchable' => true,

View file

@ -73,7 +73,7 @@
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.16", "fakerphp/faker": "^1.16",
"laravel/dusk": "^6.19", "laravel/dusk": "^6.25",
"mockery/mockery": "^1.4", "mockery/mockery": "^1.4",
"phpunit/php-token-stream": "^3.1", "phpunit/php-token-stream": "^3.1",
"phpunit/phpunit": "^9.0", "phpunit/phpunit": "^9.0",

22
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "0c1f3848f8c9ede2b5f1513e4feaa7d7", "content-hash": "4fed0ab76a34ef85a44568e470abab48",
"packages": [ "packages": [
{ {
"name": "alek13/slack", "name": "alek13/slack",
@ -78,12 +78,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/grokability/laravel-scim-server.git", "url": "https://github.com/grokability/laravel-scim-server.git",
"reference": "2c7ecc450eee59234e059ec2e7724b2d8f3a8369" "reference": "9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/2c7ecc450eee59234e059ec2e7724b2d8f3a8369", "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419",
"reference": "2c7ecc450eee59234e059ec2e7724b2d8f3a8369", "reference": "9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -133,7 +133,7 @@
"support": { "support": {
"source": "https://github.com/grokability/laravel-scim-server/tree/master" "source": "https://github.com/grokability/laravel-scim-server/tree/master"
}, },
"time": "2022-11-22T20:26:54+00:00" "time": "2023-01-12T00:32:07+00:00"
}, },
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
@ -11847,16 +11847,16 @@
}, },
{ {
"name": "laravel/dusk", "name": "laravel/dusk",
"version": "v6.25.0", "version": "v6.25.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/dusk.git", "url": "https://github.com/laravel/dusk.git",
"reference": "b4632b7493a187d31afc5c9ddec437c81b16421a" "reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/dusk/zipball/b4632b7493a187d31afc5c9ddec437c81b16421a", "url": "https://api.github.com/repos/laravel/dusk/zipball/25a595ac3dc82089a91af10dd23b0d58fd3f6d0b",
"reference": "b4632b7493a187d31afc5c9ddec437c81b16421a", "reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -11914,9 +11914,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/laravel/dusk/issues", "issues": "https://github.com/laravel/dusk/issues",
"source": "https://github.com/laravel/dusk/tree/v6.25.0" "source": "https://github.com/laravel/dusk/tree/v6.25.2"
}, },
"time": "2022-07-11T11:38:43+00:00" "time": "2022-09-29T09:37:07+00:00"
}, },
{ {
"name": "mockery/mockery", "name": "mockery/mockery",

View file

@ -102,6 +102,7 @@ return [
'pgsql' => [ 'pgsql' => [
'driver' => 'pgsql', 'driver' => 'pgsql',
'host' => env('DB_HOST', 'localhost'), 'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),

View file

@ -3,6 +3,6 @@
return [ return [
"trace" => env("SCIM_TRACE",false), "trace" => env("SCIM_TRACE",false),
// below, if we ever get 'sure' that we can change this default to 'true' we should // below, if we ever get 'sure' that we can change this default to 'true' we should
"omit_main_schema_in_return" => env('SCIM_STANDARDS_COMPLIANCE', false), "omit_main_schema_in_return" => env('SCIM_STANDARDS_COMPLIANCE', true),
"publish_routes" => false, "publish_routes" => false,
]; ];

View file

@ -44,6 +44,8 @@ class MigrationCartalystSentryInstallGroups extends Migration
*/ */
public function down() public function down()
{ {
Schema::drop('permission_groups'); // See 2014_11_04_231416_update_group_field_for_reporting.php and 2019_06_12_184327_rename_groups_table.php
Schema::dropIfExists('permission_groups');
Schema::dropIfExists('groups');
} }
} }

View file

@ -26,6 +26,6 @@ class AddPhysicalToAssets extends Migration
*/ */
public function down() public function down()
{ {
$table->dropColumn('physical'); // $table->dropColumn('physical');
} }
} }

View file

@ -37,7 +37,7 @@ class ReCreateLicensesTable extends Migration
*/ */
public function down() public function down()
{ {
// // This was most likely handled in 2013_11_17_054359_drop_licenses_table.php
Schema::drop('licenses'); Schema::dropIfExists('licenses');
} }
} }

View file

@ -31,6 +31,6 @@ class CreateLicenseSeatsTable extends Migration
*/ */
public function down() public function down()
{ {
// Schema::dropIfExists('license_seats');
} }
} }

View file

@ -23,6 +23,8 @@ class AlterWarrantyColumnOnAssets extends Migration
*/ */
public function down() public function down()
{ {
// Schema::table('assets', function ($table) {
$table->renameColumn('warranty_months', 'warrantee_months');
});
} }
} }

View file

@ -24,7 +24,7 @@ class AddEolOnModelsTable extends Migration
public function down() public function down()
{ {
Schema::table('models', function ($table) { Schema::table('models', function ($table) {
$table->dropColumn('old'); $table->dropColumn('eol');
}); });
} }
} }

View file

@ -105,10 +105,10 @@
*/ */
public function down() public function down()
{ {
Schema::table('asset_logs', function (Blueprint $table) { // Schema::table('asset_logs', function (Blueprint $table) {
$table->dropIndex('thread_id'); // $table->dropIndex('thread_id');
$table->dropColumn('thread_id'); // $table->dropColumn('thread_id');
}); // });
} }
/** /**

View file

@ -48,13 +48,19 @@ class MigrateMacAddress extends Migration
*/ */
public function down() public function down()
{ {
//
$f = \App\Models\CustomFieldset::where(['name' => 'Asset with MAC Address'])->first(); $f = \App\Models\CustomFieldset::where(['name' => 'Asset with MAC Address'])->first();
$f->fields()->delete();
$f->delete(); if ($f) {
$f->fields()->delete();
$f->delete();
}
Schema::table('models', function (Blueprint $table) { Schema::table('models', function (Blueprint $table) {
$table->renameColumn('deprecated_mac_address', 'show_mac_address'); $table->renameColumn('deprecated_mac_address', 'show_mac_address');
}); });
DB::statement('ALTER TABLE assets CHANGE _snipeit_mac_address mac_address varchar(255)');
if (Schema::hasColumn('assets', '_snipeit_mac_address')) {
DB::statement('ALTER TABLE assets CHANGE _snipeit_mac_address mac_address varchar(255)');
}
} }
} }

View file

@ -25,7 +25,7 @@ class AddShowInNavToStatusLabels extends Migration
public function down() public function down()
{ {
Schema::table('status_labels', function (Blueprint $table) { Schema::table('status_labels', function (Blueprint $table) {
$table->dropColumn('show_in_nav', 'field_encrypted'); $table->dropColumn('show_in_nav');
}); });
} }
} }

View file

@ -4,6 +4,7 @@ use App\Models\CustomField;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
/** /**
* Fixes issue #2551 where columns got donked if the field name in non-ascii * Fixes issue #2551 where columns got donked if the field name in non-ascii
@ -71,6 +72,25 @@ class FixUtf8CustomFieldColumnNames extends Migration
*/ */
public function down() public function down()
{ {
// In the up method above, updateLegacyColumnName is called and custom fields in the assets table are prefixed
// with "_snipe_it_", suffixed with "_{id of the CustomField}", and stored in custom_fields.db_column.
// The following reverses those changes.
foreach (CustomField::all() as $field) {
$currentColumnName = $field->db_column;
// "_snipeit_imei_1" becomes "_snipeit_imei"
$legacyColumnName = (string) Str::of($currentColumnName)->replaceMatches('/_(\d)+$/', '');
if (Schema::hasColumn(CustomField::$table_name, $currentColumnName)) {
Schema::table(CustomField::$table_name, function (Blueprint $table) use ($currentColumnName, $legacyColumnName) {
$table->renameColumn(
$currentColumnName,
$legacyColumnName
);
});
}
}
Schema::table('custom_fields', function ($table) { Schema::table('custom_fields', function ($table) {
$table->dropColumn('db_column'); $table->dropColumn('db_column');
$table->dropColumn('help_text'); $table->dropColumn('help_text');

View file

@ -28,7 +28,7 @@ class AddFieldsToManufacturer extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('settings', function ($table) { Schema::table('manufacturers', function ($table) {
$table->dropColumn('url'); $table->dropColumn('url');
$table->dropColumn('support_url'); $table->dropColumn('support_url');
$table->dropColumn('support_phone'); $table->dropColumn('support_phone');

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -16,7 +16,7 @@
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb", "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=c0d21166315b7c2cdd4819fa4a5e4d1e", "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=c0d21166315b7c2cdd4819fa4a5e4d1e",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=8e538625ebd4b8096e150d1aa483547b", "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/all.css": "/css/dist/all.css?id=ef030b613d45620b907cf0184a14e868", "/css/dist/all.css": "/css/dist/all.css?id=ef030b613d45620b907cf0184a14e868",
"/css/blue.png": "/css/blue.png?id=e83a6c29e04fe851f2122815b2e4b150", "/css/blue.png": "/css/blue.png?id=e83a6c29e04fe851f2122815b2e4b150",
@ -49,5 +49,5 @@
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=713b1205aa2d7c9db282f8cd5754c0e4", "/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=713b1205aa2d7c9db282f8cd5754c0e4",
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", "/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=f343f659ca1d45534d2c2c3cc30fb619", "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=f343f659ca1d45534d2c2c3cc30fb619",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=8e538625ebd4b8096e150d1aa483547b" "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da"
} }

View file

@ -84,7 +84,13 @@
color: #fff; color: #fff;
} }
} }
a.btn.btn-link.text-left{
color:@navy;
border: 1px solid #000;
}
a.btn.btn-link.text-left:hover{
color:@navy;
}
a { a {
color: @navy; color: @navy;
@ -102,10 +108,6 @@ a.btn {
&:hover { &:hover {
color: #fff; color: #fff;
text-decoration: underline; text-decoration: underline;
}
&:visited {
color: #fff;
} }
} }

View file

@ -27,6 +27,9 @@ return [
'used_by_models' => 'Used By Models', 'used_by_models' => 'Used By Models',
'order' => 'Order', 'order' => 'Order',
'create_fieldset' => 'New Fieldset', 'create_fieldset' => 'New Fieldset',
'update_fieldset' => 'Update Fieldset',
'fieldset_does_not_exist' => 'Fieldset :id does not exist',
'fieldset_updated' => 'Fieldset updated',
'create_fieldset_title' => 'Create a new fieldset', 'create_fieldset_title' => 'Create a new fieldset',
'create_field' => 'New Custom Field', 'create_field' => 'New Custom Field',
'create_field_title' => 'Create a new custom field', 'create_field_title' => 'Create a new custom field',

View file

@ -390,7 +390,8 @@ return [
'start_date' => 'Start Date', 'start_date' => 'Start Date',
'end_date' => 'End Date', 'end_date' => 'End Date',
'alt_uploaded_image_thumbnail' => 'Uploaded thumbnail', 'alt_uploaded_image_thumbnail' => 'Uploaded thumbnail',
'placeholder_kit' => 'Select a kit' 'placeholder_kit' => 'Select a kit',
'file_not_found' => 'File not found',

0
resources/views/accessories/edit.blade.php Executable file → Normal file
View file

View file

@ -290,7 +290,7 @@
@if ($accessory->company) @if ($accessory->company)
<div class="row"> <div class="row">
<div class="col-md-4" style="padding-bottom: 15px;"> <div class="col-md-4" style="padding-bottom: 15px;">
{{ trans('general.company')}} <strong> {{ trans('general.company')}}</strong>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<a href="{{ route('companies.show', $accessory->company->id) }}">{{ $accessory->company->name }} </a> <a href="{{ route('companies.show', $accessory->company->id) }}">{{ $accessory->company->name }} </a>
@ -302,7 +302,7 @@
@if ($accessory->category) @if ($accessory->category)
<div class="row"> <div class="row">
<div class="col-md-4" style="padding-bottom: 15px;"> <div class="col-md-4" style="padding-bottom: 15px;">
{{ trans('general.category')}} <strong>{{ trans('general.category')}}</strong>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<a href="{{ route('categories.show', $accessory->category->id) }}">{{ $accessory->category->name }} </a> <a href="{{ route('categories.show', $accessory->category->id) }}">{{ $accessory->category->name }} </a>
@ -327,13 +327,22 @@
<div class="row"> <div class="row">
<div class="col-md-4" style="padding-bottom: 15px;"> <div class="col-md-4" style="padding-bottom: 15px;">
Number remaining <strong>{{ trans('admin/accessories/general.remaining') }}</strong>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
{{ $accessory->numRemaining() }} {{ $accessory->numRemaining() }}
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-4" style="padding-bottom: 15px;">
<strong>{{ trans('general.checked_out') }}</strong>
</div>
<div class="col-md-8">
{{ $accessory->users_count }}
</div>
</div>
@can('checkout', \App\Models\Accessory::class) @can('checkout', \App\Models\Accessory::class)

View file

@ -60,7 +60,7 @@
@if ($snipeSettings->slack_endpoint!='') @if ($snipeSettings->slack_endpoint!='')
<i class="fab fa-slack"></i> <i class="fab fa-slack"></i>
A slack message will be sent {{ trans('general.slack_msg_note') }}
@endif @endif
</div> </div>
</div> </div>

View file

@ -1,49 +1,17 @@
@extends('layouts.default') @extends('layouts/edit-form', [
'createText' => trans('admin/custom_fields/general.create_fieldset') ,
'updateText' => trans('admin/custom_fields/general.update_fieldset'),
'helpText' => trans('admin/custom_fields/general.about_fieldsets_text'),
'helpPosition' => 'right',
'formAction' => (isset($item->id)) ? route('fieldsets.update', ['fieldset' => $item->id]) : route('fieldsets.store'),
])
{{-- Page title --}}
@section('title')
{{ trans('admin/custom_fields/general.create_fieldset') }}
@parent
@stop
@section('header_right')
<a href="{{ URL::previous() }}" class="btn btn-primary pull-right">
{{ trans('general.back') }}</a>
@stop
{{-- Page content --}}
@section('content') @section('content')
<div class="row"> @parent
<div class="col-md-9">
{{ Form::open(['route' => 'fieldsets.store', 'class'=>'form-horizontal']) }}
<!-- Horizontal Form -->
<div class="box box-default">
<div class="box-body">
<!-- Name -->
<div class="form-group {{ $errors->has('name') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">
{{ trans('admin/custom_fields/general.fieldset_name') }}
</label>
<div class="col-md-6">
<input class="form-control" type="text" name="name" id="name" value="{{ old('name') }}" required>
{!! $errors->first('name', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
</div>
</div> <!-- /.box-body-->
<div class="box-footer text-right">
<button type="submit" class="btn btn-success"><i class="fas fa-check icon-white" aria-hidden="true"></i> {{ trans('general.save') }}</button>
</div>
</div> <!-- /.box.box-default-->
{{ Form::close() }}
</div>
<div class="col-md-3">
<h2>{{ trans('admin/custom_fields/general.about_fieldsets_title') }}</h4>
<p>{{ trans('admin/custom_fields/general.about_fieldsets_text') }}</p>
</div>
</div>
@stop @stop
@section('inputFields')
@include ('partials.forms.edit.name', ['translated_name' => trans('general.name')])
@stop

View file

@ -69,8 +69,18 @@
@endforeach @endforeach
</td> </td>
<td> <td>
<nobr>
@can('update', $fieldset)
<a href="{{ route('fieldsets.edit', $fieldset->id) }}" class="btn btn-warning btn-sm">
<i class="fas fa-pencil-alt" aria-hidden="true"></i>
<span class="sr-only">{{ trans('button.edit') }}</span>
</a>
@endcan
@can('delete', $fieldset) @can('delete', $fieldset)
{{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete']) }} {{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete','style' => 'display:inline-block']) }}
@if($fieldset->models->count() > 0) @if($fieldset->models->count() > 0)
<button type="submit" class="btn btn-danger btn-sm disabled" disabled><i class="fas fa-trash"></i></button> <button type="submit" class="btn btn-danger btn-sm disabled" disabled><i class="fas fa-trash"></i></button>
@else @else
@ -78,6 +88,7 @@
@endif @endif
{{ Form::close() }} {{ Form::close() }}
@endcan @endcan
</nobr>
</td> </td>
</tr> </tr>
@endforeach @endforeach

View file

@ -15,8 +15,8 @@
<link rel="apple-touch-icon" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url(e($snipeSettings->logo)) : config('app.url').'/img/logo.png' }}"> <link rel="apple-touch-icon" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url(e($snipeSettings->logo)) : config('app.url').'/img/snipe-logo-bug.png' }}">
<link rel="apple-touch-startup-image" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url(e($snipeSettings->logo)) : config('app.url').'/img/logo.png' }}"> <link rel="apple-touch-startup-image" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url(e($snipeSettings->logo)) : config('app.url').'/img/snipe-logo-bug.png' }}">
<link rel="shortcut icon" type="image/ico" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url(e($snipeSettings->favicon)) : config('app.url').'/favicon.ico' }} "> <link rel="shortcut icon" type="image/ico" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url(e($snipeSettings->favicon)) : config('app.url').'/favicon.ico' }} ">

View file

@ -327,8 +327,8 @@
item_icon = ''; item_icon = '';
} }
// display the username if it's checked out to a user // display the username if it's checked out to a user, but don't do it if the username's there already
if (value.username) { if (value.username && !value.name.match('\\(') && !value.name.match('\\)')) {
value.name = value.name + ' (' + value.username + ')'; value.name = value.name + ' (' + value.username + ')';
} }

View file

@ -869,16 +869,19 @@
</td> </td>
<td> <td>
@if ($file->filename) @if ($file->filename)
@if ( Helper::checkUploadIsImage($file->get_src('users'))) @if ((Storage::exists('private_uploads/users/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('users'))))
<a href="{{ route('show/userfile', ['userId' => $user->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show/userfile', ['userId' => $user->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a> <a href="{{ route('show/userfile', ['userId' => $user->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show/userfile', ['userId' => $user->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@else
<i class="fa fa-times text-danger" aria-hidden="true"></i>
{{ trans('general.file_not_found') }}
@endif @endif
@endif @endif
</td> </td>
<td> <td>
{{ $file->filename }} {{ $file->filename }}
</td> </td>
<td> <td data-value="{{ (Storage::exists('private_uploads/users/'.$file->filename)) ? Storage::size('private_uploads/users/'.$file->filename) : '' }}">
{{ Helper::formatFilesizeUnits(Storage::size('private_uploads/users/'.$file->filename)) }} {{ (Storage::exists('private_uploads/users/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/users/'.$file->filename)) : '' }}
</td> </td>
<td> <td>
@ -888,10 +891,12 @@
</td> </td>
<td> <td>
@if ($file->filename) @if ($file->filename)
<a href="{{ route('show/userfile', [$user->id, $file->id]) }}" class="btn btn-default"> @if ((Storage::exists('private_uploads/users/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('users'))))
<i class="fas fa-download" aria-hidden="true"></i> <a href="{{ route('show/userfile', [$user->id, $file->id]) }}" class="btn btn-default">
<span class="sr-only">{{ trans('general.download') }}</span> <i class="fas fa-download" aria-hidden="true"></i>
</a> <span class="sr-only">{{ trans('general.download') }}</span>
</a>
@endif
@endif @endif
</td> </td>
<td>{{ $file->created_at }}</td> <td>{{ $file->created_at }}</td>
@ -949,7 +954,8 @@
@endif @endif
<th class="col-sm-3" data-field="item.serial" data-visible="false">{{ trans('admin/hardware/table.serial') }}</th> <th class="col-sm-3" data-field="item.serial" data-visible="false">{{ trans('admin/hardware/table.serial') }}</th>
<th class="col-sm-3" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th> <th class="col-sm-3" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
<th class="col-sm-2" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th> <th class="col-sm-3" data-field="note">{{ trans('general.notes') }}</th>
<th class="col-sm-2" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th>
</tr> </tr>
</thead> </thead>
</table> </table>

View file

@ -2,8 +2,8 @@
namespace Tests\Browser; namespace Tests\Browser;
use App\Models\Setting;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser; use Laravel\Dusk\Browser;
use Tests\DuskTestCase; use Tests\DuskTestCase;
@ -26,6 +26,9 @@ class LoginTest extends DuskTestCase
$user->permissions = '{"superuser": 1}'; $user->permissions = '{"superuser": 1}';
$user->save(); $user->save();
Setting::factory()->create();
$this->browse(function (Browser $browser) { $this->browse(function (Browser $browser) {
$browser->visitRoute('login') $browser->visitRoute('login')
->assertSee(trans('auth/general.login_prompt')); ->assertSee(trans('auth/general.login_prompt'));
@ -39,8 +42,5 @@ class LoginTest extends DuskTestCase
->assertPathIs('/'); ->assertPathIs('/');
$browser->screenshot('dashboard'); $browser->screenshot('dashboard');
}); });
// Delete the user afterwards
$user->delete();
} }
} }

View file

@ -5,11 +5,13 @@ namespace Tests;
use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\RemoteWebDriver;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\TestCase as BaseTestCase; use Laravel\Dusk\TestCase as BaseTestCase;
abstract class DuskTestCase extends BaseTestCase abstract class DuskTestCase extends BaseTestCase
{ {
use CreatesApplication; use CreatesApplication;
use DatabaseMigrations;
/** /**
* Prepare for Dusk test execution. * Prepare for Dusk test execution.