Compare commits

..

68 Commits

Author SHA1 Message Date
snipe
aff104fa5d Bumped version for beta 2017-09-18 18:10:43 -07:00
Daniel Meltzer
a5764351f7 Migrate weird assigned_type issues, Issue #3972 (#3973)
For a while, prior to 987536930, we did not null assigned_type on
checkin.  This migration manually nulls all assigned_type fields if
assigned_to is unset.  Add a test to AssetTest for this as well...kind
of.  We need to extract an Asset::checkin() method for 4.1 that mirrors
Asset::checkOut() to really test this.

This also fixes a separate (but related) issue.  The Asset importer did
not set assigned_type when importing and creating users.  In this
instance, we assume that if assigned_to is set and assigned_type is not,
then the item was checked out to a user and update the DB accordingly.
Also add a check in ImporterTest for this issue.
2017-09-18 16:40:13 -07:00
snipe
348becbbec Production assets generated 2017-09-18 13:01:54 -07:00
Daniel Meltzer
922d6937ae Custom field import repair (#3968)
* There is no notes field on accessories.  Fixes Importer Test.

* Fix notification test.  We should see a checkout not allowed exception when trying to check out to a location if the asset requires acceptance.

* Fix Custom field import.

Add a test for custom field import, and fix a few issues related to
importing custom fields.  This will restore v3 functionality.

* Add UI support for mapping custom fields.

This still requires the field mappings to be created/assigned in
advance, but will fetch all custom field names and allow them to be
selected when setting up custom field mappings.

This commit also updates laravel-mix to v1.4.3 and other node
dependencies to fix some build issues.

* Fix some requestable asset page/assetloc issues.  I'd love to know why laravel expections relationships to be in lower case... but thats a question for another day.
2017-09-18 12:29:08 -07:00
snipe
c53dae4b72 Possible fix for #3919 - allow later versions of mcrypted base64 keys 2017-09-14 16:43:41 -07:00
snipe
e7d72beb88 Also check for $snipeSettings in the first place
Since the preflight also uses this basic blade
2017-09-12 13:08:43 -07:00
snipe
01e3f4a4db Use site name if provided in the settings table for basic template 2017-09-12 13:01:51 -07:00
snipe
1b76880b0e Add @imanghafoori1 as a contributor 2017-09-12 12:30:21 -07:00
snipe
6c283de60a Check for status_id key - related to #3928
TODO: Fix for model number
2017-09-08 17:24:28 -07:00
snipe
4e7a6c0ccf Fixes #3928 - adds correct key generation and passport install 2017-09-08 17:23:19 -07:00
snipe
eba145503b Bumped version 2017-09-06 19:02:05 -07:00
snipe
ae8c9d6afc Updated translations 2017-09-06 18:05:32 -07:00
snipe
faeca4139d Added new languages, commented out ones with 0% translated 2017-09-06 17:51:11 -07:00
snipe
47909b93f7 Fixed deleted users/restore users view 2017-09-06 17:11:43 -07:00
snipe
472658b2fe Fixes #3924 - missing/donked language string 2017-09-06 17:11:23 -07:00
snipe
42a03a0436 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-09-06 16:25:07 -07:00
Daniel Meltzer
ae0573b3da Fix asset create (#3929)
* Fix accidental commit of ImporterTest.

* Move the name() method to the presenter

This fixes some weird collisions between laravels voodoo and our
presenter voodoo that confused php.  It's also probably a cleaner place
to put it.  Should fix #3927

* Add missing parenthesis

* Add heading to tables on locations/view page.
2017-09-06 16:24:43 -07:00
snipe
34f816097e Fixed missing quotation mark 2017-09-06 15:43:07 -07:00
snipe
c651c9f1ed Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-09-05 18:35:43 -07:00
snipe
7c390cee2c Bumped version 2017-09-05 18:35:39 -07:00
Daniel Meltzer
987536930c Assorted fixes (#3923)
* Fix some n+1 problems

* Use route in notification dropdown to make sure we link to correct page

* Work on better UI support for checkout to non-user.  Fix links on index bootstrap table, work towards eliminating assignedUser

* Remove Asset::assigneduser() relationship.  Instead add a checkedOutToUser() method and/or port to using assignedTo()

* Adjust string to fit new reality

* Fix #3780.  Move the consumables getDataView method to the ApiController.  Not entirely RESTful, but it's a weird method that probably doesn't need its own controller and the functionality would be strange to stack on the userscontroller...

* Fix file uploads to assets and restore the delete route.

* Add asset maintence edit action to index.

* Suppliers asset list should link to the related asset, not to the supplier with same ID.

* Asset models page should use polymorphic formatter on assigned to to better handle assorted item types.

* Comment out more assigneduser fallacy until we figure out the query builder approach to searching for location text.
2017-09-05 17:54:58 -07:00
snipe
10f322198f Move audited count to top of table 2017-08-31 21:31:07 -07:00
snipe
761371509d Use notifiables for slack audit notification 2017-08-31 21:30:38 -07:00
snipe
3518ea7e7d Fixes #606 - email notifications for expected checkins 2017-08-31 21:18:05 -07:00
snipe
c92eed2b3e Small HTML email tweaks 2017-08-31 21:17:02 -07:00
snipe
0054ce3071 Fixes #3907 2017-08-31 13:45:48 -07:00
snipe
b0f74466bb Removed dd 2017-08-31 11:15:52 -07:00
snipe
b4a0484295 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-08-31 11:15:03 -07:00
Daniel Meltzer
bb874012d9 Progress towards better email notifications (#3911)
Working mail from notification.  Still requires testing/cleaning

Add tests around checkout notification.

This also removes the ability to check out an asset to a location|asset
that requires acceptance/a Eula.  For 4.1 we may think about how to
support such a thing, but at present it seems to make sense to only alow
such assets to be checked out to users, who can be responsible for the
items.
2017-08-31 11:14:21 -07:00
snipe
bb8583eb14 Remove lower casing for LDAP array re: #3910 2017-08-31 11:00:08 -07:00
snipe
8d2c229bc3 Move LDAP validation into form request 2017-08-31 10:44:00 -07:00
snipe
48e6208214 Fixes #3907 - do not require username on user if LDAP import 2017-08-31 10:43:36 -07:00
snipe
22233e3ba6 Bulk asset audit form (needs more testing) 2017-08-29 16:00:22 -07:00
snipe
e439f15a64 Fixed some date math for auditing 2017-08-28 17:20:20 -07:00
snipe
42175782a5 Only pull logo if there is a value 2017-08-26 17:43:00 -07:00
snipe
c7a21e0e4d Production asset build 2017-08-26 17:39:59 -07:00
snipe
d98ffd94f9 Localized modal titles with correct headers 2017-08-26 16:16:41 -07:00
Brady Wetherington
6ad5da44f3 Formalize modals (#3898)
* Refactor Modal JS into standalone file, remove duplicated JS and HTML

* Finish fixing Bulk-checkout and checkout
2017-08-26 16:06:52 -07:00
snipe
479f422e68 Added default if no audit settings are in place 2017-08-26 15:27:50 -07:00
snipe
e10cdd57a5 Removed old getassetloist method 2017-08-26 15:22:04 -07:00
snipe
bf157773c8 Also related to #3888 2017-08-26 15:21:38 -07:00
snipe
fba3949530 Fixes #3888 - broken preview of existing assets 2017-08-26 15:21:10 -07:00
snipe
abc3dea8ac Fixed wonky datepicker on bulk checkout 2017-08-26 14:16:16 -07:00
snipe
51d74ac06d Auduting improvements 2017-08-25 18:40:20 -07:00
snipe
af835d6efc Additional setting validation for new fields 2017-08-25 17:59:01 -07:00
snipe
a7a10455ae Bumped version 2017-08-25 13:27:58 -07:00
snipe
bd02b9ed62 Audit tweaks 2017-08-25 10:18:18 -07:00
snipe
16f57e16cb Fixes #1190 - added basic audit workflow 2017-08-25 10:04:19 -07:00
snipe
af6f208c43 Reordered settings nav 2017-08-25 10:03:05 -07:00
snipe
52270fa4db Derp 2017-08-25 08:30:48 -07:00
snipe
bf3731d65c Set default min password to 10 2017-08-25 08:23:23 -07:00
snipe
233ebf06ee ANOTHER fix for enum fuckery 2017-08-25 07:36:44 -07:00
snipe
e27f6a483d Updated translations 2017-08-25 07:32:57 -07:00
snipe
19670f9dd8 Remove assigned_to constraint 2017-08-25 06:30:10 -07:00
snipe
1448229cd2 Fixes location user route 2017-08-25 06:30:00 -07:00
snipe
4721cab928 Grr. 2017-08-25 06:08:19 -07:00
snipe
08f3e78d26 Merge branch 'checkout-to-location-v2' of https://github.com/dmeltzer/snipe-it into dmeltzer-checkout-to-location-v2
# Conflicts:
#	app/Http/Controllers/Api/UsersController.php
#	app/Http/Transformers/LocationsTransformer.php
#	resources/views/locations/view.blade.php
#	routes/api.php
#	tests/_data/dump.sql
2017-08-25 06:04:22 -07:00
snipe
62227ec27d Link to location in user view 2017-08-25 05:48:32 -07:00
snipe
10711245ba Fixes #3792 - parent/child locations in API 2017-08-25 05:32:12 -07:00
snipe
29a7c8577d Fixes #3849 - fillable for accessories 2017-08-25 03:48:07 -07:00
snipe
dfb1ff81e6 Fixes settings problem in unit tests 2017-08-25 03:40:56 -07:00
snipe
021e723acf Fixed typo 2017-08-25 03:27:41 -07:00
snipe
14c0c314aa Make sure payload is always passed, even if null 2017-08-25 03:27:31 -07:00
snipe
d23ea70b08 Added auth check back to asset store 2017-08-25 03:26:50 -07:00
snipe
1b047c768b Added fullName() presenter for locations 2017-08-25 03:26:10 -07:00
Daniel Meltzer
54279f22a3 Update DB to fix tests. 2017-06-12 18:24:20 -05:00
Daniel Meltzer
dfea47a272 Fix location view display. Migrate to api controller methods and fix missing bits to make this happen. Show manager on the location view page. 2017-06-12 18:24:20 -05:00
Daniel Meltzer
f0d78091d2 Add a manager field to locations.
This is round one of the rethink of checkout-to-everything.  A location
now has a manager field, and the manager (by default) be responsible for
assets checked out to the location.
2017-06-12 18:23:50 -05:00
631 changed files with 9816 additions and 4095 deletions

View File

@@ -728,6 +728,15 @@
"contributions": [
"code"
]
},
{
"login": "imanghafoori1",
"name": "Iman",
"avatar_url": "https://avatars0.githubusercontent.com/u/6961695?v=4",
"profile": "https://github.com/imanghafoori1",
"contributions": [
"code"
]
}
]
}

View File

@@ -1,5 +1,5 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=develop)](https://travis-ci.org/snipe/snipe-it) [![Stories in Ready](https://badge.waffle.io/snipe/snipe-it.png?label=ready+for+dev&title=Ready+for+development)](http://waffle.io/snipe/snipe-it) [![Maintenance](https://img.shields.io/maintenance/yes/2017.svg)]() [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.png)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeyhead.svg?style=social)](https://twitter.com/snipeyhead) [![Zenhub](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.io) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-78-orange.svg?style=flat-square)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-79-orange.svg?style=flat-square)](#contributors)
## Snipe-IT - Open Source Asset Management System
@@ -67,7 +67,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars0.githubusercontent.com/u/8341172?v=3" width="110px;"/><br /><sub>Jay Richards</sub>](http://www.cordeos.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [<img src="https://avatars2.githubusercontent.com/u/7295127?v=3" width="110px;"/><br /><sub>Alexander Innes</sub>](https://necurity.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [<img src="https://avatars2.githubusercontent.com/u/334485?v=3" width="110px;"/><br /><sub>Danny Garcia</sub>](https://buzzedword.codes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [<img src="https://avatars2.githubusercontent.com/u/366855?v=3" width="110px;"/><br /><sub>archpoint</sub>](https://github.com/archpoint)<br />[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [<img src="https://avatars1.githubusercontent.com/u/67991?v=3" width="110px;"/><br /><sub>Jake McGraw</sub>](http://www.jakemcgraw.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [<img src="https://avatars1.githubusercontent.com/u/1714374?v=3" width="110px;"/><br /><sub>FleischKarussel</sub>](https://github.com/FleischKarussel)<br />[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/319644?v=3" width="110px;"/><br /><sub>Dylan Yi</sub>](https://github.com/feeva)<br />[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/857740?v=3" width="110px;"/><br /><sub>Gil Rutkowski</sub>](http://FlashingCursor.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [<img src="https://avatars3.githubusercontent.com/u/129360?v=3" width="110px;"/><br /><sub>Desmond Morris</sub>](http://www.desmondmorris.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [<img src="https://avatars2.githubusercontent.com/u/52936?v=3" width="110px;"/><br /><sub>Nick Peelman</sub>](http://peelman.us)<br />[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [<img src="https://avatars0.githubusercontent.com/u/53161?v=3" width="110px;"/><br /><sub>Abraham Vegh</sub>](https://abrahamvegh.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [<img src="https://avatars0.githubusercontent.com/u/2818680?v=3" width="110px;"/><br /><sub>Mohamed Rashid</sub>](https://github.com/rashivkp)<br />[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/1509456?v=3" width="110px;"/><br /><sub>Kasey</sub>](http://hinchk.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [<img src="https://avatars2.githubusercontent.com/u/10522541?v=3" width="110px;"/><br /><sub>Brett</sub>](https://github.com/BrettFagerlund)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") |
| [<img src="https://avatars2.githubusercontent.com/u/16108587?v=3" width="110px;"/><br /><sub>Jason Spriggs</sub>](http://jasonspriggs.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [<img src="https://avatars2.githubusercontent.com/u/1134568?v=3" width="110px;"/><br /><sub>Nate Felton</sub>](http://n8felton.wordpress.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [<img src="https://avatars2.githubusercontent.com/u/14036694?v=3" width="110px;"/><br /><sub>Manasses Ferreira</sub>](http://homepages.dcc.ufmg.br/~manassesferreira)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [<img src="https://avatars0.githubusercontent.com/u/15913949?v=3" width="110px;"/><br /><sub>Steve</sub>](https://github.com/steveelwood)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [<img src="https://avatars1.githubusercontent.com/u/3361683?v=3" width="110px;"/><br /><sub>matc</sub>](http://twitter.com/matc)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [<img src="https://avatars3.githubusercontent.com/u/7405702?v=3" width="110px;"/><br /><sub>Cole R. Davis</sub>](http://www.davisracingteam.com)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [<img src="https://avatars2.githubusercontent.com/u/10167681?v=3" width="110px;"/><br /><sub>gibsonjoshua55</sub>](https://github.com/gibsonjoshua55)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [<img src="https://avatars0.githubusercontent.com/u/6961695?v=4" width="110px;"/><br /><sub>Iman</sub>](https://github.com/imanghafoori1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -47,6 +47,7 @@ class RecryptFromMcrypt extends Command
// Check and see if they have a legacy app key listed in their .env
// If not, we can try to use the current APP_KEY if looks like it's old
$legacy_key = env('LEGACY_APP_KEY');
$key_parts = explode(':', $legacy_key);
$errors = array();
if (!$legacy_key) {
@@ -54,11 +55,23 @@ class RecryptFromMcrypt extends Command
return false;
}
// Check that the app key is 32 characters
// Do some basic legacy app key length checks
if (strlen($legacy_key) == 32) {
$this->comment('INFO: Your LEGACY_APP_KEY is 32 characters. Okay to continue.');
$legacy_length_check = true;
} elseif (array_key_exists('1', $key_parts) && (strlen($key_parts[1])==44)) {
$legacy_length_check = true;
} else {
$this->error('ERROR: Your LEGACY_APP_KEY is not the correct length (32 characters). Please locate your old APP_KEY and use that as your LEGACY_APP_KEY in your .env file to continue.');
$legacy_length_check = false;
}
// Check that the app key is 32 characters
if ($legacy_length_check === true) {
$this->comment('INFO: Your LEGACY_APP_KEY looks correct. Okay to continue.');
} else {
$this->error('ERROR: Your LEGACY_APP_KEY is not the correct length (32 characters or base64 followed by 44 characters for later versions). Please locate your old APP_KEY and use that as your LEGACY_APP_KEY in your .env file to continue.');
return false;
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use Illuminate\Console\Command;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
class SendExpectedCheckinAlerts extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'snipeit:expected-checkin';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check for overdue or upcoming expected checkins.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function fire()
{
$whenNotify = Carbon::now()->addDays(7);
$assets = Asset::with('assignedTo')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$this->info($whenNotify.' is deadline');
$this->info($assets->count().' assets');
foreach ($assets as $asset) {
if ($asset->assignedTo && $asset->checkoutOutToUser()) {
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
//$this->info($asset);
}
}
}
}

View File

@@ -17,6 +17,7 @@ class Kernel extends ConsoleKernel
Commands\CreateAdmin::class,
Commands\SendExpirationAlerts::class,
Commands\SendInventoryAlerts::class,
Commands\SendExpectedCheckinAlerts::class,
Commands\ObjectImportCommand::class,
Commands\Versioning::class,
Commands\SystemBackup::class,
@@ -38,6 +39,7 @@ class Kernel extends ConsoleKernel
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkins')->daily();
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Exceptions;
use Exception;
class CheckoutNotAllowed extends Exception
{
public function __toString()
{
"A checkout is not allowed under these circumstances";
}
}

View File

@@ -80,7 +80,7 @@ class Handler extends ExceptionHandler
}
}
// Try to parse 500 Errors ina bit nicer way when debug is enabled.
// Try to parse 500 Errors in a bit nicer way when debug is enabled.
if (config('app.debug')) {
return response()->json(Helper::formatStandardApiResponse('error', null, "An Error has occured! " . $e->getMessage()), 500);
}

View File

@@ -683,12 +683,11 @@ class Helper
public static function formatStandardApiResponse($status, $payload = null, $messages = null) {
$array['status'] = $status;
($payload) ? $array['payload'] = $payload : '';
$array['messages'] = $messages;
if (($messages) && (count($messages) > 0)) {
$array['messages'] = $messages;
}
($payload) ? $array['payload'] = $payload : $array['payload'] = null;
return $array;
}

View File

@@ -260,7 +260,7 @@ class AccessoriesController extends Controller
'assigned_to' => $request->get('assigned_to')
]);
$logaction = $accessory->logCheckout(e(Input::get('note')));
$logaction = $accessory->logCheckout(e(Input::get('note')), $user);
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();

View File

@@ -1,16 +1,17 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\AssetMaintenance;
use Carbon\Carbon;
use App\Models\Company;
use App\Models\Asset;
use App\Helpers\Helper;
use Auth;
use Gate;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetMaintenancesTransformer;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Company;
use Auth;
use Carbon\Carbon;
use Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Input;
/**
* This controller handles all actions related to Asset Maintenance for

View File

@@ -31,6 +31,7 @@ use TCPDF;
use Validator;
use View;
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
@@ -83,9 +84,8 @@ class AssetsController extends Controller
}
$assets = Company::scopeCompanyables(Asset::select('assets.*'))->with(
'assetLoc', 'assetstatus', 'defaultLoc', 'assetlog', 'company',
'model.category', 'model.manufacturer', 'model.fieldset', 'assigneduser','supplier');
'assetloc', 'assetstatus', 'defaultLoc', 'assetlog', 'company',
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
// If we should search on everything
if (($request->has('search')) && (count($filter) == 0)) {
$assets->TextSearch($request->input('search'));
@@ -96,7 +96,6 @@ class AssetsController extends Controller
}
}
// These are used by the API to query against specific ID numbers
if ($request->has('status_id')) {
$assets->where('status_id', '=', $request->input('status_id'));
@@ -231,7 +230,8 @@ class AssetsController extends Controller
*/
public function store(AssetRequest $request)
{
// $this->authorize('create', Asset::class);
$this->authorize('create', Asset::class);
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
@@ -279,6 +279,7 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
@@ -493,5 +494,52 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
}
/**
* Mark an asset as audited
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return JsonResponse
*/
public function audit(Request $request) {
$this->authorize('audit', Asset::class);
$rules = array(
'asset_tag' => 'required',
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable'
);
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$asset = Asset::where('asset_tag','=', $request->input('asset_tag'))->first();
if ($asset) {
$asset->next_audit_date = $request->input('next_audit_date');
if ($asset->save()) {
$log = $asset->logAudit(request('note'),request('location_id'));
return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag'=> e($asset->asset_tag),
'note'=> e($request->input('note')),
'next_audit_date' => Helper::getFormattedDateObject($log->calcNextAuditDate())
], trans('admin/hardware/message.audit.success')));
}
}
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.$request->input('asset_tag').' not found'));
}
}

View File

@@ -148,4 +148,47 @@ class ConsumablesController extends Controller
$consumable->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.delete.success')));
}
/**
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return array
*/
public function getDataView($consumableId)
{
//$consumable = Consumable::find($consumableID);
$consumable = Consumable::with(array('consumableAssignments'=>
function ($query) {
$query->orderBy('created_at', 'DESC');
},
'consumableAssignments.admin'=> function ($query) {
},
'consumableAssignments.user'=> function ($query) {
},
))->find($consumableId);
// $consumable->load('consumableAssignments.admin','consumableAssignments.user');
if (!Company::isCurrentUserHasAccess($consumable)) {
return ['total' => 0, 'rows' => []];
}
$this->authorize('view', Component::class);
$rows = array();
foreach ($consumable->consumableAssignments as $consumable_assignment) {
$rows[] = [
'name' => $consumable_assignment->user->present()->nameUrl(),
'created_at' => ($consumable_assignment->created_at->format('Y-m-d H:i:s')=='-0001-11-30 00:00:00') ? '' : $consumable_assignment->created_at->format('Y-m-d H:i:s'),
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
];
}
$consumableCount = $consumable->users->count();
$data = array('total' => $consumableCount, 'rows' => $rows);
return $data;
}
}

View File

@@ -2,9 +2,11 @@
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Http\Request;
class CustomFieldsController extends Controller
{
@@ -16,6 +18,15 @@ class CustomFieldsController extends Controller
* @since [v3.0]
* @return Array
*/
public function index()
{
$this->authorize('index', CustomFields::class);
$fields = CustomField::get();
$total = count($fields);
return (new CustomFieldsTransformer)->transformCustomFields($fields, $total);
}
public function postReorder(Request $request, $id)
{
$fieldset = CustomFieldset::find($id);

View File

@@ -21,7 +21,7 @@ class LicensesController extends Controller
public function index(Request $request)
{
$this->authorize('view', License::class);
$licenses = Company::scopeCompanyables(License::with('company', 'licenseSeatsRelation', 'manufacturer'));
$licenses = Company::scopeCompanyables(License::with('company', 'licenseSeatsRelation', 'manufacturer', 'supplier'));
if ($request->has('search')) {
$licenses = $licenses->TextSearch($request->input('search'));

View File

@@ -21,7 +21,7 @@ class LocationsController extends Controller
{
$this->authorize('view', Location::class);
$allowed_columns = ['id','name','address','address2','city','state','country','zip','created_at',
'updated_at','parent_id'];
'updated_at','parent_id', 'manager_id'];
$locations = Location::select([
'locations.id',
@@ -33,6 +33,7 @@ class LocationsController extends Controller
'locations.zip',
'locations.country',
'locations.parent_id',
'locations.manager_id',
'locations.created_at',
'locations.updated_at',
'locations.currency'

View File

@@ -19,7 +19,7 @@ class ReportsController extends Controller
public function index(Request $request)
{
$actionlogs = Actionlog::with('item', 'user', 'target');
$actionlogs = Actionlog::with('item', 'user', 'target','location');
if ($request->has('search')) {
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
@@ -36,6 +36,10 @@ class ReportsController extends Controller
->where('item_type','=',"App\\Models\\".ucwords($request->input('item_type')));
}
if ($request->has('action_type')) {
$actionlogs = $actionlogs->where('action_type','=',$request->input('action_type'))->orderBy('created_at', 'desc');
}
$allowed_columns = [
'id',
'created_at'

View File

@@ -9,6 +9,7 @@ use App\Models\Company;
use App\Models\User;
use App\Helpers\Helper;
use App\Http\Requests\SaveUserRequest;
use App\Models\Asset;
class UsersController extends Controller
{
@@ -51,6 +52,12 @@ class UsersController extends Controller
$users = $users->TextSearch($request->input('search'));
}
if (($request->has('deleted')) && ($request->input('deleted')=='true')) {
$users = $users->GetDeleted();
}
if ($request->has('company_id')) {
$users = $users->where('company_id', '=', $request->input('company_id'));
}
@@ -182,4 +189,19 @@ class UsersController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
}
/**
* Return JSON containing a list of assets assigned to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @param $userId
* @return string JSON
*/
public function assets($id)
{
$this->authorize('view', User::class);
$assets = Asset::where('assigned_to', '=', $id)->with('model')->get();
return response()->json($assets);
}
}

View File

@@ -537,17 +537,18 @@ class AssetsController extends Controller
$this->authorize('checkin', $asset);
$admin = Auth::user();
$user = $asset->assignedUser;
if($asset->assignedType() == Asset::USER) {
$user = $asset->assignedTo;
}
if (is_null($target = $asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
// This is just used for the redirect
$return_to = $asset->assigned_to;
$asset->expected_checkin = null;
$asset->last_checkout = null;
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->assigned_type = null;
$asset->accepted = null;
$asset->name = e(Input::get('name'));
@@ -566,7 +567,7 @@ class AssetsController extends Controller
$data['item_serial'] = $asset->serial;
$data['note'] = $logaction->note;
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!config('app.lock_passwords'))) {
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!empty($user->email)) && (!config('app.lock_passwords'))) {
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
@@ -575,7 +576,7 @@ class AssetsController extends Controller
}
if ($backto=='user') {
return redirect()->to("admin/users/".$return_to.'/view')->with('success', trans('admin/hardware/message.checkin.success'));
return redirect()->to("admin/users/".$user->id.'/view')->with('success', trans('admin/hardware/message.checkin.success'));
}
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkin.success'));
}
@@ -595,9 +596,12 @@ class AssetsController extends Controller
*/
public function show($assetId = null)
{
$asset = Asset::withTrashed()->find($assetId);
$settings = Setting::getSettings();
$this->authorize('view', $asset);
$settings = Setting::getSettings();
$audit_log = Actionlog::where('action_type','=','audit')->where('item_id','=',$assetId)->where('item_type','=',Asset::class)->orderBy('created_at','DESC')->first();
if (isset($asset)) {
@@ -617,7 +621,8 @@ class AssetsController extends Controller
'url' => route('qr_code/hardware', $asset->id)
);
return view('hardware/view', compact('asset', 'qr_code', 'settings'))->with('use_currency', $use_currency);
return view('hardware/view', compact('asset', 'qr_code', 'settings'))
->with('use_currency', $use_currency)->with('audit_log',$audit_log);
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist', compact('id')));
@@ -953,7 +958,7 @@ class AssetsController extends Controller
* @since [v1.0]
* @return View
*/
public function getDeleteFile($assetId = null, $fileId = null)
public function deleteFile($assetId = null, $fileId = null)
{
$asset = Asset::find($assetId);
$this->authorize('update', $asset);
@@ -1233,4 +1238,47 @@ class AssetsController extends Controller
// Redirect to the asset management page with error
return redirect()->to("hardware/bulk-checkout")->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
}
public function quickScan(Request $request)
{
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths(12)->toDateString();
return view('hardware/quickscan')->with('next_audit_date', $dt)->with('locations_list', Helper::locationsList());
}
public function audit(Request $request, $id)
{
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths(12)->toDateString();
$asset = Asset::findOrFail($id);
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list', Helper::locationsList());
}
public function auditStore(Request $request, $id)
{
$this->authorize('audit', Asset::class);
$rules = array(
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable'
);
$validator = \Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$asset = Asset::findOrFail($id);
$asset->next_audit_date = $request->input('next_audit_date');
if ($asset->save()) {
$asset->logAudit(request('note'),request('location_id'));
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success'));
}
}
}

View File

@@ -283,7 +283,7 @@ class ComponentsController extends Controller
'asset_id' => $asset_id
]);
$component->logCheckout(e(Input::get('note')), $asset_id);
$component->logCheckout(e(Input::get('note')), $asset);
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
}

View File

@@ -250,7 +250,7 @@ class ConsumablesController extends Controller
'assigned_to' => e(Input::get('assigned_to'))
]);
$logaction = $consumable->logCheckout(e(Input::get('note')));
$logaction = $consumable->logCheckout(e(Input::get('note')), $user);
$data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name;
@@ -273,47 +273,4 @@ class ConsumablesController extends Controller
}
/**
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return array
*/
public function getDataView($consumableId)
{
//$consumable = Consumable::find($consumableID);
$consumable = Consumable::with(array('consumableAssigments'=>
function ($query) {
$query->orderBy('created_at', 'DESC');
},
'consumableAssigments.admin'=> function ($query) {
},
'consumableAssigments.user'=> function ($query) {
},
))->find($consumableId);
// $consumable->load('consumableAssigments.admin','consumableAssigments.user');
if (!Company::isCurrentUserHasAccess($consumable)) {
return ['total' => 0, 'rows' => []];
}
$this->authorize('view', Component::class);
$rows = array();
foreach ($consumable->consumableAssigments as $consumable_assignment) {
$rows[] = [
'name' => $consumable_assignment->user->present()->nameUrl(),
'created_at' => ($consumable_assignment->created_at->format('Y-m-d H:i:s')=='-0001-11-30 00:00:00') ? '' : $consumable_assignment->created_at->format('Y-m-d H:i:s'),
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
];
}
$consumableCount = $consumable->users->count();
$data = array('total' => $consumableCount, 'rows' => $rows);
return $data;
}
}

View File

@@ -291,22 +291,22 @@ class LicensesController extends Controller
// Ooops.. something went wrong
return redirect()->back()->withInput()->withErrors($validator);
}
$target = null;
if ($assigned_to!='') {
// Check if the user exists
if (is_null($is_assigned_to = User::find($assigned_to))) {
if (is_null($target = User::find($assigned_to))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.user_does_not_exist'));
}
}
if ($asset_id!='') {
if (is_null($asset = Asset::find($asset_id))) {
if (is_null($target = Asset::find($asset_id))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.asset_does_not_exist'));
}
if (($asset->assigned_to!='') && (($asset->assigned_to!=$assigned_to)) && ($assigned_to!='')) {
if (($target->assigned_to!='') && (($target->assigned_to!=$assigned_to)) && ($target!='')) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.owner_doesnt_match_asset'));
}
}
@@ -332,7 +332,7 @@ class LicensesController extends Controller
// Was the asset updated?
if ($licenseSeat->save()) {
$licenseSeat->logCheckout($request->input('note'));
$licenseSeat->logCheckout($request->input('note'), $target);
$data['license_id'] =$licenseSeat->license_id;
$data['note'] = $request->input('note');

View File

@@ -63,7 +63,8 @@ class LocationsController extends Controller
return view('locations/edit')
->with('location_options', $location_options)
->with('item', new Location);
->with('item', new Location)
->with('manager_list', Helper::managerList());
}
@@ -88,6 +89,7 @@ class LocationsController extends Controller
$location->state = Input::get('state');
$location->country = Input::get('country');
$location->zip = Input::get('zip');
$location->manager_id = Input::get('manager_id');
$location->user_id = Auth::id();
if ($location->save()) {
@@ -154,7 +156,10 @@ class LocationsController extends Controller
$location_options = Location::flattenLocationsArray($location_options_array);
$location_options = array('' => 'Top Level') + $location_options;
return view('locations/edit', compact('item'))->with('location_options', $location_options);
return view('locations/edit', compact('item'))
->with('location_options', $location_options)
->with('manager_list', Helper::managerList());
}
@@ -185,6 +190,7 @@ class LocationsController extends Controller
$location->country = Input::get('country');
$location->zip = Input::get('zip');
$location->ldap_ou = Input::get('ldap_ou');
$location->manager_id = Input::get('manager_id');
// Was the location updated?
if ($location->save()) {
@@ -232,8 +238,6 @@ class LocationsController extends Controller
* the content for the locations detail page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getDataViewUsers() method that returns JSON for location users
* @see LocationsController::getDataViewAssets() method that returns JSON for location assets
* @param int $locationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
@@ -252,78 +256,4 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('error', $error);
}
/**
* Returns a JSON response that contains the users association with the
* selected location, to be used by the location detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getView() method that creates the display view
* @param $locationID
* @return array
* @internal param int $locationId
* @since [v1.8]
*/
public function getDataViewUsers($locationID)
{
$location = Location::find($locationID);
$users = User::where('location_id', '=', $location->id);
if (Input::has('search')) {
$users = $users->TextSearch(e(Input::get('search')));
}
$users = $users->get();
$rows = array();
foreach ($users as $user) {
$rows[] = array(
'name' => (string)link_to_route('users.show', e($user->present()->fullName()), ['user'=>$user->id])
);
}
$data = array('total' => $users->count(), 'rows' => $rows);
return $data;
}
/**
* Returns a JSON response that contains the assets association with the
* selected location, to be used by the location detail view.
*
* @todo This is broken for accessories and consumables.
* @todo This is a very naive implementation. Should clean this up with query scopes.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getView() method that creates the display view
* @param int $locationID
* @since [v1.8]
* @return array
*/
public function getDataViewAssets($locationID)
{
$location = Location::find($locationID)->load('assignedassets.model');
$assets = Asset::AssetsByLocation($location);
if (Input::has('search')) {
$assets = $assets->TextSearch(e(Input::get('search')));
}
$assets = $assets->get();
$rows = array();
foreach ($assets as $asset) {
$rows[] = [
'name' => (string)link_to_route('hardware.show', e($asset->present()->name()), ['hardware' => $asset->id]),
'asset_tag' => e($asset->asset_tag),
'serial' => e($asset->serial),
'model' => e($asset->model->name),
];
}
$data = array('total' => $assets->count(), 'rows' => $rows);
return $data;
}
}

View File

@@ -186,7 +186,7 @@ class ReportsController extends Controller
{
// Grab all the assets
$assets = Asset::with('model', 'assignedTo', 'assetstatus', 'defaultLoc', 'assetlog', 'company')
$assets = Asset::with( 'assignedTo', 'assetstatus', 'defaultLoc', 'assetloc', 'assetlog', 'company', 'model.category', 'model.depreciation')
->orderBy('created_at', 'DESC')->get();
return view('reports/depreciation', compact('assets'));
@@ -271,6 +271,20 @@ class ReportsController extends Controller
}
/**
* Displays audit report.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return View
*/
public function audit()
{
return view('reports/audit');
}
/**
* Displays activity report.
*
@@ -376,7 +390,7 @@ class ReportsController extends Controller
*/
public function postCustom()
{
$assets = Asset::orderBy('created_at', 'DESC')->with('company', 'assigneduser', 'assetloc', 'defaultLoc', 'assigneduser.userloc', 'model', 'supplier', 'assetstatus', 'model.manufacturer')->get();
$assets = Asset::orderBy('created_at', 'DESC')->with('company', 'assignedTo', 'assetloc', 'defaultLoc', 'model', 'supplier', 'assetstatus', 'model.manufacturer')->get();
$customfields = CustomField::get();
$rows = [ ];
@@ -538,8 +552,8 @@ class ReportsController extends Controller
if (e(Input::get('username')) == '1') {
// Only works if we're checked out to a user, not anything else.
if ($asset->assigneduser) {
$row[] = '"' .e($asset->assigneduser->username). '"';
if ($asset->checkedOutToUser()) {
$row[] = '"' .e($asset->assignedTo->username). '"';
} else {
$row[] = ''; // Empty string if unassigned
}
@@ -547,8 +561,8 @@ class ReportsController extends Controller
if (e(Input::get('employee_num')) == '1') {
// Only works if we're checked out to a user, not anything else.
if ($asset->assigneduser) {
$row[] = '"' .e($asset->assigneduser->employee_num). '"';
if ($asset->checkedOutToUser()) {
$row[] = '"' .e($asset->assignedTo->employee_num). '"';
} else {
$row[] = ''; // Empty string if unassigned
}

View File

@@ -20,6 +20,7 @@ use Auth;
use App\Models\User;
use App\Http\Requests\SetupUserRequest;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsLdapRequest;
/**
* This controller handles all actions related to Settings for
@@ -184,6 +185,7 @@ class SettingsController extends Controller
$settings->site_name = e(Input::get('site_name'));
$settings->alert_email = e(Input::get('email'));
$settings->alerts_enabled = 1;
$settings->pwd_secure_min = 10;
$settings->brand = 1;
$settings->locale = 'en';
$settings->default_currency = 'USD';
@@ -561,10 +563,12 @@ class SettingsController extends Controller
$alert_email = rtrim($request->input('alert_email'), ',');
$alert_email = trim($alert_email);
$setting->alert_email = e($alert_email);
$setting->alert_email = $alert_email;
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');
$setting->audit_interval = $request->input('audit_interval');
$setting->audit_warning_days = $request->input('audit_warning_days');
if ($setting->save()) {
return redirect()->route('settings.index')

View File

@@ -376,7 +376,6 @@ class UsersController extends Controller
}
if ($user->licenses()->count() > 0) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->licenses()->count() . ' licenses associated with them.');
}
@@ -386,6 +385,11 @@ class UsersController extends Controller
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->accessories()->count() . ' accessories associated with them.');
}
if ($user->managedLocations()->count() > 0) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.');
}
// Delete the user
$user->delete();
@@ -1147,21 +1151,7 @@ class UsersController extends Controller
}
return redirect()->route('ldap/user')->with('success', "LDAP Import successful.")->with('summary', $summary);
}
/**
* Return JSON containing a list of assets assigned to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @param $userId
* @return string JSON
*/
public function getAssetList($userId)
{
$this->authorize('view', User::class);
$assets = Asset::where('assigned_to', '=', $userId)->with('model')->get();
return response()->json($assets);
}
/**
* Exports users to CSV

View File

@@ -68,8 +68,8 @@ class ViewAssetsController extends Controller
public function getRequestableIndex()
{
$assets = Asset::with('model', 'defaultLoc', 'assetloc', 'assignedTo')->Hardware()->RequestableAssets()->get();
$models = AssetModel::with('category')->RequestableModels()->get();
$assets = Asset::with('model', 'defaultLoc', 'assetloc', 'assignedTo', 'requests')->Hardware()->RequestableAssets()->get();
$models = AssetModel::with('category', 'requests', 'assets')->RequestableModels()->get();
return view('account/requestable-assets', compact('user', 'assets', 'models'));
}

View File

@@ -34,14 +34,14 @@ class SaveUserRequest extends Request
case 'POST':
{
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
$rules['password'] = Setting::passwordComplexityRulesSaving('store');
}
// Save all fields
case 'PUT':
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
// Save only what's passed

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Session;
class SettingsLdapRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
"ldap_server" => 'sometimes|required_if:ldap_enabled,1|url|nullable',
"ldap_uname" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_basedn" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_filter" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_username_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_fname_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_lname_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_auth_filter_query" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_version" => 'sometimes|required_if:ldap_enabled,1|nullable',
];
return $rules;
}
public function response(array $errors)
{
$this->session()->flash('errors', Session::get('errors', new \Illuminate\Support\ViewErrorBag)
->put('default', new \Illuminate\Support\MessageBag($errors)));
\Input::flash();
return parent::response($errors);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Transformers;
use App\Models\Actionlog;
use App\Models\Setting;
use Gate;
use Illuminate\Database\Eloquent\Collection;
use App\Helpers\Helper;
@@ -12,23 +13,32 @@ class ActionlogsTransformer
public function transformActionlogs (Collection $actionlogs, $total)
{
$array = array();
$settings = Setting::getSettings();
foreach ($actionlogs as $actionlog) {
$array[] = self::transformActionlog($actionlog);
$array[] = self::transformActionlog($actionlog, $settings);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformActionlog (Actionlog $actionlog)
public function transformActionlog (Actionlog $actionlog, $settings = null)
{
$array = [
'id' => (int) $actionlog->id,
'icon' => $actionlog->present()->icon(),
'image' => (method_exists($actionlog->item, 'getImageUrl')) ? $actionlog->item->getImageUrl() : null,
'item' => ($actionlog->item) ? [
'id' => (int) $actionlog->item->id,
'name' => e($actionlog->item->getDisplayNameAttribute()),
'type' => e($actionlog->itemType()),
] : null,
'location' => ($actionlog->location) ? [
'id' => (int) $actionlog->location->id,
'name' => e($actionlog->location->name)
] : null,
'created_at' => Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($actionlog->updated_at, 'datetime'),
'next_audit_date' => ($actionlog->itemType()=='asset') ? Helper::getFormattedDateObject($actionlog->calcNextAuditDate(), 'date'): null,
'days_to_next_audit' => $actionlog->daysUntilNextAudit($settings->audit_interval, $actionlog->item),
'action_type' => $actionlog->present()->actionType(),
'admin' => ($actionlog->user) ? [
'id' => (int) $actionlog->user->id,

View File

@@ -38,6 +38,7 @@ class AssetMaintenancesTransformer
];
$permissions_array['available_actions'] = [
'update' => (bool) Gate::allows('update', Asset::class),
'delete' => (bool) Gate::allows('delete', Asset::class),
];

View File

@@ -62,14 +62,7 @@ class AssetsTransformer
'name'=> e($asset->defaultLoc->name)
] : null,
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
'assigned_to' => ($asset->assigneduser) ? [
'id' => (int) $asset->assigneduser->id,
'username' => e($asset->assigneduser->username),
'name' => e($asset->assigneduser->getFullNameAttribute()),
'first_name'=> e($asset->assigneduser->first_name),
'last_name'=> e($asset->assigneduser->last_name),
'employee_number' => e($asset->assigneduser->employee_num),
] : null,
'assigned_to' => $this->transformAssignedTo($asset),
'warranty' => ($asset->warranty_months > 0) ? e($asset->warranty_months . ' ' . trans('admin/hardware/form.months')) : null,
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'),
@@ -130,4 +123,24 @@ class AssetsTransformer
{
return (new DatatablesTransformer)->transformDatatables($assets);
}
public function transformAssignedTo($asset)
{
if ($asset->checkedOutToUser()) {
return $asset->assignedTo ? [
'id' => (int) $asset->assignedTo->id,
'username' => e($asset->assignedTo->username),
'name' => e($asset->assignedTo->getFullNameAttribute()),
'first_name'=> e($asset->assignedTo->first_name),
'last_name'=> e($asset->assignedTo->last_name),
'employee_number' => e($asset->assignedTo->employee_num),
'type' => 'user'
] : null;
}
return $asset->assignedTo ? [
'id' => $asset->assignedTo->id,
'name' => $asset->assignedTo->display_name,
'type' => $asset->assignedType()
] : null;
}
}

View File

@@ -25,7 +25,7 @@ class CustomFieldsTransformer
'name' => e($field->name),
'db_column_name' => e($field->db_column_name()),
'format' => e($field->format),
'required' => $field->pivot->required,
'required' => $field->pivot ? $field->pivot->required : false,
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
];

View File

@@ -9,7 +9,7 @@ use App\Helpers\Helper;
class LocationsTransformer
{
public function transformLocations (Collection $locations, $total)
public function transformLocations(Collection $locations, $total)
{
$array = array();
foreach ($locations as $location) {
@@ -18,18 +18,16 @@ class LocationsTransformer
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformLocation (Location $location = null)
public function transformLocation(Location $location = null)
{
if ($location) {
$assets_arr = [];
foreach($location->assets() as $asset) {
$assets_arr = ['id' => $asset->id];
}
$children_arr = [];
foreach($location->childLocations() as $child) {
$children_arr = ['id' => $child->id];
foreach($location->childLocations as $child) {
$children_arr[] = [
'id' => (int) $child->id,
'name' => $child->name
];
}
$array = [
@@ -41,13 +39,15 @@ class LocationsTransformer
'assets_checkedout' => $location->assets()->count(),
'assets_default' => $location->assignedassets()->count(),
'country' => e($location->country),
'assets' => $assets_arr,
'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($location->updated_at, 'datetime'),
'parent' => ($location->parent) ? [
'id' => (int) $location->parent->id,
'name'=> e($location->parent->name)
] : null,
'manager' => ($location->manager) ? (new UsersTransformer)->transformUser($location->manager) : null,
'children' => $children_arr,
];

View File

@@ -56,9 +56,10 @@ class UsersTransformer
];
$permissions_array['available_actions'] = [
'update' => Gate::allows('update', User::class) ? true : false,
'delete' => Gate::allows('delete', User::class) ? true : false,
'clone' => Gate::allows('create', User::class) ? true : false,
'update' => (Gate::allows('update', User::class) && ($user->deleted_at=='')) ? true : false,
'delete' => (Gate::allows('delete', User::class) && ($user->deleted_at=='')) ? true : false,
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at=='')) ,
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at!='')) ? true : false,
];
$array += $permissions_array;

View File

@@ -63,7 +63,9 @@ class AssetImporter extends ItemImporter
$this->item['image'] = $this->findCsvMatch($row, "image");
$this->item['warranty_months'] = intval($this->findCsvMatch($row, "warranty"));
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
if (!$this->item['status_id'] && !$editingAsset) {
// If no status ID is found
if (!array_key_exists('status_id', $this->item) && !$editingAsset) {
$this->log("No status field found, defaulting to first status.");
$this->item['status_id'] = $this->defaultStatusLabelId;
}

View File

@@ -109,7 +109,15 @@ abstract class Importer
{
$headerRow = $this->csv->fetchOne();
$results = $this->normalizeInputArray($this->csv->fetchAssoc());
$this->customFields = CustomField::All(['name']);
// Stolen From https://adamwathan.me/2016/07/14/customizing-keys-when-mapping-collections/
// This 'inverts' the fields such that we have a collection of fields indexed by name.
$cFs = CustomField::All();
$this->customFields = $cFs->reduce(function ($nameLookup, $field) {
$nameLookup[$field['name']] = $field;
return $nameLookup;
});
DB::transaction(function () use (&$results) {
Model::unguard();
$resultsCount = sizeof($results);
@@ -136,14 +144,12 @@ abstract class Importer
* @param $default string
* @return string
*/
public function findCsvMatch(array $array, $key, $default = '')
public function findCsvMatch(array $array, $key, $default = null)
{
$val = $default;
if ($customKey = $this->lookupCustomKey($key)) {
$key = $customKey;
}
$key = $this->lookupCustomKey($key);
$this->log("Custom Key: ${key}");
if (array_key_exists($key, $array)) {
@@ -163,13 +169,12 @@ abstract class Importer
*/
public function lookupCustomKey($key)
{
// dd($this->fieldMap);
if (array_key_exists($key, $this->fieldMap)) {
$this->log("Found a match in our custom map: {$key} is " . $this->fieldMap[$key]);
return $this->fieldMap[$key];
}
return null;
// Otherwise no custom key, return original.
return $key;
}
/**

View File

@@ -10,6 +10,7 @@ use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
class ItemImporter extends Importer
{
@@ -68,6 +69,7 @@ class ItemImporter extends Importer
if(get_class($this) !== UserImporter::class) {
if ($this->item["user"] = $this->createOrFetchUser($row)) {
$this->item['assigned_to'] = $this->item['user']->id;
$this->item['assigned_type'] = User::class;
}
}
}
@@ -90,7 +92,6 @@ class ItemImporter extends Importer
$item = collect($this->item);
// First Filter the item down to the model's fillable fields
$item = $item->only($model->getFillable());
// Then iterate through the item and, if we are updating, remove any blank values.
if ($updating) {
$item = $item->reject(function ($value) {

View File

@@ -60,6 +60,7 @@ class Accessory extends SnipeModel
'purchase_cost',
'purchase_date',
'model_number',
'manufacturer_id',
'qty',
'requestable'
];

View File

@@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Auth;
use Response;
use Carbon;
/**
* Model for the Actionlog (the table that keeps a historical log of
@@ -123,6 +124,10 @@ class Actionlog extends SnipeModel
return $this->belongsTo('\App\Models\ActionLog', 'thread_id');
}
public function location() {
return $this->belongsTo('\App\Models\Location', 'location_id' )->withTrashed();
}
/**
* Check if the file exists, and if it does, force a download
**/
@@ -149,6 +154,33 @@ class Actionlog extends SnipeModel
}
}
public function daysUntilNextAudit($monthInterval = 12, $asset = null) {
$now = Carbon::now();
$last_audit_date = $this->created_at;
$next_audit = $last_audit_date->addMonth($monthInterval);
$next_audit_days = $now->diffInDays($next_audit);
// Override the default setting for interval if the asset has its own next audit date
if (($asset) && ($asset->next_audit_date)) {
$override_default_next = \Carbon::parse($asset->next_audit_date);
$next_audit_days = $override_default_next->diffInDays($now);
}
return $next_audit_days;
}
public function calcNextAuditDate($monthInterval = 12, $asset = null) {
$last_audit_date = Carbon::parse($this->created_at);
// If there is an asset-specific next date already given,
if (($asset) && ($asset->next_audit_date)) {
return \Carbon::parse($asset->next_audit_date);
}
return \Carbon::parse($last_audit_date)->addMonths($monthInterval)->toDateString();
}
/**
* getListingOfActionLogsChronologicalOrder
*

View File

@@ -1,6 +1,7 @@
<?php
namespace App\Models;
use App\Exceptions\CheckoutNotAllowed;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Presenters\Presentable;
use AssetPresenter;
@@ -10,6 +11,7 @@ use Config;
use Illuminate\Database\Eloquent\SoftDeletes;
use Log;
use Watson\Validating\ValidatingTrait;
use Illuminate\Notifications\Notifiable;
/**
* Model for Assets.
@@ -19,7 +21,7 @@ use Watson\Validating\ValidatingTrait;
class Asset extends Depreciable
{
protected $presenter = 'App\Presenters\AssetPresenter';
use Loggable, Requestable, Presentable;
use Loggable, Requestable, Presentable, Notifiable;
use SoftDeletes;
const LOCATION = 'location';
@@ -66,6 +68,7 @@ class Asset extends Depreciable
'asset_tag' => 'required|min:1|max:255|unique_undeleted',
'status' => 'integer',
'purchase_cost' => 'numeric|nullable',
'next_audit_date' => 'date|nullable',
];
/**
@@ -76,6 +79,7 @@ class Asset extends Depreciable
protected $fillable = [
'asset_tag',
'assigned_to',
'assigned_type',
'company_id',
'image',
'model_id',
@@ -140,7 +144,7 @@ class Asset extends Depreciable
* @return bool
*/
//FIXME: The admin parameter is never used. Can probably be removed.
public function checkOut($target, $admin, $checkout_at = null, $expected_checkin = null, $note = null, $name = null)
public function checkOut($target, $admin = null, $checkout_at = null, $expected_checkin = null, $note = null, $name = null)
{
if (!$target) {
return false;
@@ -160,41 +164,19 @@ class Asset extends Depreciable
}
if ($this->requireAcceptance()) {
if(get_class($target) != User::class) {
throw new CheckoutNotAllowed;
}
$this->accepted="pending";
}
if ($this->save()) {
$this->logCheckout($note, $target);
// if ((($this->requireAcceptance()=='1') || ($this->getEula())) && ($user->email!='')) {
// $this->checkOutNotifyMail($log->id, $user, $checkout_at, $expected_checkin, $note);
// }
return true;
}
return false;
}
public function checkOutNotifyMail($log_id, $user, $checkout_at, $expected_checkin, $note)
{
$data['log_id'] = $log_id;
$data['eula'] = $this->getEula();
$data['first_name'] = $user->first_name;
$data['item_name'] = $this->present()->name();
$data['checkout_date'] = $checkout_at;
$data['expected_checkin'] = $expected_checkin;
$data['item_tag'] = $this->asset_tag;
$data['note'] = $note;
$data['item_serial'] = $this->serial;
$data['require_acceptance'] = $this->requireAcceptance();
if ((($this->requireAcceptance()=='1') || ($this->getEula())) && (!config('app.lock_passwords'))) {
\Mail::send('emails.accept-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_asset_delivery'));
});
}
}
public function getDetailedNameAttribute()
{
if ($this->assignedTo) {
@@ -246,16 +228,14 @@ class Asset extends Depreciable
->orderBy('created_at', 'desc');
}
/**
* Even though we allow allow for checkout to things beyond users
* this method is an easy way of seeing if we are checked out to a user.
* @return mixed
*/
public function assigneduser()
public function checkedOutToUser()
{
return $this->belongsTo('\App\Models\User', 'assigned_to')
->withTrashed();
return $this->assignedType() === self::USER;
}
public function assignedTo()
@@ -274,15 +254,19 @@ class Asset extends Depreciable
public function assetLoc()
{
if (!empty($this->assignedType())) {
// dd($this->assignedType());
if ($this->assignedType() == self::ASSET) {
return $this->assignedTo->assetloc(); // Recurse until we have a final location
} elseif ($this->assignedType() == self::LOCATION) {
return $this->assignedto->assetloc(); // Recurse until we have a final location
}
if ($this->assignedType() == self::LOCATION) {
return $this->assignedTo();
} elseif (!$this->assignedTo) {
return $this->defaultLoc();
} elseif ($this->assignedType() == self::USER) {
}
if ($this->assignedType() == self::USER) {
return $this->assignedTo->userLoc();
}
if (!$this->assignedTo) {
return $this->defaultLoc();
}
}
return $this->defaultLoc();
}
@@ -544,7 +528,7 @@ class Asset extends Depreciable
/**
* Query builder scope for pending assets
* Query builder scope for searching location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
@@ -554,8 +538,17 @@ class Asset extends Depreciable
public function scopeAssetsByLocation($query, $location)
{
return $query->where(function ($query) use ($location) {
$query->whereHas('assigneduser', function ($query) use ($location) {
$query->where('users.location_id', '=', $location->id);
$query->whereHas('assignedTo', function ($query) use ($location) {
$query->where([
['users.location_id', '=', $location->id],
['assets.assigned_type', '=', User::class]
])->orWhere([
['locations.id', '=', $location->id],
['assets.assigned_type', '=', Location::class]
])->orWhere([
['assets.rtd_location_id', '=', $location->id],
['assets.assigned_type', '=', Asset::class]
]);
})->orWhere(function ($query) use ($location) {
$query->where('assets.rtd_location_id', '=', $location->id);
$query->whereNull('assets.assigned_to');
@@ -775,18 +768,26 @@ class Asset extends Depreciable
$query->whereHas('defaultLoc', function ($query) use ($search) {
$query->where('locations.name', 'LIKE', '%'.$search.'%');
});
})->orWhere(function ($query) use ($search) {
$query->whereHas('assigneduser', function ($query) use ($search) {
$query->where(function ($query) use ($search) {
$query->where('users.first_name', 'LIKE', '%'.$search.'%')
->orWhere('users.last_name', 'LIKE', '%'.$search.'%')
->orWhere(function ($query) use ($search) {
$query->whereHas('userloc', function ($query) use ($search) {
$query->where('locations.name', 'LIKE', '%'.$search.'%');
});
});
});
});
//FIXME: This needs attention to work with checkout to not-users.
// })->orWhere(function ($query) use ($search) {
// $query->whereHas('assignedTo', function ($query) use ($search) {
// $query->where(function ($query) use ($search) {
// $query->where('assets.assigned_type', '=', User::class)
// ->join('users', 'users.id', '=', 'assets.assigned_to')
// ->where(function($query) use ($search) {
// $query->where('users.first_name', 'LIKE', '%'.$search.'%')
// ->orWhere('users.last_name', 'LIKE', '%'.$search.'%');
// });
// })->orWhere(function ($query) use ($search) {
// $query->where('assets.assigned_type', '=', Location::class)
// ->join('locations', 'locations.id', '=', 'assets.assigned_to')
// ->where('locations.name', 'LIKE', '%'.$search.'%');
// })->orWhere(function ($query) use ($search) {
// $query->where('assets.assigned_type', '=', Asset::class)
// ->join('assets as assigned_asset', 'assigned_assets.id', '=', 'assets.assigned_to')
// ->where('assigned_assets.name', 'LIKE', '%'.$search.'%');
// });
// });
})->orWhere('assets.name', 'LIKE', '%'.$search.'%')
->orWhere('assets.asset_tag', 'LIKE', '%'.$search.'%')
->orWhere('assets.serial', 'LIKE', '%'.$search.'%')
@@ -1088,14 +1089,16 @@ class Asset extends Depreciable
return $query->where(function ($query) use ($search) {
$query->whereHas('defaultLoc', function ($query) use ($search) {
$query->where('locations.id', '=', $search);
})->whereNull('assigned_to');
})->orWhere(function ($query) use ($search) {
$query->whereHas('assigneduser', function ($query) use ($search) {
$query->whereHas('userloc', function ($query) use ($search) {
$query->where('locations.id', '=', $search);
});
});
});
// FIXME: This needs porting to checkout to non-user.
// ->orWhere(function ($query) use ($search) {
// $query->whereHas('assigneduser', function ($query) use ($search) {
// $query->whereHas('userloc', function ($query) use ($search) {
// $query->where('locations.id', '=', $search);
// });
// });
// });
}

View File

@@ -73,7 +73,7 @@ class Consumable extends SnipeModel
return $this->belongsTo('\App\Models\User', 'user_id');
}
public function consumableAssigments()
public function consumableAssignments()
{
return $this->hasMany('\App\Models\ConsumableAssignment');
}

View File

@@ -110,7 +110,7 @@ class Ldap extends Model
return false;
}
if (!$user = array_change_key_case(ldap_get_attributes($connection, $entry), CASE_LOWER)) {
if (!$user = ldap_get_attributes($connection, $entry)) {
return false;
}

View File

@@ -24,6 +24,7 @@ class Location extends SnipeModel
'address' => 'max:80|nullable',
'address2' => 'max:80|nullable',
'zip' => 'min:3|max:10|nullable',
// 'manager_id' => 'exists:users'
);
/**
@@ -63,7 +64,12 @@ class Location extends SnipeModel
public function parent()
{
return $this->belongsTo('\App\Models\Location', 'parent_id');
return $this->belongsTo('\App\Models\Location', 'parent_id','id');
}
public function manager()
{
return $this->belongsTo('\App\Models\User', 'manager_id');
}
public function childLocations()

View File

@@ -7,6 +7,7 @@ use App\Models\Asset;
use App\Models\CheckoutRequest;
use App\Models\User;
use App\Notifications\CheckinNotification;
use App\Notifications\AuditNotification;
use App\Notifications\CheckoutNotification;
use Illuminate\Support\Facades\Auth;
@@ -29,11 +30,56 @@ trait Loggable
* @since [v3.4]
* @return \App\Models\Actionlog
*/
public function logCheckout($note, $target = null /*target is overridable for components*/)
public function logCheckout($note, $target /* What are we checking out to? */)
{
$log = new Actionlog;
$log = $this->determineLogItemType($log);
$log->user_id = Auth::user()->id;
// We need to special case licenses because of license_seat vs license. So much for clean polymorphism :)
if (!isset($target)) {
throw new Exception('All checkout logs require a target');
return;
}
$log->target_type = get_class($target);
$log->target_id = $target->id;
$class = get_class($target);
if ($class == Location::class) {
// We can checkout to a location
$log->location_id = $target->id;
} else if ($class== Asset::class) {
$log->location_id = $target->rtd_location_id;
} else {
$log->location_id = $target->location_id;
}
$log->note = $note;
$log->logaction('checkout');
$params = [
'item' => $this,
'target' => $target,
'admin' => $log->user,
'note' => $note,
'log_id' => $log->id
];
if ($settings = Setting::getSettings()) {
$settings->notify(new CheckoutNotification($params));
}
if (method_exists($target, 'notify')) {
$target->notify(new CheckoutNotification($params));
}
return $log;
}
/**
* Helper method to determine the log item type
*/
private function determineLogItemType($log)
{
// We need to special case licenses because of license_seat vs license. So much for clean polymorphism :
if (static::class == LicenseSeat::class) {
$log->item_type = License::class;
$log->item_id = $this->license_id;
@@ -42,52 +88,8 @@ trait Loggable
$log->item_id = $this->id;
}
$log->user_id = Auth::user()->id;
// @FIXME This needs to be generalized with new asset checkout.
if(isset($target)) {
$log->target_type = get_class($target);
$log->target_id = $target->id;
} else {
if (!is_null($this->asset_id)) {
$log->target_type = Asset::class;
$log->target_id = $this->asset_id;
} elseif (!is_null($this->assigned_to)) {
$log->target_type = User::class;
$log->target_id = $this->assigned_to;
}
}
$item = call_user_func(array($log->target_type, 'find'), $log->target_id);
if($this->assignedTo) {
$item = $this->assignedTo;
}
$class = get_class($item);
if($class == Location::class) {
// We can checkout to a location
$log->location_id = $item->id;
} else if ($class== Asset::class) {
$log->location_id = $item->rtd_location_id;
} else {
$log->location_id = $item->location_id;
}
$log->note = $note;
$log->logaction('checkout');
$params = [
'item' => $log->item,
'target' => $log->target,
'admin' => $log->user,
'note' => $note
];
if ($settings = Setting::getSettings()) {
$settings->notify(new CheckoutNotification($params));
}
return $log;
}
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @since [v3.4]
@@ -120,6 +122,41 @@ trait Loggable
return $log;
}
/**
* @author A. Gianotto <snipe@snipe.net>
* @since [v4.0]
* @return \App\Models\Actionlog
*/
public function logAudit($note, $location_id)
{
$log = new Actionlog;
$location = Location::find($location_id);
if (static::class == LicenseSeat::class) {
$log->item_type = License::class;
$log->item_id = $this->license_id;
} else {
$log->item_type = static::class;
$log->item_id = $this->id;
}
$log->location_id = ($location_id) ? $location_id : null;
$log->note = $note;
$log->user_id = Auth::user()->id;
$log->logaction('audit');
$params = [
'item' => $log->item,
'admin' => $log->user,
'location' => ($location) ? $location->name : '',
'note' => $note
];
Setting::getSettings()->notify(new AuditNotification($params));
return $log;
}
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @since [v3.5]

View File

@@ -19,9 +19,9 @@ trait Requestable
public function isRequestedBy(User $user)
{
return $this->requests()
->where('user_id', $user->id)
->exists();
$requests = $this->requests->where('user_id', $user->id);
return $requests->count() > 0;
}
public function scopeRequestedBy($query, User $user)

View File

@@ -34,17 +34,10 @@ class Setting extends Model
'labels_fontsize' => 'numeric|min:5',
'labels_pagewidth' => 'numeric|nullable',
'labels_pageheight' => 'numeric|nullable',
"ldap_server" => 'sometimes|required_if:ldap_enabled,1|url|nullable',
"ldap_uname" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_basedn" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_filter" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_username_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_fname_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_lname_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_auth_filter_query" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_version" => 'sometimes|required_if:ldap_enabled,1|nullable',
"thumbnail_max_h" => 'numeric|max:500|min:25',
"pwd_secure_min" => "numeric|required|min:5",
"audit_warning_days" => "numeric|nullable",
"audit_interval" => "numeric|nullable",
];
protected $fillable = ['site_name','email_domain','email_format','username_format'];

View File

@@ -205,6 +205,14 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
return $this->belongsTo('\App\Models\User', 'manager_id')->withTrashed();
}
/**
* Get any locations the user manages.
**/
public function managedLocations()
{
return $this->hasMany('\App\Models\Location', 'manager_id')->withTrashed();
}
/**
* Get user groups
*/

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class AuditNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
//
$this->params = $params;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint) {
$notifyBy[] = 'slack';
}
return $notifyBy;
}
public function toSlack($notifiable)
{
return (new SlackMessage)
->success()
->content(class_basename(get_class($this->params['item'])) . " Audited")
->attachment(function ($attachment) use ($notifiable) {
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$fields = [
'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->present()->fullName().'>'
];
array_key_exists('note', $this->params) && $fields['Notes'] = $this->params['note'];
array_key_exists('location', $this->params) && $fields['Location'] = $this->params['location'];
$attachment->title($item->present()->name, $item->present()->viewUrl())
->fields($fields);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -5,10 +5,11 @@ namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckoutNotification extends Notification
{
@@ -43,11 +44,12 @@ class CheckoutNotification extends Notification
}
$item = $this->params['item'];
if ((method_exists($item, 'requireAcceptance') && ($item->requireAcceptance()=='1'))
|| (method_exists($item, 'getEula') && ($item->getEula()))
) {
$notifyBy[] = 'mail';
}
$notifyBy[]='mail';
// if ((method_exists($item, 'requireAcceptance') && ($item->requireAcceptance()=='1'))
// || (method_exists($item, 'getEula') && ($item->getEula()))
// ) {
// $notifyBy[] = 'mail';
// }
return $notifyBy;
}
@@ -79,10 +81,30 @@ class CheckoutNotification extends Notification
*/
public function toMail($notifiable)
{
//TODO: Expand for non assets.
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$target = $this->params['target'];
$data = [
'eula' => method_exists($item, 'getEula') ? $item->getEula() : '',
'first_name' => $target->present()->fullName(),
'item_name' => $item->present()->name(),
'checkout_date' => $item->last_checkout,
'expected_checkin' => $item->expected_checkin,
'item_tag' => $item->asset_tag,
'note' => $this->params['note'],
'item_serial' => $item->serial,
'require_acceptance' => $item->requireAcceptance(),
'log_id' => $this->params['log_id'],
];
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', 'https://laravel.com')
->line('Thank you for using our application!');
->view('emails.accept-asset', $data)
->subject(trans('mail.Confirm_asset_delivery'));
// \Mail::send('emails.accept-asset', $data, function ($m) use ($target) {
// $m->to($target->email, $target->first_name . ' ' . $target->last_name);
// $m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
// $m->subject(trans('mail.Confirm_asset_delivery'));
// });
}
/**

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;
class ExpectedCheckinNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->params = $params;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
$item = $this->params['item'];
$notifyBy[]='mail';
return $notifyBy;
}
public function toSlack($notifiable)
{
}
/**
* Get the mail representation of the notification.
*
* @param mixed $asset
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($params)
{
$formatted_due = Carbon::parse($this->params->expected_checkin)->format('D, M j, Y');
return (new MailMessage)
->error()
->subject('Reminder: '.$this->params->present()->name().' checkin deadline approaching')
->line('Hi, '.$this->params->assignedto->first_name)
->greeting('An asset checked out to you is due to be checked back in on '.$formatted_due.'.')
->line('Asset: '.$this->params->present()->name())
->line('Serial: '.$this->params->serial)
->line('Asset Tag: '.$this->params->asset_tag)
->action('View Your Assets', route('view-assets'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -20,6 +20,7 @@ class AssetObserver
if ((isset($asset->getOriginal()['assigned_to'])) && ($asset->getAttributes()['assigned_to'] == $asset->getOriginal()['assigned_to'])
&& ($asset->getAttributes()['next_audit_date'] == $asset->getOriginal()['next_audit_date'])
&& ($asset->getAttributes()['last_checkout'] == $asset->getOriginal()['last_checkout'])
&& ($asset->getAttributes()['status_id'] == $asset->getOriginal()['status_id']))
{

View File

@@ -98,7 +98,7 @@ class AssetPresenter extends Presenter
"sortable" => true,
"title" => trans('admin/hardware/form.checkedout_to'),
"visible" => true,
"formatter" => "usersLinkObjFormatter"
"formatter" => "polymorphicItemFormatter"
], [
"field" => "employee_number",
"searchable" => false,

View File

@@ -42,4 +42,8 @@ class LocationPresenter extends Presenter
{
return '<i class="fa fa-globe"></i>';
}
public function fullName() {
return $this->name;
}
}

View File

@@ -65,6 +65,11 @@ abstract class Presenter
return '';
}
public function name()
{
return $this->model->name;
}
public function __get($property)
{
if (method_exists($this, $property)) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -85,8 +85,8 @@ return array(
array(
'permission' => 'assets.audit',
'label' => 'Audit ',
'note' => '',
'display' => false,
'note' => 'Allows the user to mark an asset as physically inventoried.',
'display' => true,
),

View File

@@ -1,7 +1,7 @@
<?php
return array (
'app_version' => 'v4.0',
'build_version' => 'beta 2',
'hash_version' => '46',
'full_hash' => 'v4.0-beta-246-g73ce5f9',
);
'build_version' => 'beta5',
'hash_version' => '9',
'full_hash' => 'v4.0-beta5-9-ga576435',
);

View File

@@ -20,7 +20,7 @@ $factory->defineAs(Actionlog::class, 'asset-checkout', function (Faker\Generator
$user = factory(App\Models\User::class)->create(['company_id' => $company->id]);
$target = factory(App\Models\User::class)->create(['company_id' => $company->id]);
// $item = factory(App\Models\Asset::class)->create(['company_id' => $company->id]);
// dd($item);
return [
'user_id' => $user->id,
'action_type' => 'checkout',

View File

@@ -1,6 +1,8 @@
<?php
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
/*
|--------------------------------------------------------------------------
@@ -69,6 +71,13 @@ $factory->state(Asset::class, 'assigned-to-asset', function ($faker) {
];
});
$factory->state(Asset::class, 'requires-acceptance', function ($faker) {
$cat = factory(Category::class)->states('asset-category', 'requires-acceptance')->create();
$model = factory(AssetModel::class)->create(['category_id' => $cat->id]);
return [
'model_id' => $model->id
];
});
$factory->define(App\Models\AssetModel::class, function (Faker\Generator $faker) {
return [

View File

@@ -15,7 +15,7 @@ $factory->define(App\Models\Category::class, function (Faker\Generator $faker) {
'name' => $faker->text(20),
'category_type' => $faker->randomElement(['asset', 'accessory', 'component', 'consumable']),
'eula_text' => $faker->paragraph(),
'require_acceptance' => $faker->boolean(),
'require_acceptance' => false,
'use_default_eula' => $faker->boolean(),
'checkin_email' => $faker->boolean()
];
@@ -44,3 +44,9 @@ $factory->state(App\Models\Category::class, 'consumable-category', function ($fa
'category_type' => 'consumable',
];
});
$factory->state(App\Models\Category::class, 'requires-acceptance', function ($faker) {
return [
'require_acceptance' => true,
];
});

View File

@@ -0,0 +1,17 @@
<?php
$factory->define(App\Models\CustomField::class, function (Faker\Generator $faker) {
return [
'name' => $faker->catchPhrase,
'format' => 'IP',
'element' => 'text',
];
});
$factory->define(App\Models\CustomFieldset::class, function (Faker\Generator $faker) {
return [
'name' => $faker->catchPhrase,
'user_id' => Auth::id()
];
});

View File

@@ -97,14 +97,6 @@ $factory->define(App\Models\Consumable::class, function (Faker\Generator $faker)
];
});
$factory->define(App\Models\CustomField::class, function (Faker\Generator $faker) {
return [
'name' => $faker->catchPhrase,
'format' => 'IP',
'element' => 'text',
];
});
$factory->define(App\Models\Department::class, function (Faker\Generator $faker) {
return [
'name' => $faker->catchPhrase,
@@ -210,5 +202,6 @@ $factory->define(App\Models\Setting::class, function ($faker) {
'brand' => 1,
'default_currency' => $faker->currencyCode,
'locale' => $faker->locale,
'pwd_secure_min' => 5,
];
});

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddManagerToLocationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('locations', function (Blueprint $table) {
//
$table->integer('manager_id')->nullable()->default(null);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('locations', function (Blueprint $table) {
//
$table->dropColumn('manager_id');
});
}
}

View File

@@ -13,6 +13,9 @@ class SetAssetArchivedToZeroDefault extends Migration
*/
public function up()
{
$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
$platform->registerDoctrineTypeMapping('enum', 'string');
Schema::table('assets', function (Blueprint $table) {
$table->boolean('archived')->default(0)->change();
});

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddAuditingTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('assets', function (Blueprint $table) {
$table->date('next_audit_date')->nullable()->default(NULL);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('assets', function (Blueprint $table) {
$table->dropColumn('next_audit_date');
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddAuditingToSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->integer('audit_interval')->nullable()->default(NULL);
$table->integer('audit_warning_days')->nullable()->default(NULL);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('audit_interval');
$table->dropColumn('audit_warning_days');
});
}
}

View File

@@ -0,0 +1,36 @@
<?php
use App\Models\Asset;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class FixAssignedTypeNotBeingNulled extends Migration
{
/**
* Run the migrations.
* There was a point in the v4 beta process where assigned_type was not nulled on checkin
* This manually nulls all assets where there is an assigned_type but not an assigned_to.
* @return void
*/
public function up()
{
// There was a point in the v4 beta process where assigned_type was not nulled on checkin
// This manually nulls all assets where there is an assigned_type but not an assigned_to.
Asset::whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]);
// Additionally, the importer did not set assigned_type when importing.
// In the case where we have an assigned_to but not an assigned_type, set the assigned_type to User.
Asset::whereNotNull('assigned_to')->whereNull('assigned_type')->update(['assigned_type' => 'App\Models\User']);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@@ -1,80 +0,0 @@
var elixir = require('laravel-elixir');
require('laravel-elixir-codeception-standalone');
require('laravel-elixir-phpcs');
require('laravel-elixir-vue-2');
var bowerPath = './bower_components';
/*
|--------------------------------------------------------------------------
| Elixir Asset Management
|--------------------------------------------------------------------------
|
| Elixir provides a clean, fluent API for defining some basic Gulp tasks
| for your Laravel application. By default, we are compiling the Sass
| file for our application, as well as publishing vendor resources.
|
*/
elixir(function(mix) {
mix.less(
[
'app.less',
bowerPath + '/iCheck/skins/minimal/*',
'AdminLTE.less',
'skins/skin-blue.less',
'overrides.less'
], 'public/assets/css/')
.copy(bowerPath + '/bootstrap-less/assets/fonts/bootstrap/**', 'public/assets/fonts')
.copy(bowerPath + '/font-awesome/fonts/*', 'public/build/fonts');
mix.webpack(
// jQuery is loaded from vue.js webpack process
'./resources/assets/js/vue.js',
'./resources/assets/js/vue-dist.js'
);
mix.scripts([
bowerPath + '/tether/dist/js/tether.js',
'vue-dist.js',
bowerPath + '/jquery-ui/jquery-ui.js',
bowerPath + '/jquery-slimscroll/jquery.slimscroll.js',
bowerPath + '/jquery.iframe-transport/jquery.iframe-transport.js',
bowerPath + '/blueimp-file-upload/js/jquery.fileupload.js',
bowerPath + '/fastclick/lib/fastclick.js',
bowerPath + '/bootstrap-colorpicker/dist/js/bootstrap-colorpicker.js',
bowerPath + '/bootstrap-datepicker/dist/js/bootstrap-datepicker.js',
bowerPath + '/iCheck/icheck.js',
bowerPath + '/select2/dist/js/select2.full.js',
bowerPath + '/ekko-lightbox/dist/ekko-lightbox.js',
'snipeit.js',
'app.js'
],'public/assets/js');
mix.version(['assets/css/app.css','assets/js/all.js']);
// mix.codeception(null, { flags: '--report' });
// mix.phpcs([
// 'app/**/*.php',
// 'tests/unit/*.php',
// 'tests/functional/*.php',
// 'tests/acceptance/*.php'
// ], {
// bin: 'vendor/bin/phpcs',
// standard: 'PSR2'
// });
});

View File

@@ -10,14 +10,14 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js "
},
"devDependencies": {
"axios": "^0.15.3",
"axios": "^0.16.2",
"babel-preset-latest": "^6.24.1",
"cross-env": "^3.2.4",
"cross-env": "^5.0.5",
"jquery": "^3.1.1",
"laravel-mix": "0.12.1",
"laravel-mix": "1.4.3",
"lodash": "^4.17.4",
"vue": "2.3.3",
"vue-template-compiler": "2.3.3"
"vue": "2.4.4",
"vue-template-compiler": "2.4.4"
},
"dependencies": {
"blueimp-file-upload": "^9.18.0",
@@ -32,6 +32,8 @@
"jquery-ui": "^1.12.1",
"jquery-ui-bundle": "^1.12.1",
"jquery.iframe-transport": "^1.0.0",
"less": "^2.7.2",
"less-loader": "^4.0.5",
"papaparse": "^4.3.3",
"select2": "^4.0.3",
"tether": "^1.4.0",

52
public/js/dist/all.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -130,12 +130,14 @@ tr {
{id: 'jobtitle', text: 'Job Title' },
{id: 'phone_number', text: 'Phone Number' },
],
customFields: [],
},
columnMappings: this.file.field_map || {},
activeColumn: null,
}
},
created() {
this.fetchCustomFields();
window.eventHub.$on('showDetails', this.toggleExtendedDisplay)
this.populateSelect2ActiveItems();
},
@@ -143,7 +145,7 @@ tr {
columns() {
switch(this.options.importType) {
case 'asset':
return this.columnOptions.general.concat(this.columnOptions.assets);
return this.columnOptions.general.concat(this.columnOptions.assets).concat(this.columnOptions.customFields);
case 'license':
return this.columnOptions.general.concat(this.columnOptions.licenses);
case 'user':
@@ -153,6 +155,18 @@ tr {
}
},
methods: {
fetchCustomFields() {
this.$http.get('/api/v1/fields')
.then( ({data}) => {
data = data.rows;
data.forEach((item) => {
this.columnOptions.customFields.push({
'id': item.db_column_name,
'text': item.name,
})
})
});
},
postSave() {
this.statusText = "Processing...";
this.$http.post('/api/v1/imports/process/'+this.file.id, {
@@ -208,4 +222,4 @@ tr {
select2: require('../select2.vue')
}
}
</script>
</script>

View File

@@ -0,0 +1,127 @@
/*
*
* Snipe-IT Universal Modal support
*
* Enables modal dialogs to create sub-resources throughout Snipe-IT
*
*/
/*
HOW TO USE
Create a Button looking like this:
<a href='{{ route('modal.user') }}' data-toggle="modal" data-target="#createModal" data-dependency="user" data-select='assigned_to' class="btn btn-sm btn-default">New</a>
If you don't have access to Blade commands (like {{ and }}, etc), you can hard-code a URL as the 'href'
data-toggle="modal" - required for Bootstrap Modals
data-target="#createModal" - fixed ID for the modal, do not change
data-dependency="user" - which Snipe-IT model you're going to be creating.
data-select="assigned_to" - What is the *ID* of the select-dropdown that you're going to be adding to, if the modal-create was a
success? Be on the lookout for duplicate ID's, it will confuse this library!
class="btn btn-sm btn-default" - makes it look button-ey, feel free to change :)
*/
$(function () {
console.warn("Loading up Modal functionality.");
//handle modal-add-interstitial calls
var model, select;
if($('#createModal').length == 0) {
$('body').append('<div class="modal fade" id="createModal"></div><!-- /.modal -->');
}
$('#createModal').on("show.bs.modal", function (event) {
var link = $(event.relatedTarget);
model = link.data("dependency");
select = link.data("select");
// console.warn("Uh, href is: "+link.attr('href'));
// console.dir(link);
$('#createModal').load(link.attr('href'),function () {
//do we need to re-select2 this, after load? Probably.
$('#createModal').find('select.select2').select2();
});
});
$('#createModal').on('click','#modal-save', function () {
console.warn("MODAL SAVE CALLED FOR MODAL!");
var data = {};
console.warn("We are about to SAVE!!! for model: "+model+" and select ID: "+select);
$('.modal-body input:visible').each(function (index, elem) {
console.warn("["+index+"]: "+elem.id+" = "+$(elem).val());
var bits = elem.id.split("-");
if (bits[0] === "modal") {
data[bits[1]] = $(elem).val();
}
});
//this can probably get replaced with a normal 'serialize' instead
$('.modal-body select:visible').each(function (index, elem) {
var bits = elem.id.split("-");
data[bits[1]] = $(elem).val();
});
data._token = Laravel.csrfToken;
console.log(data);
$.ajax({
type: 'POST',
url: "/api/v1/" + model + "s",
headers: {
"X-Requested-With": 'XMLHttpRequest',
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
},
data: data,
success: function (result) {
// {"status":"error","messages":{"name":["The name field is required."]}}
if(result.status == "error") {
var error_message="";
for(var field in result.messages) {
error_message+="Problem(s) with field '"+field+"': "+result.messages[field].join(", ");
}
//window.alert("Error adding "+model+": "+error_message);
$('#modal_error_msg').html(error_message).show();
return false;
}
var id = result.payload.id;
var name = result.payload.name || (result.payload.first_name + " " + result.payload.last_name);
if(!id || !name) {
console.error("Could not find resulting name or ID from modal-create. Name: "+name+", id: "+id);
return false;
}
$('#createModal').modal('hide');
$('#createModal').html("");
// "select" is the original drop-down menu that someone
// clicked 'add' on to add a new 'thing'
// this code adds the newly created object to that select
var selector = document.getElementById(select);
if(!selector) {
console.error("Could not find original <select> element with an id of: "+select);
return false;
}
//console.log(document.getElementById(select));
console.dir(selector);
selector.options[selector.length] = new Option(name, id);
selector.selectedIndex = selector.length - 1;
$(selector).trigger("change");
if(fetchCustomFields) {
fetchCustomFields();
}
},
error: function (result) {
// console.log('Error: ' + result.responseJSON.error.message );
msg = result.responseJSON.messages || result.responseJSON.error;
$('#modal_error_msg').html("Server Error: "+msg).show();
//window.alert("Unable to add new " + model + " - error: " + msg);
}
});
});
});

View File

@@ -28,7 +28,7 @@ return array(
'fieldset' => array(
'does_not_exist' => 'Fieldset does not exist',
'create' => array(
'error' => 'Fieldset was not created, please try again.',

View File

@@ -7,7 +7,7 @@ return array(
'asset' => 'Asset',
'bulk_checkout' => 'Checkout Assets to User',
'checkin' => 'Checkin Asset',
'checkout' => 'Checkout Asset to User',
'checkout' => 'Checkout Asset',
'clone' => 'Clone Asset',
'deployable' => 'Deployable',
'deleted' => 'This asset has been deleted. <a href="/hardware/:asset_id/restore">Click here to restore it</a>.',

View File

@@ -24,6 +24,12 @@ return array(
'success' => 'Asset restored successfully.'
),
'audit' => array(
'error' => 'Asset audit was unsuccessful. Please try again.',
'success' => 'Asset audit successfully logged.'
),
'deletefile' => array(
'error' => 'File not deleted. Please try again.',
'success' => 'File successfully deleted.',

View File

@@ -10,6 +10,10 @@ return array(
'alert_interval' => 'Expiring Alerts Threshold (in days)',
'alert_inv_threshold' => 'Inventory Alert Threshold',
'asset_ids' => 'Asset IDs',
'audit_interval' => 'Audit Interval',
'audit_interval_help' => 'If you are required to regularly physically audit your assets, enter the interval in months.',
'audit_warning_days' => 'Audit Warning Threshold',
'audit_warning_days_help' => 'How many days in advance should we warn you when assets are due for auditing?',
'auto_increment_assets' => 'Generate auto-incrementing asset IDs',
'auto_increment_prefix' => 'Prefix (optional)',
'auto_incrementing_help' => 'Enable auto-incrementing asset IDs first to set this',
@@ -63,6 +67,8 @@ return array(
'ldap_email' => 'LDAP Email',
'load_remote_text' => 'Remote Scripts',
'load_remote_help_text' => 'This Snipe-IT install can load scripts from the outside world.',
'login_note' => 'Login Note',
'login_note_help' => 'Optionally include a few sentences on your login screen, for example to assist people who have found a lost or stolen device. This field accepts <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>',
'logo' => 'Logo',
'full_multiple_companies_support_help_text' => 'Restricting users (including admins) assigned to companies to their company\'s assets.',
'full_multiple_companies_support_text' => 'Full Multiple Companies Support',
@@ -71,6 +77,12 @@ return array(
'php' => 'PHP Version',
'php_gd_info' => 'You must install php-gd to display QR codes, see install instructions.',
'php_gd_warning' => 'PHP Image Processing and GD plugin is NOT installed.',
'pwd_secure_complexity' => 'Password Complexity',
'pwd_secure_complexity_help' => 'Select whichever password complexity rules you wish to enforce.',
'pwd_secure_min' => 'Password minimum characters',
'pwd_secure_min_help' => 'Minimum permitted value is 5',
'pwd_secure_uncommon' => 'Prevent common passwords',
'pwd_secure_uncommon_help' => 'This will disallow users from using common passwords from the top 10,000 passwords reported in breaches.',
'qr_help' => 'Enable QR Codes first to set this',
'qr_text' => 'QR Code Text',
'setting' => 'Setting',
@@ -105,6 +117,8 @@ return array(
'width_w' => 'w',
'height_h' => 'h',
'text_pt' => 'pt',
'thumbnail_max_h' => 'Max thumbnail height',
'thumbnail_max_h_help' => 'Maximum height in pixels that thumbnails may display in the listing view. Min 25, max 500.',
'two_factor' => 'Two Factor Authentication',
'two_factor_secret' => 'Two-Factor Code',
'two_factor_enrollment' => 'Two-Factor Enrollment',

View File

@@ -13,6 +13,7 @@ return array(
'filetype_info' => 'Allowed filetypes are png, gif, jpg, jpeg, doc, docx, pdf, txt, zip, and rar.',
'history_user' => 'History for :name',
'info' => 'Info',
'restore_user' => 'Click here to restore them.',
'last_login' => 'Last Login',
'ldap_config_text' => 'LDAP configuration settings can be found Admin > Settings. The (optional) selected location will be set for all imported users.',
'software_user' => 'Software Checked out to :name',

View File

@@ -31,6 +31,7 @@ return array(
'create' => 'There was an issue creating the user. Please try again.',
'update' => 'There was an issue updating the user. Please try again.',
'delete' => 'There was an issue deleting the user. Please try again.',
'delete_has_assets' => 'This user has items assigned and could not be deleted.',
'unsuspend' => 'There was an issue unsuspending the user. Please try again.',
'import' => 'There was an issue importing users. Please try again.',
'asset_already_accepted' => 'This asset has already been accepted.',
@@ -40,6 +41,7 @@ return array(
'ldap_could_not_bind' => 'Could not bind to the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server: ',
'ldap_could_not_search' => 'Could not search the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
'ldap_could_not_get_entries' => 'Could not get entries from the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
'password_ldap' => 'The password for this account is managed by LDAP/Active Directory. Please contact your IT department to change your password. ',
),
'deletefile' => array(

View File

@@ -19,6 +19,7 @@ return array(
'location' => 'Location',
'lock_passwords' => 'Login details cannot be changed on this installation.',
'manager' => 'Manager',
'managed_locations' => 'Managed Locations',
'name' => 'Name',
'notes' => 'Notes',
'password_confirm' => 'Confirm Password',

View File

@@ -18,11 +18,15 @@
'asset_report' => 'Asset Report',
'asset_tag' => 'Asset Tag',
'assets_available' => 'assets available',
'audit' => 'Audit',
'audit_report' => 'Audit Log',
'assets' => 'Assets',
'avatar_delete' => 'Delete Avatar',
'avatar_upload' => 'Upload Avatar',
'back' => 'Back',
'bad_data' => 'Nothing found. Maybe bad data?',
'bulkaudit' => 'Bulk Audit',
'bulkaudit_status' => 'Audit Status',
'bulk_checkout' => 'Bulk Checkout',
'cancel' => 'Cancel',
'categories' => 'Categories',
@@ -52,6 +56,8 @@
'current' => 'Current',
'custom_report' => 'Custom Asset Report',
'dashboard' => 'Dashboard',
'days' => 'days',
'days_to_next_audit' => 'Days to Next Audit',
'date' => 'Date',
'debug_warning' => 'Warning!',
'debug_warning_text' => 'This application is running in production mode with debugging enabled. This can expose sensitive data if your application is accessible to the outside world. Disable debug mode by setting the <code>APP_DEBUG</code> value in your <code>.env</code> file to <code>false</code>.',
@@ -117,6 +123,8 @@
'moreinfo' => 'More Info',
'name' => 'Name',
'next' => 'Next',
'next_audit_date' => 'Next Audit Date',
'last_audit' => 'Last Audit',
'new' => 'new!',
'no_depreciation' => 'No Depreciation',
'no_results' => 'No Results.',

View File

@@ -13,60 +13,91 @@ return array(
|
*/
"accepted" => "The :attribute must be accepted.",
"active_url" => "The :attribute is not a valid URL.",
"after" => "The :attribute must be a date after :date.",
"alpha" => "The :attribute may only contain letters.",
"alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.",
"alpha_num" => "The :attribute may only contain letters and numbers.",
"before" => "The :attribute must be a date before :date.",
"between" => array(
"numeric" => "The :attribute must be between :min - :max.",
"file" => "The :attribute must be between :min - :max kilobytes.",
"string" => "The :attribute must be between :min - :max characters.",
),
"boolean" => "The :attribute must be true or false.",
"confirmed" => "The :attribute confirmation does not match.",
"date" => "The :attribute is not a valid date.",
"date_format" => "The :attribute does not match the format :format.",
"different" => "The :attribute and :other must be different.",
"digits" => "The :attribute must be :digits digits.",
"digits_between" => "The :attribute must be between :min and :max digits.",
"email" => "The :attribute format is invalid.",
"exists" => "The selected :attribute is invalid.",
"email_array" => "One or more email addresses is invalid.",
"image" => "The :attribute must be an image.",
"in" => "The selected :attribute is invalid.",
"integer" => "The :attribute must be an integer.",
"ip" => "The :attribute must be a valid IP address.",
"max" => array(
"numeric" => "The :attribute may not be greater than :max.",
"file" => "The :attribute may not be greater than :max kilobytes.",
"string" => "The :attribute may not be greater than :max characters.",
),
"mimes" => "The :attribute must be a file of type: :values.",
"min" => array(
"numeric" => "The :attribute must be at least :min.",
"file" => "The :attribute must be at least :min kilobytes.",
"string" => "The :attribute must be at least :min characters.",
),
"not_in" => "The selected :attribute is invalid.",
"numeric" => "The :attribute must be a number.",
"regex" => "The :attribute format is invalid.",
"required" => "The :attribute field is required.",
"required_if" => "The :attribute field is required when :other is :value.",
"required_with" => "The :attribute field is required when :values is present.",
"required_without" => "The :attribute field is required when :values is not present.",
"same" => "The :attribute and :other must match.",
"size" => array(
"numeric" => "The :attribute must be :size.",
"file" => "The :attribute must be :size kilobytes.",
"string" => "The :attribute must be :size characters.",
),
"unique" => "The :attribute has already been taken.",
"url" => "The :attribute format is invalid.",
"statuslabel_type" => "You must select a valid status label type",
"unique_undeleted" => "The :attribute must be unique.",
'accepted' => 'The :attribute must be accepted.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
'alpha' => 'The :attribute may only contain letters.',
'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.',
'alpha_num' => 'The :attribute may only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => 'The :attribute must be a date before :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
'between' => [
'numeric' => 'The :attribute must be between :min and :max.',
'file' => 'The :attribute must be between :min and :max kilobytes.',
'string' => 'The :attribute must be between :min and :max characters.',
'array' => 'The :attribute must have between :min and :max items.',
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'date' => 'The :attribute is not a valid date.',
'date_format' => 'The :attribute does not match the format :format.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
'string' => 'The :attribute may not be greater than :max characters.',
'array' => 'The :attribute may not have more than :max items.',
],
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
'numeric' => 'The :attribute must be at least :min.',
'file' => 'The :attribute must be at least :min kilobytes.',
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'not_in' => 'The selected :attribute is invalid.',
'numeric' => 'The :attribute must be a number.',
'present' => 'The :attribute field must be present.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values is present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
/*
@@ -80,8 +111,14 @@ return array(
|
*/
'custom' => array(),
'alpha_space' => "The :attribute field contains a character that is not allowed.",
'custom' => [
'alpha_space' => "The :attribute field contains a character that is not allowed.",
"email_array" => "One or more email addresses is invalid.",
"hashed_pass" => "Your current password is incorrect",
'dumbpwd' => 'That password is too common.',
"statuslabel_type" => "You must select a valid status label type",
"unique_undeleted" => "The :attribute must be unique.",
],
/*
|--------------------------------------------------------------------------
@@ -94,6 +131,6 @@ return array(
|
*/
'attributes' => array(),
'attributes' => [],
);

View File

@@ -28,7 +28,7 @@ return array(
'fieldset' => array(
'does_not_exist' => 'مجموعة الحقول غير موجودة',
'create' => array(
'error' => 'لم يتم إنشاء مجموعة-الحقول، الرجاء المحاولة مرة اخرى.',

View File

@@ -2,20 +2,20 @@
return array(
'does_not_exist' => 'Department does not exist.',
'assoc_users' => 'This department is currently associated with at least one user and cannot be deleted. Please update your users to no longer reference this department and try again. ',
'does_not_exist' => 'القسم الاداري غير موجود.',
'assoc_users' => 'هذا القسم الإداري مرتبط حاليا مع مستخدم واحد على الأقل ولا يمكن حذفه. الرجاء تحديث تفاصيل المستخدمين حيث لا تحتوي هذا القسم وحاول مرة أخرى. ',
'create' => array(
'error' => 'Department was not created, please try again.',
'success' => 'Department created successfully.'
'error' => 'لم يتم إنشاء القسم الإداري، الرجاء المحاولة مرة أخرى.',
'success' => 'تم انشاء القسم الاداري بنجاح.'
),
'update' => array(
'error' => 'Department was not updated, please try again',
'success' => 'Department updated successfully.'
'error' => 'لم يتم تحديث القسم الإداري، الرجاء المحاولة مرة أخرى',
'success' => 'تم تحديث القسم الاداري بنجاح.'
),
'delete' => array(
'confirm' => 'Are you sure you wish to delete this department?',
'error' => 'There was an issue deleting the department. Please try again.',
'success' => 'The department was deleted successfully.'
'confirm' => 'هل أنت متأكد من رغبتك في حذف هذا القسم؟',
'error' => 'حدثت مشكلة اثناء عملية حذف القسم الاداري. الرجاء المحاولة مرة اُخرى.',
'success' => 'تم حذف القسم الاداري بنجاح.'
)
);

View File

@@ -2,10 +2,10 @@
return array(
'id' => 'ID',
'name' => 'Department Name',
'manager' => 'Manager',
'location' => 'Location',
'create' => 'Create Department',
'update' => 'Update Department',
'id' => 'رقم التعريف',
'name' => 'اسم القسم الإداري',
'manager' => 'المدير',
'location' => 'الموقع',
'create' => 'إنشاء قسم اداري',
'update' => 'تحديث قسم اداري',
);

View File

@@ -2,9 +2,9 @@
return array(
'bulk_delete' => 'تاكيد إجراء حذف متعدد للاُصول',
'bulk_delete_help' => 'Review the assets for bulk deletion below. Once deleted, these assets can be restored, but they will no longer be associated with any users they are currently assigned to.',
'bulk_delete_warn' => 'You are about to delete :asset_count assets.',
'bulk_update' => 'Bulk Update Assets',
'bulk_delete_help' => 'الرجاء مراجعة الأصول المعينة للحذف بالجملة أدناه. بمجرد الحذف، يمكن استعادة هذه الأصول، لكنها لن تكون مقترنة مع أي من المستخدمين اللذين تم تعيين الاصول لهم من قبل.',
'bulk_delete_warn' => 'أنت على وشك حذف اصول asset_count: .',
'bulk_update' => 'تحديث الاصول بالجملة',
'bulk_update_help' => 'This form allows you to update multiple assets at once. Only fill in the fields you need to change. Any fields left blank will remain unchanged. ',
'bulk_update_warn' => 'You are about to edit the properties of :asset_count assets.',
'checkedout_to' => 'Checked Out To',

View File

@@ -7,7 +7,7 @@ return array(
'asset' => 'أصل',
'bulk_checkout' => 'Checkout Assets to User',
'checkin' => 'Checkin Asset',
'checkout' => 'Checkout Asset to User',
'checkout' => 'Checkout Asset',
'clone' => 'استنساخ الأصل',
'deployable' => 'قابل للنشر',
'deleted' => 'This asset has been deleted. <a href="/hardware/:asset_id/restore">Click here to restore it</a>.',

View File

@@ -24,6 +24,12 @@ return array(
'success' => 'Asset restored successfully.'
),
'audit' => array(
'error' => 'Asset audit was unsuccessful. Please try again.',
'success' => 'Asset audit successfully logged.'
),
'deletefile' => array(
'error' => 'لم يتم حذف الملف. الرجاء المحاولة مرة اخرى.',
'success' => 'تم حذف الملف بنجاح.',

View File

@@ -10,6 +10,10 @@ return array(
'alert_interval' => 'Expiring Alerts Threshold (in days)',
'alert_inv_threshold' => 'Inventory Alert Threshold',
'asset_ids' => 'Asset IDs',
'audit_interval' => 'Audit Interval',
'audit_interval_help' => 'If you are required to regularly physically audit your assets, enter the interval in months.',
'audit_warning_days' => 'Audit Warning Threshold',
'audit_warning_days_help' => 'How many days in advance should we warn you when assets are due for auditing?',
'auto_increment_assets' => 'Generate auto-incrementing asset IDs',
'auto_increment_prefix' => 'البادئة (اختياري)',
'auto_incrementing_help' => 'Enable auto-incrementing asset IDs first to set this',
@@ -63,6 +67,8 @@ return array(
'ldap_email' => 'LDAP Email',
'load_remote_text' => 'Remote Scripts',
'load_remote_help_text' => 'This Snipe-IT install can load scripts from the outside world.',
'login_note' => 'Login Note',
'login_note_help' => 'Optionally include a few sentences on your login screen, for example to assist people who have found a lost or stolen device. This field accepts <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>',
'logo' => 'Logo',
'full_multiple_companies_support_help_text' => 'Restricting users (including admins) assigned to companies to their company\'s assets.',
'full_multiple_companies_support_text' => 'Full Multiple Companies Support',
@@ -71,6 +77,12 @@ return array(
'php' => 'PHP Version',
'php_gd_info' => 'You must install php-gd to display QR codes, see install instructions.',
'php_gd_warning' => 'PHP Image Processing and GD plugin is NOT installed.',
'pwd_secure_complexity' => 'Password Complexity',
'pwd_secure_complexity_help' => 'Select whichever password complexity rules you wish to enforce.',
'pwd_secure_min' => 'Password minimum characters',
'pwd_secure_min_help' => 'Minimum permitted value is 5',
'pwd_secure_uncommon' => 'Prevent common passwords',
'pwd_secure_uncommon_help' => 'This will disallow users from using common passwords from the top 10,000 passwords reported in breaches.',
'qr_help' => 'Enable QR Codes first to set this',
'qr_text' => 'QR Code Text',
'setting' => 'Setting',
@@ -105,6 +117,8 @@ return array(
'width_w' => 'w',
'height_h' => 'h',
'text_pt' => 'pt',
'thumbnail_max_h' => 'Max thumbnail height',
'thumbnail_max_h_help' => 'Maximum height in pixels that thumbnails may display in the listing view. Min 25, max 500.',
'two_factor' => 'Two Factor Authentication',
'two_factor_secret' => 'Two-Factor Code',
'two_factor_enrollment' => 'Two-Factor Enrollment',

View File

@@ -13,6 +13,7 @@ return array(
'filetype_info' => 'انواع صيغ الملفات المسوح بها هي png, gif, jpg, jpeg, doc, docx, pdf, txt, zip, and rar.',
'history_user' => 'الأرشيف الخاص بـ :name',
'info' => 'Info',
'restore_user' => 'Click here to restore them.',
'last_login' => 'آخر دخول للمستخدم',
'ldap_config_text' => 'LDAP configuration settings can be found Admin > Settings. The (optional) selected location will be set for all imported users.',
'software_user' => 'البرامج المسجلة لـ :name',

View File

@@ -31,6 +31,7 @@ return array(
'create' => 'حدث خطأ ما أثناء إنشاء هذا المستخدم. حاول مرة أخرى.',
'update' => 'حدث خطأ أثناء تحديث هذا المستخدم. حاول مرة أخرى.',
'delete' => 'حدث خطأ ما أثناء حذف هذا المستخدم. حاول مرة أخرى.',
'delete_has_assets' => 'This user has items assigned and could not be deleted.',
'unsuspend' => 'حدث خطأ ما أثناء إلغاء التعليق عن المستخدم. حاول مرة أخرى.',
'import' => 'حدث خطأ أثناء استيراد المستخدمين. حاول مرة أخرى.',
'asset_already_accepted' => 'هذا الجهاز تم قبوله مسبقاً.',
@@ -40,6 +41,7 @@ return array(
'ldap_could_not_bind' => 'Could not bind to the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server: ',
'ldap_could_not_search' => 'Could not search the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
'ldap_could_not_get_entries' => 'Could not get entries from the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
'password_ldap' => 'The password for this account is managed by LDAP/Active Directory. Please contact your IT department to change your password. ',
),
'deletefile' => array(

View File

@@ -19,6 +19,7 @@ return array(
'location' => 'الموقع',
'lock_passwords' => 'لا يمكن تغيير تفاصيل الدخول بالنسبة لهذا التنصيب.',
'manager' => 'المدير',
'managed_locations' => 'Managed Locations',
'name' => 'الاسم',
'notes' => 'مُلاحظات',
'password_confirm' => 'تأكيد كلمة المرور',

View File

@@ -18,11 +18,15 @@
'asset_report' => 'Asset Report',
'asset_tag' => 'وسم الأصول',
'assets_available' => 'الأصول المتاحة',
'audit' => 'Audit',
'audit_report' => 'Audit Log',
'assets' => 'الأصول',
'avatar_delete' => 'حذف الصورة الرمزية',
'avatar_upload' => 'رفع صورة رمزية',
'back' => 'الرجوع للخلف',
'bad_data' => 'Nothing found. Maybe bad data?',
'bulkaudit' => 'Bulk Audit',
'bulkaudit_status' => 'Audit Status',
'bulk_checkout' => 'Bulk Checkout',
'cancel' => 'إلغاء',
'categories' => 'التصنيفات',
@@ -52,6 +56,8 @@
'current' => 'الحالي',
'custom_report' => 'Custom Asset Report',
'dashboard' => 'Dashboard',
'days' => 'days',
'days_to_next_audit' => 'Days to Next Audit',
'date' => 'التاريخ',
'debug_warning' => 'Warning!',
'debug_warning_text' => 'This application is running in production mode with debugging enabled. This can expose sensitive data if your application is accessible to the outside world. Disable debug mode by setting the <code>APP_DEBUG</code> value in your <code>.env</code> file to <code>false</code>.',
@@ -117,6 +123,8 @@
'moreinfo' => 'المزيد من المعلومات',
'name' => 'الإسم',
'next' => 'Next',
'next_audit_date' => 'Next Audit Date',
'last_audit' => 'Last Audit',
'new' => 'new!',
'no_depreciation' => 'لا يوجد إستهلاك',
'no_results' => 'لا يوجد نتائج.',

View File

@@ -13,60 +13,91 @@ return array(
|
*/
"accepted" => ":attribute يجب ان يكون مقبولا.",
"active_url" => ":attribute موقع غير صحيح.",
"after" => ":attribute يجب ان يكون تاريخ قبل :date.",
"alpha" => "The :attribute may only contain letters.",
"alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.",
"alpha_num" => "The :attribute may only contain letters and numbers.",
"before" => ":attribute يجب ان يكون تاريخ قبل :date.",
"between" => array(
"numeric" => ":attribute يجب ان يكون بين :min - :max.",
"file" => "The :attribute must be between :min - :max kilobytes.",
"string" => "The :attribute must be between :min - :max characters.",
),
"boolean" => "The :attribute must be true or false.",
"confirmed" => "The :attribute confirmation does not match.",
"date" => ":attribute تاريخ غير صحيح.",
"date_format" => "The :attribute does not match the format :format.",
"different" => "The :attribute and :other must be different.",
"digits" => "The :attribute must be :digits digits.",
"digits_between" => "The :attribute must be between :min and :max digits.",
"email" => "The :attribute format is invalid.",
"exists" => "The selected :attribute is invalid.",
"email_array" => "One or more email addresses is invalid.",
"image" => "The :attribute must be an image.",
"in" => "The selected :attribute is invalid.",
"integer" => "The :attribute must be an integer.",
"ip" => "The :attribute must be a valid IP address.",
"max" => array(
"numeric" => "The :attribute may not be greater than :max.",
"file" => "The :attribute may not be greater than :max kilobytes.",
"string" => "The :attribute may not be greater than :max characters.",
),
"mimes" => "The :attribute must be a file of type: :values.",
"min" => array(
"numeric" => "The :attribute must be at least :min.",
"file" => "The :attribute must be at least :min kilobytes.",
"string" => "The :attribute must be at least :min characters.",
),
"not_in" => "The selected :attribute is invalid.",
"numeric" => "The :attribute must be a number.",
"regex" => "The :attribute format is invalid.",
"required" => "The :attribute field is required.",
"required_if" => "The :attribute field is required when :other is :value.",
"required_with" => "The :attribute field is required when :values is present.",
"required_without" => "The :attribute field is required when :values is not present.",
"same" => "The :attribute and :other must match.",
"size" => array(
"numeric" => "The :attribute must be :size.",
"file" => "The :attribute must be :size kilobytes.",
"string" => "The :attribute must be :size characters.",
),
"unique" => "The :attribute has already been taken.",
"url" => "The :attribute format is invalid.",
"statuslabel_type" => "You must select a valid status label type",
"unique_undeleted" => "The :attribute must be unique.",
'accepted' => ':attribute يجب ان يكون مقبولا.',
'active_url' => ':attribute موقع غير صحيح.',
'after' => ':attribute يجب ان يكون تاريخ قبل :date.',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
'alpha' => 'The :attribute may only contain letters.',
'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.',
'alpha_num' => 'The :attribute may only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => ':attribute يجب ان يكون تاريخ قبل :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
'between' => [
'numeric' => ':attribute يجب ان يكون بين :min - :max.',
'file' => 'The :attribute must be between :min and :max kilobytes.',
'string' => 'The :attribute must be between :min and :max characters.',
'array' => 'The :attribute must have between :min and :max items.',
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'date' => ':attribute تاريخ غير صحيح.',
'date_format' => 'The :attribute does not match the format :format.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
'string' => 'The :attribute may not be greater than :max characters.',
'array' => 'The :attribute may not have more than :max items.',
],
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
'numeric' => 'The :attribute must be at least :min.',
'file' => 'The :attribute must be at least :min kilobytes.',
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'not_in' => 'The selected :attribute is invalid.',
'numeric' => 'The :attribute must be a number.',
'present' => 'The :attribute field must be present.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values is present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
/*
@@ -80,8 +111,14 @@ return array(
|
*/
'custom' => array(),
'alpha_space' => "The :attribute field contains a character that is not allowed.",
'custom' => [
'alpha_space' => "The :attribute field contains a character that is not allowed.",
"email_array" => "One or more email addresses is invalid.",
"hashed_pass" => "Your current password is incorrect",
'dumbpwd' => 'That password is too common.',
"statuslabel_type" => "You must select a valid status label type",
"unique_undeleted" => "The :attribute must be unique.",
],
/*
|--------------------------------------------------------------------------
@@ -94,6 +131,6 @@ return array(
|
*/
'attributes' => array(),
'attributes' => [],
);

View File

@@ -12,8 +12,8 @@
'success' => 'Поддръжката на актив създадена успешно.'
],
'edit' => [
'error' => 'Поддръжката на активи не беше редактирана, моля опитайте отново.',
'success' => 'Поддръжката на активи редактирана успешно.'
'error' => 'Asset Maintenance was not edited, please try again.',
'success' => 'Asset Maintenance edited successfully.'
],
'asset_maintenance_incomplete' => 'Все още неприключила',
'warranty' => 'Гаранция',

View File

@@ -28,7 +28,7 @@ return array(
'fieldset' => array(
'does_not_exist' => 'Fieldset does not exist',
'create' => array(
'error' => 'Fieldset не беше създаден, моля опитайте отново.',

View File

@@ -2,10 +2,10 @@
return array(
'id' => 'ID',
'id' => 'ИД№',
'name' => 'Department Name',
'manager' => 'Manager',
'location' => 'Location',
'manager' => 'Ръководител',
'location' => 'Местоположение',
'create' => 'Create Department',
'update' => 'Update Department',
);

View File

@@ -7,7 +7,7 @@ return array(
'asset' => 'Актив',
'bulk_checkout' => 'Отписване на активи към потребител',
'checkin' => 'Връщане на актив',
'checkout' => 'Изписване на актив към потребител',
'checkout' => 'Checkout Asset',
'clone' => 'Копиране на актив',
'deployable' => 'Може да бъде предоставен',
'deleted' => 'Активът беше изтрит. <a href="/hardware/:asset_id/restore">Възстановяване</a>.',

View File

@@ -23,6 +23,12 @@ return array(
'success' => 'Активът възстановен успешно.'
),
'audit' => array(
'error' => 'Asset audit was unsuccessful. Please try again.',
'success' => 'Asset audit successfully logged.'
),
'deletefile' => array(
'error' => 'Файлът не беше изтрит. Моля опитайте отново.',
'success' => 'Файлът изтрит успешно.',

View File

@@ -29,8 +29,8 @@ return array(
),
'bulkedit' => array(
'error' => 'No fields were changed, so nothing was updated.',
'success' => 'Models updated.'
'error' => 'Няма полета, който да са се променили, така че нищо не е осъвременено.',
'success' => 'Моделите са осъвременени.'
),
);

Some files were not shown because too many files have changed in this diff Show More